docks-kit 0.14.4 → 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/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/parseArgs.ts +1 -1
- package/cli/src/engine.ts +1 -1
- package/cli/src/generated/sotPayload.ts +1 -1
- package/cli/src/main.ts +26 -27
- 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/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 {
|
package/cli/src/commands/sync.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Effect, Option } from "effect"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
4
|
import { existsSync } from "node:fs"
|
|
5
5
|
import { join } from "node:path"
|
|
6
6
|
import { bail, engine } from "../engine"
|
|
7
7
|
import type { Logger } from "../engine-native/logger"
|
|
8
|
-
import {
|
|
9
|
-
advisorCatalog,
|
|
10
|
-
advisorFlagGrammar,
|
|
11
|
-
effortCatalog,
|
|
12
|
-
effortFlagGrammar
|
|
13
|
-
} from "../efforts"
|
|
14
8
|
import { kitHome } from "../kitHome"
|
|
15
|
-
import { modelCatalog, type Tool } from "../manifests"
|
|
16
9
|
import { LoggerService } from "../services"
|
|
17
10
|
|
|
18
11
|
/** Best-effort update autodetection: nudge (never block, never fail) when
|
|
@@ -38,90 +31,66 @@ const updateNudge = (logger: Logger): void => {
|
|
|
38
31
|
|
|
39
32
|
const VALID_TARGETS = ["claude", "codex", "agents"]
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"--force": "--force was renamed to --reconcile",
|
|
46
|
-
"--remove-plugins":
|
|
47
|
-
"--remove-plugins was renamed to --prune (it also removes marketplaces + kit-managed skills)",
|
|
48
|
-
"--680k": "--680k was renamed to --claude-compact-window=680k",
|
|
49
|
-
"--permissive": "--permissive was renamed to --claude-permissive",
|
|
50
|
-
"--supabase": "--supabase was renamed to --claude-plugin=supabase",
|
|
51
|
-
"--n8n": "--n8n was renamed to --claude-plugin=n8n",
|
|
52
|
-
"--skip-rtk": "--skip-rtk was renamed to --skip-bubblewrap",
|
|
53
|
-
"--claude": "--claude was renamed: pass the target as a word, e.g. 'sync claude'",
|
|
54
|
-
"--codex": "--codex was renamed: pass the target as a word, e.g. 'sync codex'",
|
|
55
|
-
"--agents": "--agents was renamed: pass the target as a word, e.g. 'sync agents'"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const modelCatalogHint = (t: Tool): string => {
|
|
59
|
-
const c = modelCatalog(t)
|
|
60
|
-
const list = c.models
|
|
61
|
-
.map((m) => ` ${m.id}${m.note !== undefined ? ` — ${m.note}` : ""}`)
|
|
62
|
-
.join("\n")
|
|
63
|
-
return `Available ${t} models (kit-verified ${c.verified} — SoT/models.json):\n${list}`
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const targets = Args.text({ name: "target" }).pipe(
|
|
67
|
-
Args.withDescription("Sync targets: claude, codex, agents (default: all three)"),
|
|
68
|
-
Args.repeated
|
|
34
|
+
const targets = Argument.variadic(
|
|
35
|
+
Argument.string("target").pipe(
|
|
36
|
+
Argument.withDescription("Sync targets: claude, codex, agents (default: all three)")
|
|
37
|
+
)
|
|
69
38
|
)
|
|
70
39
|
|
|
71
|
-
const dryRun =
|
|
72
|
-
|
|
40
|
+
const dryRun = Flag.boolean("dry-run").pipe(
|
|
41
|
+
Flag.withDescription("Preview without applying")
|
|
73
42
|
)
|
|
74
|
-
const reconcile =
|
|
75
|
-
|
|
43
|
+
const reconcile = Flag.boolean("reconcile").pipe(
|
|
44
|
+
Flag.withDescription("Reconcile kit-owned settings with SoT (SoT keys win; user-only keys preserved; permissions arrays replaced)")
|
|
76
45
|
)
|
|
77
|
-
const prune =
|
|
78
|
-
|
|
46
|
+
const prune = Flag.boolean("prune").pipe(
|
|
47
|
+
Flag.withDescription("Uninstall kit-managed installs not in SoT (plugins, marketplaces, universal skills)")
|
|
79
48
|
)
|
|
80
|
-
const skipBubblewrap =
|
|
81
|
-
|
|
49
|
+
const skipBubblewrap = Flag.boolean("skip-bubblewrap").pipe(
|
|
50
|
+
Flag.withDescription("Skip optional bubblewrap bootstrap (Codex Linux sandbox)")
|
|
82
51
|
)
|
|
83
|
-
const skipPluginRefresh =
|
|
84
|
-
|
|
52
|
+
const skipPluginRefresh = Flag.boolean("skip-plugin-refresh").pipe(
|
|
53
|
+
Flag.withDescription("Install missing plugins but skip refresh-only updates for existing plugins")
|
|
85
54
|
)
|
|
86
|
-
const yes =
|
|
87
|
-
|
|
55
|
+
const yes = Flag.boolean("yes").pipe(
|
|
56
|
+
Flag.withDescription("Auto-accept toolchain prompts (containers/CI)")
|
|
88
57
|
)
|
|
89
|
-
const verbose =
|
|
90
|
-
|
|
91
|
-
|
|
58
|
+
const verbose = Flag.boolean("verbose").pipe(
|
|
59
|
+
Flag.withAlias("v"),
|
|
60
|
+
Flag.withDescription("Also print no-op confirmations (already in sync, up to date, left as-is)")
|
|
92
61
|
)
|
|
93
|
-
const claudeModel =
|
|
94
|
-
|
|
95
|
-
|
|
62
|
+
const claudeModel = Flag.string("claude-model").pipe(
|
|
63
|
+
Flag.withDescription("Deploy-time modifier: set deployed Claude model (see `docks-kit models claude`)"),
|
|
64
|
+
Flag.optional
|
|
96
65
|
)
|
|
97
|
-
const claudeEffort =
|
|
98
|
-
|
|
99
|
-
|
|
66
|
+
const claudeEffort = Flag.string("claude-effort").pipe(
|
|
67
|
+
Flag.withDescription("Deploy-time modifier: set Claude effortLevel (bare flag shows valid levels)"),
|
|
68
|
+
Flag.optional
|
|
100
69
|
)
|
|
101
|
-
const claudeAdvisor =
|
|
102
|
-
|
|
103
|
-
|
|
70
|
+
const claudeAdvisor = Flag.string("claude-advisor").pipe(
|
|
71
|
+
Flag.withDescription("Deploy-time modifier: set Claude advisor on/off/default"),
|
|
72
|
+
Flag.optional
|
|
104
73
|
)
|
|
105
|
-
const claudeCompactWindow =
|
|
106
|
-
|
|
107
|
-
|
|
74
|
+
const claudeCompactWindow = Flag.string("claude-compact-window").pipe(
|
|
75
|
+
Flag.withDescription("Deploy-time modifier: set deployed autocompact window in tokens (e.g. 680000 or 680k)"),
|
|
76
|
+
Flag.optional
|
|
108
77
|
)
|
|
109
|
-
const claudePermissive =
|
|
110
|
-
|
|
78
|
+
const claudePermissive = Flag.boolean("claude-permissive").pipe(
|
|
79
|
+
Flag.withDescription("Deploy-time modifier: empty permissions.ask/deny in deployed settings (sandboxes)")
|
|
111
80
|
)
|
|
112
|
-
const claudePlugin =
|
|
113
|
-
|
|
81
|
+
const claudePlugin = Flag.string("claude-plugin").pipe(
|
|
82
|
+
Flag.withDescription(
|
|
114
83
|
"Sticky opt-in plugin(s); repeatable and/or comma-separated (known: supabase, n8n)"
|
|
115
84
|
),
|
|
116
|
-
|
|
85
|
+
Flag.atLeast(0)
|
|
117
86
|
)
|
|
118
|
-
const codexModel =
|
|
119
|
-
|
|
120
|
-
|
|
87
|
+
const codexModel = Flag.string("codex-model").pipe(
|
|
88
|
+
Flag.withDescription("Deploy-time modifier: set deployed Codex model (see `docks-kit models codex`)"),
|
|
89
|
+
Flag.optional
|
|
121
90
|
)
|
|
122
|
-
const codexEffort =
|
|
123
|
-
|
|
124
|
-
|
|
91
|
+
const codexEffort = Flag.string("codex-effort").pipe(
|
|
92
|
+
Flag.withDescription("Deploy-time modifier: set Codex model_reasoning_effort (bare flag shows valid levels)"),
|
|
93
|
+
Flag.optional
|
|
125
94
|
)
|
|
126
95
|
|
|
127
96
|
export const syncCommand = Command.make(
|
|
@@ -146,24 +115,6 @@ export const syncCommand = Command.make(
|
|
|
146
115
|
},
|
|
147
116
|
(config) =>
|
|
148
117
|
Effect.gen(function* () {
|
|
149
|
-
for (const t of config.targets) {
|
|
150
|
-
if (VALID_TARGETS.includes(t)) continue
|
|
151
|
-
if (t === "--claude-model" || t === "--codex-model") {
|
|
152
|
-
const tool: Tool = t === "--claude-model" ? "claude" : "codex"
|
|
153
|
-
return yield* bail(`${modelCatalogHint(tool)}\n${t} requires a value: ${t}=<model>`)
|
|
154
|
-
}
|
|
155
|
-
if (t === "--claude-effort" || t === "--codex-effort") {
|
|
156
|
-
const tool: Tool = t === "--claude-effort" ? "claude" : "codex"
|
|
157
|
-
return yield* bail(`${effortCatalog(tool)}\n${t} requires a value: ${effortFlagGrammar(tool)}`)
|
|
158
|
-
}
|
|
159
|
-
if (t === "--claude-advisor") {
|
|
160
|
-
return yield* bail(`${advisorCatalog()}\n${t} requires a value: ${advisorFlagGrammar()}`)
|
|
161
|
-
}
|
|
162
|
-
const hint = LEGACY_HINTS[t]
|
|
163
|
-
if (hint !== undefined) {
|
|
164
|
-
return yield* bail(hint)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
118
|
const bad = config.targets.filter((t) => !VALID_TARGETS.includes(t))
|
|
168
119
|
if (bad.length > 0) {
|
|
169
120
|
return yield* bail(
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
2
|
import { Effect, Option } from "effect"
|
|
3
3
|
import { bail, engine } from "../engine"
|
|
4
4
|
|
|
5
5
|
const MANAGED = ["bun", "effect-solutions"]
|
|
6
6
|
|
|
7
|
-
const op =
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const op = Argument.string("op").pipe(
|
|
8
|
+
Argument.withDescription("check (default) | ensure <tool>"),
|
|
9
|
+
Argument.optional
|
|
10
10
|
)
|
|
11
|
-
const tool =
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const tool = Argument.string("tool").pipe(
|
|
12
|
+
Argument.withDescription(`Managed tool for ensure: ${MANAGED.join(", ")}`),
|
|
13
|
+
Argument.optional
|
|
14
14
|
)
|
|
15
|
-
const yes =
|
|
16
|
-
|
|
15
|
+
const yes = Flag.boolean("yes").pipe(
|
|
16
|
+
Flag.withDescription("Auto-accept above-verified installs")
|
|
17
17
|
)
|
|
18
|
-
const verbose =
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const verbose = Flag.boolean("verbose").pipe(
|
|
19
|
+
Flag.withAlias("v"),
|
|
20
|
+
Flag.withDescription("Also print no-op confirmations (present, up to date)")
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
export const toolchainCommand = Command.make("toolchain", { op, tool, yes, verbose }, (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 { spawnSync } from "node:child_process"
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs"
|
|
@@ -6,8 +6,8 @@ import { join } from "node:path"
|
|
|
6
6
|
import { bail, compiled } from "../engine"
|
|
7
7
|
import { kitHome } from "../kitHome"
|
|
8
8
|
|
|
9
|
-
const noSync =
|
|
10
|
-
|
|
9
|
+
const noSync = Flag.boolean("no-sync").pipe(
|
|
10
|
+
Flag.withDescription("Update the kit only; skip the chained flag-less sync")
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
const git = (home: string, args: Array<string>): { ok: boolean; out: string } => {
|
|
@@ -23,7 +23,7 @@ export class ExitError extends Error {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const KNOWN_CLAUDE_OPTIN_PLUGINS = ["supabase", "n8n"]
|
|
26
|
+
export const KNOWN_CLAUDE_OPTIN_PLUGINS = ["supabase", "n8n"]
|
|
27
27
|
const MODIFIER_FLAGS = new Set<ModifierFlag>([
|
|
28
28
|
"--claude-model",
|
|
29
29
|
"--claude-effort",
|
package/cli/src/engine.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { DependencyManagerService, LoggerService, PlatformService } from "./serv
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The single seam between the typed CLI and EngineNative. Engine execution
|
|
10
|
-
* stays in-process after
|
|
10
|
+
* stays in-process after effect/unstable/cli has parsed pickers and flag spellings.
|
|
11
11
|
*/
|
|
12
12
|
const bashRemovedMessage = "bash engine removed — recover at tag bash-engine-final"
|
|
13
13
|
const bashEngineRequested = (): boolean => process.env["DOCKS_KIT_ENGINE"] === "bash"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by cli/scripts/generate-sot-payload.ts. DO NOT EDIT.
|
|
2
2
|
// Edit SoT/, notification.mp3, or package.json, then run: bun cli/scripts/generate-sot-payload.ts
|
|
3
3
|
|
|
4
|
-
export const GENERATED_PACKAGE_VERSION = "0.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.15.0"
|
|
5
5
|
|
|
6
6
|
export const GENERATED_PAYLOAD_TEXT = {
|
|
7
7
|
"SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
|
package/cli/src/main.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { Command } from "
|
|
3
|
-
import {
|
|
2
|
+
import { Command, CliOutput } from "effect/unstable/cli"
|
|
3
|
+
import { BunRuntime, BunServices } from "@effect/platform-bun"
|
|
4
4
|
import { Console, Effect, Layer } from "effect"
|
|
5
|
-
import { engine } from "./engine"
|
|
6
5
|
import { EngineServicesLive } from "./services"
|
|
7
6
|
import { docsCommand } from "./commands/docs"
|
|
8
7
|
import { modelCommand } from "./commands/model"
|
|
@@ -14,6 +13,7 @@ import { syncCommand } from "./commands/sync"
|
|
|
14
13
|
import { toolchainCommand } from "./commands/toolchain"
|
|
15
14
|
import { updateCommand } from "./commands/update"
|
|
16
15
|
import { GENERATED_PACKAGE_VERSION } from "./generated/sotPayload"
|
|
16
|
+
import { prepareArgv } from "./argv"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
const root = Command.make("docks-kit", {}, () =>
|
|
@@ -50,8 +50,16 @@ const root = Command.make("docks-kit", {}, () =>
|
|
|
50
50
|
])
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
// v4's default formatter renders `name vversion`, but the `docks-kit` launcher
|
|
54
|
+
// compares `--version` against the bare `package.json` version. The trailing
|
|
55
|
+
// newline reproduces the characterized byte-for-byte output of the v3 CLI.
|
|
56
|
+
const bareVersionFormatter: CliOutput.Formatter = {
|
|
57
|
+
...CliOutput.defaultFormatter(),
|
|
58
|
+
formatVersion: (_name, version) => `${version}\n`
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
// Harness-private raw channel:
|
|
54
|
-
// `DOCKS_KIT_ENGINE=native-raw` bypasses
|
|
62
|
+
// `DOCKS_KIT_ENGINE=native-raw` bypasses effect/unstable/cli and hands the raw engine
|
|
55
63
|
// argv to EngineNative so golden tests drive the internal vocabulary directly.
|
|
56
64
|
// PUBLIC engine execution lives at the engine.ts seam after the CLI has
|
|
57
65
|
// parsed/normalized pickers, --flag value forms, and non-engine commands.
|
|
@@ -60,27 +68,18 @@ if (process.env["DOCKS_KIT_ENGINE"] === "native-raw") {
|
|
|
60
68
|
process.exit(await runEngineNative(process.argv.slice(2)))
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const emptyTextOptions = new Set([
|
|
72
|
-
"--claude-model=",
|
|
73
|
-
"--claude-effort=",
|
|
74
|
-
"--claude-advisor=",
|
|
75
|
-
"--codex-model=",
|
|
76
|
-
"--codex-effort=",
|
|
77
|
-
])
|
|
78
|
-
const argv = process.argv.flatMap((a, index, all) => {
|
|
79
|
-
if (a.startsWith("--claude-plugin=")) {
|
|
80
|
-
return ["--claude-plugin", a.slice("--claude-plugin=".length)]
|
|
81
|
-
}
|
|
82
|
-
if (emptyTextOptions.has(a)) return [a.slice(0, -1), ""]
|
|
83
|
-
return [a]
|
|
84
|
-
})
|
|
71
|
+
// Validate and normalize before parsing because the kit refuses to guess at unrecognized
|
|
72
|
+
// or duplicated flags, and Effect 4 would otherwise negate `--no-<flag>` into a real
|
|
73
|
+
// mutating run. This seam owns argument normalization.
|
|
74
|
+
const prepared = prepareArgv(process.argv.slice(2))
|
|
75
|
+
if (prepared.kind === "reject") {
|
|
76
|
+
process.stderr.write(`${prepared.message}\n`)
|
|
77
|
+
process.exit(prepared.exitCode)
|
|
78
|
+
}
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
Command.runWith(root, { version: GENERATED_PACKAGE_VERSION })(prepared.args).pipe(
|
|
81
|
+
Effect.provide(
|
|
82
|
+
Layer.mergeAll(BunServices.layer, EngineServicesLive, CliOutput.layer(bareVersionFormatter))
|
|
83
|
+
),
|
|
84
|
+
BunRuntime.runMain
|
|
85
|
+
)
|
package/cli/src/services.ts
CHANGED
|
@@ -8,14 +8,14 @@ import { Context, Layer } from "effect"
|
|
|
8
8
|
import { makeLogger, type Logger, type LoggerSinks } from "./engine-native/logger"
|
|
9
9
|
import { makeEngineServices, makePlatform, type DependencyManager, type Platform } from "./engine-native/services"
|
|
10
10
|
|
|
11
|
-
export class LoggerService extends Context.
|
|
11
|
+
export class LoggerService extends Context.Service<LoggerService, Logger>()("docks-kit/Logger") {}
|
|
12
12
|
|
|
13
|
-
export class DependencyManagerService extends Context.
|
|
13
|
+
export class DependencyManagerService extends Context.Service<
|
|
14
14
|
DependencyManagerService,
|
|
15
15
|
DependencyManager
|
|
16
|
-
>() {}
|
|
16
|
+
>()("docks-kit/DependencyManager") {}
|
|
17
17
|
|
|
18
|
-
export class PlatformService extends Context.
|
|
18
|
+
export class PlatformService extends Context.Service<PlatformService, Platform>()("docks-kit/Platform") {}
|
|
19
19
|
|
|
20
20
|
const live = makeEngineServices()
|
|
21
21
|
|
package/docks-kit
CHANGED
|
@@ -69,7 +69,7 @@ fi
|
|
|
69
69
|
|
|
70
70
|
# Sentinel is a real dependency dir, not bare node_modules/ — a failed or
|
|
71
71
|
# partial install leaves node_modules/ present and would suppress the repair.
|
|
72
|
-
if [[ ! -d "$REPO_DIR/node_modules
|
|
72
|
+
if [[ ! -d "$REPO_DIR/node_modules/effect" ]]; then
|
|
73
73
|
echo "[docks-kit] Installing CLI dependencies (bun install --frozen-lockfile)..." >&2
|
|
74
74
|
(cd "$REPO_DIR" && "$BUN" install --frozen-lockfile >/dev/null)
|
|
75
75
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docks-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Portable AI coding agent config kit — SoT sync engine + typed CLI for Claude Code, Codex, and universal agent skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,15 +35,13 @@
|
|
|
35
35
|
"test:ci": "bun run check:generated && bun run typecheck && bun run test:unit && bun run test:runtime:posix && bun run golden:dryrun && bun run golden:mutation"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@effect/
|
|
39
|
-
"
|
|
40
|
-
"@effect/platform-bun": "0.90.0",
|
|
41
|
-
"effect": "3.21.4"
|
|
38
|
+
"@effect/platform-bun": "4.0.0-beta.107",
|
|
39
|
+
"effect": "4.0.0-beta.107"
|
|
42
40
|
},
|
|
43
41
|
"devDependencies": {
|
|
44
|
-
"@effect/vitest": "0.
|
|
42
|
+
"@effect/vitest": "4.0.0-beta.107",
|
|
45
43
|
"@types/bun": "^1.3.0",
|
|
46
44
|
"typescript": "7.0.2",
|
|
47
|
-
"vitest": "
|
|
45
|
+
"vitest": "4.1.10"
|
|
48
46
|
}
|
|
49
47
|
}
|