Skip to content

Commit 6f54b41

Browse files
authored
Merge pull request #188 from konard/issue-187-5d4f617e5a5a
feat(core): record device hostname when cloning repositories
2 parents 82329d7 + db785ce commit 6f54b41

File tree

10 files changed

+76
-17
lines changed

10 files changed

+76
-17
lines changed

packages/api/src/api/contracts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type ProjectDetails = ProjectSummary & {
2525
readonly envProjectPath: string
2626
readonly codexAuthPath: string
2727
readonly codexHome: string
28+
readonly clonedOnHostname?: string | undefined
2829
}
2930

3031
export type CreateProjectRequest = {

packages/api/src/services/projects.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ const toProjectDetails = (
114114
envGlobalPath: project.envGlobalPath,
115115
envProjectPath: project.envProjectPath,
116116
codexAuthPath: project.codexAuthPath,
117-
codexHome: project.codexHome
117+
codexHome: project.codexHome,
118+
clonedOnHostname: project.clonedOnHostname
118119
})
119120

120121
const findProjectById = (projectId: string) =>

packages/app/src/docker-git/menu-render-select.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,12 @@ export const buildSelectLabels = (
9090
items.map((item, index) => {
9191
const prefix = index === selected ? ">" : " "
9292
const refLabel = formatRepoRef(item.repoRef)
93+
const hostLabel = item.clonedOnHostname === undefined ? "" : ` @${item.clonedOnHostname}`
9394
const runtime = runtimeForProject(runtimeByProject, item)
9495
const runtimeSuffix = purpose === "Down" || purpose === "Delete"
9596
? ` [${renderRuntimeLabel(runtime)}]`
9697
: ` [started=${renderStartedAtCompact(runtime)}]`
97-
return `${prefix} ${index + 1}. ${item.displayName} (${refLabel})${runtimeSuffix}`
98+
return `${prefix} ${index + 1}. ${item.displayName} (${refLabel})${hostLabel}${runtimeSuffix}`
9899
})
99100

100101
export type SelectListWindow = {

packages/app/tests/docker-git/parser.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ describe("parseArgs", () => {
3636
expect(command.config.serviceName).toBe("dg-repo")
3737
expect(command.config.volumeName).toBe("dg-repo-home")
3838
expect(command.config.sshPort).toBe(defaultTemplateConfig.sshPort)
39+
expect(typeof command.config.clonedOnHostname).toBe("string")
40+
expect(String(command.config.clonedOnHostname).length).toBeGreaterThan(0)
3941
}))
4042

4143
it.effect("parses create resource limit flags", () =>

packages/lib/src/core/command-builders.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Either } from "effect"
2+
import { hostname } from "node:os"
23

34
import { expandContainerHome } from "../usecases/scrap-path.js"
45
import { resolveAutoAgentFlags } from "./auto-agent-flags.js"
@@ -198,12 +199,14 @@ type BuildTemplateConfigInput = {
198199
readonly enableMcpPlaywright: boolean
199200
readonly agentMode: AgentMode | undefined
200201
readonly agentAuto: boolean
202+
readonly clonedOnHostname: string
201203
}
202204

203205
const buildTemplateConfig = ({
204206
agentAuto,
205207
agentMode,
206208
claudeAuthLabel,
209+
clonedOnHostname,
207210
codexAuthLabel,
208211
cpuLimit,
209212
dockerNetworkMode,
@@ -242,7 +245,8 @@ const buildTemplateConfig = ({
242245
enableMcpPlaywright,
243246
pnpmVersion: defaultTemplateConfig.pnpmVersion,
244247
agentMode,
245-
agentAuto
248+
agentAuto,
249+
clonedOnHostname
246250
})
247251

248252
// CHANGE: build a typed create command from raw options (CLI or API)
@@ -295,7 +299,8 @@ export const buildCreateCommand = (
295299
claudeAuthLabel,
296300
enableMcpPlaywright: behavior.enableMcpPlaywright,
297301
agentMode,
298-
agentAuto
302+
agentAuto,
303+
clonedOnHostname: hostname()
299304
})
300305
}
301306
})

packages/lib/src/core/domain.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface TemplateConfig {
4747
readonly pnpmVersion: string
4848
readonly agentMode?: AgentMode | undefined
4949
readonly agentAuto?: boolean | undefined
50+
readonly clonedOnHostname?: string | undefined
5051
}
5152

