@prover-coder-ai/docker-git 1.0.21 → 1.0.23

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 (70) hide show
  1. package/.package.json.release.bak +5 -3
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +31 -1
  4. package/dist/src/docker-git/main.js +10259 -12
  5. package/dist/src/docker-git/main.js.map +1 -0
  6. package/package.json +4 -4
  7. package/src/app/program.ts +16 -13
  8. package/src/docker-git/cli/parser-options.ts +6 -0
  9. package/src/docker-git/cli/parser.ts +1 -0
  10. package/src/docker-git/cli/usage.ts +9 -4
  11. package/src/docker-git/menu-actions.ts +5 -2
  12. package/src/docker-git/menu-create.ts +9 -13
  13. package/src/docker-git/menu-render.ts +1 -1
  14. package/tests/docker-git/parser-helpers.ts +76 -0
  15. package/tests/docker-git/parser-network-options.test.ts +47 -0
  16. package/tests/docker-git/parser.test.ts +30 -71
  17. package/vite.docker-git.config.ts +34 -0
  18. package/dist/main.js +0 -930
  19. package/dist/main.js.map +0 -1
  20. package/dist/src/app/main.js +0 -15
  21. package/dist/src/app/program.js +0 -61
  22. package/dist/src/docker-git/cli/input.js +0 -21
  23. package/dist/src/docker-git/cli/parser-apply.js +0 -22
  24. package/dist/src/docker-git/cli/parser-attach.js +0 -19
  25. package/dist/src/docker-git/cli/parser-auth.js +0 -90
  26. package/dist/src/docker-git/cli/parser-clone.js +0 -40
  27. package/dist/src/docker-git/cli/parser-create.js +0 -1
  28. package/dist/src/docker-git/cli/parser-mcp-playwright.js +0 -18
  29. package/dist/src/docker-git/cli/parser-options.js +0 -134
  30. package/dist/src/docker-git/cli/parser-panes.js +0 -19
  31. package/dist/src/docker-git/cli/parser-scrap.js +0 -74
  32. package/dist/src/docker-git/cli/parser-sessions.js +0 -69
  33. package/dist/src/docker-git/cli/parser-shared.js +0 -26
  34. package/dist/src/docker-git/cli/parser-state.js +0 -62
  35. package/dist/src/docker-git/cli/parser.js +0 -47
  36. package/dist/src/docker-git/cli/read-command.js +0 -17
  37. package/dist/src/docker-git/cli/usage.js +0 -113
  38. package/dist/src/docker-git/menu-actions.js +0 -135
  39. package/dist/src/docker-git/menu-auth-data.js +0 -90
  40. package/dist/src/docker-git/menu-auth-helpers.js +0 -20
  41. package/dist/src/docker-git/menu-auth.js +0 -159
  42. package/dist/src/docker-git/menu-buffer-input.js +0 -9
  43. package/dist/src/docker-git/menu-create.js +0 -199
  44. package/dist/src/docker-git/menu-input-handler.js +0 -109
  45. package/dist/src/docker-git/menu-input-utils.js +0 -47
  46. package/dist/src/docker-git/menu-input.js +0 -2
  47. package/dist/src/docker-git/menu-labeled-env.js +0 -33
  48. package/dist/src/docker-git/menu-menu.js +0 -46
  49. package/dist/src/docker-git/menu-project-auth-claude.js +0 -43
  50. package/dist/src/docker-git/menu-project-auth-data.js +0 -165
  51. package/dist/src/docker-git/menu-project-auth.js +0 -124
  52. package/dist/src/docker-git/menu-render-auth.js +0 -45
  53. package/dist/src/docker-git/menu-render-common.js +0 -26
  54. package/dist/src/docker-git/menu-render-layout.js +0 -14
  55. package/dist/src/docker-git/menu-render-project-auth.js +0 -37
  56. package/dist/src/docker-git/menu-render-select.js +0 -129
  57. package/dist/src/docker-git/menu-render.js +0 -137
  58. package/dist/src/docker-git/menu-select-actions.js +0 -66
  59. package/dist/src/docker-git/menu-select-connect.js +0 -6
  60. package/dist/src/docker-git/menu-select-load.js +0 -12
  61. package/dist/src/docker-git/menu-select-order.js +0 -21
  62. package/dist/src/docker-git/menu-select-runtime.js +0 -82
  63. package/dist/src/docker-git/menu-select-view.js +0 -15
  64. package/dist/src/docker-git/menu-select.js +0 -98
  65. package/dist/src/docker-git/menu-shared.js +0 -180
  66. package/dist/src/docker-git/menu-startup.js +0 -57
  67. package/dist/src/docker-git/menu-types.js +0 -21
  68. package/dist/src/docker-git/menu.js +0 -226
  69. package/dist/src/docker-git/program.js +0 -43
  70. package/dist/src/docker-git/tmux.js +0 -176
