@prover-coder-ai/docker-git 1.0.10 → 1.0.12

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.
@@ -34,7 +34,8 @@ export const parseClone = (args) => {
34
34
  const withRef = resolvedRepo.repoRef !== undefined && raw.repoRef === undefined
35
35
  ? { ...withDefaults, repoRef: resolvedRepo.repoRef }
36
36
  : withDefaults;
37
+ const openSsh = raw.openSsh ?? true;
37
38
  const create = yield* _(buildCreateCommand(withRef));
38
- return { ...create, waitForClone: true };
39
+ return { ...create, waitForClone: true, openSsh };
39
40
  });
40
41
  };
@@ -0,0 +1,18 @@
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
+ }));
@@ -31,6 +31,8 @@ const valueOptionSpecByFlag = new Map(valueOptionSpecs.map((spec) => [spec.flag,
31
31
  const booleanFlagUpdaters = {
32
32
  "--up": (raw) => ({ ...raw, up: true }),
33
33
  "--no-up": (raw) => ({ ...raw, up: false }),
34
+ "--ssh": (raw) => ({ ...raw, openSsh: true }),
35
+ "--no-ssh": (raw) => ({ ...raw, openSsh: false }),
34
36
  "--force": (raw) => ({ ...raw, force: true }),
35
37
  "--force-env": (raw) => ({ ...raw, forceEnv: true }),
36
38
  "--mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: true }),
@@ -4,6 +4,7 @@ import { parseAttach } from "./parser-attach.js";
4
4
  import { parseAuth } from "./parser-auth.js";
5
5
  import { parseClone } from "./parser-clone.js";
6
6
  import { buildCreateCommand } from "./parser-create.js";
7
+ import { parseMcpPlaywright } from "./parser-mcp-playwright.js";
7
8
  import { parseRawOptions } from "./parser-options.js";
8
9
  import { parsePanes } from "./parser-panes.js";
9
10
  import { parseScrap } from "./parser-scrap.js";
@@ -40,6 +41,6 @@ export const parseArgs = (args) => {
40
41
  command: command ?? ""
41
42
  };
42
43
  return Match.value(command)
43
- .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("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)), Match.when("state", () => parseState(rest)))
44
- .pipe(Match.orElse(() => Either.left(unknownCommandError)));
44
+ .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)));
45
46
  };
@@ -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 mcp-playwright [<url>] [options]
5
6
  docker-git attach [<url>] [options]
6
7
  docker-git panes [<url>] [options]
7
8
  docker-git scrap <action> [<url>] [options]
@@ -17,6 +18,7 @@ Commands:
17
18
  menu Interactive menu (default when no args)
18
19
  create, init Generate docker development environment
19
20
  clone Create + run container and clone repo
21
+ mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
20
22
  attach, tmux Open tmux workspace for a docker-git project
21
23
  panes, terms List tmux panes for a docker-git project
22
24
  scrap Export/import project scrap (session snapshot + rebuildable deps)
@@ -49,6 +51,7 @@ Options:
49
51
  --lines <n> Tail last N lines for sessions logs (default: 200)
50
52
  --include-default Show default/system processes in sessions list
51
53
  --up | --no-up Run docker compose up after init (default: --up)
54
+ --ssh | --no-ssh Auto-open SSH after create/clone (default: clone=--ssh, create=--no-ssh)
52
55
  --mcp-playwright | --no-mcp-playwright Enable Playwright MCP + Chromium sidecar (default: --no-mcp-playwright)
53
56
  --force Overwrite existing files and wipe compose volumes (docker compose down -v)
54
57
  --force-env Reset project env defaults only (keep workspace volume/data)
@@ -1,6 +1,7 @@
1
1
  import { createProject } from "@effect-template/lib/usecases/actions";
2
2
  import { authCodexLogin, authCodexLogout, authCodexStatus, authGithubLogin, authGithubLogout, authGithubStatus } from "@effect-template/lib/usecases/auth";
3
3
  import { renderError } from "@effect-template/lib/usecases/errors";
4
+ import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright";
4
5
  import { downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects";
5
6
  import { exportScrap, importScrap } from "@effect-template/lib/usecases/scrap";
6
7
  import { stateCommit, stateInit, statePath, statePull, statePush, stateStatus, stateSync } from "@effect-template/lib/usecases/state-repo";
@@ -22,7 +23,7 @@ const logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)),
22
23
  const logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
