@prover-coder-ai/docker-git 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.package.json.release.bak +4 -3
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +28 -1
  4. package/dist/src/docker-git/main.js +10256 -12
  5. package/dist/src/docker-git/main.js.map +1 -0
  6. package/package.json +3 -4
  7. package/src/docker-git/cli/parser-apply.ts +28 -0
  8. package/src/docker-git/cli/parser-clone.ts +3 -9
  9. package/src/docker-git/cli/parser-options.ts +71 -22
  10. package/src/docker-git/cli/parser.ts +2 -0
  11. package/src/docker-git/cli/usage.ts +11 -3
  12. package/src/docker-git/menu-actions.ts +5 -2
  13. package/src/docker-git/menu-create.ts +9 -13
  14. package/src/docker-git/menu-render.ts +1 -1
  15. package/src/docker-git/program.ts +2 -0
  16. package/tests/docker-git/entrypoint-auth.test.ts +14 -3
  17. package/tests/docker-git/parser-network-options.test.ts +47 -0
  18. package/tests/docker-git/parser.test.ts +105 -18
  19. package/vite.docker-git.config.ts +34 -0
  20. package/dist/main.js +0 -905
  21. package/dist/main.js.map +0 -1
  22. package/dist/src/app/main.js +0 -15
  23. package/dist/src/app/program.js +0 -61
  24. package/dist/src/docker-git/cli/input.js +0 -21
  25. package/dist/src/docker-git/cli/parser-attach.js +0 -19
  26. package/dist/src/docker-git/cli/parser-auth.js +0 -90
  27. package/dist/src/docker-git/cli/parser-clone.js +0 -41
  28. package/dist/src/docker-git/cli/parser-create.js +0 -1
  29. package/dist/src/docker-git/cli/parser-mcp-playwright.js +0 -18
  30. package/dist/src/docker-git/cli/parser-options.js +0 -109
  31. package/dist/src/docker-git/cli/parser-panes.js +0 -19
  32. package/dist/src/docker-git/cli/parser-scrap.js +0 -74
  33. package/dist/src/docker-git/cli/parser-sessions.js +0 -69
  34. package/dist/src/docker-git/cli/parser-shared.js +0 -26
  35. package/dist/src/docker-git/cli/parser-state.js +0 -62
  36. package/dist/src/docker-git/cli/parser.js +0 -46
  37. package/dist/src/docker-git/cli/read-command.js +0 -17
  38. package/dist/src/docker-git/cli/usage.js +0 -108
  39. package/dist/src/docker-git/menu-actions.js +0 -135
  40. package/dist/src/docker-git/menu-auth-data.js +0 -90
  41. package/dist/src/docker-git/menu-auth-helpers.js +0 -20
  42. package/dist/src/docker-git/menu-auth.js +0 -159
  43. package/dist/src/docker-git/menu-buffer-input.js +0 -9
  44. package/dist/src/docker-git/menu-create.js +0 -199
  45. package/dist/src/docker-git/menu-input-handler.js +0 -109
  46. package/dist/src/docker-git/menu-input-utils.js +0 -47
  47. package/dist/src/docker-git/menu-input.js +0 -2
  48. package/dist/src/docker-git/menu-labeled-env.js +0 -33
  49. package/dist/src/docker-git/menu-menu.js +0 -46
  50. package/dist/src/docker-git/menu-project-auth-claude.js +0 -43
  51. package/dist/src/docker-git/menu-project-auth-data.js +0 -165
  52. package/dist/src/docker-git/menu-project-auth.js +0 -124
  53. package/dist/src/docker-git/menu-render-auth.js +0 -45
  54. package/dist/src/docker-git/menu-render-common.js +0 -26
  55. package/dist/src/docker-git/menu-render-layout.js +0 -14
  56. package/dist/src/docker-git/menu-render-project-auth.js +0 -37
  57. package/dist/src/docker-git/menu-render-select.js +0 -129
  58. package/dist/src/docker-git/menu-render.js +0 -137
  59. package/dist/src/docker-git/menu-select-actions.js +0 -66
  60. package/dist/src/docker-git/menu-select-connect.js +0 -6
  61. package/dist/src/docker-git/menu-select-load.js +0 -12
  62. package/dist/src/docker-git/menu-select-order.js +0 -21
  63. package/dist/src/docker-git/menu-select-runtime.js +0 -82
  64. package/dist/src/docker-git/menu-select-view.js +0 -15
  65. package/dist/src/docker-git/menu-select.js +0 -98
  66. package/dist/src/docker-git/menu-shared.js +0 -180
  67. package/dist/src/docker-git/menu-startup.js +0 -57
  68. package/dist/src/docker-git/menu-types.js +0 -21
  69. package/dist/src/docker-git/menu.js +0 -226
  70. package/dist/src/docker-git/program.js +0 -42
  71. package/dist/src/docker-git/tmux.js +0 -176
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prover-coder-ai/docker-git",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Minimal Vite-powered TypeScript console starter using Effect",
5
5
  "main": "dist/src/docker-git/main.js",
