docks-kit 0.14.3 → 0.15.0
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/AGENTS.md +3 -1
- package/README.md +11 -5
- package/cli/docs/flags.md +6 -0
- package/cli/docs/overview.md +1 -1
- package/cli/src/argv.ts +433 -0
- package/cli/src/commands/docs.ts +6 -6
- package/cli/src/commands/model.ts +11 -11
- package/cli/src/commands/models.ts +6 -6
- package/cli/src/commands/plugins.ts +6 -6
- package/cli/src/commands/skills.ts +6 -6
- package/cli/src/commands/status.ts +3 -3
- package/cli/src/commands/sync.ts +43 -92
- package/cli/src/commands/toolchain.ts +12 -12
- package/cli/src/commands/update.ts +3 -3
- package/cli/src/engine-native/DESIGN.md +15 -4
- package/cli/src/engine-native/bun.ts +22 -21
- package/cli/src/engine-native/claudeSync.ts +30 -31
- package/cli/src/engine-native/codexSync.ts +24 -24
- package/cli/src/engine-native/deps.ts +43 -35
- package/cli/src/engine-native/exec.ts +61 -5
- package/cli/src/engine-native/index.ts +141 -33
- package/cli/src/engine-native/logger.ts +114 -14
- package/cli/src/engine-native/modes.ts +4 -4
- package/cli/src/engine-native/parseArgs.ts +1 -1
- package/cli/src/engine-native/services.ts +5 -5
- package/cli/src/engine-native/skillsSync.ts +19 -20
- package/cli/src/engine-native/toolchain.ts +40 -27
- package/cli/src/engine.ts +2 -2
- package/cli/src/generated/sotPayload.ts +1 -1
- package/cli/src/main.ts +27 -28
- package/cli/src/services.ts +4 -4
- package/docks-kit +1 -1
- package/package.json +5 -7
package/AGENTS.md
CHANGED
|
@@ -21,7 +21,7 @@ Bun source.
|
|
|
21
21
|
|------|---------|
|
|
22
22
|
| `docks-kit` | CLI launcher: on supported hosts, runs the platform binary in `cli/dist/` only when its `--version` matches `package.json`, otherwise Bun-from-source (auto-installs Bun + `node_modules`). Unsupported hosts fail before source fallback. No-Bun recovery is the standalone platform release binary |
|
|
23
23
|
| `cli/src/engine-native/` | EngineNative implementation for `sync`, `model`, and `toolchain`; idempotent, flag-gated for destructive reconciliation |
|
|
24
|
-
| `cli/` | Effect
|
|
24
|
+
| `cli/` | Effect 4 beta CLI + bundled docs topics |
|
|
25
25
|
| `SoT/models.json` | Kit-verified Claude and Codex model catalog |
|
|
26
26
|
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative) |
|
|
27
27
|
| `SoT/.claude/bin/` | Dependency-free Bun runtime programs for Claude's statusline, SessionStart, and Notification |
|
|
@@ -51,6 +51,8 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
51
51
|
|
|
52
52
|
- **Idempotent operations.** Every EngineNative sync step must be safe to re-run. Settings merges, plugin installs, and marketplace adds are all idempotent — re-running with no SoT changes is a no-op.
|
|
53
53
|
- **Removed bash engine.** The bash engine was removed after the `bash-engine-final` tag. `DOCKS_KIT_ENGINE=bash` must fail with the removed-engine message; engine bugs are fixed forward in EngineNative.
|
|
54
|
+
- **Effect 4 CLI stack.** The CLI pins `effect@4.0.0-beta.107` (including `effect/unstable/cli`), `@effect/platform-bun@4.0.0-beta.107` (`BunServices.layer`, `BunRuntime.runMain`), `@effect/vitest@4.0.0-beta.107`, and `vitest@4.1.10` (required by the `@effect/vitest` peer range). `@effect/cli` and `@effect/platform` are removed and must not be reintroduced.
|
|
55
|
+
- **Effect skill routing.** Effect work in this checkout must use the `effect-v4` skill from the already-enabled `effect-kit@docks` plugin; `effect-ts-setup`, `effect-ts-port`, and `effect-ts-specialist` target Effect 3.x and do not apply. Verify migration and API call shapes against the installed declarations under `node_modules/effect/dist/unstable/cli/`, never from memory or a mutable dist-tag.
|
|
54
56
|
- **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, and `agents`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync codex` for Codex-only config edits); targets can be combined with `--dry-run`, `--skip-bubblewrap` (skip optional bubblewrap bootstrap for the Codex Linux sandbox), `--skip-plugin-refresh` (install missing plugins without refreshing existing caches; used by `docks-kit update`), `--reconcile`, `--prune`, `--yes` (auto-accept toolchain prompts), and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
|
|
55
57
|
- **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync, including the home-relative `~/.local/bin/session-relay` artifact installed outside `~/.claude`; see `CLAUDE.md` § Pruning stale artifacts.
|
|
56
58
|
- **`--reconcile` / `--prune` are the kit-owned reconcile flags.** Orthogonal — `--reconcile` reconciles the settings layer (SoT-declared keys/tables/arrays win; user-only keys and nested objects are preserved; permissions arrays are replaced wholesale by SoT). `--prune` uninstalls kit-managed installations not in the SoT (plugins, marketplaces, and `~/.agents/skills/*` entries tracked in `~/.agents/.kit-managed-skills`). Combine for a full reset to SoT's kit-managed scope. User-only additions outside the kit's scope (custom env vars, mcpServers, manually-installed skills, third-party plugins not declared in SoT) are always preserved. Each tool's per-tool file documents the specific paths and diff recipes.
|
package/README.md
CHANGED
|
@@ -52,9 +52,15 @@ docks-kit docs [topic] self-documentation (9 topics)
|
|
|
52
52
|
--help --version --wizard --completions built-in
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
The CLI is a typed front-end
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
The CLI is a typed Effect 4 beta front-end on Bun; all mutation runs through
|
|
56
|
+
EngineNative in `cli/src/engine-native/`, with golden-regression coverage for
|
|
57
|
+
dry-run output, mutation snapshots, and argv logs. Its dependency graph is
|
|
58
|
+
`effect@4.0.0-beta.107` (including `effect/unstable/cli`),
|
|
59
|
+
`@effect/platform-bun@4.0.0-beta.107` (`BunServices.layer` and
|
|
60
|
+
`BunRuntime.runMain`), `@effect/vitest@4.0.0-beta.107`, and `vitest@4.1.10`
|
|
61
|
+
(required by the `@effect/vitest` peer range). `@effect/cli` and
|
|
62
|
+
`@effect/platform` are removed: neither has a 4.x release, the CLI API moved
|
|
63
|
+
into core, and the standalone platform package was unused here.
|
|
58
64
|
|
|
59
65
|
### Key flags (`docks-kit sync`)
|
|
60
66
|
|
|
@@ -106,7 +112,7 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
106
112
|
| `SoT/toolchain.json` | Verified-version floors |
|
|
107
113
|
| `cli/src/engine-native/` | EngineNative sync/model/toolchain implementation |
|
|
108
114
|
| `cli/src/generated/sotPayload.ts` | Generated in-memory payload used by standalone and npm installs |
|
|
109
|
-
| `cli/` | docks-kit CLI (Effect
|
|
115
|
+
| `cli/` | docks-kit CLI (Effect 4 beta on Bun) + bundled docs topics |
|
|
110
116
|
| `docks-kit` | Launcher (binary → bun-from-source) |
|
|
111
117
|
| `install.sh` | Global installer (Bun bootstrap + `bun add -g`) |
|
|
112
118
|
| `docs/plans/` | Multi-commit work-item plans |
|
|
@@ -128,7 +134,7 @@ Details: `docks-kit docs platforms`.
|
|
|
128
134
|
Tagging `cli-v*` builds four standalone binaries (Linux x64/arm64 and macOS
|
|
129
135
|
x64/arm64) plus `SHA256SUMS` and attaches them to the GitHub release; npm
|
|
130
136
|
publishes the exact package tarball through trusted publishing with OIDC provenance.
|
|
131
|
-
Package `docks-kit` 0.
|
|
137
|
+
Package `docks-kit` 0.15.0 bundles the CLI + generated payload, so npm releases
|
|
132
138
|
are versioned config snapshots without shipping the authoring `SoT/` tree.
|
|
133
139
|
|
|
134
140
|
## Deeper docs
|
package/cli/docs/flags.md
CHANGED
|
@@ -23,6 +23,12 @@ docks-kit sync claude agents # two
|
|
|
23
23
|
| `--yes` | Auto-accept toolchain above-verified prompts (containers/CI) |
|
|
24
24
|
| `--verbose` / `-v` | Also print no-op confirmations (already in sync, up to date, left as-is); accepted on `sync`, `model`, and `toolchain` |
|
|
25
25
|
|
|
26
|
+
## Environment overrides
|
|
27
|
+
|
|
28
|
+
| Variable | Effect |
|
|
29
|
+
|----------|--------|
|
|
30
|
+
| `DOCKS_KIT_SYNC_CONCURRENCY=1\|2\|3` | Maximum selected sync pipelines running at once. Default `3`; use `1` for serial golden/debug execution. Invalid values fail `sync` with exit 2 but do not affect `model` or `toolchain`. |
|
|
31
|
+
|
|
26
32
|
## Per-tool flags
|
|
27
33
|
|
|
28
34
|
| Flag | Effect |
|
package/cli/docs/overview.md
CHANGED
|
@@ -16,7 +16,7 @@ AI-assisted dev environment on every machine.
|
|
|
16
16
|
| `SoT/toolchain.json` | Verified-version floors for external tools (see `docks-kit docs toolchain`) |
|
|
17
17
|
| `cli/src/generated/sotPayload.ts` | Deterministic generated payload embedded in standalone/npm execution |
|
|
18
18
|
| `cli/src/engine-native/` | EngineNative mutation logic for sync/model/toolchain |
|
|
19
|
-
| `cli/` | This CLI (Effect
|
|
19
|
+
| `cli/` | This CLI (Effect 4 beta on Bun) plus bundled docs |
|
|
20
20
|
| `docks-kit` | Launcher: version-matching compiled binary → bun-from-source, with Bun auto-install |
|
|
21
21
|
|
|
22
22
|
## Design rules
|
package/cli/src/argv.ts
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { GlobalFlag } from "effect/unstable/cli"
|
|
2
|
+
import { docsCommand } from "./commands/docs"
|
|
3
|
+
import { modelCommand } from "./commands/model"
|
|
4
|
+
import { modelsCommand } from "./commands/models"
|
|
5
|
+
import { pluginsCommand } from "./commands/plugins"
|
|
6
|
+
import { skillsCommand } from "./commands/skills"
|
|
7
|
+
import { statusCommand } from "./commands/status"
|
|
8
|
+
import { syncCommand } from "./commands/sync"
|
|
9
|
+
import { toolchainCommand } from "./commands/toolchain"
|
|
10
|
+
import { updateCommand } from "./commands/update"
|
|
11
|
+
import {
|
|
12
|
+
advisorCatalog,
|
|
13
|
+
advisorFlagGrammar,
|
|
14
|
+
effortCatalog,
|
|
15
|
+
effortFlagGrammar
|
|
16
|
+
} from "./efforts"
|
|
17
|
+
import { KNOWN_CLAUDE_OPTIN_PLUGINS } from "./engine-native/parseArgs"
|
|
18
|
+
import { modelCatalog, type Tool } from "./manifests"
|
|
19
|
+
|
|
20
|
+
export type ArgvOutcome =
|
|
21
|
+
| { readonly kind: "reject"; readonly message: string; readonly exitCode: number }
|
|
22
|
+
| { readonly kind: "accept"; readonly args: ReadonlyArray<string> }
|
|
23
|
+
|
|
24
|
+
interface FlagSurface {
|
|
25
|
+
readonly longFlags: ReadonlyArray<string>
|
|
26
|
+
readonly aliases: ReadonlyMap<string, string>
|
|
27
|
+
readonly valueFlags: ReadonlyArray<string>
|
|
28
|
+
readonly repeatableFlags: ReadonlyArray<string>
|
|
29
|
+
}
|
|
30
|
+
interface GlobalFlagSurface extends FlagSurface {
|
|
31
|
+
readonly actionFlags: ReadonlySet<string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface CommandValue {
|
|
35
|
+
readonly name: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const flagMetadata = (
|
|
40
|
+
param: unknown
|
|
41
|
+
): {
|
|
42
|
+
readonly name: string
|
|
43
|
+
readonly aliases: ReadonlyArray<string>
|
|
44
|
+
readonly takesValue: boolean
|
|
45
|
+
readonly repeatable: boolean
|
|
46
|
+
} => {
|
|
47
|
+
let current = param
|
|
48
|
+
let repeatable = false
|
|
49
|
+
|
|
50
|
+
while (typeof current === "object" && current !== null) {
|
|
51
|
+
if (!("_tag" in current)) break
|
|
52
|
+
if (current._tag === "Variadic") repeatable = true
|
|
53
|
+
if (current._tag !== "Single") {
|
|
54
|
+
if (!("param" in current)) break
|
|
55
|
+
current = current.param
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!("name" in current) || typeof current.name !== "string") {
|
|
60
|
+
throw new Error("Effect CLI exposed a flag without a string name")
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
!("aliases" in current) ||
|
|
64
|
+
!Array.isArray(current.aliases) ||
|
|
65
|
+
!current.aliases.every((alias) => typeof alias === "string")
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(`Effect CLI exposed invalid aliases for --${current.name}`)
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
!("primitiveType" in current) ||
|
|
71
|
+
typeof current.primitiveType !== "object" ||
|
|
72
|
+
current.primitiveType === null ||
|
|
73
|
+
!("_tag" in current.primitiveType) ||
|
|
74
|
+
typeof current.primitiveType._tag !== "string"
|
|
75
|
+
) {
|
|
76
|
+
throw new Error(`Effect CLI exposed no primitive type for --${current.name}`)
|
|
77
|
+
}
|
|
78
|
+
const aliases: ReadonlyArray<string> = current.aliases
|
|
79
|
+
return {
|
|
80
|
+
name: current.name,
|
|
81
|
+
aliases,
|
|
82
|
+
takesValue: current.primitiveType._tag !== "Boolean",
|
|
83
|
+
repeatable
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error("Effect CLI exposed an unsupported flag parameter")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const flagSurface = (params: ReadonlyArray<unknown>): FlagSurface => {
|
|
91
|
+
const longFlags: Array<string> = []
|
|
92
|
+
const aliases = new Map<string, string>()
|
|
93
|
+
const valueFlags: Array<string> = []
|
|
94
|
+
const repeatableFlags: Array<string> = []
|
|
95
|
+
|
|
96
|
+
for (const param of params) {
|
|
97
|
+
const metadata = flagMetadata(param)
|
|
98
|
+
const longName = `--${metadata.name}`
|
|
99
|
+
longFlags.push(longName)
|
|
100
|
+
if (metadata.takesValue) valueFlags.push(longName)
|
|
101
|
+
if (metadata.repeatable) repeatableFlags.push(longName)
|
|
102
|
+
for (const alias of metadata.aliases) aliases.set(`-${alias}`, longName)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { longFlags, aliases, valueFlags, repeatableFlags }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const commandSurface = (command: CommandValue): FlagSurface => {
|
|
109
|
+
if (!("config" in command)) {
|
|
110
|
+
throw new Error(`Effect CLI did not expose flags for '${command.name}'`)
|
|
111
|
+
}
|
|
112
|
+
const config = command.config
|
|
113
|
+
if (
|
|
114
|
+
typeof config !== "object" ||
|
|
115
|
+
config === null ||
|
|
116
|
+
!("flags" in config) ||
|
|
117
|
+
!Array.isArray(config.flags)
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(`Effect CLI did not expose flags for '${command.name}'`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return flagSurface(config.flags)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const globalSurface = (): GlobalFlagSurface => {
|
|
126
|
+
const builtIns: unknown = GlobalFlag.BuiltIns
|
|
127
|
+
if (!Array.isArray(builtIns)) {
|
|
128
|
+
throw new Error("Effect CLI did not expose its built-in global flags")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const actionFlags = new Set<string>()
|
|
132
|
+
const flags = builtIns.map((builtIn) => {
|
|
133
|
+
if (
|
|
134
|
+
(typeof builtIn !== "object" && typeof builtIn !== "function") ||
|
|
135
|
+
builtIn === null ||
|
|
136
|
+
!("flag" in builtIn) ||
|
|
137
|
+
!("_tag" in builtIn) ||
|
|
138
|
+
typeof builtIn._tag !== "string"
|
|
139
|
+
) {
|
|
140
|
+
throw new Error("Effect CLI exposed an unsupported built-in global flag")
|
|
141
|
+
}
|
|
142
|
+
if (builtIn._tag === "Action") {
|
|
143
|
+
// Only a presence-based action is idempotent. `--completions` is an action
|
|
144
|
+
// that takes a value, and Effect 4 would silently keep the first one, which
|
|
145
|
+
// is exactly what the duplicate rule exists to refuse.
|
|
146
|
+
const metadata = flagMetadata(builtIn.flag)
|
|
147
|
+
if (!metadata.takesValue) actionFlags.add(`--${metadata.name}`)
|
|
148
|
+
}
|
|
149
|
+
return builtIn.flag
|
|
150
|
+
})
|
|
151
|
+
return { ...flagSurface(flags), actionFlags }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const COMMANDS: ReadonlyArray<CommandValue> = [
|
|
155
|
+
syncCommand,
|
|
156
|
+
updateCommand,
|
|
157
|
+
modelCommand,
|
|
158
|
+
toolchainCommand,
|
|
159
|
+
modelsCommand,
|
|
160
|
+
statusCommand,
|
|
161
|
+
pluginsCommand,
|
|
162
|
+
skillsCommand,
|
|
163
|
+
docsCommand
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
// A Map, not a plain object: an object literal answers `toString` and friends from
|
|
167
|
+
// `Object.prototype`, so an unknown subcommand with such a name would slip past the
|
|
168
|
+
// unknown-command guard and dereference a surface that was never built.
|
|
169
|
+
const COMMAND_SURFACES: ReadonlyMap<string, FlagSurface> = new Map(
|
|
170
|
+
COMMANDS.map((command) => [command.name, commandSurface(command)])
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const GLOBAL_SURFACE: GlobalFlagSurface = globalSurface()
|
|
174
|
+
|
|
175
|
+
const LEGACY_HINTS: Readonly<Record<string, string>> = {
|
|
176
|
+
"--force": "--force was renamed to --reconcile",
|
|
177
|
+
"--remove-plugins":
|
|
178
|
+
"--remove-plugins was renamed to --prune (it also removes marketplaces + kit-managed skills)",
|
|
179
|
+
"--680k": "--680k was renamed to --claude-compact-window=680k",
|
|
180
|
+
"--permissive": "--permissive was renamed to --claude-permissive",
|
|
181
|
+
"--supabase": "--supabase was renamed to --claude-plugin=supabase",
|
|
182
|
+
"--n8n": "--n8n was renamed to --claude-plugin=n8n",
|
|
183
|
+
"--skip-rtk": "--skip-rtk was renamed to --skip-bubblewrap",
|
|
184
|
+
"--claude": "--claude was renamed: pass the target as a word, e.g. 'sync claude'",
|
|
185
|
+
"--codex": "--codex was renamed: pass the target as a word, e.g. 'sync codex'",
|
|
186
|
+
"--agents": "--agents was renamed: pass the target as a word, e.g. 'sync agents'"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const flagNameOf = (token: string): string => {
|
|
190
|
+
const equals = token.indexOf("=")
|
|
191
|
+
return equals === -1 ? token : token.slice(0, equals)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const canonicalFlagName = (
|
|
195
|
+
name: string,
|
|
196
|
+
surface: FlagSurface | undefined
|
|
197
|
+
): string | undefined => {
|
|
198
|
+
if (surface === undefined) return undefined
|
|
199
|
+
const aliased = surface.aliases.get(name)
|
|
200
|
+
if (aliased !== undefined) return aliased
|
|
201
|
+
return surface.longFlags.includes(name) ? name : undefined
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const declaredFlagName = (
|
|
205
|
+
name: string,
|
|
206
|
+
commandSurface: FlagSurface | undefined
|
|
207
|
+
): string | undefined =>
|
|
208
|
+
canonicalFlagName(name, commandSurface) ?? canonicalFlagName(name, GLOBAL_SURFACE)
|
|
209
|
+
|
|
210
|
+
const declaredInAnySurface = (name: string): boolean => {
|
|
211
|
+
if (canonicalFlagName(name, GLOBAL_SURFACE) !== undefined) return true
|
|
212
|
+
return COMMANDS.some(
|
|
213
|
+
(command) => canonicalFlagName(name, COMMAND_SURFACES.get(command.name)) !== undefined
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const takesValueInAnySurface = (name: string): boolean => {
|
|
218
|
+
const globalName = canonicalFlagName(name, GLOBAL_SURFACE)
|
|
219
|
+
if (globalName !== undefined && GLOBAL_SURFACE.valueFlags.includes(globalName)) return true
|
|
220
|
+
return COMMANDS.some((command) => {
|
|
221
|
+
const surface = COMMAND_SURFACES.get(command.name)
|
|
222
|
+
if (surface === undefined) return false
|
|
223
|
+
const canonicalName = canonicalFlagName(name, surface)
|
|
224
|
+
return canonicalName !== undefined && surface.valueFlags.includes(canonicalName)
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The resolved subcommand word, or undefined at the root. */
|
|
229
|
+
export const subcommandName = (args: ReadonlyArray<string>): string | undefined => {
|
|
230
|
+
for (let index = 0; index < args.length; index++) {
|
|
231
|
+
const token = args[index]
|
|
232
|
+
if (token === "--") return undefined
|
|
233
|
+
const name = flagNameOf(token)
|
|
234
|
+
if (token === name && takesValueInAnySurface(name)) {
|
|
235
|
+
const next = args[index + 1]
|
|
236
|
+
if (next === "--") return undefined
|
|
237
|
+
if (
|
|
238
|
+
next !== undefined &&
|
|
239
|
+
!(next.startsWith("-") && declaredInAnySurface(flagNameOf(next)))
|
|
240
|
+
) {
|
|
241
|
+
index++
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!token.startsWith("-")) return token
|
|
246
|
+
}
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface ScannedFlag {
|
|
251
|
+
readonly token: string
|
|
252
|
+
readonly name: string
|
|
253
|
+
readonly canonicalName: string | undefined
|
|
254
|
+
readonly hasValue: boolean
|
|
255
|
+
readonly takesValue: boolean
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
interface ArgvNormalization {
|
|
259
|
+
readonly flagIndex: number
|
|
260
|
+
readonly valueIndex: number
|
|
261
|
+
readonly token: string
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface ScannedArgv {
|
|
265
|
+
readonly flags: ReadonlyArray<ScannedFlag>
|
|
266
|
+
readonly normalizations: ReadonlyArray<ArgvNormalization>
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const scanArgv = (
|
|
270
|
+
args: ReadonlyArray<string>,
|
|
271
|
+
commandSurface: FlagSurface | undefined
|
|
272
|
+
): ScannedArgv => {
|
|
273
|
+
const flags: Array<ScannedFlag> = []
|
|
274
|
+
const normalizations: Array<ArgvNormalization> = []
|
|
275
|
+
|
|
276
|
+
for (let index = 0; index < args.length; index++) {
|
|
277
|
+
const token = args[index]
|
|
278
|
+
if (token === "--") break
|
|
279
|
+
if (!token.startsWith("-")) continue
|
|
280
|
+
|
|
281
|
+
const name = flagNameOf(token)
|
|
282
|
+
const canonicalName = declaredFlagName(name, commandSurface)
|
|
283
|
+
let hasValue = token.includes("=")
|
|
284
|
+
const takesValue =
|
|
285
|
+
canonicalName !== undefined &&
|
|
286
|
+
(commandSurface?.valueFlags.includes(canonicalName) === true ||
|
|
287
|
+
GLOBAL_SURFACE.valueFlags.includes(canonicalName))
|
|
288
|
+
|
|
289
|
+
if (!hasValue && takesValue) {
|
|
290
|
+
const next = args[index + 1]
|
|
291
|
+
const nextIsRecognizedFlag =
|
|
292
|
+
next !== undefined &&
|
|
293
|
+
next.startsWith("-") &&
|
|
294
|
+
declaredFlagName(flagNameOf(next), commandSurface) !== undefined
|
|
295
|
+
// A legitimate value may begin with `-`; only a recognized flag proves it is missing.
|
|
296
|
+
if (next !== undefined && next !== "--" && !nextIsRecognizedFlag) {
|
|
297
|
+
hasValue = true
|
|
298
|
+
if (next.startsWith("-")) {
|
|
299
|
+
// Effect 4's lexer treats any `-`-leading token as an option, so a legitimate
|
|
300
|
+
// dash-leading value only survives in the inline form.
|
|
301
|
+
normalizations.push({
|
|
302
|
+
flagIndex: index,
|
|
303
|
+
valueIndex: index + 1,
|
|
304
|
+
token: `${token}=${next}`
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
index++
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
flags.push({ token, name, canonicalName, hasValue, takesValue })
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { flags, normalizations }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const normalizeArgv = (
|
|
318
|
+
args: ReadonlyArray<string>,
|
|
319
|
+
normalizations: ReadonlyArray<ArgvNormalization>
|
|
320
|
+
): ReadonlyArray<string> => {
|
|
321
|
+
if (normalizations.length === 0) return args
|
|
322
|
+
|
|
323
|
+
const normalized: Array<string> = []
|
|
324
|
+
let normalizationIndex = 0
|
|
325
|
+
for (let index = 0; index < args.length; index++) {
|
|
326
|
+
const normalization = normalizations[normalizationIndex]
|
|
327
|
+
if (normalization !== undefined && normalization.flagIndex === index) {
|
|
328
|
+
normalized.push(normalization.token)
|
|
329
|
+
index = normalization.valueIndex
|
|
330
|
+
normalizationIndex++
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
normalized.push(args[index])
|
|
334
|
+
}
|
|
335
|
+
return normalized
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const reject = (message: string): ArgvOutcome => ({
|
|
339
|
+
kind: "reject",
|
|
340
|
+
message,
|
|
341
|
+
exitCode: 2
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const modelCatalogHint = (tool: Tool): string => {
|
|
345
|
+
const catalog = modelCatalog(tool)
|
|
346
|
+
const list = catalog.models
|
|
347
|
+
.map((model) => ` ${model.id}${model.note !== undefined ? ` — ${model.note}` : ""}`)
|
|
348
|
+
.join("\n")
|
|
349
|
+
return `Available ${tool} models (kit-verified ${catalog.verified} — SoT/models.json):\n${list}`
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const missingModifierValue = (flag: string): string | undefined => {
|
|
353
|
+
switch (flag) {
|
|
354
|
+
case "--claude-model":
|
|
355
|
+
case "--codex-model": {
|
|
356
|
+
const tool: Tool = flag === "--claude-model" ? "claude" : "codex"
|
|
357
|
+
return `${modelCatalogHint(tool)}\n${flag} requires a value: ${flag}=<model>`
|
|
358
|
+
}
|
|
359
|
+
case "--claude-effort":
|
|
360
|
+
case "--codex-effort": {
|
|
361
|
+
const tool: Tool = flag === "--claude-effort" ? "claude" : "codex"
|
|
362
|
+
return `${effortCatalog(tool)}\n${flag} requires a value: ${effortFlagGrammar(tool)}`
|
|
363
|
+
}
|
|
364
|
+
case "--claude-advisor":
|
|
365
|
+
return `${advisorCatalog()}\n${flag} requires a value: ${advisorFlagGrammar()}`
|
|
366
|
+
case "--claude-compact-window":
|
|
367
|
+
return `${flag} requires a value: ${flag}=<tokens> (e.g. 680k)`
|
|
368
|
+
case "--claude-plugin":
|
|
369
|
+
return `${flag} requires a value: ${flag}=<${KNOWN_CLAUDE_OPTIN_PLUGINS.join("|")}>`
|
|
370
|
+
default:
|
|
371
|
+
return undefined
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Validate the argument list, then hand back the arguments Effect 4 should parse. */
|
|
376
|
+
export const prepareArgv = (args: ReadonlyArray<string>): ArgvOutcome => {
|
|
377
|
+
const subcommand = subcommandName(args)
|
|
378
|
+
const commandSurface = subcommand === undefined ? undefined : COMMAND_SURFACES.get(subcommand)
|
|
379
|
+
const { flags, normalizations } = scanArgv(args, commandSurface)
|
|
380
|
+
const unknownCommandWouldMisdiagnoseFlag =
|
|
381
|
+
subcommand !== undefined &&
|
|
382
|
+
commandSurface === undefined &&
|
|
383
|
+
flags.some((flag) => flag.canonicalName === undefined)
|
|
384
|
+
if (unknownCommandWouldMisdiagnoseFlag) {
|
|
385
|
+
return reject(`unknown command '${subcommand}'`)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (subcommand === "sync") {
|
|
389
|
+
for (const flag of flags) {
|
|
390
|
+
const hint = LEGACY_HINTS[flag.name]
|
|
391
|
+
if (hint !== undefined) return reject(hint)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Effect 4 expands clustered shorts, but the kit declares no clusterable alias
|
|
396
|
+
// pair, so refusing a cluster as one unknown flag is the conservative direction.
|
|
397
|
+
for (const flag of flags) {
|
|
398
|
+
if (flag.canonicalName !== undefined) continue
|
|
399
|
+
const scope = subcommand === undefined ? "" : ` for '${subcommand}'`
|
|
400
|
+
return reject(`unknown flag ${flag.token}${scope}`)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for (const flag of flags) {
|
|
404
|
+
if (!flag.token.includes("=") || flag.takesValue) continue
|
|
405
|
+
// These booleans are presence-based; Effect 4 would otherwise let
|
|
406
|
+
// `--dry-run=false` read as a dry run while performing a real mutating sync.
|
|
407
|
+
return reject(`flag ${flag.name} does not take a value`)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const seen = new Set<string>()
|
|
411
|
+
for (const flag of flags) {
|
|
412
|
+
const name = flag.canonicalName
|
|
413
|
+
if (name === undefined) continue
|
|
414
|
+
const duplicateAllowed =
|
|
415
|
+
commandSurface?.repeatableFlags.includes(name) === true ||
|
|
416
|
+
GLOBAL_SURFACE.repeatableFlags.includes(name) ||
|
|
417
|
+
GLOBAL_SURFACE.actionFlags.has(name)
|
|
418
|
+
if (!duplicateAllowed && seen.has(name)) {
|
|
419
|
+
return reject(`flag ${name} was given more than once`)
|
|
420
|
+
}
|
|
421
|
+
seen.add(name)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (subcommand === "sync") {
|
|
425
|
+
for (const flag of flags) {
|
|
426
|
+
if (flag.hasValue) continue
|
|
427
|
+
const message = missingModifierValue(flag.name)
|
|
428
|
+
if (message !== undefined) return reject(message)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return { kind: "accept", args: normalizeArgv(args, normalizations) }
|
|
433
|
+
}
|
package/cli/src/commands/docs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Console, Effect, Option } from "effect"
|
|
3
3
|
import { bail } from "../engine"
|
|
4
4
|
import overview from "../../docs/overview.md" with { type: "text" }
|
|
@@ -23,12 +23,12 @@ const TOPICS: Record<string, { summary: string; body: string }> = {
|
|
|
23
23
|
"platforms": { summary: "Platform support: Linux/macOS x64 and arm64", body: platforms }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const topic =
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const topic = Argument.string("topic").pipe(
|
|
27
|
+
Argument.withDescription(`One of: ${Object.keys(TOPICS).join(", ")}`),
|
|
28
|
+
Argument.optional
|
|
29
29
|
)
|
|
30
|
-
const json =
|
|
31
|
-
|
|
30
|
+
const json = Flag.boolean("json").pipe(
|
|
31
|
+
Flag.withDescription("List topics as JSON")
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
export const docsCommand = Command.make("docs", { topic, json }, (config) =>
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag, Prompt } from "effect/unstable/cli"
|
|
2
2
|
import { Effect, Option } from "effect"
|
|
3
3
|
import { bail, engine } from "../engine"
|
|
4
4
|
import { modelCatalog, type Tool } from "../manifests"
|
|
5
5
|
|
|
6
|
-
const tool =
|
|
7
|
-
|
|
6
|
+
const tool = Argument.string("tool").pipe(
|
|
7
|
+
Argument.withDescription("Which tool: claude | codex")
|
|
8
8
|
)
|
|
9
|
-
const value =
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const value = Argument.string("value").pipe(
|
|
10
|
+
Argument.withDescription("Model to set (omit to view current + pick interactively on a TTY)"),
|
|
11
|
+
Argument.optional
|
|
12
12
|
)
|
|
13
|
-
const dryRun =
|
|
14
|
-
|
|
13
|
+
const dryRun = Flag.boolean("dry-run").pipe(
|
|
14
|
+
Flag.withDescription("Preview without applying")
|
|
15
15
|
)
|
|
16
|
-
const verbose =
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const verbose = Flag.boolean("verbose").pipe(
|
|
17
|
+
Flag.withAlias("v"),
|
|
18
|
+
Flag.withDescription("Also print no-op confirmations (already in sync, up to date)")
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
const KEEP = "__keep__"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Console, Effect, Option } from "effect"
|
|
3
3
|
import { bail } from "../engine"
|
|
4
4
|
import { modelCatalog, type Tool } from "../manifests"
|
|
5
5
|
|
|
6
|
-
const tool =
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const tool = Argument.string("tool").pipe(
|
|
7
|
+
Argument.withDescription("claude | codex (omit for both tool catalogs)"),
|
|
8
|
+
Argument.optional
|
|
9
9
|
)
|
|
10
|
-
const json =
|
|
11
|
-
|
|
10
|
+
const json = Flag.boolean("json").pipe(
|
|
11
|
+
Flag.withDescription("Machine-readable output")
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
const renderTool = (t: Tool) =>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Console, Effect, Option } from "effect"
|
|
3
3
|
import { bail } from "../engine"
|
|
4
4
|
import { pluginsView } from "../manifests"
|
|
5
5
|
|
|
6
|
-
const action =
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const action = Argument.string("action").pipe(
|
|
7
|
+
Argument.withDescription("list (default)"),
|
|
8
|
+
Argument.optional
|
|
9
9
|
)
|
|
10
|
-
const json =
|
|
11
|
-
|
|
10
|
+
const json = Flag.boolean("json").pipe(
|
|
11
|
+
Flag.withDescription("Machine-readable output")
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
export const pluginsCommand = Command.make("plugins", { action, json }, (config) =>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Console, Effect, Option } from "effect"
|
|
3
3
|
import { bail } from "../engine"
|
|
4
4
|
import { skillsView } from "../manifests"
|
|
5
5
|
|
|
6
|
-
const action =
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const action = Argument.string("action").pipe(
|
|
7
|
+
Argument.withDescription("list (default)"),
|
|
8
|
+
Argument.optional
|
|
9
9
|
)
|
|
10
|
-
const json =
|
|
11
|
-
|
|
10
|
+
const json = Flag.boolean("json").pipe(
|
|
11
|
+
Flag.withDescription("Machine-readable output")
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
export const skillsCommand = Command.make("skills", { action, json }, (config) =>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command,
|
|
1
|
+
import { Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Console, Effect } from "effect"
|
|
3
3
|
import { engineCapture } from "../engine"
|
|
4
4
|
import {
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "../manifests"
|
|
12
12
|
import { kitHome } from "../kitHome"
|
|
13
13
|
|
|
14
|
-
const json =
|
|
15
|
-
|
|
14
|
+
const json = Flag.boolean("json").pipe(
|
|
15
|
+
Flag.withDescription("Machine-readable output")
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
interface Drift {
|