Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/src/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type ProjectDetails = ProjectSummary & {
readonly envProjectPath: string
readonly codexAuthPath: string
readonly codexHome: string
readonly clonedOnHostname?: string | undefined
}

export type CreateProjectRequest = {
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/services/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ const toProjectDetails = (
envGlobalPath: project.envGlobalPath,
envProjectPath: project.envProjectPath,
codexAuthPath: project.codexAuthPath,
codexHome: project.codexHome
codexHome: project.codexHome,
clonedOnHostname: project.clonedOnHostname
})

const findProjectById = (projectId: string) =>
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/docker-git/menu-render-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ export const buildSelectLabels = (
items.map((item, index) => {
const prefix = index === selected ? ">" : " "
const refLabel = formatRepoRef(item.repoRef)
const hostLabel = item.clonedOnHostname === undefined ? "" : ` @${item.clonedOnHostname}`
const runtime = runtimeForProject(runtimeByProject, item)
const runtimeSuffix = purpose === "Down" || purpose === "Delete"
? ` [${renderRuntimeLabel(runtime)}]`
: ` [started=${renderStartedAtCompact(runtime)}]`
return `${prefix} ${index + 1}. ${item.displayName} (${refLabel})${runtimeSuffix}`
return `${prefix} ${index + 1}. ${item.displayName} (${refLabel})${hostLabel}${runtimeSuffix}`
})

export type SelectListWindow = {
Expand Down
2 changes: 2 additions & 0 deletions packages/app/tests/docker-git/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ describe("parseArgs", () => {
expect(command.config.serviceName).toBe("dg-repo")
expect(command.config.volumeName).toBe("dg-repo-home")
expect(command.config.sshPort).toBe(defaultTemplateConfig.sshPort)
expect(typeof command.config.clonedOnHostname).toBe("string")
expect(String(command.config.clonedOnHostname).length).toBeGreaterThan(0)
}))

it.effect("parses create resource limit flags", () =>
Expand Down
9 changes: 7 additions & 2 deletions packages/lib/src/core/command-builders.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Either } from "effect"
import { hostname } from "node:os"

import { expandContainerHome } from "../usecases/scrap-path.js"
import { resolveAutoAgentFlags } from "./auto-agent-flags.js"
Expand Down Expand Up @@ -198,12 +199,14 @@ type BuildTemplateConfigInput = {
readonly enableMcpPlaywright: boolean
readonly agentMode: AgentMode | undefined
readonly agentAuto: boolean
readonly clonedOnHostname: string
}

const buildTemplateConfig = ({
agentAuto,
agentMode,
claudeAuthLabel,
clonedOnHostname,
codexAuthLabel,
cpuLimit,
dockerNetworkMode,
Expand Down Expand Up @@ -242,7 +245,8 @@ const buildTemplateConfig = ({
enableMcpPlaywright,
pnpmVersion: defaultTemplateConfig.pnpmVersion,
agentMode,
agentAuto
agentAuto,
clonedOnHostname
})

// CHANGE: build a typed create command from raw options (CLI or API)
Expand Down Expand Up @@ -295,7 +299,8 @@ export const buildCreateCommand = (
claudeAuthLabel,
enableMcpPlaywright: behavior.enableMcpPlaywright,
agentMode,
agentAuto
agentAuto,
clonedOnHostname: hostname()
})
}
})
1 change: 1 addition & 0 deletions packages/lib/src/core/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface TemplateConfig {
readonly pnpmVersion: string
readonly agentMode?: AgentMode | undefined
readonly agentAuto?: boolean | undefined
readonly clonedOnHostname?: string | undefined
}