6
6
  "bin": {
@@ -35,8 +35,7 @@
35
35
  "@effect/schema": "^0.75.5",
36
36
  "effect": "^3.19.14",
37
37
  "ink": "^5.0.1",
38
- "react": "^18.3.1",
39
- "@effect-template/lib": "1.0.0"
38
+ "react": "^18.3.1"
40
39
  },
41
40
  "scripts": {
42
41
  "prebuild": "pnpm -C ../lib build",
@@ -48,7 +47,7 @@
48
47
  "lint:tests": "PATH=../../scripts:$PATH vibecode-linter tests/",
49
48
  "lint:effect": "PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .",
50
49
  "prebuild:docker-git": "pnpm -C ../lib build",
51
- "build:docker-git": "tsc -p tsconfig.build.json",
50
+ "build:docker-git": "vite build --config vite.docker-git.config.ts",
52
51
  "check": "pnpm run typecheck",
53
52
  "clone": "pnpm -C ../.. run clone",
54
53
  "docker-git": "node dist/src/docker-git/main.js",
@@ -0,0 +1,28 @@
1
+ import { Either } from "effect"
2
+
3
+ import { type ApplyCommand, type ParseError } from "@effect-template/lib/core/domain"
4
+
5
+ import { parseProjectDirWithOptions } from "./parser-shared.js"
6
+
7
+ // CHANGE: parse "apply" command for existing docker-git projects
8
+ // WHY: update managed docker-git config on the current project/container without creating a new project
9
+ // QUOTE(ТЗ): "Не создавать новый... а прямо в текущем обновить её на актуальную"
10
+ // REF: issue-72-followup-apply-current-config
11
+ // SOURCE: n/a
12
+ // FORMAT THEOREM: forall argv: parseApply(argv) = cmd -> deterministic(cmd)
13
+ // PURITY: CORE
14
+ // EFFECT: Effect<ApplyCommand, ParseError, never>
15
+ // INVARIANT: projectDir is never empty
16
+ // COMPLEXITY: O(n) where n = |argv|
17
+ export const parseApply = (
18
+ args: ReadonlyArray<string>
19
+ ): Either.Either<ApplyCommand, ParseError> =>
20
+ Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
21
+ _tag: "Apply",
22
+ projectDir,
23
+ runUp: raw.up ?? true,
24
+ gitTokenLabel: raw.gitTokenLabel,
25
+ codexTokenLabel: raw.codexTokenLabel,
26
+ claudeTokenLabel: raw.claudeTokenLabel,
27
+ enableMcpPlaywright: raw.enableMcpPlaywright
28
+ }))
@@ -2,12 +2,7 @@ import { Either } from "effect"
2
2
 
3
3
  import { buildCreateCommand, nonEmpty } from "@effect-template/lib/core/command-builders"
4
4
  import type { RawOptions } from "@effect-template/lib/core/command-options"
5
- import {
6
- type Command,
7
- defaultTemplateConfig,
8
- type ParseError,
9
- resolveRepoInput
10
- } from "@effect-template/lib/core/domain"
5
+ import { type Command, type ParseError, resolveRepoInput } from "@effect-template/lib/core/domain"
11
6
 
12
7
  import { parseRawOptions } from "./parser-options.js"
13
8
  import { resolveWorkspaceRepoPath, splitPositionalRepo } from "./parser-shared.js"
