@prover-coder-ai/docker-git 1.0.20 → 1.0.21
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.
- package/.package.json.release.bak +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/main.js +60 -35
- package/dist/main.js.map +1 -1
- package/dist/src/docker-git/cli/parser-apply.js +22 -0
- package/dist/src/docker-git/cli/parser-clone.js +3 -4
- package/dist/src/docker-git/cli/parser-options.js +44 -19
- package/dist/src/docker-git/cli/parser.js +2 -1
- package/dist/src/docker-git/cli/usage.js +6 -1
- package/dist/src/docker-git/program.js +2 -1
- package/package.json +1 -1
- package/src/docker-git/cli/parser-apply.ts +28 -0
- package/src/docker-git/cli/parser-clone.ts +3 -9
- package/src/docker-git/cli/parser-options.ts +65 -22
- package/src/docker-git/cli/parser.ts +2 -0
- package/src/docker-git/cli/usage.ts +6 -1
- package/src/docker-git/program.ts +2 -0
- package/tests/docker-git/entrypoint-auth.test.ts +14 -3
- package/tests/docker-git/parser.test.ts +89 -17
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Either } from "effect";
|
|
2
|
+
import {} from "@effect-template/lib/core/domain";
|
|
3
|
+
import { parseProjectDirWithOptions } from "./parser-shared.js";
|
|
4
|
+
// CHANGE: parse "apply" command for existing docker-git projects
|
|
5
|
+
// WHY: update managed docker-git config on the current project/container without creating a new project
|
|
6
|
+
// QUOTE(ТЗ): "Не создавать новый... а прямо в текущем обновить её на актуальную"
|
|
7
|
+
// REF: issue-72-followup-apply-current-config
|
|
8
|
+
// SOURCE: n/a
|
|
9
|
+
// FORMAT THEOREM: forall argv: parseApply(argv) = cmd -> deterministic(cmd)
|
|
10
|
+
// PURITY: CORE
|
|
11
|
+
// EFFECT: Effect<ApplyCommand, ParseError, never>
|
|
12
|
+
// INVARIANT: projectDir is never empty
|
|
13
|
+
// COMPLEXITY: O(n) where n = |argv|
|
|
14
|
+
export const parseApply = (args) => Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
|
|
15
|
+
_tag: "Apply",
|
|
16
|
+
projectDir,
|
|
17
|
+
runUp: raw.up ?? true,
|
|
18
|
+
gitTokenLabel: raw.gitTokenLabel,
|
|
19
|
+
codexTokenLabel: raw.codexTokenLabel,
|
|
20
|
+
claudeTokenLabel: raw.claudeTokenLabel,
|
|
21
|
+
enableMcpPlaywright: raw.enableMcpPlaywright
|
|
22
|
+
}));
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { Either } from "effect";
|
|
2
2
|
import { buildCreateCommand, nonEmpty } from "@effect-template/lib/core/command-builders";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveRepoInput } from "@effect-template/lib/core/domain";
|
|
4
4
|
import { parseRawOptions } from "./parser-options.js";
|
|
5
5
|
import { resolveWorkspaceRepoPath, splitPositionalRepo } from "./parser-shared.js";
|
|
6
6
|
const applyCloneDefaults = (raw, rawRepoUrl, resolvedRepo) => {
|
|
7
7
|
const repoPath = resolveWorkspaceRepoPath(resolvedRepo);
|
|
8
|
-
const
|
|
9
|
-
const homeDir = `/home/${sshUser}`;
|
|
8
|
+
const targetHome = "~";
|
|
10
9
|
return {
|
|
11
10
|
...raw,
|
|
12
11
|
repoUrl: rawRepoUrl,
|
|
13
12
|
outDir: raw.outDir ?? `.docker-git/${repoPath}`,
|
|
14
|
-
targetDir: raw.targetDir ?? `${
|
|
13
|
+
targetDir: raw.targetDir ?? `${targetHome}/workspaces/${repoPath}`
|
|
15
14
|
};
|
|
16
15
|
};
|
|
17
16
|
// CHANGE: parse clone command with positional repo url
|
|
@@ -19,6 +19,9 @@ const valueOptionSpecs = [
|
|
|
19
19
|
{ flag: "--archive", key: "archivePath" },
|
|
20
20
|
{ flag: "--mode", key: "scrapMode" },
|
|
21
21
|
{ flag: "--label", key: "label" },
|
|
22
|
+
{ flag: "--git-token", key: "gitTokenLabel" },
|
|
23
|
+
{ flag: "--codex-token", key: "codexTokenLabel" },
|
|
24
|
+
{ flag: "--claude-token", key: "claudeTokenLabel" },
|
|
22
25
|
{ flag: "--token", key: "token" },
|
|
23
26
|
{ flag: "--scopes", key: "scopes" },
|
|
24
27
|
{ flag: "--message", key: "message" },
|
|
@@ -60,6 +63,9 @@ const valueFlagUpdaters = {
|
|
|
60
63
|
archivePath: (raw, value) => ({ ...raw, archivePath: value }),
|
|
61
64
|
scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
|
|
62
65
|
label: (raw, value) => ({ ...raw, label: value }),
|
|
66
|
+
gitTokenLabel: (raw, value) => ({ ...raw, gitTokenLabel: value }),
|
|
67
|
+
codexTokenLabel: (raw, value) => ({ ...raw, codexTokenLabel: value }),
|
|
68
|
+
claudeTokenLabel: (raw, value) => ({ ...raw, claudeTokenLabel: value }),
|
|
63
69
|
token: (raw, value) => ({ ...raw, token: value }),
|
|
64
70
|
scopes: (raw, value) => ({ ...raw, scopes: value }),
|
|
65
71
|
message: (raw, value) => ({ ...raw, message: value }),
|
|
@@ -79,30 +85,49 @@ export const applyCommandValueFlag = (raw, token, value) => {
|
|
|
79
85
|
const update = valueFlagUpdaters[valueSpec.key];
|
|
80
86
|
return Either.right(update(raw, value));
|
|
81
87
|
};
|
|
88
|
+
const parseInlineValueToken = (raw, token) => {
|
|
89
|
+
const equalIndex = token.indexOf("=");
|
|
90
|
+
if (equalIndex <= 0 || !token.startsWith("-")) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const flag = token.slice(0, equalIndex);
|
|
94
|
+
const inlineValue = token.slice(equalIndex + 1);
|
|
95
|
+
return applyCommandValueFlag(raw, flag, inlineValue);
|
|
96
|
+
};
|
|
97
|
+
const parseRawOptionsStep = (args, index, raw) => {
|
|
98
|
+
const token = args[index] ?? "";
|
|
99
|
+
const inlineApplied = parseInlineValueToken(raw, token);
|
|
100
|
+
if (inlineApplied !== null) {
|
|
101
|
+
return Either.isLeft(inlineApplied)
|
|
102
|
+
? { _tag: "error", error: inlineApplied.left }
|
|
103
|
+
: { _tag: "ok", raw: inlineApplied.right, nextIndex: index + 1 };
|
|
104
|
+
}
|
|
105
|
+
const booleanApplied = applyCommandBooleanFlag(raw, token);
|
|
106
|
+
if (booleanApplied !== null) {
|
|
107
|
+
return { _tag: "ok", raw: booleanApplied, nextIndex: index + 1 };
|
|
108
|
+
}
|
|
109
|
+
if (!token.startsWith("-")) {
|
|
110
|
+
return { _tag: "error", error: { _tag: "UnexpectedArgument", value: token } };
|
|
111
|
+
}
|
|
112
|
+
const value = args[index + 1];
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } };
|
|
115
|
+
}
|
|
116
|
+
const nextRaw = applyCommandValueFlag(raw, token, value);
|
|
117
|
+
return Either.isLeft(nextRaw)
|
|
118
|
+
? { _tag: "error", error: nextRaw.left }
|
|
119
|
+
: { _tag: "ok", raw: nextRaw.right, nextIndex: index + 2 };
|
|
120
|
+
};
|
|
82
121
|
export const parseRawOptions = (args) => {
|
|
83
122
|
let index = 0;
|
|
84
123
|
let raw = {};
|
|
85
124
|
while (index < args.length) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
raw = booleanApplied;
|
|
90
|
-
index += 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (!token.startsWith("-")) {
|
|
94
|
-
return Either.left({ _tag: "UnexpectedArgument", value: token });
|
|
95
|
-
}
|
|
96
|
-
const value = args[index + 1];
|
|
97
|
-
if (value === undefined) {
|
|
98
|
-
return Either.left({ _tag: "MissingOptionValue", option: token });
|
|
99
|
-
}
|
|
100
|
-
const nextRaw = applyCommandValueFlag(raw, token, value);
|
|
101
|
-
if (Either.isLeft(nextRaw)) {
|
|
102
|
-
return Either.left(nextRaw.left);
|
|
125
|
+
const step = parseRawOptionsStep(args, index, raw);
|
|
126
|
+
if (step._tag === "error") {
|
|
127
|
+
return Either.left(step.error);
|
|
103
128
|
}
|
|
104
|
-
raw =
|
|
105
|
-
index
|
|
129
|
+
raw = step.raw;
|
|
130
|
+
index = step.nextIndex;
|
|
106
131
|
}
|
|
107
132
|
return Either.right(raw);
|
|
108
133
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Either, Match } from "effect";
|
|
2
2
|
import {} from "@effect-template/lib/core/domain";
|
|
3
|
+
import { parseApply } from "./parser-apply.js";
|
|
3
4
|
import { parseAttach } from "./parser-attach.js";
|
|
4
5
|
import { parseAuth } from "./parser-auth.js";
|
|
5
6
|
import { parseClone } from "./parser-clone.js";
|
|
@@ -42,5 +43,5 @@ export const parseArgs = (args) => {
|
|
|
42
43
|
};
|
|
43
44
|
return Match.value(command)
|
|
44
45
|
.pipe(Match.when("create", () => parseCreate(rest)), Match.when("init", () => parseCreate(rest)), Match.when("clone", () => parseClone(rest)), Match.when("attach", () => parseAttach(rest)), Match.when("tmux", () => parseAttach(rest)), Match.when("panes", () => parsePanes(rest)), Match.when("terms", () => parsePanes(rest)), Match.when("terminals", () => parsePanes(rest)), Match.when("sessions", () => parseSessions(rest)), Match.when("scrap", () => parseScrap(rest)), Match.when("mcp-playwright", () => parseMcpPlaywright(rest)), Match.when("help", () => Either.right(helpCommand)), Match.when("ps", () => Either.right(statusCommand)), Match.when("status", () => Either.right(statusCommand)), Match.when("down-all", () => Either.right(downAllCommand)), Match.when("stop-all", () => Either.right(downAllCommand)), Match.when("kill-all", () => Either.right(downAllCommand)), Match.when("menu", () => Either.right(menuCommand)), Match.when("ui", () => Either.right(menuCommand)), Match.when("auth", () => parseAuth(rest)))
|
|
45
|
-
.pipe(Match.when("state", () => parseState(rest)), Match.orElse(() => Either.left(unknownCommandError)));
|
|
46
|
+
.pipe(Match.when("apply", () => parseApply(rest)), Match.when("state", () => parseState(rest)), Match.orElse(() => Either.left(unknownCommandError)));
|
|
46
47
|
};
|
|
@@ -2,6 +2,7 @@ import { Match } from "effect";
|
|
|
2
2
|
export const usageText = `docker-git menu
|
|
3
3
|
docker-git create --repo-url <url> [options]
|
|
4
4
|
docker-git clone <url> [options]
|
|
5
|
+
docker-git apply [<url>] [options]
|
|
5
6
|
docker-git mcp-playwright [<url>] [options]
|
|
6
7
|
docker-git attach [<url>] [options]
|
|
7
8
|
docker-git panes [<url>] [options]
|
|
@@ -18,6 +19,7 @@ Commands:
|
|
|
18
19
|
menu Interactive menu (default when no args)
|
|
19
20
|
create, init Generate docker development environment
|
|
20
21
|
clone Create + run container and clone repo
|
|
22
|
+
apply Apply docker-git config to an existing project/container (current dir by default)
|
|
21
23
|
mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
|
|
22
24
|
attach, tmux Open tmux workspace for a docker-git project
|
|
23
25
|
panes, terms List tmux panes for a docker-git project
|
|
@@ -31,7 +33,7 @@ Commands:
|
|
|
31
33
|
Options:
|
|
32
34
|
--repo-ref <ref> Git ref/branch (default: main)
|
|
33
35
|
--branch, -b <ref> Alias for --repo-ref
|
|
34
|
-
--target-dir <path> Target dir inside container (create default: /home/dev/app, clone default:
|
|
36
|
+
--target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: ~/workspaces/<org>/<repo>[/issue-<id>|/pr-<id>])
|
|
35
37
|
--ssh-port <port> Local SSH port (default: 2222)
|
|
36
38
|
--ssh-user <user> SSH user inside container (default: dev)
|
|
37
39
|
--container-name <name> Docker container name (default: dg-<repo>)
|
|
@@ -46,6 +48,9 @@ Options:
|
|
|
46
48
|
--project-dir <path> Project directory for attach (default: .)
|
|
47
49
|
--archive <path> Scrap snapshot directory (default: .orch/scrap/session)
|
|
48
50
|
--mode <session> Scrap mode (default: session)
|
|
51
|
+
--git-token <label> Token label for clone/create (maps to GITHUB_TOKEN__<LABEL>, example: agiens)
|
|
52
|
+
--codex-token <label> Codex auth label for clone/create (maps to CODEX_AUTH_LABEL, example: agien)
|
|
53
|
+
--claude-token <label> Claude auth label for clone/create (maps to CLAUDE_AUTH_LABEL, example: agien)
|
|
49
54
|
--wipe | --no-wipe Wipe workspace before scrap import (default: --wipe)
|
|
50
55
|
--lines <n> Tail last N lines for sessions logs (default: 200)
|
|
51
56
|
--include-default Show default/system processes in sessions list
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createProject } from "@effect-template/lib/usecases/actions";
|
|
2
|
+
import { applyProjectConfig } from "@effect-template/lib/usecases/apply";
|
|
2
3
|
import { authClaudeLogin, authClaudeLogout, authClaudeStatus, authCodexLogin, authCodexLogout, authCodexStatus, authGithubLogin, authGithubLogout, authGithubStatus } from "@effect-template/lib/usecases/auth";
|
|
3
4
|
import { renderError } from "@effect-template/lib/usecases/errors";
|
|
4
5
|
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright";
|
|
@@ -23,7 +24,7 @@ const logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)),
|
|
|
23
24
|
const logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
24
25
|
const handleNonBaseCommand = (command) => Match.value(command)
|
|
25
26
|
.pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "AuthClaudeLogin" }, (cmd) => authClaudeLogin(cmd)), Match.when({ _tag: "AuthClaudeStatus" }, (cmd) => authClaudeStatus(cmd)), Match.when({ _tag: "AuthClaudeLogout" }, (cmd) => authClaudeLogout(cmd)), Match.when({ _tag: "Attach" }, (cmd) => attachTmux(cmd)), Match.when({ _tag: "Panes" }, (cmd) => listTmuxPanes(cmd)), Match.when({ _tag: "SessionsList" }, (cmd) => listTerminalSessions(cmd)), Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd)))
|
|
26
|
-
.pipe(Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
27
|
+
.pipe(Match.when({ _tag: "Apply" }, (cmd) => applyProjectConfig(cmd)), Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
27
28
|
// CHANGE: compose CLI program with typed errors and shell effects
|
|
28
29
|
// WHY: keep a thin entry layer over pure parsing and template generation
|
|
29
30
|
// QUOTE(ТЗ): "CLI команду... создавать докер образы"
|
package/package.json
CHANGED
|
@@ -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
|
|
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 ?? `${
|
|
21
|
+
targetDir: raw.targetDir ?? `${targetHome}/workspaces/${repoPath}`
|
|
28
22
|
}
|
|
29
23
|
}
|
|
30
24
|
|
|
@@ -23,6 +23,9 @@ interface ValueOptionSpec {
|
|
|
23
23
|
| "archivePath"
|
|
24
24
|
| "scrapMode"
|
|
25
25
|
| "label"
|
|
26
|
+
| "gitTokenLabel"
|
|
27
|
+
| "codexTokenLabel"
|
|
28
|
+
| "claudeTokenLabel"
|
|
26
29
|
| "token"
|
|
27
30
|
| "scopes"
|
|
28
31
|
| "message"
|
|
@@ -51,6 +54,9 @@ const valueOptionSpecs: ReadonlyArray<ValueOptionSpec> = [
|
|
|
51
54
|
{ flag: "--archive", key: "archivePath" },
|
|
52
55
|
{ flag: "--mode", key: "scrapMode" },
|
|
53
56
|
{ flag: "--label", key: "label" },
|
|
57
|
+
{ flag: "--git-token", key: "gitTokenLabel" },
|
|
58
|
+
{ flag: "--codex-token", key: "codexTokenLabel" },
|
|
59
|
+
{ flag: "--claude-token", key: "claudeTokenLabel" },
|
|
54
60
|
{ flag: "--token", key: "token" },
|
|
55
61
|
{ flag: "--scopes", key: "scopes" },
|
|
56
62
|
{ flag: "--message", key: "message" },
|
|
@@ -99,6 +105,9 @@ const valueFlagUpdaters: { readonly [K in ValueKey]: (raw: RawOptions, value: st
|
|
|
99
105
|
archivePath: (raw, value) => ({ ...raw, archivePath: value }),
|
|
100
106
|
scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
|
|
101
107
|
label: (raw, value) => ({ ...raw, label: value }),
|
|
108
|
+
gitTokenLabel: (raw, value) => ({ ...raw, gitTokenLabel: value }),
|
|
109
|
+
codexTokenLabel: (raw, value) => ({ ...raw, codexTokenLabel: value }),
|
|
110
|
+
claudeTokenLabel: (raw, value) => ({ ...raw, claudeTokenLabel: value }),
|
|
102
111
|
token: (raw, value) => ({ ...raw, token: value }),
|
|
103
112
|
scopes: (raw, value) => ({ ...raw, scopes: value }),
|
|
104
113
|
message: (raw, value) => ({ ...raw, message: value }),
|
|
@@ -126,34 +135,68 @@ export const applyCommandValueFlag = (
|
|
|
126
135
|
return Either.right(update(raw, value))
|
|
127
136
|
}
|
|
128
137
|
|
|
138
|
+
type ParseRawOptionsStep =
|
|
139
|
+
| { readonly _tag: "ok"; readonly raw: RawOptions; readonly nextIndex: number }
|
|
140
|
+
| { readonly _tag: "error"; readonly error: ParseError }
|
|
141
|
+
|
|
142
|
+
const parseInlineValueToken = (
|
|
143
|
+
raw: RawOptions,
|
|
144
|
+
token: string
|
|
145
|
+
): Either.Either<RawOptions, ParseError> | null => {
|
|
146
|
+
const equalIndex = token.indexOf("=")
|
|
147
|
+
if (equalIndex <= 0 || !token.startsWith("-")) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const flag = token.slice(0, equalIndex)
|
|
152
|
+
const inlineValue = token.slice(equalIndex + 1)
|
|
153
|
+
return applyCommandValueFlag(raw, flag, inlineValue)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const parseRawOptionsStep = (
|
|
157
|
+
args: ReadonlyArray<string>,
|
|
158
|
+
index: number,
|
|
159
|
+
raw: RawOptions
|
|
160
|
+
): ParseRawOptionsStep => {
|
|
161
|
+
const token = args[index] ?? ""
|
|
162
|
+
const inlineApplied = parseInlineValueToken(raw, token)
|
|
163
|
+
if (inlineApplied !== null) {
|
|
164
|
+
return Either.isLeft(inlineApplied)
|
|
165
|
+
? { _tag: "error", error: inlineApplied.left }
|
|
166
|
+
: { _tag: "ok", raw: inlineApplied.right, nextIndex: index + 1 }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const booleanApplied = applyCommandBooleanFlag(raw, token)
|
|
170
|
+
if (booleanApplied !== null) {
|
|
171
|
+
return { _tag: "ok", raw: booleanApplied, nextIndex: index + 1 }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!token.startsWith("-")) {
|
|
175
|
+
return { _tag: "error", error: { _tag: "UnexpectedArgument", value: token } }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const value = args[index + 1]
|
|
179
|
+
if (value === undefined) {
|
|
180
|
+
return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const nextRaw = applyCommandValueFlag(raw, token, value)
|
|
184
|
+
return Either.isLeft(nextRaw)
|
|
185
|
+
? { _tag: "error", error: nextRaw.left }
|
|
186
|
+
: { _tag: "ok", raw: nextRaw.right, nextIndex: index + 2 }
|
|
187
|
+
}
|
|
188
|
+
|
|
129
189
|
export const parseRawOptions = (args: ReadonlyArray<string>): Either.Either<RawOptions, ParseError> => {
|
|
130
190
|
let index = 0
|
|
131
191
|
let raw: RawOptions = {}
|
|
132
192
|
|
|
133
193
|
while (index < args.length) {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
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)
|
|
194
|
+
const step = parseRawOptionsStep(args, index, raw)
|
|
195
|
+
if (step._tag === "error") {
|
|
196
|
+
return Either.left(step.error)
|
|
154
197
|
}
|
|
155
|
-
raw =
|
|
156
|
-
index
|
|
198
|
+
raw = step.raw
|
|
199
|
+
index = step.nextIndex
|
|
157
200
|
}
|
|
158
201
|
|
|
159
202
|
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
|
)
|
|
@@ -5,6 +5,7 @@ import type { ParseError } from "@effect-template/lib/core/domain"
|
|
|
5
5
|
export const usageText = `docker-git menu
|
|
6
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]
|
|
@@ -21,6 +22,7 @@ Commands:
|
|
|
21
22
|
menu Interactive menu (default when no args)
|
|
22
23
|
create, init Generate docker development environment
|
|
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
|
|
@@ -34,7 +36,7 @@ Commands:
|
|
|
34
36
|
Options:
|
|
35
37
|
--repo-ref <ref> Git ref/branch (default: main)
|
|
36
38
|
--branch, -b <ref> Alias for --repo-ref
|
|
37
|
-
--target-dir <path> Target dir inside container (create default: /home/dev/app, clone default:
|
|
39
|
+
--target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: ~/workspaces/<org>/<repo>[/issue-<id>|/pr-<id>])
|
|
38
40
|
--ssh-port <port> Local SSH port (default: 2222)
|
|
39
41
|
--ssh-user <user> SSH user inside container (default: dev)
|
|
40
42
|
--container-name <name> Docker container name (default: dg-<repo>)
|
|
@@ -49,6 +51,9 @@ Options:
|
|
|
49
51
|
--project-dir <path> Project directory for attach (default: .)
|
|
50
52
|
--archive <path> Scrap snapshot directory (default: .orch/scrap/session)
|
|
51
53
|
--mode <session> Scrap mode (default: session)
|
|
54
|
+
--git-token <label> Token label for clone/create (maps to GITHUB_TOKEN__<LABEL>, example: agiens)
|
|
55
|
+
--codex-token <label> Codex auth label for clone/create (maps to CODEX_AUTH_LABEL, example: agien)
|
|
56
|
+
--claude-token <label> Claude auth label for clone/create (maps to CLAUDE_AUTH_LABEL, example: agien)
|
|
52
57
|
--wipe | --no-wipe Wipe workspace before scrap import (default: --wipe)
|
|
53
58
|
--lines <n> Tail last N lines for sessions logs (default: 200)
|
|
54
59
|
--include-default Show default/system processes in sessions list
|
|
@@ -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("
|
|
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("
|
|
25
|
-
expect(entrypoint).toContain("
|
|
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
|
}))
|