@prover-coder-ai/docker-git 1.0.22 → 1.0.24
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/README.md +3 -0
- package/dist/src/docker-git/main.js +5 -2
- package/dist/src/docker-git/main.js.map +1 -1
- package/package.json +5 -1
- package/.jscpd.json +0 -16
- package/.package.json.release.bak +0 -110
- package/CHANGELOG.md +0 -133
- package/biome.json +0 -34
- package/eslint.config.mts +0 -305
- package/eslint.effect-ts-check.config.mjs +0 -220
- package/linter.config.json +0 -33
- package/src/app/main.ts +0 -18
- package/src/app/program.ts +0 -75
- package/src/docker-git/cli/input.ts +0 -29
- package/src/docker-git/cli/parser-apply.ts +0 -28
- package/src/docker-git/cli/parser-attach.ts +0 -22
- package/src/docker-git/cli/parser-auth.ts +0 -154
- package/src/docker-git/cli/parser-clone.ts +0 -50
- package/src/docker-git/cli/parser-create.ts +0 -3
- package/src/docker-git/cli/parser-mcp-playwright.ts +0 -24
- package/src/docker-git/cli/parser-options.ts +0 -211
- package/src/docker-git/cli/parser-panes.ts +0 -22
- package/src/docker-git/cli/parser-scrap.ts +0 -106
- package/src/docker-git/cli/parser-sessions.ts +0 -101
- package/src/docker-git/cli/parser-shared.ts +0 -51
- package/src/docker-git/cli/parser-state.ts +0 -86
- package/src/docker-git/cli/parser.ts +0 -82
- package/src/docker-git/cli/read-command.ts +0 -26
- package/src/docker-git/cli/usage.ts +0 -129
- package/src/docker-git/main.ts +0 -18
- package/src/docker-git/menu-actions.ts +0 -273
- package/src/docker-git/menu-auth-data.ts +0 -184
- package/src/docker-git/menu-auth-helpers.ts +0 -30
- package/src/docker-git/menu-auth.ts +0 -311
- package/src/docker-git/menu-buffer-input.ts +0 -18
- package/src/docker-git/menu-create.ts +0 -310
- package/src/docker-git/menu-input-handler.ts +0 -183
- package/src/docker-git/menu-input-utils.ts +0 -85
- package/src/docker-git/menu-input.ts +0 -2
- package/src/docker-git/menu-labeled-env.ts +0 -37
- package/src/docker-git/menu-menu.ts +0 -58
- package/src/docker-git/menu-project-auth-claude.ts +0 -70
- package/src/docker-git/menu-project-auth-data.ts +0 -292
- package/src/docker-git/menu-project-auth.ts +0 -271
- package/src/docker-git/menu-render-auth.ts +0 -65
- package/src/docker-git/menu-render-common.ts +0 -67
- package/src/docker-git/menu-render-layout.ts +0 -30
- package/src/docker-git/menu-render-project-auth.ts +0 -70
- package/src/docker-git/menu-render-select.ts +0 -250
- package/src/docker-git/menu-render.ts +0 -292
- package/src/docker-git/menu-select-actions.ts +0 -150
- package/src/docker-git/menu-select-connect.ts +0 -27
- package/src/docker-git/menu-select-load.ts +0 -33
- package/src/docker-git/menu-select-order.ts +0 -37
- package/src/docker-git/menu-select-runtime.ts +0 -143
- package/src/docker-git/menu-select-view.ts +0 -25
- package/src/docker-git/menu-select.ts +0 -145
- package/src/docker-git/menu-shared.ts +0 -256
- package/src/docker-git/menu-startup.ts +0 -83
- package/src/docker-git/menu-types.ts +0 -170
- package/src/docker-git/menu.ts +0 -303
- package/src/docker-git/program.ts +0 -154
- package/src/docker-git/tmux.ts +0 -292
- package/tests/app/main.test.ts +0 -65
- package/tests/docker-git/entrypoint-auth.test.ts +0 -40
- package/tests/docker-git/fixtures/project-item.ts +0 -24
- package/tests/docker-git/menu-select-connect.test.ts +0 -55
- package/tests/docker-git/menu-select-order.test.ts +0 -84
- package/tests/docker-git/menu-startup.test.ts +0 -51
- package/tests/docker-git/parser-network-options.test.ts +0 -47
- package/tests/docker-git/parser.test.ts +0 -340
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -20
- package/vite.config.ts +0 -32
- package/vite.docker-git.config.ts +0 -34
- package/vitest.config.ts +0 -85
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { Either } from "effect"
|
|
2
|
-
|
|
3
|
-
import type { RawOptions } from "@effect-template/lib/core/command-options"
|
|
4
|
-
import type { ParseError } from "@effect-template/lib/core/domain"
|
|
5
|
-
|
|
6
|
-
interface ValueOptionSpec {
|
|
7
|
-
readonly flag: string
|
|
8
|
-
readonly key:
|
|
9
|
-
| "repoUrl"
|
|
10
|
-
| "repoRef"
|
|
11
|
-
| "targetDir"
|
|
12
|
-
| "sshPort"
|
|
13
|
-
| "sshUser"
|
|
14
|
-
| "containerName"
|
|
15
|
-
| "serviceName"
|
|
16
|
-
| "volumeName"
|
|
17
|
-
| "secretsRoot"
|
|
18
|
-
| "authorizedKeysPath"
|
|
19
|
-
| "envGlobalPath"
|
|
20
|
-
| "envProjectPath"
|
|
21
|
-
| "codexAuthPath"
|
|
22
|
-
| "codexHome"
|
|
23
|
-
| "dockerNetworkMode"
|
|
24
|
-
| "dockerSharedNetworkName"
|
|
25
|
-
| "archivePath"
|
|
26
|
-
| "scrapMode"
|
|
27
|
-
| "label"
|
|
28
|
-
| "gitTokenLabel"
|
|
29
|
-
| "codexTokenLabel"
|
|
30
|
-
| "claudeTokenLabel"
|
|
31
|
-
| "token"
|
|
32
|
-
| "scopes"
|
|
33
|
-
| "message"
|
|
34
|
-
| "outDir"
|
|
35
|
-
| "projectDir"
|
|
36
|
-
| "lines"
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const valueOptionSpecs: ReadonlyArray<ValueOptionSpec> = [
|
|
40
|
-
{ flag: "--repo-url", key: "repoUrl" },
|
|
41
|
-
{ flag: "--repo-ref", key: "repoRef" },
|
|
42
|
-
{ flag: "--branch", key: "repoRef" },
|
|
43
|
-
{ flag: "-b", key: "repoRef" },
|
|
44
|
-
{ flag: "--target-dir", key: "targetDir" },
|
|
45
|
-
{ flag: "--ssh-port", key: "sshPort" },
|
|
46
|
-
{ flag: "--ssh-user", key: "sshUser" },
|
|
47
|
-
{ flag: "--container-name", key: "containerName" },
|
|
48
|
-
{ flag: "--service-name", key: "serviceName" },
|
|
49
|
-
{ flag: "--volume-name", key: "volumeName" },
|
|
50
|
-
{ flag: "--secrets-root", key: "secretsRoot" },
|
|
51
|
-
{ flag: "--authorized-keys", key: "authorizedKeysPath" },
|
|
52
|
-
{ flag: "--env-global", key: "envGlobalPath" },
|
|
53
|
-
{ flag: "--env-project", key: "envProjectPath" },
|
|
54
|
-
{ flag: "--codex-auth", key: "codexAuthPath" },
|
|
55
|
-
{ flag: "--codex-home", key: "codexHome" },
|
|
56
|
-
{ flag: "--network-mode", key: "dockerNetworkMode" },
|
|
57
|
-
{ flag: "--shared-network", key: "dockerSharedNetworkName" },
|
|
58
|
-
{ flag: "--archive", key: "archivePath" },
|
|
59
|
-
{ flag: "--mode", key: "scrapMode" },
|
|
60
|
-
{ flag: "--label", key: "label" },
|
|
61
|
-
{ flag: "--git-token", key: "gitTokenLabel" },
|
|
62
|
-
{ flag: "--codex-token", key: "codexTokenLabel" },
|
|
63
|
-
{ flag: "--claude-token", key: "claudeTokenLabel" },
|
|
64
|
-
{ flag: "--token", key: "token" },
|
|
65
|
-
{ flag: "--scopes", key: "scopes" },
|
|
66
|
-
{ flag: "--message", key: "message" },
|
|
67
|
-
{ flag: "-m", key: "message" },
|
|
68
|
-
{ flag: "--out-dir", key: "outDir" },
|
|
69
|
-
{ flag: "--project-dir", key: "projectDir" },
|
|
70
|
-
{ flag: "--lines", key: "lines" }
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
const valueOptionSpecByFlag: ReadonlyMap<string, ValueOptionSpec> = new Map(
|
|
74
|
-
valueOptionSpecs.map((spec) => [spec.flag, spec])
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
type ValueKey = ValueOptionSpec["key"]
|
|
78
|
-
|
|
79
|
-
const booleanFlagUpdaters: Readonly<Record<string, (raw: RawOptions) => RawOptions>> = {
|
|
80
|
-
"--up": (raw) => ({ ...raw, up: true }),
|
|
81
|
-
"--no-up": (raw) => ({ ...raw, up: false }),
|
|
82
|
-
"--ssh": (raw) => ({ ...raw, openSsh: true }),
|
|
83
|
-
"--no-ssh": (raw) => ({ ...raw, openSsh: false }),
|
|
84
|
-
"--force": (raw) => ({ ...raw, force: true }),
|
|
85
|
-
"--force-env": (raw) => ({ ...raw, forceEnv: true }),
|
|
86
|
-
"--mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: true }),
|
|
87
|
-
"--no-mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: false }),
|
|
88
|
-
"--wipe": (raw) => ({ ...raw, wipe: true }),
|
|
89
|
-
"--no-wipe": (raw) => ({ ...raw, wipe: false }),
|
|
90
|
-
"--web": (raw) => ({ ...raw, authWeb: true }),
|
|
91
|
-
"--include-default": (raw) => ({ ...raw, includeDefault: true })
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const valueFlagUpdaters: { readonly [K in ValueKey]: (raw: RawOptions, value: string) => RawOptions } = {
|
|
95
|
-
repoUrl: (raw, value) => ({ ...raw, repoUrl: value }),
|
|
96
|
-
repoRef: (raw, value) => ({ ...raw, repoRef: value }),
|
|
97
|
-
targetDir: (raw, value) => ({ ...raw, targetDir: value }),
|
|
98
|
-
sshPort: (raw, value) => ({ ...raw, sshPort: value }),
|
|
99
|
-
sshUser: (raw, value) => ({ ...raw, sshUser: value }),
|
|
100
|
-
containerName: (raw, value) => ({ ...raw, containerName: value }),
|
|
101
|
-
serviceName: (raw, value) => ({ ...raw, serviceName: value }),
|
|
102
|
-
volumeName: (raw, value) => ({ ...raw, volumeName: value }),
|
|
103
|
-
secretsRoot: (raw, value) => ({ ...raw, secretsRoot: value }),
|
|
104
|
-
authorizedKeysPath: (raw, value) => ({ ...raw, authorizedKeysPath: value }),
|
|
105
|
-
envGlobalPath: (raw, value) => ({ ...raw, envGlobalPath: value }),
|
|
106
|
-
envProjectPath: (raw, value) => ({ ...raw, envProjectPath: value }),
|
|
107
|
-
codexAuthPath: (raw, value) => ({ ...raw, codexAuthPath: value }),
|
|
108
|
-
codexHome: (raw, value) => ({ ...raw, codexHome: value }),
|
|
109
|
-
dockerNetworkMode: (raw, value) => ({ ...raw, dockerNetworkMode: value }),
|
|
110
|
-
dockerSharedNetworkName: (raw, value) => ({ ...raw, dockerSharedNetworkName: value }),
|
|
111
|
-
archivePath: (raw, value) => ({ ...raw, archivePath: value }),
|
|
112
|
-
scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
|
|
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 }),
|
|
117
|
-
token: (raw, value) => ({ ...raw, token: value }),
|
|
118
|
-
scopes: (raw, value) => ({ ...raw, scopes: value }),
|
|
119
|
-
message: (raw, value) => ({ ...raw, message: value }),
|
|
120
|
-
outDir: (raw, value) => ({ ...raw, outDir: value }),
|
|
121
|
-
projectDir: (raw, value) => ({ ...raw, projectDir: value }),
|
|
122
|
-
lines: (raw, value) => ({ ...raw, lines: value })
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const applyCommandBooleanFlag = (raw: RawOptions, token: string): RawOptions | null => {
|
|
126
|
-
const updater = booleanFlagUpdaters[token]
|
|
127
|
-
return updater ? updater(raw) : null
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export const applyCommandValueFlag = (
|
|
131
|
-
raw: RawOptions,
|
|
132
|
-
token: string,
|
|
133
|
-
value: string
|
|
134
|
-
): Either.Either<RawOptions, ParseError> => {
|
|
135
|
-
const valueSpec = valueOptionSpecByFlag.get(token)
|
|
136
|
-
if (valueSpec === undefined) {
|
|
137
|
-
return Either.left({ _tag: "UnknownOption", option: token })
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const update = valueFlagUpdaters[valueSpec.key]
|
|
141
|
-
return Either.right(update(raw, value))
|
|
142
|
-
}
|
|
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
|
-
|
|
195
|
-
export const parseRawOptions = (args: ReadonlyArray<string>): Either.Either<RawOptions, ParseError> => {
|
|
196
|
-
let index = 0
|
|
197
|
-
let raw: RawOptions = {}
|
|
198
|
-
|
|
199
|
-
while (index < args.length) {
|
|
200
|
-
const step = parseRawOptionsStep(args, index, raw)
|
|
201
|
-
if (step._tag === "error") {
|
|
202
|
-
return Either.left(step.error)
|
|
203
|
-
}
|
|
204
|
-
raw = step.raw
|
|
205
|
-
index = step.nextIndex
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return Either.right(raw)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export { type RawOptions } from "@effect-template/lib/core/command-options"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Either } from "effect"
|
|
2
|
-
|
|
3
|
-
import { type PanesCommand, type ParseError } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseProjectDirArgs } from "./parser-shared.js"
|
|
6
|
-
|
|
7
|
-
// CHANGE: parse panes command into a project selection
|
|
8
|
-
// WHY: allow listing tmux panes without attaching
|
|
9
|
-
// QUOTE(ТЗ): "покажи команду ... отобразит терминалы"
|
|
10
|
-
// REF: user-request-2026-02-02-panes
|
|
11
|
-
// SOURCE: n/a
|
|
12
|
-
// FORMAT THEOREM: forall argv: parsePanes(argv) = cmd -> deterministic(cmd)
|
|
13
|
-
// PURITY: CORE
|
|
14
|
-
// EFFECT: Effect<PanesCommand, ParseError, never>
|
|
15
|
-
// INVARIANT: projectDir is never empty
|
|
16
|
-
// COMPLEXITY: O(n) where n = |argv|
|
|
17
|
-
export const parsePanes = (args: ReadonlyArray<string>): Either.Either<PanesCommand, ParseError> => {
|
|
18
|
-
return Either.map(parseProjectDirArgs(args), ({ projectDir }) => ({
|
|
19
|
-
_tag: "Panes",
|
|
20
|
-
projectDir
|
|
21
|
-
}))
|
|
22
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { Either, Match } from "effect"
|
|
2
|
-
|
|
3
|
-
import type { Command, ParseError } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseProjectDirWithOptions } from "./parser-shared.js"
|
|
6
|
-
|
|
7
|
-
const missingRequired = (option: string): ParseError => ({
|
|
8
|
-
_tag: "MissingRequiredOption",
|
|
9
|
-
option
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const invalidScrapAction = (value: string): ParseError => ({
|
|
13
|
-
_tag: "InvalidOption",
|
|
14
|
-
option: "scrap",
|
|
15
|
-
reason: `unknown action: ${value}`
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const defaultSessionArchiveDir = ".orch/scrap/session"
|
|
19
|
-
|
|
20
|
-
const invalidScrapMode = (value: string): ParseError => ({
|
|
21
|
-
_tag: "InvalidOption",
|
|
22
|
-
option: "--mode",
|
|
23
|
-
reason: `unknown value: ${value} (expected session)`
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const parseScrapMode = (raw: string | undefined): Either.Either<"session", ParseError> => {
|
|
27
|
-
const value = raw?.trim()
|
|
28
|
-
if (!value || value.length === 0) {
|
|
29
|
-
return Either.right("session")
|
|
30
|
-
}
|
|
31
|
-
if (value === "session") {
|
|
32
|
-
return Either.right("session")
|
|
33
|
-
}
|
|
34
|
-
if (value === "recipe") {
|
|
35
|
-
// Backwards/semantic alias: "recipe" behaves like "session" (git state + rebuildable deps).
|
|
36
|
-
return Either.right("session")
|
|
37
|
-
}
|
|
38
|
-
return Either.left(invalidScrapMode(value))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const makeScrapExportCommand = (projectDir: string, archivePath: string, mode: "session"): Command => ({
|
|
42
|
-
_tag: "ScrapExport",
|
|
43
|
-
projectDir,
|
|
44
|
-
archivePath,
|
|
45
|
-
mode
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const makeScrapImportCommand = (
|
|
49
|
-
projectDir: string,
|
|
50
|
-
archivePath: string,
|
|
51
|
-
wipe: boolean,
|
|
52
|
-
mode: "session"
|
|
53
|
-
): Command => ({
|
|
54
|
-
_tag: "ScrapImport",
|
|
55
|
-
projectDir,
|
|
56
|
-
archivePath,
|
|
57
|
-
wipe,
|
|
58
|
-
mode
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// CHANGE: parse scrap session export/import commands
|
|
62
|
-
// WHY: store a small reproducible snapshot (git state + secrets) instead of large caches like node_modules
|
|
63
|
-
// QUOTE(ТЗ): "не должно быть старого режима где он качает весь шлак типо node_modules"
|
|
64
|
-
// REF: user-request-2026-02-15
|
|
65
|
-
// SOURCE: n/a
|
|
66
|
-
// FORMAT THEOREM: forall argv: parseScrap(argv) = cmd -> deterministic(cmd)
|
|
67
|
-
// PURITY: CORE
|
|
68
|
-
// EFFECT: Effect<Command, ParseError, never>
|
|
69
|
-
// INVARIANT: export/import always resolves a projectDir
|
|
70
|
-
// COMPLEXITY: O(n) where n = |argv|
|
|
71
|
-
export const parseScrap = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
|
|
72
|
-
const action = args[0]?.trim()
|
|
73
|
-
if (!action || action.length === 0) {
|
|
74
|
-
return Either.left(missingRequired("scrap <action>"))
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const rest = args.slice(1)
|
|
78
|
-
|
|
79
|
-
return Match.value(action).pipe(
|
|
80
|
-
Match.when(
|
|
81
|
-
"export",
|
|
82
|
-
() =>
|
|
83
|
-
Either.flatMap(
|
|
84
|
-
parseProjectDirWithOptions(rest),
|
|
85
|
-
({ projectDir, raw }) =>
|
|
86
|
-
Either.map(parseScrapMode(raw.scrapMode), (mode) => {
|
|
87
|
-
const archivePathRaw = raw.archivePath?.trim()
|
|
88
|
-
if (archivePathRaw && archivePathRaw.length > 0) {
|
|
89
|
-
return makeScrapExportCommand(projectDir, archivePathRaw, mode)
|
|
90
|
-
}
|
|
91
|
-
return makeScrapExportCommand(projectDir, defaultSessionArchiveDir, mode)
|
|
92
|
-
})
|
|
93
|
-
)
|
|
94
|
-
),
|
|
95
|
-
Match.when("import", () =>
|
|
96
|
-
Either.flatMap(parseProjectDirWithOptions(rest), ({ projectDir, raw }) => {
|
|
97
|
-
const archivePath = raw.archivePath?.trim()
|
|
98
|
-
if (!archivePath || archivePath.length === 0) {
|
|
99
|
-
return Either.left(missingRequired("--archive"))
|
|
100
|
-
}
|
|
101
|
-
return Either.map(parseScrapMode(raw.scrapMode), (mode) =>
|
|
102
|
-
makeScrapImportCommand(projectDir, archivePath, raw.wipe ?? true, mode))
|
|
103
|
-
})),
|
|
104
|
-
Match.orElse(() => Either.left(invalidScrapAction(action)))
|
|
105
|
-
)
|
|
106
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Either, Match } from "effect"
|
|
2
|
-
|
|
3
|
-
import { type ParseError, type SessionsCommand } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseProjectDirWithOptions } from "./parser-shared.js"
|
|
6
|
-
|
|
7
|
-
const defaultLines = 200
|
|
8
|
-
|
|
9
|
-
const parsePositiveInt = (
|
|
10
|
-
option: string,
|
|
11
|
-
raw: string
|
|
12
|
-
): Either.Either<number, ParseError> => {
|
|
13
|
-
const value = Number.parseInt(raw, 10)
|
|
14
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
15
|
-
const error: ParseError = {
|
|
16
|
-
_tag: "InvalidOption",
|
|
17
|
-
option,
|
|
18
|
-
reason: "expected positive integer"
|
|
19
|
-
}
|
|
20
|
-
return Either.left(error)
|
|
21
|
-
}
|
|
22
|
-
return Either.right(value)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const parseList = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
|
|
26
|
-
Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
|
|
27
|
-
_tag: "SessionsList",
|
|
28
|
-
projectDir,
|
|
29
|
-
includeDefault: raw.includeDefault === true
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
|
-
const parsePidContext = (
|
|
33
|
-
args: ReadonlyArray<string>
|
|
34
|
-
): Either.Either<
|
|
35
|
-
{ readonly pid: number; readonly projectDir: string; readonly raw: { readonly lines?: string } },
|
|
36
|
-
ParseError
|
|
37
|
-
> =>
|
|
38
|
-
Either.gen(function*(_) {
|
|
39
|
-
const pidRaw = args[0]
|
|
40
|
-
if (!pidRaw) {
|
|
41
|
-
const error: ParseError = { _tag: "MissingRequiredOption", option: "pid" }
|
|
42
|
-
return yield* _(Either.left(error))
|
|
43
|
-
}
|
|
44
|
-
const pid = yield* _(parsePositiveInt("pid", pidRaw))
|
|
45
|
-
const { projectDir, raw } = yield* _(parseProjectDirWithOptions(args.slice(1)))
|
|
46
|
-
return { pid, projectDir, raw }
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const parseKill = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
|
|
50
|
-
Either.map(parsePidContext(args), ({ pid, projectDir }) => ({
|
|
51
|
-
_tag: "SessionsKill",
|
|
52
|
-
projectDir,
|
|
53
|
-
pid
|
|
54
|
-
}))
|
|
55
|
-
|
|
56
|
-
const parseLogs = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
|
|
57
|
-
Either.gen(function*(_) {
|
|
58
|
-
const { pid, projectDir, raw } = yield* _(parsePidContext(args))
|
|
59
|
-
const lines = raw.lines ? yield* _(parsePositiveInt("--lines", raw.lines)) : defaultLines
|
|
60
|
-
return { _tag: "SessionsLogs", projectDir, pid, lines }
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// CHANGE: parse sessions command into list/kill/logs actions
|
|
64
|
-
// WHY: surface container terminal sessions and background processes from CLI
|
|
65
|
-
// QUOTE(ТЗ): "CLI команду которая из докера вернёт запущенные терминал сессии"
|
|
66
|
-
// REF: user-request-2026-02-04-terminal-sessions
|
|
67
|
-
// SOURCE: n/a
|
|
68
|
-
// FORMAT THEOREM: forall argv: parseSessions(argv) = cmd -> deterministic(cmd)
|
|
69
|
-
// PURITY: CORE
|
|
70
|
-
// EFFECT: Effect<SessionsCommand, ParseError, never>
|
|
71
|
-
// INVARIANT: pid/lines must be positive integers
|
|
72
|
-
// COMPLEXITY: O(n) where n = |argv|
|
|
73
|
-
export const parseSessions = (
|
|
74
|
-
args: ReadonlyArray<string>
|
|
75
|
-
): Either.Either<SessionsCommand, ParseError> => {
|
|
76
|
-
if (args.length === 0) {
|
|
77
|
-
return parseList(args)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const first = args[0] ?? ""
|
|
81
|
-
if (first.startsWith("-")) {
|
|
82
|
-
return parseList(args)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const rest = args.slice(1)
|
|
86
|
-
return Match.value(first).pipe(
|
|
87
|
-
Match.when("list", () => parseList(rest)),
|
|
88
|
-
Match.when("kill", () => parseKill(rest)),
|
|
89
|
-
Match.when("stop", () => parseKill(rest)),
|
|
90
|
-
Match.when("logs", () => parseLogs(rest)),
|
|
91
|
-
Match.when("log", () => parseLogs(rest)),
|
|
92
|
-
Match.orElse(() => {
|
|
93
|
-
const error: ParseError = {
|
|
94
|
-
_tag: "InvalidOption",
|
|
95
|
-
option: "sessions",
|
|
96
|
-
reason: `unknown action ${first}`
|
|
97
|
-
}
|
|
98
|
-
return Either.left(error)
|
|
99
|
-
})
|
|
100
|
-
)
|
|
101
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Either } from "effect"
|
|
2
|
-
|
|
3
|
-
import { deriveRepoPathParts, type ParseError, resolveRepoInput } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseRawOptions, type RawOptions } from "./parser-options.js"
|
|
6
|
-
|
|
7
|
-
type PositionalRepo = {
|
|
8
|
-
readonly positionalRepoUrl: string | undefined
|
|
9
|
-
readonly restArgs: ReadonlyArray<string>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const resolveWorkspaceRepoPath = (
|
|
13
|
-
resolvedRepo: ReturnType<typeof resolveRepoInput>
|
|
14
|
-
): string => {
|
|
15
|
-
const baseParts = deriveRepoPathParts(resolvedRepo.repoUrl).pathParts
|
|
16
|
-
const projectParts = resolvedRepo.workspaceSuffix ? [...baseParts, resolvedRepo.workspaceSuffix] : baseParts
|
|
17
|
-
return projectParts.join("/")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const splitPositionalRepo = (args: ReadonlyArray<string>): PositionalRepo => {
|
|
21
|
-
const first = args[0]
|
|
22
|
-
const positionalRepoUrl = first !== undefined && !first.startsWith("-") ? first : undefined
|
|
23
|
-
const restArgs = positionalRepoUrl ? args.slice(1) : args
|
|
24
|
-
return { positionalRepoUrl, restArgs }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const parseProjectDirWithOptions = (
|
|
28
|
-
args: ReadonlyArray<string>,
|
|
29
|
-
defaultProjectDir: string = "."
|
|
30
|
-
): Either.Either<{ readonly projectDir: string; readonly raw: RawOptions }, ParseError> =>
|
|
31
|
-
Either.gen(function*(_) {
|
|
32
|
-
const { positionalRepoUrl, restArgs } = splitPositionalRepo(args)
|
|
33
|
-
const raw = yield* _(parseRawOptions(restArgs))
|
|
34
|
-
const rawRepoUrl = raw.repoUrl ?? positionalRepoUrl
|
|
35
|
-
const repoPath = rawRepoUrl ? resolveWorkspaceRepoPath(resolveRepoInput(rawRepoUrl)) : null
|
|
36
|
-
const projectDir = raw.projectDir ??
|
|
37
|
-
(repoPath
|
|
38
|
-
? `.docker-git/${repoPath}`
|
|
39
|
-
: defaultProjectDir)
|
|
40
|
-
|
|
41
|
-
return { projectDir, raw }
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
export const parseProjectDirArgs = (
|
|
45
|
-
args: ReadonlyArray<string>,
|
|
46
|
-
defaultProjectDir: string = "."
|
|
47
|
-
): Either.Either<{ readonly projectDir: string }, ParseError> =>
|
|
48
|
-
Either.map(
|
|
49
|
-
parseProjectDirWithOptions(args, defaultProjectDir),
|
|
50
|
-
({ projectDir }) => ({ projectDir })
|
|
51
|
-
)
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { Either, Match } from "effect"
|
|
2
|
-
|
|
3
|
-
import type { Command, ParseError } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseRawOptions } from "./parser-options.js"
|
|
6
|
-
|
|
7
|
-
const invalidStateAction = (value: string): ParseError => ({
|
|
8
|
-
_tag: "InvalidOption",
|
|
9
|
-
option: "state",
|
|
10
|
-
reason: `unknown action: ${value}`
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const unexpectedArgs = (value: string): Either.Either<Command, ParseError> =>
|
|
14
|
-
Either.left({ _tag: "UnexpectedArgument", value })
|
|
15
|
-
|
|
16
|
-
const parseStateInit = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
|
|
17
|
-
Either.flatMap(parseRawOptions(args), (raw) => {
|
|
18
|
-
const repoUrl = raw.repoUrl?.trim()
|
|
19
|
-
if (!repoUrl || repoUrl.length === 0) {
|
|
20
|
-
return Either.left({ _tag: "MissingRequiredOption", option: "--repo-url" })
|
|
21
|
-
}
|
|
22
|
-
return Either.right({
|
|
23
|
-
_tag: "StateInit",
|
|
24
|
-
repoUrl,
|
|
25
|
-
repoRef: raw.repoRef?.trim() && raw.repoRef.trim().length > 0 ? raw.repoRef.trim() : "main"
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const parseStateCommit = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
|
|
30
|
-
Either.flatMap(parseRawOptions(args), (raw) => {
|
|
31
|
-
const message = raw.message?.trim()
|
|
32
|
-
if (!message || message.length === 0) {
|
|
33
|
-
return Either.left({ _tag: "MissingRequiredOption", option: "--message" })
|
|
34
|
-
}
|
|
35
|
-
return Either.right({ _tag: "StateCommit", message })
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const parseStateSync = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
|
|
39
|
-
Either.map(parseRawOptions(args), (raw) => {
|
|
40
|
-
const message = raw.message?.trim()
|
|
41
|
-
return { _tag: "StateSync", message: message && message.length > 0 ? message : null }
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
export const parseState = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
|
|
45
|
-
const action = args[0]?.trim()
|
|
46
|
-
if (!action || action.length === 0) {
|
|
47
|
-
return Either.left({ _tag: "MissingRequiredOption", option: "state <action>" })
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const rest = args.slice(1)
|
|
51
|
-
|
|
52
|
-
return Match.value(action).pipe(
|
|
53
|
-
Match.when("path", () => {
|
|
54
|
-
if (rest.length > 0) {
|
|
55
|
-
return unexpectedArgs(rest[0] ?? "")
|
|
56
|
-
}
|
|
57
|
-
const command: Command = { _tag: "StatePath" }
|
|
58
|
-
return Either.right(command)
|
|
59
|
-
}),
|
|
60
|
-
Match.when("init", () => parseStateInit(rest)),
|
|
61
|
-
Match.when("pull", () => {
|
|
62
|
-
if (rest.length > 0) {
|
|
63
|
-
return unexpectedArgs(rest[0] ?? "")
|
|
64
|
-
}
|
|
65
|
-
const command: Command = { _tag: "StatePull" }
|
|
66
|
-
return Either.right(command)
|
|
67
|
-
}),
|
|
68
|
-
Match.when("push", () => {
|
|
69
|
-
if (rest.length > 0) {
|
|
70
|
-
return unexpectedArgs(rest[0] ?? "")
|
|
71
|
-
}
|
|
72
|
-
const command: Command = { _tag: "StatePush" }
|
|
73
|
-
return Either.right(command)
|
|
74
|
-
}),
|
|
75
|
-
Match.when("status", () => {
|
|
76
|
-
if (rest.length > 0) {
|
|
77
|
-
return unexpectedArgs(rest[0] ?? "")
|
|
78
|
-
}
|
|
79
|
-
const command: Command = { _tag: "StateStatus" }
|
|
80
|
-
return Either.right(command)
|
|
81
|
-
}),
|
|
82
|
-
Match.when("commit", () => parseStateCommit(rest)),
|
|
83
|
-
Match.when("sync", () => parseStateSync(rest)),
|
|
84
|
-
Match.orElse(() => Either.left(invalidStateAction(action)))
|
|
85
|
-
)
|
|
86
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Either, Match } from "effect"
|
|
2
|
-
|
|
3
|
-
import { type Command, type ParseError } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseApply } from "./parser-apply.js"
|
|
6
|
-
import { parseAttach } from "./parser-attach.js"
|
|
7
|
-
import { parseAuth } from "./parser-auth.js"
|
|
8
|
-
import { parseClone } from "./parser-clone.js"
|
|
9
|
-
import { buildCreateCommand } from "./parser-create.js"
|
|
10
|
-
import { parseMcpPlaywright } from "./parser-mcp-playwright.js"
|
|
11
|
-
import { parseRawOptions } from "./parser-options.js"
|
|
12
|
-
import { parsePanes } from "./parser-panes.js"
|
|
13
|
-
import { parseScrap } from "./parser-scrap.js"
|
|
14
|
-
import { parseSessions } from "./parser-sessions.js"
|
|
15
|
-
import { parseState } from "./parser-state.js"
|
|
16
|
-
import { usageText } from "./usage.js"
|
|
17
|
-
|
|
18
|
-
const isHelpFlag = (token: string): boolean => token === "--help" || token === "-h"
|
|
19
|
-
|
|
20
|
-
const helpCommand: Command = { _tag: "Help", message: usageText }
|
|
21
|
-
const menuCommand: Command = { _tag: "Menu" }
|
|
22
|
-
const statusCommand: Command = { _tag: "Status" }
|
|
23
|
-
const downAllCommand: Command = { _tag: "DownAll" }
|
|
24
|
-
|
|
25
|
-
const parseCreate = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
|
|
26
|
-
Either.flatMap(parseRawOptions(args), (raw) => buildCreateCommand(raw))
|
|
27
|
-
|
|
28
|
-
// CHANGE: parse CLI arguments into a typed command
|
|
29
|
-
// WHY: enforce deterministic, pure parsing before any effects run
|
|
30
|
-
// QUOTE(ТЗ): "Надо написать CLI команду с помощью которой мы будем создавать докер образы"
|
|
31
|
-
// REF: user-request-2026-01-07
|
|
32
|
-
// SOURCE: n/a
|
|
33
|
-
// FORMAT THEOREM: forall argv: parse(argv) = cmd -> deterministic(cmd)
|
|
34
|
-
// PURITY: CORE
|
|
35
|
-
// EFFECT: Effect<Command, ParseError, never>
|
|
36
|
-
// INVARIANT: parse does not perform IO and returns the same result for same argv
|
|
37
|
-
// COMPLEXITY: O(n) where n = |argv|
|
|
38
|
-
export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
|
|
39
|
-
if (args.length === 0) {
|
|
40
|
-
return Either.right(menuCommand)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (args.some((arg) => isHelpFlag(arg))) {
|
|
44
|
-
return Either.right(helpCommand)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const command = args[0]
|
|
48
|
-
const rest = args.slice(1)
|
|
49
|
-
const unknownCommandError: ParseError = {
|
|
50
|
-
_tag: "UnknownCommand",
|
|
51
|
-
command: command ?? ""
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return Match.value(command)
|
|
55
|
-
.pipe(
|
|
56
|
-
Match.when("create", () => parseCreate(rest)),
|
|
57
|
-
Match.when("init", () => parseCreate(rest)),
|
|
58
|
-
Match.when("clone", () => parseClone(rest)),
|
|
59
|
-
Match.when("attach", () => parseAttach(rest)),
|
|
60
|
-
Match.when("tmux", () => parseAttach(rest)),
|
|
61
|
-
Match.when("panes", () => parsePanes(rest)),
|
|
62
|
-
Match.when("terms", () => parsePanes(rest)),
|
|
63
|
-
Match.when("terminals", () => parsePanes(rest)),
|
|
64
|
-
Match.when("sessions", () => parseSessions(rest)),
|
|
65
|
-
Match.when("scrap", () => parseScrap(rest)),
|
|
66
|
-
Match.when("mcp-playwright", () => parseMcpPlaywright(rest)),
|
|
67
|
-
Match.when("help", () => Either.right(helpCommand)),
|
|
68
|
-
Match.when("ps", () => Either.right(statusCommand)),
|
|
69
|
-
Match.when("status", () => Either.right(statusCommand)),
|
|
70
|
-
Match.when("down-all", () => Either.right(downAllCommand)),
|
|
71
|
-
Match.when("stop-all", () => Either.right(downAllCommand)),
|
|
72
|
-
Match.when("kill-all", () => Either.right(downAllCommand)),
|
|
73
|
-
Match.when("menu", () => Either.right(menuCommand)),
|
|
74
|
-
Match.when("ui", () => Either.right(menuCommand)),
|
|
75
|
-
Match.when("auth", () => parseAuth(rest))
|
|
76
|
-
)
|
|
77
|
-
.pipe(
|
|
78
|
-
Match.when("apply", () => parseApply(rest)),
|
|
79
|
-
Match.when("state", () => parseState(rest)),
|
|
80
|
-
Match.orElse(() => Either.left(unknownCommandError))
|
|
81
|
-
)
|
|
82
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Effect, Either, pipe } from "effect"
|
|
2
|
-
|
|
3
|
-
import { type Command, type ParseError } from "@effect-template/lib/core/domain"
|
|
4
|
-
|
|
5
|
-
import { parseArgs } from "./parser.js"
|
|
6
|
-
|
|
7
|
-
// CHANGE: read and parse CLI arguments from process.argv
|
|
8
|
-
// WHY: keep IO at the boundary and delegate parsing to CORE
|
|
9
|
-
// QUOTE(ТЗ): "Надо написать CLI команду"
|
|
10
|
-
// REF: user-request-2026-01-07
|
|
11
|
-
// SOURCE: n/a
|
|
12
|
-
// FORMAT THEOREM: forall argv: read(argv) -> parse(argv)
|
|
13
|
-
// PURITY: SHELL
|
|
14
|
-
// EFFECT: Effect<Command, ParseError, never>
|
|
15
|
-
// INVARIANT: errors are typed as ParseError
|
|
16
|
-
// COMPLEXITY: O(n) where n = |argv|
|
|
17
|
-
export const readCommand: Effect.Effect<Command, ParseError> = pipe(
|
|
18
|
-
Effect.sync(() => process.argv.slice(2)),
|
|
19
|
-
Effect.map((args) => parseArgs(args)),
|
|
20
|
-
Effect.flatMap((result) =>
|
|
21
|
-
Either.match(result, {
|
|
22
|
-
onLeft: (error) => Effect.fail(error),
|
|
23
|
-
onRight: (command) => Effect.succeed(command)
|
|
24
|
-
})
|
|
25
|
-
)
|
|
26
|
-
)
|