@@ -18,13 +13,12 @@ const applyCloneDefaults = (
18
13
  resolvedRepo: ReturnType<typeof resolveRepoInput>
19
14
  ): RawOptions => {
20
15
  const repoPath = resolveWorkspaceRepoPath(resolvedRepo)
21
- const sshUser = raw.sshUser?.trim() ?? defaultTemplateConfig.sshUser
22
- const homeDir = `/home/${sshUser}`
16
+ const targetHome = "~"
23
17
  return {
24
18
  ...raw,
25
19
  repoUrl: rawRepoUrl,
26
20
  outDir: raw.outDir ?? `.docker-git/${repoPath}`,
27
- targetDir: raw.targetDir ?? `${homeDir}/${repoPath}`
21
+ targetDir: raw.targetDir ?? `${targetHome}/workspaces/${repoPath}`
28
22
  }
29
23
  }
30
24
 
@@ -20,9 +20,14 @@ interface ValueOptionSpec {
20
20
  | "envProjectPath"
21
21
  | "codexAuthPath"
22
22
  | "codexHome"
23
+ | "dockerNetworkMode"
24
+ | "dockerSharedNetworkName"
23
25
  | "archivePath"
24
26
  | "scrapMode"
25
27
  | "label"
28
+ | "gitTokenLabel"
29
+ | "codexTokenLabel"
30
+ | "claudeTokenLabel"
26
31
  | "token"
27
32
  | "scopes"
28
33
  | "message"
@@ -48,9 +53,14 @@ const valueOptionSpecs: ReadonlyArray<ValueOptionSpec> = [
48
53
  { flag: "--env-project", key: "envProjectPath" },
49
54
  { flag: "--codex-auth", key: "codexAuthPath" },
50
55
  { flag: "--codex-home", key: "codexHome" },
56
+ { flag: "--network-mode", key: "dockerNetworkMode" },
57
+ { flag: "--shared-network", key: "dockerSharedNetworkName" },
51
58
  { flag: "--archive", key: "archivePath" },
52
59
  { flag: "--mode", key: "scrapMode" },
53
60
  { flag: "--label", key: "label" },
61
+ { flag: "--git-token", key: "gitTokenLabel" },
62
+ { flag: "--codex-token", key: "codexTokenLabel" },
63
+ { flag: "--claude-token", key: "claudeTokenLabel" },
54
64
  { flag: "--token", key: "token" },
55
65
  { flag: "--scopes", key: "scopes" },
56
66
  { flag: "--message", key: "message" },
@@ -96,9 +106,14 @@ const valueFlagUpdaters: { readonly [K in ValueKey]: (raw: RawOptions, value: st
96
106
  envProjectPath: (raw, value) => ({ ...raw, envProjectPath: value }),
97
107
  codexAuthPath: (raw, value) => ({ ...raw, codexAuthPath: value }),
98
108
  codexHome: (raw, value) => ({ ...raw, codexHome: value }),
109
+ dockerNetworkMode: (raw, value) => ({ ...raw, dockerNetworkMode: value }),
110
+ dockerSharedNetworkName: (raw, value) => ({ ...raw, dockerSharedNetworkName: value }),
99
111
  archivePath: (raw, value) => ({ ...raw, archivePath: value }),
100
112
  scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
101
113
  label: (raw, value) => ({ ...raw, label: value }),
114
+ gitTokenLabel: (raw, value) => ({ ...raw, gitTokenLabel: value }),
115
+ codexTokenLabel: (raw, value) => ({ ...raw, codexTokenLabel: value }),
116
+ claudeTokenLabel: (raw, value) => ({ ...raw, claudeTokenLabel: value }),
102
117
  token: (raw, value) => ({ ...raw, token: value }),
103
118
  scopes: (raw, value) => ({ ...raw, scopes: value }),
104
119
  message: (raw, value) => ({ ...raw, message: value }),
@@ -126,34 +141,68 @@ export const applyCommandValueFlag = (
126
141
  return Either.right(update(raw, value))
127
142
  }
128
143
 
144
+ type ParseRawOptionsStep =
145
+ | { readonly _tag: "ok"; readonly raw: RawOptions; readonly nextIndex: number }
146
+ | { readonly _tag: "error"; readonly error: ParseError }
147
+
148
+ const parseInlineValueToken = (
149
+ raw: RawOptions,
150
+ token: string
151
+ ): Either.Either<RawOptions, ParseError> | null => {
152
+ const equalIndex = token.indexOf("=")
153
+ if (equalIndex <= 0 || !token.startsWith("-")) {
154
+ return null
155
+ }
156
+
157
+ const flag = token.slice(0, equalIndex)
158
+ const inlineValue = token.slice(equalIndex + 1)
159
+ return applyCommandValueFlag(raw, flag, inlineValue)
160
+ }
161
+
162
+ const parseRawOptionsStep = (
163
+ args: ReadonlyArray<string>,
164
+ index: number,
165
+ raw: RawOptions
166
+ ): ParseRawOptionsStep => {
167
+ const token = args[index] ?? ""
168
+ const inlineApplied = parseInlineValueToken(raw, token)
169
+ if (inlineApplied !== null) {
170
+ return Either.isLeft(inlineApplied)
171
+ ? { _tag: "error", error: inlineApplied.left }
172
+ : { _tag: "ok", raw: inlineApplied.right, nextIndex: index + 1 }
173
+ }
174
+
175
+ const booleanApplied = applyCommandBooleanFlag(raw, token)
176
+ if (booleanApplied !== null) {
177
+ return { _tag: "ok", raw: booleanApplied, nextIndex: index + 1 }
178
+ }
179
+
180
+ if (!token.startsWith("-")) {
181
+ return { _tag: "error", error: { _tag: "UnexpectedArgument", value: token } }
182
+ }
183
+
184
+ const value = args[index + 1]
185
+ if (value === undefined) {
186
+ return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } }
187
+ }
188
+
189
+ const nextRaw = applyCommandValueFlag(raw, token, value)
190
+ return Either.isLeft(nextRaw)
191
+ ? { _tag: "error", error: nextRaw.left }
192
+ : { _tag: "ok", raw: nextRaw.right, nextIndex: index + 2 }
193
+ }
194
+
129
195
  export const parseRawOptions = (args: ReadonlyArray<string>): Either.Either<RawOptions, ParseError> => {
130
196
  let index = 0
131
197
  let raw: RawOptions = {}
132
198
 
133
199
  while (index < args.length) {
134
- const token = args[index] ?? ""
135
- const booleanApplied = applyCommandBooleanFlag(raw, token)
136
- if (booleanApplied !== null) {
137
- raw = booleanApplied
138
- index += 1
139
- continue
140
- }
141
-
142
- if (!token.startsWith("-")) {
143
- return Either.left({ _tag: "UnexpectedArgument", value: token })
144
- }
145
-
146
- const value = args[index + 1]
147
- if (value === undefined) {
148
- return Either.left({ _tag: "MissingOptionValue", option: token })
149
- }
150
-
151
- const nextRaw = applyCommandValueFlag(raw, token, value)
152
- if (Either.isLeft(nextRaw)) {
153
- return Either.left(nextRaw.left)
200
+ const step = parseRawOptionsStep(args, index, raw)
201
+ if (step._tag === "error") {
202
+ return Either.left(step.error)
154
203
  }
155
- raw = nextRaw.right
156
- index += 2
204
+ raw = step.raw
205
+ index = step.nextIndex
157
206
  }
158
207
 
159
208
  return Either.right(raw)
@@ -2,6 +2,7 @@ import { Either, Match } from "effect"
2
2
 
3
3
  import { type Command, type ParseError } from "@effect-template/lib/core/domain"
4
4
 
5
+ import { parseApply } from "./parser-apply.js"
5
6
  import { parseAttach } from "./parser-attach.js"
6
7
  import { parseAuth } from "./parser-auth.js"
7
8
  import { parseClone } from "./parser-clone.js"
@@ -74,6 +75,7 @@ export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, P
74
75
  Match.when("auth", () => parseAuth(rest))
75
76
  )
76
77
  .pipe(
78
+ Match.when("apply", () => parseApply(rest)),
77
79
  Match.when("state", () => parseState(rest)),
78
80
  Match.orElse(() => Either.left(unknownCommandError))
79
81
  )
@@ -3,8 +3,9 @@ import { Match } from "effect"
3
3
  import type { ParseError } from "@effect-template/lib/core/domain"
4
4
 
5
5
  export const usageText = `docker-git menu
6
- docker-git create --repo-url <url> [options]
6
+ docker-git create [--repo-url <url>] [options]
7
7
  docker-git clone <url> [options]
8
+ docker-git apply [<url>] [options]
8
9
  docker-git mcp-playwright [<url>] [options]
9
10
  docker-git attach [<url>] [options]
10
11
  docker-git panes [<url>] [options]
@@ -19,8 +20,9 @@ docker-git state <action> [options]
19
20
 
20
21
  Commands:
21
22
  menu Interactive menu (default when no args)
22
- create, init Generate docker development environment
23
+ create, init Generate docker development environment (repo URL optional)
23
24
  clone Create + run container and clone repo
25
+ apply Apply docker-git config to an existing project/container (current dir by default)
24
26
  mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
25
27
  attach, tmux Open tmux workspace for a docker-git project
26
28
  panes, terms List tmux panes for a docker-git project
@@ -32,9 +34,10 @@ Commands:
32
34
  state Manage docker-git state directory via git (sync across machines)
33
35
 
34
36
  Options:
37
+ --repo-url <url> Repository URL (create: optional; clone: required via positional arg or flag)
35
38
  --repo-ref <ref> Git ref/branch (default: main)
36
39
  --branch, -b <ref> Alias for --repo-ref
37
- --target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: /home/dev/<org>/<repo>[/issue-<id>|/pr-<id>])
40
+ --target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: ~/workspaces/<org>/<repo>[/issue-<id>|/pr-<id>])
38
41
  --ssh-port <port> Local SSH port (default: 2222)
39
42
  --ssh-user <user> SSH user inside container (default: dev)
40
43
  --container-name <name> Docker container name (default: dg-<repo>)
@@ -45,10 +48,15 @@ Options:
45
48
  --env-project <path> Host path to project env file (default: ./.orch/env/project.env)
46
49
  --codex-auth <path> Host path for Codex auth cache (default: <projectsRoot>/.orch/auth/codex)
47
50
  --codex-home <path> Container path for Codex auth (default: /home/dev/.codex)
51
+ --network-mode <mode> Compose network mode: shared|project (default: shared)
52
+ --shared-network <name> Shared Docker network name when network-mode=shared (default: docker-git-shared)
48
53
  --out-dir <path> Output directory (default: <projectsRoot>/<org>/<repo>[/issue-<id>|/pr-<id>])
49
54
  --project-dir <path> Project directory for attach (default: .)
50
55
  --archive <path> Scrap snapshot directory (default: .orch/scrap/session)
51
56
  --mode <session> Scrap mode (default: session)
57
+ --git-token <label> Token label for clone/create (maps to GITHUB_TOKEN__<LABEL>, example: agiens)
58
+ --codex-token <label> Codex auth label for clone/create (maps to CODEX_AUTH_LABEL, example: agien)
59
+ --claude-token <label> Claude auth label for clone/create (maps to CLAUDE_AUTH_LABEL, example: agien)
52
60
  --wipe | --no-wipe Wipe workspace before scrap import (default: --wipe)
53
61
  --lines <n> Tail last N lines for sessions logs (default: 200)
54
62
  --include-default Show default/system processes in sessions list
@@ -9,6 +9,7 @@ import {
9
9
  listProjectStatus,
10
10
  listRunningProjectItems
11
11
  } from "@effect-template/lib/usecases/projects"
12
+ import { gcProjectNetworkByTemplate } from "@effect-template/lib/usecases/docker-network-gc"
12
13
  import { runDockerComposeUpWithPortCheck } from "@effect-template/lib/usecases/projects-up"
13
14
  import { Effect, Match, pipe } from "effect"
14
15
 
@@ -149,8 +150,10 @@ const handleMenuAction = (
149
150
  withProjectConfig(state, setMessage, () =>
150
151
  runDockerComposeLogs(state.activeDir ?? state.cwd))),
151
152
  Match.when({ _tag: "Down" }, () =>
152
- withProjectConfig(state, setMessage, () =>
153
- runDockerComposeDown(state.activeDir ?? state.cwd))),
153
+ withProjectConfig(state, setMessage, (config) =>
154
+ runDockerComposeDown(state.activeDir ?? state.cwd).pipe(
155
+ Effect.zipRight(gcProjectNetworkByTemplate(state.activeDir ?? state.cwd, config.template))
156
+ ))),
154
157
  Match.when({ _tag: "DownAll" }, () =>
155
158
  pipe(
156
159
  downAllDockerGitProjects,
@@ -46,11 +46,16 @@ type CreateReturnContext = CreateContext & {
46
46
  }
47
47
 
48
48
  export const buildCreateArgs = (input: CreateInputs): ReadonlyArray<string> => {
49
- const args: Array<string> = ["create", "--repo-url", input.repoUrl]
49
+ const args: Array<string> = ["create"]
50
+ if (input.repoUrl.length > 0) {
51
+ args.push("--repo-url", input.repoUrl)
52
+ }
50
53
  if (input.repoRef.length > 0) {
51
54
  args.push("--repo-ref", input.repoRef)
52
55
  }
53
- args.push("--out-dir", input.outDir)
56
+ if (input.outDir.length > 0) {
57
+ args.push("--out-dir", input.outDir)
58
+ }
54
59
  if (!input.runUp) {
55
60
  args.push("--no-up")
56
61
  }
@@ -106,8 +111,8 @@ export const resolveCreateInputs = (
106
111
  values: Partial<CreateInputs>
107
112
  ): CreateInputs => {
108
113
  const repoUrl = values.repoUrl ?? ""
109
- const resolvedRepoRef = repoUrl.length > 0 ? resolveRepoInput(repoUrl).repoRef : undefined
110
- const outDir = values.outDir ?? (repoUrl.length > 0 ? resolveDefaultOutDir(cwd, repoUrl) : "")
114
+ const resolvedRepoRef = resolveRepoInput(repoUrl).repoRef
115
+ const outDir = values.outDir ?? resolveDefaultOutDir(cwd, repoUrl)
111
116
 
112
117
  return {
113
118
  repoUrl,
@@ -179,10 +184,6 @@ const applyCreateStep = (input: {
179
184
  }): boolean =>
180
185
  Match.value(input.step).pipe(
181
186
  Match.when("repoUrl", () => {
182
- if (input.buffer.length === 0) {
183
- input.setMessage("Repo URL is required.")
184
- return false
185
- }
186
187
  input.nextValues.repoUrl = input.buffer
187
188
  input.nextValues.outDir = resolveDefaultOutDir(input.cwd, input.buffer)
188
189
  return true
@@ -222,11 +223,6 @@ const finalizeCreateFlow = (input: {
222
223
  readonly setActiveDir: (dir: string | null) => void
223
224
  }) => {
224
225
  const inputs = resolveCreateInputs(input.state.cwd, input.nextValues)
225
- if (inputs.repoUrl.length === 0) {
226
- input.setMessage("Repo URL is required.")
227
- return
228
- }
229
-
230
226
  const parsed = parseArgs(buildCreateArgs(inputs))
231
227
  if (Either.isLeft(parsed)) {
232
228
  input.setMessage(formatParseError(parsed.left))
@@ -28,7 +28,7 @@ import { createSteps, menuItems } from "./menu-types.js"
28
28
 
29
29
  export const renderStepLabel = (step: CreateStep, defaults: CreateInputs): string =>
30
30
  Match.value(step).pipe(
31
- Match.when("repoUrl", () => "Repo URL"),
31
+ Match.when("repoUrl", () => "Repo URL (optional for empty workspace)"),
32
32
  Match.when("repoRef", () => `Repo ref [${defaults.repoRef}]`),
33
33
  Match.when("outDir", () => `Output dir [${defaults.outDir}]`),
34
34
  Match.when("runUp", () => `Run docker compose up now? [${defaults.runUp ? "Y" : "n"}]`),
@@ -1,5 +1,6 @@
1
1
  import type { Command, ParseError } from "@effect-template/lib/core/domain"
2
2
  import { createProject } from "@effect-template/lib/usecases/actions"
3
+ import { applyProjectConfig } from "@effect-template/lib/usecases/apply"
3
4
  import {
4
5
  authClaudeLogin,
5
6
  authClaudeLogout,
@@ -97,6 +98,7 @@ const handleNonBaseCommand = (command: NonBaseCommand) =>
97
98
  Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd))
98
99
  )
99
100
  .pipe(
101
+ Match.when({ _tag: "Apply" }, (cmd) => applyProjectConfig(cmd)),
100
102
  Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)),
101
103
  Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)),
102
104
  Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)),
@@ -17,12 +17,23 @@ describe("renderEntrypoint auth bridge", () => {
17
17
  "GIT_AUTH_TOKEN=\"${GIT_AUTH_TOKEN:-${GITHUB_TOKEN:-${GH_TOKEN:-}}}\""
18
18
  )
19
19
  expect(entrypoint).toContain("GITHUB_TOKEN=\"${GITHUB_TOKEN:-${GH_TOKEN:-}}\"")
20
- expect(entrypoint).toContain("if [[ -n \"$GH_TOKEN\" || -n \"$GITHUB_TOKEN\" ]]; then")
20
+ expect(entrypoint).toContain("AUTH_LABEL_RAW=\"${GIT_AUTH_LABEL:-${GITHUB_AUTH_LABEL:-}}\"")
21
+ expect(entrypoint).toContain("LABELED_GITHUB_TOKEN_KEY=\"GITHUB_TOKEN__$RESOLVED_AUTH_LABEL\"")
22
+ expect(entrypoint).toContain("LABELED_GIT_TOKEN_KEY=\"GIT_AUTH_TOKEN__$RESOLVED_AUTH_LABEL\"")
23
+ expect(entrypoint).toContain("if [[ -n \"$EFFECTIVE_GH_TOKEN\" ]]; then")
21
24
  expect(entrypoint).toContain(String.raw`printf "export GITHUB_TOKEN=%q\n" "$EFFECTIVE_GITHUB_TOKEN"`)
25
+ expect(entrypoint).toContain(String.raw`printf "export GH_TOKEN=%q\n" "$EFFECTIVE_GH_TOKEN"`)
26
+ expect(entrypoint).toContain(String.raw`printf "export GIT_AUTH_TOKEN=%q\n" "$EFFECTIVE_GITHUB_TOKEN"`)
22
27
  expect(entrypoint).toContain("docker_git_upsert_ssh_env \"GITHUB_TOKEN\" \"$EFFECTIVE_GITHUB_TOKEN\"")
28
+ expect(entrypoint).toContain("docker_git_upsert_ssh_env \"GH_TOKEN\" \"$EFFECTIVE_GH_TOKEN\"")
29
+ expect(entrypoint).toContain("docker_git_upsert_ssh_env \"GIT_AUTH_TOKEN\" \"$EFFECTIVE_GITHUB_TOKEN\"")
23
30
  expect(entrypoint).toContain("GIT_CREDENTIAL_HELPER_PATH=\"/usr/local/bin/docker-git-credential-helper\"")
24
- expect(entrypoint).toContain("token=\"$GITHUB_TOKEN\"")
25
- expect(entrypoint).toContain("token=\"$GH_TOKEN\"")
31
+ expect(entrypoint).toContain("CLAUDE_REAL_BIN=\"/usr/local/bin/.docker-git-claude-real\"")
32
+ expect(entrypoint).toContain("CLAUDE_WRAPPER_BIN=\"/usr/local/bin/claude\"")
33
+ expect(entrypoint).toContain("cat <<'EOF' > \"$CLAUDE_WRAPPER_BIN\"")
34
+ expect(entrypoint).toContain("CLAUDE_CONFIG_DIR=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"")
35
+ expect(entrypoint).toContain("token=\"${GITHUB_TOKEN:-}\"")
36
+ expect(entrypoint).toContain("token=\"${GH_TOKEN:-}\"")
26
37
  expect(entrypoint).toContain(String.raw`printf "%s\n" "password=$token"`)
27
38
  expect(entrypoint).toContain("git config --global credential.helper")
28
39
  }))
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Either } from "effect"
3
+
4
+ import { parseArgs } from "../../src/docker-git/cli/parser.js"
5
+
6
+ describe("parseArgs network options", () => {
7
+ it.effect("parses create network mode options", () =>
8
+ Effect.sync(() => {
9
+ const parsed = parseArgs([
10
+ "create",
11
+ "--repo-url",
12
+ "https://github.com/org/repo.git",
13
+ "--network-mode",
14
+ "project",
15
+ "--shared-network",
16
+ "ignored-shared-network"
17
+ ])
18
+ if (Either.isLeft(parsed)) {
19
+ throw new Error(`unexpected parse error: ${parsed.left._tag}`)
20
+ }
21
+ const command = parsed.right
22
+ if (command._tag !== "Create") {
23
+ throw new Error("expected Create command")
24
+ }
25
+ expect(command.config.dockerNetworkMode).toBe("project")
26
+ expect(command.config.dockerSharedNetworkName).toBe("ignored-shared-network")
27
+ }))
28
+
29
+ it.effect("fails on invalid network mode", () =>
30
+ Effect.sync(() => {
31
+ const command = parseArgs([
32
+ "create",
33
+ "--repo-url",
34
+ "https://github.com/org/repo.git",
35
+ "--network-mode",
36
+ "invalid"
37
+ ])
38
+ Either.match(command, {
39
+ onLeft: (error) => {
40
+ expect(error._tag).toBe("InvalidOption")
41
+ },
42
+ onRight: () => {
43
+ throw new Error("expected parse error")
44
+ }
45
+ })
46
+ }))
47
+ })