5253
export interface ProjectConfig {

packages/lib/src/shell/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ const TemplateConfigSchema = Schema.Struct({
5959
enableMcpPlaywright: Schema.optionalWith(Schema.Boolean, {
6060
default: () => defaultTemplateConfig.enableMcpPlaywright
6161
}),
62-
pnpmVersion: Schema.String
62+
pnpmVersion: Schema.String,
63+
clonedOnHostname: Schema.optional(Schema.String)
6364
})
6465

6566
const ProjectConfigSchema = Schema.Struct({

packages/lib/src/usecases/menu-helpers.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,28 @@ export const formatConnectionInfo = (
1616
authorizedKeysPath: string,
1717
authorizedKeysExists: boolean,
1818
sshCommand: string
19-
): string =>
20-
`Project directory: ${cwd}
19+
): string => {
20+
const hostnameLabel = config.template.clonedOnHostname === undefined
21+
? ""
22+
: `\nCloned on device: ${config.template.clonedOnHostname}`
23+
return `Project directory: ${cwd}
2124
` +
22-
`Container: ${config.template.containerName}
25+
`Container: ${config.template.containerName}
2326
` +
24-
`Service: ${config.template.serviceName}
27+
`Service: ${config.template.serviceName}
2528
` +
26-
`SSH command: ${sshCommand}
29+
`SSH command: ${sshCommand}
2730
` +
28-
`Repo: ${config.template.repoUrl} (${config.template.repoRef})
31+
`Repo: ${config.template.repoUrl} (${config.template.repoRef})
2932
` +
30-
`Workspace: ${config.template.targetDir}
33+
`Workspace: ${config.template.targetDir}
3134
` +
32-
`Authorized keys: ${authorizedKeysPath}${authorizedKeysExists ? "" : " (missing)"}
35+
`Authorized keys: ${authorizedKeysPath}${authorizedKeysExists ? "" : " (missing)"}
3336
` +
34-
`Env global: ${config.template.envGlobalPath}
37+
`Env global: ${config.template.envGlobalPath}
3538
` +
36-
`Env project: ${config.template.envProjectPath}
39+
`Env project: ${config.template.envProjectPath}
3740
` +
38-
`Codex auth: ${config.template.codexAuthPath} -> ${config.template.codexHome}`
41+
`Codex auth: ${config.template.codexAuthPath} -> ${config.template.codexHome}` +
42+
hostnameLabel
43+
}

packages/lib/src/usecases/projects-core.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type ProjectItem = {
6060
readonly envProjectPath: string
6161
readonly codexAuthPath: string
6262
readonly codexHome: string
63+
readonly clonedOnHostname?: string | undefined
6364
}
6465

6566
export type ProjectStatus = {
@@ -203,7 +204,8 @@ export const loadProjectItem = (
203204
envGlobalPath: resolvePathFromCwd(path, projectDir, template.envGlobalPath),
204205
envProjectPath: resolvePathFromCwd(path, projectDir, template.envProjectPath),
205206
codexAuthPath: resolvePathFromCwd(path, projectDir, template.codexAuthPath),
206-
codexHome: template.codexHome
207+
codexHome: template.codexHome,
208+
clonedOnHostname: template.clonedOnHostname
207209
}
208210
})
209211

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from "@effect/vitest"
2+
3+
import type { ProjectConfig } from "../../src/core/domain.js"
4+
import { defaultTemplateConfig } from "../../src/core/domain.js"
5+
import { formatConnectionInfo } from "../../src/usecases/menu-helpers.js"
6+
7+
const makeProjectConfig = (overrides: Partial<ProjectConfig["template"]> = {}): ProjectConfig => ({
8+
schemaVersion: 1,
9+
template: {
10+
...defaultTemplateConfig,
11+
repoUrl: "https://github.com/org/repo.git",
12+
containerName: "dg-test",
13+
serviceName: "dg-test",
14+
sshUser: "dev",
15+
targetDir: "/home/dev/org/repo",
16+
volumeName: "dg-test-home",
17+
dockerGitPath: "/workspace/.docker-git",
18+
authorizedKeysPath: "/workspace/authorized_keys",
19+
envGlobalPath: "/workspace/.orch/env/global.env",
20+
envProjectPath: "/workspace/.orch/env/project.env",
21+
codexAuthPath: "/workspace/.orch/auth/codex",
22+
codexSharedAuthPath: "/workspace/.orch/auth/codex-shared",
23+
geminiAuthPath: "/workspace/.orch/auth/gemini",
24+
...overrides
25+
}
26+
})
27+
28+
describe("formatConnectionInfo", () => {
29+
it("includes clonedOnHostname when present", () => {
30+
const config = makeProjectConfig({ clonedOnHostname: "my-laptop" })
31+
const output = formatConnectionInfo("/project", config, "/keys", true, "ssh dev@localhost")
32+
expect(output).toContain("Cloned on device: my-laptop")
33+
})
34+
35+
it("omits clonedOnHostname line when undefined", () => {
36+
const config = makeProjectConfig()
37+
const output = formatConnectionInfo("/project", config, "/keys", true, "ssh dev@localhost")
38+
expect(output).not.toContain("Cloned on device")
39+
})
40+
})

0 commit comments

Comments
 (0)