23
24
  const handleNonBaseCommand = (command) => Match.value(command)
24
25
  .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: "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)), Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)))
25
- .pipe(Match.exhaustive);
26
+ .pipe(Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
26
27
  // CHANGE: compose CLI program with typed errors and shell effects
27
28
  // WHY: keep a thin entry layer over pure parsing and template generation
28
29
  // QUOTE(ТЗ): "CLI команду... создавать докер образы"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prover-coder-ai/docker-git",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Minimal Vite-powered TypeScript console starter using Effect",
5
5
  "main": "dist/src/docker-git/main.js",
6
6
  "bin": {
@@ -49,7 +49,8 @@ export const parseClone = (args: ReadonlyArray<string>): Either.Either<Command,
49
49
  const withRef = resolvedRepo.repoRef !== undefined && raw.repoRef === undefined
50
50
  ? { ...withDefaults, repoRef: resolvedRepo.repoRef }
51
51
  : withDefaults
52
+ const openSsh = raw.openSsh ?? true
52
53
  const create = yield* _(buildCreateCommand(withRef))
53
- return { ...create, waitForClone: true }
54
+ return { ...create, waitForClone: true, openSsh }
54
55
  })
55
56
  }
@@ -0,0 +1,25 @@
1
+ import { Either } from "effect"
2
+
3
+ import { type McpPlaywrightUpCommand, type ParseError } from "@effect-template/lib/core/domain"
4
+
5
+ import { parseProjectDirWithOptions } from "./parser-shared.js"
6
+
7
+ // CHANGE: parse "mcp-playwright" command for existing docker-git projects
8
+ // WHY: allow enabling Playwright MCP in an already created container/project dir
9
+ // QUOTE(ТЗ): "Добавить возможность поднимать MCP Playrgiht в контейнере который уже создан"
10
+ // REF: issue-29
11
+ // SOURCE: n/a
12
+ // FORMAT THEOREM: forall argv: parseMcpPlaywright(argv) = cmd -> deterministic(cmd)
13
+ // PURITY: CORE
14
+ // EFFECT: Effect<McpPlaywrightUpCommand, ParseError, never>
15
+ // INVARIANT: projectDir is never empty
16
+ // COMPLEXITY: O(n) where n = |argv|
17
+ export const parseMcpPlaywright = (
18
+ args: ReadonlyArray<string>
19
+ ): Either.Either<McpPlaywrightUpCommand, ParseError> =>
20
+ Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
21
+ _tag: "McpPlaywrightUp",
22
+ projectDir,
23
+ runUp: raw.up ?? true
24
+ }))
25
+
@@ -69,6 +69,8 @@ type ValueKey = ValueOptionSpec["key"]
69
69
  const booleanFlagUpdaters: Readonly<Record<string, (raw: RawOptions) => RawOptions>> = {
70
70
  "--up": (raw) => ({ ...raw, up: true }),
71
71
  "--no-up": (raw) => ({ ...raw, up: false }),
72
+ "--ssh": (raw) => ({ ...raw, openSsh: true }),
73
+ "--no-ssh": (raw) => ({ ...raw, openSsh: false }),
72
74
  "--force": (raw) => ({ ...raw, force: true }),
73
75
  "--force-env": (raw) => ({ ...raw, forceEnv: true }),
74
76
  "--mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: true }),
@@ -6,6 +6,7 @@ import { parseAttach } from "./parser-attach.js"
6
6
  import { parseAuth } from "./parser-auth.js"
7
7
  import { parseClone } from "./parser-clone.js"
8
8
  import { buildCreateCommand } from "./parser-create.js"
9
+ import { parseMcpPlaywright } from "./parser-mcp-playwright.js"
9
10
  import { parseRawOptions } from "./parser-options.js"
10
11
  import { parsePanes } from "./parser-panes.js"
11
12
  import { parseScrap } from "./parser-scrap.js"
@@ -61,6 +62,7 @@ export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, P
61
62
  Match.when("terminals", () => parsePanes(rest)),
62
63
  Match.when("sessions", () => parseSessions(rest)),
63
64
  Match.when("scrap", () => parseScrap(rest)),
65
+ Match.when("mcp-playwright", () => parseMcpPlaywright(rest)),
64
66
  Match.when("help", () => Either.right(helpCommand)),
65
67
  Match.when("ps", () => Either.right(statusCommand)),
66
68
  Match.when("status", () => Either.right(statusCommand)),
@@ -69,8 +71,10 @@ export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, P
69
71
  Match.when("kill-all", () => Either.right(downAllCommand)),
70
72
  Match.when("menu", () => Either.right(menuCommand)),
71
73
  Match.when("ui", () => Either.right(menuCommand)),
72
- Match.when("auth", () => parseAuth(rest)),
73
- Match.when("state", () => parseState(rest))
74
+ Match.when("auth", () => parseAuth(rest))
75
+ )
76
+ .pipe(
77
+ Match.when("state", () => parseState(rest)),
78
+ Match.orElse(() => Either.left(unknownCommandError))
74
79
  )
