docks-kit 0.3.0 → 0.5.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.
@@ -10,11 +10,19 @@ import { homedir } from "node:os"
10
10
 
11
11
  import { kitHome } from "../kitHome"
12
12
  import { makeEngineServices, type EngineServices, type Logger } from "./services"
13
+ import type { BunRuntimeState } from "./bun"
13
14
  import { claudeNextSteps, claudeSummary, claudeSync } from "./claudeSync"
14
15
  import { codexNextSteps, codexSummary, codexSync } from "./codexSync"
15
16
  import { skillsNextSteps, skillsSummary, skillsSync } from "./skillsSync"
16
17
  import { modeModel, modeToolchain } from "./modes"
17
- import { ExitError, parseArgs, preflight, validateModelFlags } from "./parseArgs"
18
+ import { ExitError, parseArgs, validateModifierFlags } from "./parseArgs"
19
+
20
+ export type ModifierFlag =
21
+ | "--claude-model"
22
+ | "--claude-effort"
23
+ | "--claude-advisor"
24
+ | "--codex-model"
25
+ | "--codex-effort"
18
26
 
19
27
  export interface Ctx {
20
28
  readonly repoDir: string
@@ -30,9 +38,15 @@ export interface Ctx {
30
38
  claudePermissive: boolean
31
39
  claudePlugins: Array<string>
32
40
  claudeModel: string
41
+ claudeEffort: string
42
+ claudeAdvisor: string
33
43
  codexModel: string
44
+ codexEffort: string
45
+ /** Distinguishes an explicitly empty modifier from an option that was not supplied. */
46
+ modifierFlags?: Set<ModifierFlag>
34
47
  /** Injected capability seam (logger/deps/platform) — see services.ts. */
35
48
  readonly services: EngineServices
49
+ bunRuntime?: BunRuntimeState
36
50
  targetFilterSet: boolean
37
51
  syncClaude: boolean
38
52
  syncCodex: boolean
@@ -64,7 +78,11 @@ function makeCtx(services: EngineServices): Ctx {
64
78
  claudePermissive: env["CLAUDE_PERMISSIVE"] === "1",
65
79
  claudePlugins: (env["CLAUDE_PLUGINS"] ?? "").split(" ").filter((s) => s !== ""),
66
80
  claudeModel: env["CLAUDE_MODEL"] ?? "",
81
+ claudeEffort: "",
82
+ claudeAdvisor: "",
67
83
  codexModel: env["CODEX_MODEL"] ?? "",
84
+ codexEffort: "",
85
+ modifierFlags: new Set(),
68
86
  services,
69
87
  targetFilterSet: false,
70
88
  syncClaude: false,
@@ -77,11 +95,10 @@ function makeCtx(services: EngineServices): Ctx {
77
95
  function engineSync(ctx: Ctx, args: ReadonlyArray<string>): number {
78
96
  const { echo } = ctx.services.logger
79
97
  parseArgs(ctx, args)
80
- preflight(ctx)
81
- validateModelFlags(ctx)
98
+ validateModifierFlags(ctx)
82
99
 
83
100
  const claudeRan = ctx.syncClaude
84
- if (claudeRan) claudeSync(ctx)
101
+ const claudeRuntime = claudeRan ? claudeSync(ctx) : undefined
85
102
 
86
103
  const codexRan = ctx.syncCodex
87
104
  if (codexRan) codexSync(ctx)
@@ -91,7 +108,7 @@ function engineSync(ctx: Ctx, args: ReadonlyArray<string>): number {
91
108
  echo("")
92
109
  echo("--- Sync complete ---")
93
110
  echo(`Repo: ${ctx.repoDir}`)
94
- if (claudeRan) claudeSummary(ctx)
111
+ if (claudeRuntime !== undefined) claudeSummary(ctx, claudeRuntime)
95
112
  if (codexRan) codexSummary(ctx)
96
113
  if (skillsState !== undefined) skillsSummary(ctx, skillsState)
97
114
 
@@ -6,13 +6,14 @@ import { p } from "./exec"
6
6
  import { readFileSync } from "node:fs"
7
7
  import { payloadText } from "../payload"
8
8
 
9
- import { syncClaudeModel } from "./claudeModel"
9
+ import { syncClaudeModel } from "./claudeSettingsModifiers"
10
10
  import { syncCodexModel } from "./codexToml"
11
11
  import type { Ctx } from "./index"
12
12
  import { isObject, parseJson, type Json } from "./jq"
13
13
  import { printModels, validateClaudeModel, validateCodexModel } from "./models"
14
- import { rtkInstall } from "./claudeSync"
15
- import { agentBrowserInstall, bunBootstrap, effectSolutionsInstall } from "./skillsSync"
14
+ import { ensureRtk } from "./claudeSync"
15
+ import { bunBootstrap } from "./bun"
16
+ import { agentBrowserInstall, effectSolutionsInstall } from "./skillsSync"
16
17
  import { ensure, report } from "./toolchain"
17
18
 
18
19
  export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
@@ -134,10 +135,9 @@ export function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): number {
134
135
  }
135
136
  switch (tool) {
136
137
  case "rtk":
137
- return ensure(ctx, "rtk", rtkInstall(ctx))
138
+ return ensureRtk(ctx, "cannot download RTK installer; toolchain ensure rtk aborted", 1)
138
139
  case "bun":
139
- // skills::_bun_bootstrap >/dev/null the found-bun stdout is discarded.
140
- return bunBootstrap(ctx, ctx.services) !== "" ? 0 : 1
140
+ return bunBootstrap(ctx, ctx.services).kind === "ready" ? 0 : 1
141
141
  case "effect-solutions":
142
142
  return ensure(ctx, "effect-solutions", effectSolutionsInstall(ctx))
143
143
  case "agent-browser":
@@ -1,10 +1,20 @@
1
1
  /**
2
2
  * EngineNative flag layer: usage / target selection / compact-window parsing /
3
- * optional-plugin parsing / model validation / preflight. ExitError mirrors an
3
+ * optional-plugin parsing / model validation. ExitError mirrors an
4
4
  * early parser exit and is caught once in runEngineNative.
5
5
  */
6
6
 
7
- import type { Ctx } from "./index"
7
+ import type { Ctx, ModifierFlag } from "./index"
8
+ import {
9
+ CLAUDE_ADVISOR_STATES,
10
+ advisorCatalog,
11
+ advisorFlagGrammar,
12
+ advisorValueGrammar,
13
+ effortCatalog,
14
+ effortFlagGrammar,
15
+ effortValueGrammar,
16
+ isEffortModifierValue
17
+ } from "../efforts"
8
18
  import { printModels, validateClaudeModel, validateCodexModel } from "./models"
9
19
 
10
20
  export class ExitError extends Error {
@@ -14,6 +24,13 @@ export class ExitError extends Error {
14
24
  }
15
25
 
16
26
  const KNOWN_CLAUDE_OPTIN_PLUGINS = ["supabase", "n8n"]
27
+ const MODIFIER_FLAGS = new Set<ModifierFlag>([
28
+ "--claude-model",
29
+ "--claude-effort",
30
+ "--claude-advisor",
31
+ "--codex-model",
32
+ "--codex-effort"
33
+ ])
17
34
 
18
35
  function usage(ctx: Ctx): void {
19
36
  const { echo } = ctx.services.logger
@@ -41,6 +58,8 @@ function usage(ctx: Ctx): void {
41
58
  echo(
42
59
  " --claude-model=<m> set deployed ~/.claude/settings.json model (aliases: best|opus|fable|sonnet|haiku, full claude-* IDs, or 'default' to unset)"
43
60
  )
61
+ echo(` ${effortFlagGrammar("claude").padEnd(39)} set deployed effortLevel`)
62
+ echo(` ${advisorFlagGrammar().padEnd(39)} set deployed advisor state`)
44
63
  echo(
45
64
  " --claude-compact-window=<n> set deployed autocompact window in tokens (e.g. 680000 or 680k) for disposable sessions"
46
65
  )
@@ -48,6 +67,7 @@ function usage(ctx: Ctx): void {
48
67
  " --claude-permissive empty permissions.ask/deny in deployed settings (sandboxes/containers; unattended commits + pushes)"
49
68
  )
50
69
  echo(" --codex-model=<m> set deployed ~/.codex/config.toml model (e.g. gpt-5.5)")
70
+ echo(` ${effortFlagGrammar("codex").padEnd(39)} set deployed model_reasoning_effort`)
51
71
  echo("")
52
72
  echo("Sticky opt-ins (installed + enabled until --prune)")
53
73
  echo(
@@ -81,9 +101,42 @@ function selectTarget(ctx: Ctx, target: string): void {
81
101
  ctx.targetFilterSet = true
82
102
  }
83
103
 
104
+ function setModifier(ctx: Ctx, flag: ModifierFlag, value: string): void {
105
+ switch (flag) {
106
+ case "--claude-model":
107
+ ctx.claudeModel = value
108
+ break
109
+ case "--claude-effort":
110
+ ctx.claudeEffort = value
111
+ break
112
+ case "--claude-advisor":
113
+ ctx.claudeAdvisor = value
114
+ break
115
+ case "--codex-model":
116
+ ctx.codexModel = value
117
+ break
118
+ case "--codex-effort":
119
+ ctx.codexEffort = value
120
+ break
121
+ }
122
+ const flags = ctx.modifierFlags ?? new Set<ModifierFlag>()
123
+ flags.add(flag)
124
+ ctx.modifierFlags = flags
125
+ }
126
+
127
+ function isModifierFlag(value: string): value is ModifierFlag {
128
+ return MODIFIER_FLAGS.has(value as ModifierFlag)
129
+ }
130
+
84
131
  export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
85
132
  const { err } = ctx.services.logger
86
- for (const arg of args) {
133
+ for (let index = 0; index < args.length; index += 1) {
134
+ const arg = args[index] ?? ""
135
+ if (isModifierFlag(arg) && args[index + 1] === "") {
136
+ setModifier(ctx, arg, "")
137
+ index += 1
138
+ continue
139
+ }
87
140
  switch (arg) {
88
141
  case "claude":
89
142
  case "codex":
@@ -116,6 +169,18 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
116
169
  printModels(ctx, "codex")
117
170
  err("--codex-model requires a value: --codex-model=<model>")
118
171
  throw new ExitError(2)
172
+ case "--claude-effort":
173
+ printCatalog(ctx, effortCatalog("claude"))
174
+ err(`--claude-effort requires a value: ${effortFlagGrammar("claude")}`)
175
+ throw new ExitError(2)
176
+ case "--codex-effort":
177
+ printCatalog(ctx, effortCatalog("codex"))
178
+ err(`--codex-effort requires a value: ${effortFlagGrammar("codex")}`)
179
+ throw new ExitError(2)
180
+ case "--claude-advisor":
181
+ printCatalog(ctx, advisorCatalog())
182
+ err(`--claude-advisor requires a value: ${advisorFlagGrammar()}`)
183
+ throw new ExitError(2)
119
184
  case "--claude-compact-window":
120
185
  err("--claude-compact-window requires a value: --claude-compact-window=<tokens> (e.g. 680k)")
121
186
  throw new ExitError(2)
@@ -159,9 +224,15 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
159
224
  break
160
225
  }
161
226
  if (arg.startsWith("--claude-model=")) {
162
- ctx.claudeModel = arg.slice("--claude-model=".length)
227
+ setModifier(ctx, "--claude-model", arg.slice("--claude-model=".length))
163
228
  } else if (arg.startsWith("--codex-model=")) {
164
- ctx.codexModel = arg.slice("--codex-model=".length)
229
+ setModifier(ctx, "--codex-model", arg.slice("--codex-model=".length))
230
+ } else if (arg.startsWith("--claude-effort=")) {
231
+ setModifier(ctx, "--claude-effort", arg.slice("--claude-effort=".length))
232
+ } else if (arg.startsWith("--codex-effort=")) {
233
+ setModifier(ctx, "--codex-effort", arg.slice("--codex-effort=".length))
234
+ } else if (arg.startsWith("--claude-advisor=")) {
235
+ setModifier(ctx, "--claude-advisor", arg.slice("--claude-advisor=".length))
165
236
  } else if (arg.startsWith("--claude-compact-window=")) {
166
237
  const parsed = parseCompactWindow(arg.slice("--claude-compact-window=".length))
167
238
  if (parsed === undefined) {
@@ -184,25 +255,15 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
184
255
  }
185
256
  }
186
257
 
187
- export function preflight(ctx: Ctx): void {
188
- const { err } = ctx.services.logger
189
- if (ctx.syncClaude || ctx.syncCodex) {
190
- if (ctx.services.deps.probe("jq").state === "missing") {
191
- err(`jq is required (deployed statusline/hooks call it). Install: ${ctx.services.deps.spec("jq").installHint()}`)
192
- throw new ExitError(1)
193
- }
194
- }
195
- if (ctx.syncClaude) {
196
- if (ctx.services.deps.probe("curl").state === "missing") {
197
- err(`curl is required. Install: ${ctx.services.deps.spec("curl").installHint()}`)
198
- throw new ExitError(1)
199
- }
200
- }
258
+ function printCatalog(ctx: Ctx, catalog: string): void {
259
+ for (const line of catalog.split("\n")) ctx.services.logger.echo(line)
201
260
  }
202
261
 
203
- export function validateModelFlags(ctx: Ctx): void {
262
+ export function validateModifierFlags(ctx: Ctx): void {
204
263
  const { err, warn } = ctx.services.logger
205
- if (ctx.claudeModel !== "") {
264
+ const supplied = (flag: ModifierFlag, value: string): boolean =>
265
+ value !== "" || ctx.modifierFlags?.has(flag) === true
266
+ if (supplied("--claude-model", ctx.claudeModel)) {
206
267
  if (!ctx.syncClaude) {
207
268
  warn("--claude-model ignored: claude target not selected")
208
269
  ctx.claudeModel = ""
@@ -212,7 +273,27 @@ export function validateModelFlags(ctx: Ctx): void {
212
273
  throw new ExitError(2)
213
274
  }
214
275
  }
215
- if (ctx.codexModel !== "") {
276
+ if (supplied("--claude-effort", ctx.claudeEffort)) {
277
+ if (!ctx.syncClaude) {
278
+ warn("--claude-effort ignored: claude target not selected")
279
+ ctx.claudeEffort = ""
280
+ } else if (!isEffortModifierValue("claude", ctx.claudeEffort)) {
281
+ printCatalog(ctx, effortCatalog("claude"))
282
+ err(`Invalid Claude effort '${ctx.claudeEffort}' — valid: ${effortValueGrammar("claude")}`)
283
+ throw new ExitError(2)
284
+ }
285
+ }
286
+ if (supplied("--claude-advisor", ctx.claudeAdvisor)) {
287
+ if (!ctx.syncClaude) {
288
+ warn("--claude-advisor ignored: claude target not selected")
289
+ ctx.claudeAdvisor = ""
290
+ } else if (!CLAUDE_ADVISOR_STATES.some((state) => state === ctx.claudeAdvisor)) {
291
+ printCatalog(ctx, advisorCatalog())
292
+ err(`Invalid Claude advisor state '${ctx.claudeAdvisor}' — valid: ${advisorValueGrammar()}`)
293
+ throw new ExitError(2)
294
+ }
295
+ }
296
+ if (supplied("--codex-model", ctx.codexModel)) {
216
297
  if (!ctx.syncCodex) {
217
298
  warn("--codex-model ignored: codex target not selected")
218
299
  ctx.codexModel = ""
@@ -222,4 +303,14 @@ export function validateModelFlags(ctx: Ctx): void {
222
303
  throw new ExitError(2)
223
304
  }
224
305
  }
306
+ if (supplied("--codex-effort", ctx.codexEffort)) {
307
+ if (!ctx.syncCodex) {
308
+ warn("--codex-effort ignored: codex target not selected")
309
+ ctx.codexEffort = ""
310
+ } else if (!isEffortModifierValue("codex", ctx.codexEffort)) {
311
+ printCatalog(ctx, effortCatalog("codex"))
312
+ err(`Invalid Codex effort '${ctx.codexEffort}' — valid: ${effortValueGrammar("codex")}`)
313
+ throw new ExitError(2)
314
+ }
315
+ }
225
316
  }
@@ -0,0 +1,11 @@
1
+ export function powerShellLiteral(value: string): string {
2
+ return `'${value.replaceAll("'", "''")}'`
3
+ }
4
+
5
+ export function encodePowerShellCommand(script: string): string {
6
+ return Buffer.from(script, "utf16le").toString("base64")
7
+ }
8
+
9
+ export function decodePowerShellCommand(encoded: string): string {
10
+ return Buffer.from(encoded, "base64").toString("utf16le")
11
+ }
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import { spawnSync } from "node:child_process"
8
8
  import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, rmdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs"
9
- import { tmpdir } from "node:os"
10
9
  import { p, writeFileIfChanged } from "./exec"
10
+ import { bunBootstrap } from "./bun"
11
11
  import type { Ctx } from "./index"
12
12
  import { compareCodepoints } from "./jq"
13
13
  import type { EngineServices, Platform } from "./services"
@@ -277,39 +277,6 @@ function syncAgentBrowserCli(ctx: Ctx, manifest: string): void {
277
277
  }
278
278
  }
279
279
 
280
- /** skills::_find_bun — resolved bun path or "". */
281
- function findBun(ctx: Ctx): string {
282
- return ctx.services.deps.path("bun")
283
- }
284
-
285
- /** skills::_bun_bootstrap — bun path or "" after a failed bootstrap. */
286
- export function bunBootstrap(ctx: Ctx, services: EngineServices): string {
287
- const { change, warn } = services.logger
288
- let bun = findBun(ctx)
289
- if (bun !== "") return bun
290
-
291
- if (services.deps.probe("curl").state === "missing") {
292
- warn("Bun and curl both missing — cannot bootstrap Bun. Install Bun manually, then re-run sync.")
293
- return ""
294
- }
295
- const pin = field(ctx, "bun", "verified")
296
- warn(`Bun not found — installing Bun${pin !== "" ? ` ${pin} (kit-verified)` : ""}...`)
297
- const installer = p(tmpdir(), `bun-install-${process.pid}.sh`)
298
- const dl = spawnSync("curl", ["-fsSL", "https://bun.sh/install", "-o", installer], { stdio: "ignore" })
299
- if (dl.error === undefined && dl.status === 0) {
300
- spawnSync("bash", [installer, ...(pin !== "" ? [`bun-v${pin}`] : [])], { stdio: "ignore" })
301
- }
302
- rmSync(installer, { force: true })
303
- bun = findBun(ctx)
304
- if (bun === "") {
305
- warn("Bun install failed. Install manually: curl -fsSL https://bun.sh/install -o /tmp/bun.sh && bash /tmp/bun.sh")
306
- return ""
307
- }
308
- const version = services.deps.version("bun")
309
- change(`Bun installed (${version !== "" ? version : "version unknown"})`)
310
- return bun
311
- }
312
-
313
280
  /** skills::_effect_solutions_install. */
314
281
  export function effectSolutionsInstall(
315
282
  ctx: Ctx
@@ -319,8 +286,9 @@ export function effectSolutionsInstall(
319
286
  const verb = mode === "upgrade" ? "Upgrading" : "Installing"
320
287
  const pkg = `effect-solutions@${version !== "" ? version : "latest"}`
321
288
 
322
- const bun = bunBootstrap(ctx, services)
323
- if (bun === "") return 1
289
+ const bunState = bunBootstrap(ctx, services)
290
+ if (bunState.kind === "deferred") return 1
291
+ const bun = bunState.executable
324
292
 
325
293
  verbose(`${verb} effect-solutions CLI via bun${version !== "" ? ` (pinned ${version})` : ""}...`)
326
294
  if (spawnSync(bun, ["add", "-g", pkg], { stdio: "ignore" }).status !== 0) {