@@ -1,40 +0,0 @@
1
- import { Either } from "effect";
2
- import { buildCreateCommand, nonEmpty } from "@effect-template/lib/core/command-builders";
3
- import { resolveRepoInput } from "@effect-template/lib/core/domain";
4
- import { parseRawOptions } from "./parser-options.js";
5
- import { resolveWorkspaceRepoPath, splitPositionalRepo } from "./parser-shared.js";
6
- const applyCloneDefaults = (raw, rawRepoUrl, resolvedRepo) => {
7
- const repoPath = resolveWorkspaceRepoPath(resolvedRepo);
8
- const targetHome = "~";
9
- return {
10
- ...raw,
11
- repoUrl: rawRepoUrl,
12
- outDir: raw.outDir ?? `.docker-git/${repoPath}`,
13
- targetDir: raw.targetDir ?? `${targetHome}/workspaces/${repoPath}`
14
- };
15
- };
16
- // CHANGE: parse clone command with positional repo url
17
- // WHY: allow "docker-git clone <url>" to build + run a container
18
- // QUOTE(ТЗ): "docker-git clone url"
19
- // REF: user-request-2026-01-27
20
- // SOURCE: n/a
21
- // FORMAT THEOREM: forall argv: parseClone(argv) = cmd -> deterministic(cmd)
22
- // PURITY: CORE
23
- // EFFECT: Effect<Command, ParseError, never>
24
- // INVARIANT: first positional arg is treated as repo url
25
- // COMPLEXITY: O(n) where n = |argv|
26
- export const parseClone = (args) => {
27
- const { positionalRepoUrl, restArgs } = splitPositionalRepo(args);
28
- return Either.gen(function* (_) {
29
- const raw = yield* _(parseRawOptions(restArgs));
30
- const rawRepoUrl = yield* _(nonEmpty("--repo-url", raw.repoUrl ?? positionalRepoUrl));
31
- const resolvedRepo = resolveRepoInput(rawRepoUrl);
32
- const withDefaults = applyCloneDefaults(raw, rawRepoUrl, resolvedRepo);
33
- const withRef = resolvedRepo.repoRef !== undefined && raw.repoRef === undefined
34
- ? { ...withDefaults, repoRef: resolvedRepo.repoRef }
35
- : withDefaults;
36
- const openSsh = raw.openSsh ?? true;
37
- const create = yield* _(buildCreateCommand(withRef));
38
- return { ...create, waitForClone: true, openSsh };
39
- });
40
- };
@@ -1 +0,0 @@
1
- export { buildCreateCommand, nonEmpty } from "@effect-template/lib/core/command-builders";
@@ -1,18 +0,0 @@
1
- import { Either } from "effect";
2
- import {} from "@effect-template/lib/core/domain";
3
- import { parseProjectDirWithOptions } from "./parser-shared.js";
4
- // CHANGE: parse "mcp-playwright" command for existing docker-git projects
5
- // WHY: allow enabling Playwright MCP in an already created container/project dir
6
- // QUOTE(ТЗ): "Добавить возможность поднимать MCP Playrgiht в контейнере который уже создан"
7
- // REF: issue-29
8
- // SOURCE: n/a
9
- // FORMAT THEOREM: forall argv: parseMcpPlaywright(argv) = cmd -> deterministic(cmd)
10
- // PURITY: CORE
11
- // EFFECT: Effect<McpPlaywrightUpCommand, ParseError, never>
12
- // INVARIANT: projectDir is never empty
13
- // COMPLEXITY: O(n) where n = |argv|
14
- export const parseMcpPlaywright = (args) => Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
15
- _tag: "McpPlaywrightUp",
16
- projectDir,
17
- runUp: raw.up ?? true
18
- }));
@@ -1,134 +0,0 @@
1
- import { Either } from "effect";
2
- const valueOptionSpecs = [
3
- { flag: "--repo-url", key: "repoUrl" },
4
- { flag: "--repo-ref", key: "repoRef" },
5
- { flag: "--branch", key: "repoRef" },
6
- { flag: "-b", key: "repoRef" },
7
- { flag: "--target-dir", key: "targetDir" },
8
- { flag: "--ssh-port", key: "sshPort" },
9
- { flag: "--ssh-user", key: "sshUser" },
10
- { flag: "--container-name", key: "containerName" },
11
- { flag: "--service-name", key: "serviceName" },
12
- { flag: "--volume-name", key: "volumeName" },
13
- { flag: "--secrets-root", key: "secretsRoot" },
14
- { flag: "--authorized-keys", key: "authorizedKeysPath" },
15
- { flag: "--env-global", key: "envGlobalPath" },
16
- { flag: "--env-project", key: "envProjectPath" },
17
- { flag: "--codex-auth", key: "codexAuthPath" },
18
- { flag: "--codex-home", key: "codexHome" },
19
- { flag: "--archive", key: "archivePath" },
20
- { flag: "--mode", key: "scrapMode" },
21
- { flag: "--label", key: "label" },
22
- { flag: "--git-token", key: "gitTokenLabel" },
23
- { flag: "--codex-token", key: "codexTokenLabel" },
24
- { flag: "--claude-token", key: "claudeTokenLabel" },
25
- { flag: "--token", key: "token" },
26
- { flag: "--scopes", key: "scopes" },
27
- { flag: "--message", key: "message" },
28
- { flag: "-m", key: "message" },
29
- { flag: "--out-dir", key: "outDir" },
30
- { flag: "--project-dir", key: "projectDir" },
31
- { flag: "--lines", key: "lines" }
32
- ];
33
- const valueOptionSpecByFlag = new Map(valueOptionSpecs.map((spec) => [spec.flag, spec]));
34
- const booleanFlagUpdaters = {
35
- "--up": (raw) => ({ ...raw, up: true }),
36
- "--no-up": (raw) => ({ ...raw, up: false }),
37
- "--ssh": (raw) => ({ ...raw, openSsh: true }),
38
- "--no-ssh": (raw) => ({ ...raw, openSsh: false }),
39
- "--force": (raw) => ({ ...raw, force: true }),
40
- "--force-env": (raw) => ({ ...raw, forceEnv: true }),
41
- "--mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: true }),
42
- "--no-mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: false }),
43
- "--wipe": (raw) => ({ ...raw, wipe: true }),
44
- "--no-wipe": (raw) => ({ ...raw, wipe: false }),
45
- "--web": (raw) => ({ ...raw, authWeb: true }),
46
- "--include-default": (raw) => ({ ...raw, includeDefault: true })
47
- };
48
- const valueFlagUpdaters = {
49
- repoUrl: (raw, value) => ({ ...raw, repoUrl: value }),
50
- repoRef: (raw, value) => ({ ...raw, repoRef: value }),
51
- targetDir: (raw, value) => ({ ...raw, targetDir: value }),
52
- sshPort: (raw, value) => ({ ...raw, sshPort: value }),
53
- sshUser: (raw, value) => ({ ...raw, sshUser: value }),
54
- containerName: (raw, value) => ({ ...raw, containerName: value }),
55
- serviceName: (raw, value) => ({ ...raw, serviceName: value }),
56
- volumeName: (raw, value) => ({ ...raw, volumeName: value }),
57
- secretsRoot: (raw, value) => ({ ...raw, secretsRoot: value }),
58
- authorizedKeysPath: (raw, value) => ({ ...raw, authorizedKeysPath: value }),
59
- envGlobalPath: (raw, value) => ({ ...raw, envGlobalPath: value }),
60
- envProjectPath: (raw, value) => ({ ...raw, envProjectPath: value }),
61
- codexAuthPath: (raw, value) => ({ ...raw, codexAuthPath: value }),
62
- codexHome: (raw, value) => ({ ...raw, codexHome: value }),
63
- archivePath: (raw, value) => ({ ...raw, archivePath: value }),
64
- scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
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 }),
69
- token: (raw, value) => ({ ...raw, token: value }),
70
- scopes: (raw, value) => ({ ...raw, scopes: value }),
71
- message: (raw, value) => ({ ...raw, message: value }),
72
- outDir: (raw, value) => ({ ...raw, outDir: value }),
73
- projectDir: (raw, value) => ({ ...raw, projectDir: value }),
74
- lines: (raw, value) => ({ ...raw, lines: value })
75
- };
76
- export const applyCommandBooleanFlag = (raw, token) => {
77
- const updater = booleanFlagUpdaters[token];
78
- return updater ? updater(raw) : null;
79
- };
80
- export const applyCommandValueFlag = (raw, token, value) => {
81
- const valueSpec = valueOptionSpecByFlag.get(token);
82
- if (valueSpec === undefined) {
83
- return Either.left({ _tag: "UnknownOption", option: token });
84
- }
85
- const update = valueFlagUpdaters[valueSpec.key];
86
- return Either.right(update(raw, value));
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
- };
121
- export const parseRawOptions = (args) => {
122
- let index = 0;
123
- let raw = {};
124
- while (index < args.length) {
125
- const step = parseRawOptionsStep(args, index, raw);
126
- if (step._tag === "error") {
127
- return Either.left(step.error);
128
- }
129
- raw = step.raw;
130
- index = step.nextIndex;
131
- }
132
- return Either.right(raw);
133
- };
134
- export {} from "@effect-template/lib/core/command-options";
@@ -1,19 +0,0 @@
1
- import { Either } from "effect";
2
- import {} from "@effect-template/lib/core/domain";
3
- import { parseProjectDirArgs } from "./parser-shared.js";
4
- // CHANGE: parse panes command into a project selection
5
- // WHY: allow listing tmux panes without attaching
6
- // QUOTE(ТЗ): "покажи команду ... отобразит терминалы"
7
- // REF: user-request-2026-02-02-panes
8
- // SOURCE: n/a
9
- // FORMAT THEOREM: forall argv: parsePanes(argv) = cmd -> deterministic(cmd)
10
- // PURITY: CORE
11
- // EFFECT: Effect<PanesCommand, ParseError, never>
12
- // INVARIANT: projectDir is never empty
13
- // COMPLEXITY: O(n) where n = |argv|
14
- export const parsePanes = (args) => {
15
- return Either.map(parseProjectDirArgs(args), ({ projectDir }) => ({
16
- _tag: "Panes",
17
- projectDir
18
- }));
19
- };
@@ -1,74 +0,0 @@
1
- import { Either, Match } from "effect";
2
- import { parseProjectDirWithOptions } from "./parser-shared.js";
3
- const missingRequired = (option) => ({
4
- _tag: "MissingRequiredOption",
5
- option
6
- });
7
- const invalidScrapAction = (value) => ({
8
- _tag: "InvalidOption",
9
- option: "scrap",
10
- reason: `unknown action: ${value}`
11
- });
12
- const defaultSessionArchiveDir = ".orch/scrap/session";
13
- const invalidScrapMode = (value) => ({
14
- _tag: "InvalidOption",
15
- option: "--mode",
16
- reason: `unknown value: ${value} (expected session)`
17
- });
18
- const parseScrapMode = (raw) => {
19
- const value = raw?.trim();
20
- if (!value || value.length === 0) {
21
- return Either.right("session");
22
- }
23
- if (value === "session") {
24
- return Either.right("session");
25
- }
26
- if (value === "recipe") {
27
- // Backwards/semantic alias: "recipe" behaves like "session" (git state + rebuildable deps).
28
- return Either.right("session");
29
- }
30
- return Either.left(invalidScrapMode(value));
31
- };
32
- const makeScrapExportCommand = (projectDir, archivePath, mode) => ({
33
- _tag: "ScrapExport",
34
- projectDir,
35
- archivePath,
36
- mode
37
- });
38
- const makeScrapImportCommand = (projectDir, archivePath, wipe, mode) => ({
39
- _tag: "ScrapImport",
40
- projectDir,
41
- archivePath,
42
- wipe,
43
- mode
44
- });
45
- // CHANGE: parse scrap session export/import commands
46
- // WHY: store a small reproducible snapshot (git state + secrets) instead of large caches like node_modules
47
- // QUOTE(ТЗ): "не должно быть старого режима где он качает весь шлак типо node_modules"
48
- // REF: user-request-2026-02-15
49
- // SOURCE: n/a
50
- // FORMAT THEOREM: forall argv: parseScrap(argv) = cmd -> deterministic(cmd)
51
- // PURITY: CORE
52
- // EFFECT: Effect<Command, ParseError, never>
53
- // INVARIANT: export/import always resolves a projectDir
54
- // COMPLEXITY: O(n) where n = |argv|
55
- export const parseScrap = (args) => {
56
- const action = args[0]?.trim();
57
- if (!action || action.length === 0) {
58
- return Either.left(missingRequired("scrap <action>"));
59
- }
60
- const rest = args.slice(1);
61
- return Match.value(action).pipe(Match.when("export", () => Either.flatMap(parseProjectDirWithOptions(rest), ({ projectDir, raw }) => Either.map(parseScrapMode(raw.scrapMode), (mode) => {
62
- const archivePathRaw = raw.archivePath?.trim();
63
- if (archivePathRaw && archivePathRaw.length > 0) {
64
- return makeScrapExportCommand(projectDir, archivePathRaw, mode);
65
- }
66
- return makeScrapExportCommand(projectDir, defaultSessionArchiveDir, mode);
67
- }))), Match.when("import", () => Either.flatMap(parseProjectDirWithOptions(rest), ({ projectDir, raw }) => {
68
- const archivePath = raw.archivePath?.trim();
69
- if (!archivePath || archivePath.length === 0) {
70
- return Either.left(missingRequired("--archive"));
71
- }
72
- return Either.map(parseScrapMode(raw.scrapMode), (mode) => makeScrapImportCommand(projectDir, archivePath, raw.wipe ?? true, mode));
73
- })), Match.orElse(() => Either.left(invalidScrapAction(action))));
74
- };
@@ -1,69 +0,0 @@
1
- import { Either, Match } from "effect";
2
- import {} from "@effect-template/lib/core/domain";
3
- import { parseProjectDirWithOptions } from "./parser-shared.js";
4
- const defaultLines = 200;
5
- const parsePositiveInt = (option, raw) => {
6
- const value = Number.parseInt(raw, 10);
7
- if (!Number.isFinite(value) || value <= 0) {
8
- const error = {
9
- _tag: "InvalidOption",
10
- option,
11
- reason: "expected positive integer"
12
- };
13
- return Either.left(error);
14
- }
15
- return Either.right(value);
16
- };
17
- const parseList = (args) => Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
18
- _tag: "SessionsList",
19
- projectDir,
20
- includeDefault: raw.includeDefault === true
21
- }));
22
- const parsePidContext = (args) => Either.gen(function* (_) {
23
- const pidRaw = args[0];
24
- if (!pidRaw) {
25
- const error = { _tag: "MissingRequiredOption", option: "pid" };
26
- return yield* _(Either.left(error));
27
- }
28
- const pid = yield* _(parsePositiveInt("pid", pidRaw));
29
- const { projectDir, raw } = yield* _(parseProjectDirWithOptions(args.slice(1)));
30
- return { pid, projectDir, raw };
31
- });
32
- const parseKill = (args) => Either.map(parsePidContext(args), ({ pid, projectDir }) => ({
33
- _tag: "SessionsKill",
34
- projectDir,
35
- pid
36
- }));
37
- const parseLogs = (args) => Either.gen(function* (_) {
38
- const { pid, projectDir, raw } = yield* _(parsePidContext(args));
39
- const lines = raw.lines ? yield* _(parsePositiveInt("--lines", raw.lines)) : defaultLines;
40
- return { _tag: "SessionsLogs", projectDir, pid, lines };
41
- });
42
- // CHANGE: parse sessions command into list/kill/logs actions
43
- // WHY: surface container terminal sessions and background processes from CLI
44
- // QUOTE(ТЗ): "CLI команду которая из докера вернёт запущенные терминал сессии"
45
- // REF: user-request-2026-02-04-terminal-sessions
46
- // SOURCE: n/a
47
- // FORMAT THEOREM: forall argv: parseSessions(argv) = cmd -> deterministic(cmd)
48
- // PURITY: CORE
49
- // EFFECT: Effect<SessionsCommand, ParseError, never>
50
- // INVARIANT: pid/lines must be positive integers
51
- // COMPLEXITY: O(n) where n = |argv|
52
- export const parseSessions = (args) => {
53
- if (args.length === 0) {
54
- return parseList(args);
55
- }
56
- const first = args[0] ?? "";
57
- if (first.startsWith("-")) {
58
- return parseList(args);
59
- }
60
- const rest = args.slice(1);
61
- return Match.value(first).pipe(Match.when("list", () => parseList(rest)), Match.when("kill", () => parseKill(rest)), Match.when("stop", () => parseKill(rest)), Match.when("logs", () => parseLogs(rest)), Match.when("log", () => parseLogs(rest)), Match.orElse(() => {
62
- const error = {
63
- _tag: "InvalidOption",
64
- option: "sessions",
65
- reason: `unknown action ${first}`
66
- };
67
- return Either.left(error);
68
- }));
69
- };
@@ -1,26 +0,0 @@
1
- import { Either } from "effect";
2
- import { deriveRepoPathParts, resolveRepoInput } from "@effect-template/lib/core/domain";
3
- import { parseRawOptions } from "./parser-options.js";
4
- export const resolveWorkspaceRepoPath = (resolvedRepo) => {
5
- const baseParts = deriveRepoPathParts(resolvedRepo.repoUrl).pathParts;
6
- const projectParts = resolvedRepo.workspaceSuffix ? [...baseParts, resolvedRepo.workspaceSuffix] : baseParts;
7
- return projectParts.join("/");
8
- };
9
- export const splitPositionalRepo = (args) => {
10
- const first = args[0];
11
- const positionalRepoUrl = first !== undefined && !first.startsWith("-") ? first : undefined;
12
- const restArgs = positionalRepoUrl ? args.slice(1) : args;
13
- return { positionalRepoUrl, restArgs };
14
- };
15
- export const parseProjectDirWithOptions = (args, defaultProjectDir = ".") => Either.gen(function* (_) {
16
- const { positionalRepoUrl, restArgs } = splitPositionalRepo(args);
17
- const raw = yield* _(parseRawOptions(restArgs));
18
- const rawRepoUrl = raw.repoUrl ?? positionalRepoUrl;
19
- const repoPath = rawRepoUrl ? resolveWorkspaceRepoPath(resolveRepoInput(rawRepoUrl)) : null;
20
- const projectDir = raw.projectDir ??
21
- (repoPath
22
- ? `.docker-git/${repoPath}`
23
- : defaultProjectDir);
24
- return { projectDir, raw };
25
- });
26
- export const parseProjectDirArgs = (args, defaultProjectDir = ".") => Either.map(parseProjectDirWithOptions(args, defaultProjectDir), ({ projectDir }) => ({ projectDir }));
@@ -1,62 +0,0 @@
1
- import { Either, Match } from "effect";
2
- import { parseRawOptions } from "./parser-options.js";
3
- const invalidStateAction = (value) => ({
4
- _tag: "InvalidOption",
5
- option: "state",
6
- reason: `unknown action: ${value}`
7
- });
8
- const unexpectedArgs = (value) => Either.left({ _tag: "UnexpectedArgument", value });
9
- const parseStateInit = (args) => Either.flatMap(parseRawOptions(args), (raw) => {
10
- const repoUrl = raw.repoUrl?.trim();
11
- if (!repoUrl || repoUrl.length === 0) {
12
- return Either.left({ _tag: "MissingRequiredOption", option: "--repo-url" });
13
- }
14
- return Either.right({
15
- _tag: "StateInit",
16
- repoUrl,
17
- repoRef: raw.repoRef?.trim() && raw.repoRef.trim().length > 0 ? raw.repoRef.trim() : "main"
18
- });
19
- });
20
- const parseStateCommit = (args) => Either.flatMap(parseRawOptions(args), (raw) => {
21
- const message = raw.message?.trim();
22
- if (!message || message.length === 0) {
23
- return Either.left({ _tag: "MissingRequiredOption", option: "--message" });
24
- }
25
- return Either.right({ _tag: "StateCommit", message });
26
- });
27
- const parseStateSync = (args) => Either.map(parseRawOptions(args), (raw) => {
28
- const message = raw.message?.trim();
29
- return { _tag: "StateSync", message: message && message.length > 0 ? message : null };
30
- });
31
- export const parseState = (args) => {
32
- const action = args[0]?.trim();
33
- if (!action || action.length === 0) {
34
- return Either.left({ _tag: "MissingRequiredOption", option: "state <action>" });
35
- }
36
- const rest = args.slice(1);
37
- return Match.value(action).pipe(Match.when("path", () => {
38
- if (rest.length > 0) {
39
- return unexpectedArgs(rest[0] ?? "");
40
- }
41
- const command = { _tag: "StatePath" };
42
- return Either.right(command);
43
- }), Match.when("init", () => parseStateInit(rest)), Match.when("pull", () => {
44
- if (rest.length > 0) {
45
- return unexpectedArgs(rest[0] ?? "");
46
- }
47
- const command = { _tag: "StatePull" };
48
- return Either.right(command);
49
- }), Match.when("push", () => {
50
- if (rest.length > 0) {
51
- return unexpectedArgs(rest[0] ?? "");
52
- }
53
- const command = { _tag: "StatePush" };
54
- return Either.right(command);
55
- }), Match.when("status", () => {
56
- if (rest.length > 0) {
57
- return unexpectedArgs(rest[0] ?? "");
58
- }
59
- const command = { _tag: "StateStatus" };
60
- return Either.right(command);
61
- }), Match.when("commit", () => parseStateCommit(rest)), Match.when("sync", () => parseStateSync(rest)), Match.orElse(() => Either.left(invalidStateAction(action))));
62
- };
@@ -1,47 +0,0 @@
1
- import { Either, Match } from "effect";
2
- import {} from "@effect-template/lib/core/domain";
3
- import { parseApply } from "./parser-apply.js";
4
- import { parseAttach } from "./parser-attach.js";
5
- import { parseAuth } from "./parser-auth.js";
6
- import { parseClone } from "./parser-clone.js";
7
- import { buildCreateCommand } from "./parser-create.js";
8
- import { parseMcpPlaywright } from "./parser-mcp-playwright.js";
9
- import { parseRawOptions } from "./parser-options.js";
10
- import { parsePanes } from "./parser-panes.js";
11
- import { parseScrap } from "./parser-scrap.js";
12
- import { parseSessions } from "./parser-sessions.js";
13
- import { parseState } from "./parser-state.js";
14
- import { usageText } from "./usage.js";
15
- const isHelpFlag = (token) => token === "--help" || token === "-h";
16
- const helpCommand = { _tag: "Help", message: usageText };
17
- const menuCommand = { _tag: "Menu" };
18
- const statusCommand = { _tag: "Status" };
19
- const downAllCommand = { _tag: "DownAll" };
20
- const parseCreate = (args) => Either.flatMap(parseRawOptions(args), (raw) => buildCreateCommand(raw));
21
- // CHANGE: parse CLI arguments into a typed command
22
- // WHY: enforce deterministic, pure parsing before any effects run
23
- // QUOTE(ТЗ): "Надо написать CLI команду с помощью которой мы будем создавать докер образы"
24
- // REF: user-request-2026-01-07
25
- // SOURCE: n/a
26
- // FORMAT THEOREM: forall argv: parse(argv) = cmd -> deterministic(cmd)
27
- // PURITY: CORE
28
- // EFFECT: Effect<Command, ParseError, never>
29
- // INVARIANT: parse does not perform IO and returns the same result for same argv
30
- // COMPLEXITY: O(n) where n = |argv|
31
- export const parseArgs = (args) => {
32
- if (args.length === 0) {
33
- return Either.right(menuCommand);
34
- }
35
- if (args.some((arg) => isHelpFlag(arg))) {
36
- return Either.right(helpCommand);
37
- }
38
- const command = args[0];
39
- const rest = args.slice(1);
40
- const unknownCommandError = {
41
- _tag: "UnknownCommand",
42
- command: command ?? ""
43
- };
44
- return Match.value(command)
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)))
46
- .pipe(Match.when("apply", () => parseApply(rest)), Match.when("state", () => parseState(rest)), Match.orElse(() => Either.left(unknownCommandError)));
47
- };
@@ -1,17 +0,0 @@
1
- import { Effect, Either, pipe } from "effect";
2
- import {} from "@effect-template/lib/core/domain";
3
- import { parseArgs } from "./parser.js";
4
- // CHANGE: read and parse CLI arguments from process.argv
5
- // WHY: keep IO at the boundary and delegate parsing to CORE
6
- // QUOTE(ТЗ): "Надо написать CLI команду"
7
- // REF: user-request-2026-01-07
8
- // SOURCE: n/a
9
- // FORMAT THEOREM: forall argv: read(argv) -> parse(argv)
10
- // PURITY: SHELL
11
- // EFFECT: Effect<Command, ParseError, never>
12
- // INVARIANT: errors are typed as ParseError
13
- // COMPLEXITY: O(n) where n = |argv|
14
- export const readCommand = pipe(Effect.sync(() => process.argv.slice(2)), Effect.map((args) => parseArgs(args)), Effect.flatMap((result) => Either.match(result, {
15
- onLeft: (error) => Effect.fail(error),
16
- onRight: (command) => Effect.succeed(command)
17
- })));
@@ -1,113 +0,0 @@
1
- import { Match } from "effect";
2
- export const usageText = `docker-git menu
3
- docker-git create --repo-url <url> [options]
4
- docker-git clone <url> [options]
5
- docker-git apply [<url>] [options]
6
- docker-git mcp-playwright [<url>] [options]
7
- docker-git attach [<url>] [options]
8
- docker-git panes [<url>] [options]
9
- docker-git scrap <action> [<url>] [options]
10
- docker-git sessions [list] [<url>] [options]
11
- docker-git sessions kill <pid> [<url>] [options]
12
- docker-git sessions logs <pid> [<url>] [options]
13
- docker-git ps
14
- docker-git down-all
15
- docker-git auth <provider> <action> [options]
16
- docker-git state <action> [options]
17
-
18
- Commands:
19
- menu Interactive menu (default when no args)
20
- create, init Generate docker development environment
21
- clone Create + run container and clone repo
22
- apply Apply docker-git config to an existing project/container (current dir by default)
23
- mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
24
- attach, tmux Open tmux workspace for a docker-git project
25
- panes, terms List tmux panes for a docker-git project
26
- scrap Export/import project scrap (session snapshot + rebuildable deps)
27
- sessions List/kill/log container terminal processes
28
- ps, status Show docker compose status for all docker-git projects
29
- down-all Stop all docker-git containers (docker compose down)
30
- auth Manage GitHub/Codex/Claude Code auth for docker-git
31
- state Manage docker-git state directory via git (sync across machines)
32
-
33
- Options:
34
- --repo-ref <ref> Git ref/branch (default: main)
35
- --branch, -b <ref> Alias for --repo-ref
36
- --target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: ~/workspaces/<org>/<repo>[/issue-<id>|/pr-<id>])
37
- --ssh-port <port> Local SSH port (default: 2222)
38
- --ssh-user <user> SSH user inside container (default: dev)
39
- --container-name <name> Docker container name (default: dg-<repo>)
40
- --service-name <name> Compose service name (default: dg-<repo>)
41
- --volume-name <name> Docker volume name (default: dg-<repo>-home)
42
- --authorized-keys <path> Host path to authorized_keys (default: <projectsRoot>/authorized_keys)
43
- --env-global <path> Host path to shared env file (default: <projectsRoot>/.orch/env/global.env)
44
- --env-project <path> Host path to project env file (default: ./.orch/env/project.env)
45
- --codex-auth <path> Host path for Codex auth cache (default: <projectsRoot>/.orch/auth/codex)
46
- --codex-home <path> Container path for Codex auth (default: /home/dev/.codex)
47
- --out-dir <path> Output directory (default: <projectsRoot>/<org>/<repo>[/issue-<id>|/pr-<id>])
48
- --project-dir <path> Project directory for attach (default: .)
49
- --archive <path> Scrap snapshot directory (default: .orch/scrap/session)
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)
54
- --wipe | --no-wipe Wipe workspace before scrap import (default: --wipe)
55
- --lines <n> Tail last N lines for sessions logs (default: 200)
56
- --include-default Show default/system processes in sessions list
57
- --up | --no-up Run docker compose up after init (default: --up)
58
- --ssh | --no-ssh Auto-open SSH after create/clone (default: clone=--ssh, create=--no-ssh)
59
- --mcp-playwright | --no-mcp-playwright Enable Playwright MCP + Chromium sidecar (default: --no-mcp-playwright)
60
- --force Overwrite existing files and wipe compose volumes (docker compose down -v)
61
- --force-env Reset project env defaults only (keep workspace volume/data)
62
- -h, --help Show this help
63
-
64
- Container runtime env (set via .orch/env/project.env):
65
- CODEX_SHARE_AUTH=1|0 Share Codex auth.json across projects (default: 1)
66
- CODEX_AUTO_UPDATE=1|0 Auto-update Codex CLI on container start (default: 1)
67
- DOCKER_GIT_ZSH_AUTOSUGGEST=1|0 Enable zsh-autosuggestions (default: 1)
68
- DOCKER_GIT_ZSH_AUTOSUGGEST_STYLE=... zsh-autosuggestions highlight style (default: fg=8,italic)
69
- DOCKER_GIT_ZSH_AUTOSUGGEST_STRATEGY=... Suggestion sources (default: history completion)
70
- MCP_PLAYWRIGHT_ISOLATED=1|0 Isolated browser contexts (recommended for many Codex; default: 1)
71
- MCP_PLAYWRIGHT_CDP_ENDPOINT=http://... Override CDP endpoint (default: http://dg-<repo>-browser:9223)
72
-
73
- Auth providers:
74
- github, gh GitHub CLI auth (tokens saved to env file)
75
- codex Codex CLI auth (stored under .orch/auth/codex)
76
- claude, cc Claude Code CLI auth (OAuth cache stored under .orch/auth/claude)
77
-
78
- Auth actions:
79
- login Run login flow and store credentials
80
- status Show current auth status
81
- logout Remove stored credentials
82
-
83
- Auth options:
84
- --label <label> Account label (default: default)
85
- --token <token> GitHub token override (login only; useful for non-interactive/CI)
86
- --web Force OAuth web flow (login only; ignores --token)
87
- --scopes <scopes> GitHub scopes (login only, default: repo,workflow,read:org)
88
- --env-global <path> Env file path for GitHub tokens (default: <projectsRoot>/.orch/env/global.env)
89
- --codex-auth <path> Codex auth root path (default: <projectsRoot>/.orch/auth/codex)
90
-
91
- State actions:
92
- state path Print current projects root (default: ~/.docker-git; override via DOCKER_GIT_PROJECTS_ROOT)
93
- state init --repo-url <url> [-b] Init / bind state dir to a git remote (use a private repo)
94
- state status Show git status for the state dir
95
- state pull git pull (state dir)
96
- state commit -m <message> Commit all changes in the state dir
97
- state sync [-m <message>] Commit (if needed) + fetch/rebase + push (state dir); on conflict pushes a PR branch
98
- state push git push (state dir)
99
-
100
- State options:
101
- --message, -m <message> Commit message for state commit
102
- `;
103
- // CHANGE: normalize parse errors into user-facing messages
104
- // WHY: keep formatting deterministic and centralized
105
- // QUOTE(ТЗ): "Надо написать CLI команду"
106
- // REF: user-request-2026-01-07
107
- // SOURCE: n/a
108
- // FORMAT THEOREM: forall e: format(e) = s -> deterministic(s)
109
- // PURITY: CORE
110
- // EFFECT: Effect<string, never, never>
111
- // INVARIANT: each ParseError maps to exactly one message
112
- // COMPLEXITY: O(1)
113
- export const formatParseError = (error) => Match.value(error).pipe(Match.when({ _tag: "UnknownCommand" }, ({ command }) => `Unknown command: ${command}`), Match.when({ _tag: "UnknownOption" }, ({ option }) => `Unknown option: ${option}`), Match.when({ _tag: "MissingOptionValue" }, ({ option }) => `Missing value for option: ${option}`), Match.when({ _tag: "MissingRequiredOption" }, ({ option }) => `Missing required option: ${option}`), Match.when({ _tag: "InvalidOption" }, ({ option, reason }) => `Invalid option ${option}: ${reason}`), Match.when({ _tag: "UnexpectedArgument" }, ({ value }) => `Unexpected argument: ${value}`), Match.exhaustive);