75
- .pipe(Match.orElse(() => Either.left(unknownCommandError)))
76
80
  }
@@ -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 mcp-playwright [<url>] [options]
8
9
  docker-git attach [<url>] [options]
9
10
  docker-git panes [<url>] [options]
10
11
  docker-git scrap <action> [<url>] [options]
@@ -20,6 +21,7 @@ Commands:
20
21
  menu Interactive menu (default when no args)
21
22
  create, init Generate docker development environment
22
23
  clone Create + run container and clone repo
24
+ mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
23
25
  attach, tmux Open tmux workspace for a docker-git project
24
26
  panes, terms List tmux panes for a docker-git project
25
27
  scrap Export/import project scrap (session snapshot + rebuildable deps)
@@ -52,6 +54,7 @@ Options:
52
54
  --lines <n> Tail last N lines for sessions logs (default: 200)
53
55
  --include-default Show default/system processes in sessions list
54
56
  --up | --no-up Run docker compose up after init (default: --up)
57
+ --ssh | --no-ssh Auto-open SSH after create/clone (default: clone=--ssh, create=--no-ssh)
55
58
  --mcp-playwright | --no-mcp-playwright Enable Playwright MCP + Chromium sidecar (default: --no-mcp-playwright)
56
59
  --force Overwrite existing files and wipe compose volumes (docker compose down -v)
57
60
  --force-env Reset project env defaults only (keep workspace volume/data)
@@ -10,6 +10,7 @@ import {
10
10
  } from "@effect-template/lib/usecases/auth"
11
11
  import type { AppError } from "@effect-template/lib/usecases/errors"
12
12
  import { renderError } from "@effect-template/lib/usecases/errors"
13
+ import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright"
13
14
  import { downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects"
14
15
  import { exportScrap, importScrap } from "@effect-template/lib/usecases/scrap"
15
16
  import {
@@ -92,7 +93,10 @@ const handleNonBaseCommand = (command: NonBaseCommand) =>
92
93
  Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)),
93
94
  Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd))
94
95
  )
95
- .pipe(Match.exhaustive)
96
+ .pipe(
97
+ Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)),
98
+ Match.exhaustive
99
+ )
96
100
 
97
101
  // CHANGE: compose CLI program with typed errors and shell effects
98
102
  // WHY: keep a thin entry layer over pure parsing and template generation