export interface ProjectConfig {
Expand Down
3 changes: 2 additions & 1 deletion packages/lib/src/shell/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ const TemplateConfigSchema = Schema.Struct({
enableMcpPlaywright: Schema.optionalWith(Schema.Boolean, {
default: () => defaultTemplateConfig.enableMcpPlaywright
}),
pnpmVersion: Schema.String
pnpmVersion: Schema.String,
clonedOnHostname: Schema.optional(Schema.String)
})

const ProjectConfigSchema = Schema.Struct({
Expand Down
27 changes: 16 additions & 11 deletions packages/lib/src/usecases/menu-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@ export const formatConnectionInfo = (
authorizedKeysPath: string,
authorizedKeysExists: boolean,
sshCommand: string
): string =>
`Project directory: ${cwd}
): string => {
const hostnameLabel = config.template.clonedOnHostname === undefined
? ""
: `\nCloned on device: ${config.template.clonedOnHostname}`
return `Project directory: ${cwd}
` +
`Container: ${config.template.containerName}
`Container: ${config.template.containerName}
` +
`Service: ${config.template.serviceName}
`Service: ${config.template.serviceName}
` +
`SSH command: ${sshCommand}
`SSH command: ${sshCommand}
` +
`Repo: ${config.template.repoUrl} (${config.template.repoRef})
`Repo: ${config.template.repoUrl} (${config.template.repoRef})
` +
`Workspace: ${config.template.targetDir}
`Workspace: ${config.template.targetDir}
` +
`Authorized keys: ${authorizedKeysPath}${authorizedKeysExists ? "" : " (missing)"}
`Authorized keys: ${authorizedKeysPath}${authorizedKeysExists ? "" : " (missing)"}
` +
`Env global: ${config.template.envGlobalPath}
`Env global: ${config.template.envGlobalPath}
` +
`Env project: ${config.template.envProjectPath}
`Env project: ${config.template.envProjectPath}
` +
`Codex auth: ${config.template.codexAuthPath} -> ${config.template.codexHome}`
`Codex auth: ${config.template.codexAuthPath} -> ${config.template.codexHome}` +
hostnameLabel
}
4 changes: 3 additions & 1 deletion packages/lib/src/usecases/projects-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type ProjectItem = {
readonly envProjectPath: string
readonly codexAuthPath: string
readonly codexHome: string
readonly clonedOnHostname?: string | undefined
}

export type ProjectStatus = {
Expand Down Expand Up @@ -203,7 +204,8 @@ export const loadProjectItem = (
envGlobalPath: resolvePathFromCwd(path, projectDir, template.envGlobalPath),
envProjectPath: resolvePathFromCwd(path, projectDir, template.envProjectPath),
codexAuthPath: resolvePathFromCwd(path, projectDir, template.codexAuthPath),
codexHome: template.codexHome
codexHome: template.codexHome,
clonedOnHostname: template.clonedOnHostname
}
})

Expand Down
40 changes: 40 additions & 0 deletions packages/lib/tests/usecases/connection-info.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from "@effect/vitest"

import type { ProjectConfig } from "../../src/core/domain.js"
import { defaultTemplateConfig } from "../../src/core/domain.js"
import { formatConnectionInfo } from "../../src/usecases/menu-helpers.js"

const makeProjectConfig = (overrides: Partial<ProjectConfig["template"]> = {}): ProjectConfig => ({
schemaVersion: 1,
template: {
...defaultTemplateConfig,
repoUrl: "https://github.com/org/repo.git",
containerName: "dg-test",
serviceName: "dg-test",
sshUser: "dev",
targetDir: "/home/dev/org/repo",
volumeName: "dg-test-home",
dockerGitPath: "/workspace/.docker-git",
authorizedKeysPath: "/workspace/authorized_keys",
envGlobalPath: "/workspace/.orch/env/global.env",
envProjectPath: "/workspace/.orch/env/project.env",
codexAuthPath: "/workspace/.orch/auth/codex",
codexSharedAuthPath: "/workspace/.orch/auth/codex-shared",
geminiAuthPath: "/workspace/.orch/auth/gemini",
...overrides
}
})

describe("formatConnectionInfo", () => {
it("includes clonedOnHostname when present", () => {
const config = makeProjectConfig({ clonedOnHostname: "my-laptop" })
const output = formatConnectionInfo("/project", config, "/keys", true, "ssh dev@localhost")
expect(output).toContain("Cloned on device: my-laptop")
})

it("omits clonedOnHostname line when undefined", () => {
const config = makeProjectConfig()
const output = formatConnectionInfo("/project", config, "/keys", true, "ssh dev@localhost")
expect(output).not.toContain("Cloned on device")
})
})