@@ -56,6 +56,8 @@ describe("parseArgs", () => {
56
56
  it.effect("parses create command with defaults", () =>
57
57
  expectCreateCommand(["create", "--repo-url", "https://github.com/org/repo.git"], (command) => {
58
58
  expectCreateDefaults(command)
59
+ expect(command.openSsh).toBe(false)
60
+ expect(command.waitForClone).toBe(false)
59
61
  expect(command.config.containerName).toBe("dg-repo")
60
62
  expect(command.config.serviceName).toBe("dg-repo")
61
63
  expect(command.config.volumeName).toBe("dg-repo-home")
@@ -67,6 +69,8 @@ describe("parseArgs", () => {
67
69
  expect(command.config.repoUrl).toBe("https://github.com/org/repo.git")
68
70
  expect(command.config.repoRef).toBe("issue-9")
69
71
  expect(command.outDir).toBe(".docker-git/org/repo/issue-9")
72
+ expect(command.openSsh).toBe(false)
73
+ expect(command.waitForClone).toBe(false)
70
74
  expect(command.config.containerName).toBe("dg-repo-issue-9")
71
75
  expect(command.config.serviceName).toBe("dg-repo-issue-9")
72
76
  expect(command.config.volumeName).toBe("dg-repo-issue-9-home")
@@ -77,6 +81,8 @@ describe("parseArgs", () => {
77
81
  it.effect("parses clone command with positional repo url", () =>
78
82
  expectCreateCommand(["clone", "https://github.com/org/repo.git"], (command) => {
79
83
  expectCreateDefaults(command)
84
+ expect(command.openSsh).toBe(true)
85
+ expect(command.waitForClone).toBe(true)
80
86
  expect(command.config.targetDir).toBe("/home/dev/org/repo")
81
87
  }))
82
88
 
@@ -85,6 +91,16 @@ describe("parseArgs", () => {
85
91
  expect(command.config.repoRef).toBe("feature-x")
86
92
  }))
87
93
 
94
+ it.effect("supports disabling SSH auto-open for clone", () =>
95
+ expectCreateCommand(["clone", "https://github.com/org/repo.git", "--no-ssh"], (command) => {
96
+ expect(command.openSsh).toBe(false)
97
+ }))
98
+
99
+ it.effect("supports enabling SSH auto-open for create", () =>
100
+ expectCreateCommand(["create", "--repo-url", "https://github.com/org/repo.git", "--ssh"], (command) => {
101
+ expect(command.openSsh).toBe(true)
102
+ }))
103
+
88
104
  it.effect("parses force-env flag for clone", () =>
89
105
  expectCreateCommand(["clone", "https://github.com/org/repo.git", "--force-env"], (command) => {
90
106
  expect(command.force).toBe(false)
@@ -136,6 +152,34 @@ describe("parseArgs", () => {
136
152
  expect(command.projectDir).toBe(".docker-git/org/repo/issue-7")
137
153
  }))
138
154
 
155
+ it.effect("parses mcp-playwright command in current directory", () =>
156
+ Effect.sync(() => {
157
+ const command = parseOrThrow(["mcp-playwright"])
158
+ if (command._tag !== "McpPlaywrightUp") {
159
+ throw new Error("expected McpPlaywrightUp command")
160
+ }
161
+ expect(command.projectDir).toBe(".")
162
+ expect(command.runUp).toBe(true)
163
+ }))
164
+
165
+ it.effect("parses mcp-playwright command with --no-up", () =>
166
+ Effect.sync(() => {
167
+ const command = parseOrThrow(["mcp-playwright", "--no-up"])
168
+ if (command._tag !== "McpPlaywrightUp") {
169
+ throw new Error("expected McpPlaywrightUp command")
170
+ }
171
+ expect(command.runUp).toBe(false)
172
+ }))
173
+
174
+ it.effect("parses mcp-playwright with positional repo url into project dir", () =>
175
+ Effect.sync(() => {
176
+ const command = parseOrThrow(["mcp-playwright", "https://github.com/org/repo.git"])
177
+ if (command._tag !== "McpPlaywrightUp") {
178
+ throw new Error("expected McpPlaywrightUp command")
179
+ }
180
+ expect(command.projectDir).toBe(".docker-git/org/repo")
181
+ }))
182
+
139
183
  it.effect("parses down-all command", () =>
140
184
  Effect.sync(() => {
141
185
  const command = parseOrThrow(["down-all"])