docks-kit 0.15.0 → 0.15.2
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 +36 -2
- package/README.md +1 -1
- package/cli/docs/install.md +18 -0
- package/cli/src/commands/model.ts +3 -0
- package/cli/src/commands/models.ts +5 -3
- package/cli/src/commands/status.ts +145 -32
- package/cli/src/commands/sync.ts +4 -0
- package/cli/src/commands/update.ts +114 -20
- package/cli/src/efforts.ts +5 -5
- package/cli/src/engine-native/DESIGN.md +1 -1
- package/cli/src/engine-native/bun.ts +37 -11
- package/cli/src/engine-native/claudeSettingsModifiers.ts +29 -11
- package/cli/src/engine-native/claudeSync.ts +39 -31
- package/cli/src/engine-native/codexSync.ts +163 -44
- package/cli/src/engine-native/codexToml.ts +12 -7
- package/cli/src/engine-native/deps.ts +17 -15
- package/cli/src/engine-native/exec.ts +1 -14
- package/cli/src/engine-native/index.ts +17 -4
- package/cli/src/engine-native/models.ts +2 -9
- package/cli/src/engine-native/modes.ts +31 -21
- package/cli/src/engine-native/os.ts +0 -8
- package/cli/src/engine-native/parseArgs.ts +147 -44
- package/cli/src/engine-native/services.ts +1 -5
- package/cli/src/engine-native/settings.ts +3 -2
- package/cli/src/engine-native/skillsSync.ts +30 -26
- package/cli/src/engine-native/toolchain.ts +17 -13
- package/cli/src/engine.ts +38 -9
- package/cli/src/generated/sotPayload.ts +3 -3
- package/cli/src/kitHome.ts +79 -16
- package/cli/src/main.ts +12 -2
- package/cli/src/manifests.ts +17 -10
- package/cli/src/payload.ts +2 -5
- package/docks-kit +3 -3
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* substitution: stdout with trailing newlines stripped, empty on failure.
|
|
5
5
|
*/
|
|
6
6
|
import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process"
|
|
7
|
-
import { accessSync,
|
|
7
|
+
import { accessSync, constants, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
8
8
|
import { delimiter, isAbsolute, join } from "node:path"
|
|
9
9
|
|
|
10
10
|
/** Keep engine paths slash-separated so rendered output is host-stable. */
|
|
@@ -101,23 +101,10 @@ export function isExecutable(p: string): boolean {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function fileExists(p: string): boolean {
|
|
105
|
-
return existsSync(p)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
104
|
// Change-detection primitives (Output Policy in DESIGN.md): operations report
|
|
109
105
|
// changed:boolean so unchanged repeat runs log at verbose instead of [ok].
|
|
110
106
|
|
|
111
107
|
/** Write only when the content differs; returns whether a write happened. */
|
|
112
|
-
/** Add missing +x bits; returns whether a repair actually happened. */
|
|
113
|
-
export function ensureExecutable(path: string): boolean {
|
|
114
|
-
const mode = statSync(path).mode
|
|
115
|
-
const want = mode | 0o111
|
|
116
|
-
if (mode === want) return false
|
|
117
|
-
chmodSync(path, want)
|
|
118
|
-
return true
|
|
119
|
-
}
|
|
120
|
-
|
|
121
108
|
export function writeTextIfChanged(path: string, content: string): boolean {
|
|
122
109
|
if (existsSync(path) && readFileSync(path, "utf8") === content) return false
|
|
123
110
|
writeFileSync(path, content)
|
|
@@ -17,12 +17,15 @@ import { claudeNextSteps, claudeSummary, claudeSync, type ClaudeRuntimeState } f
|
|
|
17
17
|
import { codexNextSteps, codexSummary, codexSync } from "./codexSync"
|
|
18
18
|
import { normalizeManifest, skillsNextSteps, skillsSummary, skillsSync, type SkillsState } from "./skillsSync"
|
|
19
19
|
import { modeModel, modeToolchain } from "./modes"
|
|
20
|
-
import { ExitError, parseArgs, validateModifierFlags } from "./parseArgs"
|
|
20
|
+
import { ExitError, parseArgs, parseClaudePlugin, parseCompactWindow, validateModifierFlags } from "./parseArgs"
|
|
21
21
|
|
|
22
22
|
export type ModifierFlag =
|
|
23
23
|
| "--claude-model"
|
|
24
24
|
| "--claude-effort"
|
|
25
25
|
| "--claude-advisor"
|
|
26
|
+
| "--claude-compact-window"
|
|
27
|
+
| "--claude-permissive"
|
|
28
|
+
| "--claude-plugin"
|
|
26
29
|
| "--codex-model"
|
|
27
30
|
| "--codex-effort"
|
|
28
31
|
|
|
@@ -117,6 +120,16 @@ export interface Ctx {
|
|
|
117
120
|
function makeCtx(services: EngineServices): Ctx {
|
|
118
121
|
const env = process.env
|
|
119
122
|
const home = env["HOME"] !== undefined && env["HOME"] !== "" ? env["HOME"] : homedir()
|
|
123
|
+
const compactWindowSource = env["CLAUDE_COMPACT_WINDOW"] ?? ""
|
|
124
|
+
const claudeCompactWindow = compactWindowSource === "" ? "" : parseCompactWindow(compactWindowSource)
|
|
125
|
+
if (claudeCompactWindow === undefined) {
|
|
126
|
+
services.logger.err("CLAUDE_COMPACT_WINDOW expects a token count (e.g. 680000 or 680k)")
|
|
127
|
+
throw new ExitError(2)
|
|
128
|
+
}
|
|
129
|
+
const claudePlugins = (env["CLAUDE_PLUGINS"] ?? "")
|
|
130
|
+
.split(" ")
|
|
131
|
+
.filter((plugin) => plugin !== "")
|
|
132
|
+
.map((plugin) => parseClaudePlugin(plugin, services.logger.err))
|
|
120
133
|
return {
|
|
121
134
|
repoDir: kitHome(),
|
|
122
135
|
home,
|
|
@@ -128,9 +141,9 @@ function makeCtx(services: EngineServices): Ctx {
|
|
|
128
141
|
reconcile: env["RECONCILE"] === "1",
|
|
129
142
|
prune: env["PRUNE"] === "1",
|
|
130
143
|
assumeYes: env["ASSUME_YES"] === "1",
|
|
131
|
-
claudeCompactWindow
|
|
144
|
+
claudeCompactWindow,
|
|
132
145
|
claudePermissive: env["CLAUDE_PERMISSIVE"] === "1",
|
|
133
|
-
claudePlugins
|
|
146
|
+
claudePlugins,
|
|
134
147
|
claudeModel: env["CLAUDE_MODEL"] ?? "",
|
|
135
148
|
claudeEffort: "",
|
|
136
149
|
claudeAdvisor: "",
|
|
@@ -278,8 +291,8 @@ export async function runEngineNative(argv: ReadonlyArray<string>, services?: En
|
|
|
278
291
|
deps: baseServices.deps,
|
|
279
292
|
platform: baseServices.platform
|
|
280
293
|
}
|
|
281
|
-
ctx = makeCtx(runServices)
|
|
282
294
|
try {
|
|
295
|
+
ctx = makeCtx(runServices)
|
|
283
296
|
switch (argv[0]) {
|
|
284
297
|
case "model":
|
|
285
298
|
return modeModel(ctx, argv.slice(1))
|
|
@@ -6,16 +6,9 @@ import type { Ctx } from "./index"
|
|
|
6
6
|
import { isObject, parseJson, type Json } from "./jq"
|
|
7
7
|
import { payloadDisplayPath, payloadText } from "../payload"
|
|
8
8
|
|
|
9
|
-
function catalog(): Json | undefined {
|
|
10
|
-
try {
|
|
11
|
-
return parseJson(payloadText("SoT/models.json"))
|
|
12
|
-
} catch {
|
|
13
|
-
return undefined
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
9
|
|
|
17
10
|
function toolEntry(tool: string): { [k: string]: Json } | undefined {
|
|
18
|
-
const doc =
|
|
11
|
+
const doc = parseJson(payloadText("SoT/models.json"))
|
|
19
12
|
if (doc === undefined || !isObject(doc)) return undefined
|
|
20
13
|
const entry = doc[tool]
|
|
21
14
|
return entry !== undefined && isObject(entry) ? entry : undefined
|
|
@@ -37,7 +30,7 @@ export function printModels(ctx: Ctx, tool: string): void {
|
|
|
37
30
|
const { echo, warn } = ctx.services.logger
|
|
38
31
|
const entry = toolEntry(tool)
|
|
39
32
|
if (entry === undefined) {
|
|
40
|
-
warn(`Model catalog unavailable (${payloadDisplayPath("SoT/models.json"
|
|
33
|
+
warn(`Model catalog unavailable (${payloadDisplayPath("SoT/models.json")})`)
|
|
41
34
|
return
|
|
42
35
|
}
|
|
43
36
|
const verified = typeof entry["verified"] === "string" ? entry["verified"] : "?"
|
|
@@ -37,19 +37,29 @@ export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
37
37
|
if (value === "") {
|
|
38
38
|
if (tool === "claude") {
|
|
39
39
|
const deployed = p(ctx.home, ".claude", "settings.json")
|
|
40
|
-
|
|
40
|
+
const result = readConfig(deployed)
|
|
41
|
+
if (result.kind === "missing") {
|
|
41
42
|
warn("~/.claude/settings.json missing")
|
|
42
43
|
return 0
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
if (result.kind === "read-error") {
|
|
46
|
+
err(`Failed to read ~/.claude/settings.json: ${String(result.error)}`)
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
echo(`deployed: ${jsonModelText(result.data)}`)
|
|
45
50
|
echo(`SoT: ${jsonModelText(payloadText("SoT/.claude/settings.json"))}`)
|
|
46
51
|
} else {
|
|
47
52
|
const deployed = p(ctx.home, ".codex", "config.toml")
|
|
48
|
-
|
|
53
|
+
const result = readConfig(deployed)
|
|
54
|
+
if (result.kind === "missing") {
|
|
49
55
|
warn("~/.codex/config.toml missing")
|
|
50
56
|
return 0
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
if (result.kind === "read-error") {
|
|
59
|
+
err(`Failed to read ~/.codex/config.toml: ${String(result.error)}`)
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
echo(`deployed: ${tomlModelText(result.data)}`)
|
|
53
63
|
echo(`SoT: ${tomlModelText(payloadText("SoT/.codex/config.toml"))}`)
|
|
54
64
|
}
|
|
55
65
|
printModels(ctx, tool)
|
|
@@ -74,20 +84,23 @@ export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
74
84
|
return 0
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
type ConfigReadResult =
|
|
88
|
+
| { readonly kind: "missing" }
|
|
89
|
+
| { readonly kind: "read-error"; readonly error: unknown }
|
|
90
|
+
| { readonly kind: "data"; readonly data: string }
|
|
91
|
+
|
|
92
|
+
function readConfig(file: string): ConfigReadResult {
|
|
78
93
|
try {
|
|
79
|
-
readFileSync(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
return { kind: "data", data: readFileSync(file, "utf8") }
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const code =
|
|
97
|
+
typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
98
|
+
? error.code
|
|
99
|
+
: undefined
|
|
100
|
+
return code === "ENOENT" ? { kind: "missing" } : { kind: "read-error", error }
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
|
|
86
|
-
/** `jq -r '.model // "default (unset)"'` — empty on unparseable input. */
|
|
87
|
-
function jsonModelField(file: string): string {
|
|
88
|
-
return jsonModelText(readFileSync(file, "utf8"))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
104
|
function jsonModelText(text: string): string {
|
|
92
105
|
const doc = parseJson(text)
|
|
93
106
|
if (doc === undefined) return ""
|
|
@@ -97,9 +110,6 @@ function jsonModelText(text: string): string {
|
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
/** `awk -F'"' '/^model[[:space:]]*=/{print $2; exit}'`. */
|
|
100
|
-
function tomlModelField(file: string): string {
|
|
101
|
-
return tomlModelText(readFileSync(file, "utf8"))
|
|
102
|
-
}
|
|
103
113
|
|
|
104
114
|
function tomlModelText(text: string): string {
|
|
105
115
|
for (const line of text.split("\n")) {
|
|
@@ -110,9 +120,9 @@ function tomlModelText(text: string): string {
|
|
|
110
120
|
|
|
111
121
|
export async function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): Promise<number> {
|
|
112
122
|
const { err } = ctx.services.logger
|
|
113
|
-
const words = args.filter((
|
|
114
|
-
const op = words[0] ??
|
|
115
|
-
const tool = words[1] ??
|
|
123
|
+
const words = args.filter((arg) => !arg.startsWith("--"))
|
|
124
|
+
const op = words[0] ?? "check"
|
|
125
|
+
const tool = words[1] ?? ""
|
|
116
126
|
for (const arg of args) {
|
|
117
127
|
if (arg === "--yes") ctx.assumeYes = true
|
|
118
128
|
else if (arg === "--verbose") {
|
|
@@ -128,7 +138,7 @@ export async function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): Prom
|
|
|
128
138
|
err("Usage: toolchain [check|ensure <tool>] [--yes]")
|
|
129
139
|
return 2
|
|
130
140
|
}
|
|
131
|
-
if (tool === ""
|
|
141
|
+
if (tool === "") {
|
|
132
142
|
err("Usage: toolchain ensure <tool> [--yes]")
|
|
133
143
|
return 2
|
|
134
144
|
}
|
|
@@ -14,11 +14,3 @@ export function platformName(pf: NodeJS.Platform = rawPlatform()): PlatformName
|
|
|
14
14
|
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" : "unknown"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function isLinux(): boolean {
|
|
18
|
-
return rawPlatform() === "linux"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Shell-rc exports apply only on supported hosts. */
|
|
22
|
-
export function shellRcApplicable(): boolean {
|
|
23
|
-
return platformName() !== "unknown"
|
|
24
|
-
}
|
|
@@ -24,13 +24,96 @@ export class ExitError extends Error {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const KNOWN_CLAUDE_OPTIN_PLUGINS = ["supabase", "n8n"]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
|
|
28
|
+
interface ModifierMetadata {
|
|
29
|
+
readonly target: "claude" | "codex"
|
|
30
|
+
readonly ignoredWarning: string
|
|
31
|
+
readonly hasValue: (ctx: Ctx) => boolean
|
|
32
|
+
readonly clear: (ctx: Ctx) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MODIFIER_METADATA = {
|
|
36
|
+
"--claude-model": {
|
|
37
|
+
target: "claude",
|
|
38
|
+
ignoredWarning: "--claude-model ignored: claude target not selected",
|
|
39
|
+
hasValue: (ctx) => ctx.claudeModel !== "",
|
|
40
|
+
clear: (ctx) => {
|
|
41
|
+
ctx.claudeModel = ""
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"--claude-effort": {
|
|
45
|
+
target: "claude",
|
|
46
|
+
ignoredWarning: "--claude-effort ignored: claude target not selected",
|
|
47
|
+
hasValue: (ctx) => ctx.claudeEffort !== "",
|
|
48
|
+
clear: (ctx) => {
|
|
49
|
+
ctx.claudeEffort = ""
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"--claude-advisor": {
|
|
53
|
+
target: "claude",
|
|
54
|
+
ignoredWarning: "--claude-advisor ignored: claude target not selected",
|
|
55
|
+
hasValue: (ctx) => ctx.claudeAdvisor !== "",
|
|
56
|
+
clear: (ctx) => {
|
|
57
|
+
ctx.claudeAdvisor = ""
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"--claude-compact-window": {
|
|
61
|
+
target: "claude",
|
|
62
|
+
ignoredWarning: "--claude-compact-window ignored: claude target not selected",
|
|
63
|
+
hasValue: (ctx) => ctx.claudeCompactWindow !== "",
|
|
64
|
+
clear: (ctx) => {
|
|
65
|
+
ctx.claudeCompactWindow = ""
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"--claude-permissive": {
|
|
69
|
+
target: "claude",
|
|
70
|
+
ignoredWarning: "--claude-permissive ignored: claude target not selected",
|
|
71
|
+
hasValue: (ctx) => ctx.claudePermissive,
|
|
72
|
+
clear: (ctx) => {
|
|
73
|
+
ctx.claudePermissive = false
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"--claude-plugin": {
|
|
77
|
+
target: "claude",
|
|
78
|
+
ignoredWarning: "--claude-plugin ignored: claude target not selected",
|
|
79
|
+
hasValue: (ctx) => ctx.claudePlugins.length > 0,
|
|
80
|
+
clear: (ctx) => {
|
|
81
|
+
ctx.claudePlugins = []
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"--codex-model": {
|
|
85
|
+
target: "codex",
|
|
86
|
+
ignoredWarning: "--codex-model ignored: codex target not selected",
|
|
87
|
+
hasValue: (ctx) => ctx.codexModel !== "",
|
|
88
|
+
clear: (ctx) => {
|
|
89
|
+
ctx.codexModel = ""
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"--codex-effort": {
|
|
93
|
+
target: "codex",
|
|
94
|
+
ignoredWarning: "--codex-effort ignored: codex target not selected",
|
|
95
|
+
hasValue: (ctx) => ctx.codexEffort !== "",
|
|
96
|
+
clear: (ctx) => {
|
|
97
|
+
ctx.codexEffort = ""
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} satisfies Record<ModifierFlag, ModifierMetadata>
|
|
101
|
+
|
|
102
|
+
type ScalarModifierFlag =
|
|
103
|
+
| "--claude-model"
|
|
104
|
+
| "--claude-effort"
|
|
105
|
+
| "--claude-advisor"
|
|
106
|
+
| "--codex-model"
|
|
107
|
+
| "--codex-effort"
|
|
108
|
+
|
|
109
|
+
const SCALAR_MODIFIER_FLAGS: Record<ScalarModifierFlag, true> = {
|
|
110
|
+
"--claude-model": true,
|
|
111
|
+
"--claude-effort": true,
|
|
112
|
+
"--claude-advisor": true,
|
|
113
|
+
"--codex-model": true,
|
|
114
|
+
"--codex-effort": true
|
|
115
|
+
}
|
|
116
|
+
|
|
34
117
|
|
|
35
118
|
function usage(ctx: Ctx): void {
|
|
36
119
|
const { echo } = ctx.services.logger
|
|
@@ -86,13 +169,33 @@ export function parseCompactWindow(v: string): string | undefined {
|
|
|
86
169
|
return /^[0-9]+$/.test(v) ? v : undefined
|
|
87
170
|
}
|
|
88
171
|
|
|
89
|
-
function
|
|
90
|
-
const { err } = ctx.services.logger
|
|
172
|
+
export function parseClaudePlugin(name: string, err: (message: string) => void): string {
|
|
91
173
|
if (!KNOWN_CLAUDE_OPTIN_PLUGINS.includes(name)) {
|
|
92
174
|
err(`Unknown opt-in plugin '${name}'. Known: ${KNOWN_CLAUDE_OPTIN_PLUGINS.join(", ")}`)
|
|
93
175
|
throw new ExitError(2)
|
|
94
176
|
}
|
|
95
|
-
|
|
177
|
+
return name
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function markModifier(ctx: Ctx, flag: ModifierFlag): void {
|
|
181
|
+
const flags = ctx.modifierFlags ?? new Set<ModifierFlag>()
|
|
182
|
+
flags.add(flag)
|
|
183
|
+
ctx.modifierFlags = flags
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function addClaudePlugin(ctx: Ctx, name: string): void {
|
|
187
|
+
if (name === "") {
|
|
188
|
+
printCatalog(
|
|
189
|
+
ctx,
|
|
190
|
+
`Available Claude optional plugins:\n${KNOWN_CLAUDE_OPTIN_PLUGINS.map((plugin) => ` ${plugin}`).join("\n")}`
|
|
191
|
+
)
|
|
192
|
+
ctx.services.logger.err(
|
|
193
|
+
`Invalid Claude plugin '' — valid: ${KNOWN_CLAUDE_OPTIN_PLUGINS.join("|")}`
|
|
194
|
+
)
|
|
195
|
+
throw new ExitError(2)
|
|
196
|
+
}
|
|
197
|
+
ctx.claudePlugins.push(parseClaudePlugin(name, ctx.services.logger.err))
|
|
198
|
+
markModifier(ctx, "--claude-plugin")
|
|
96
199
|
}
|
|
97
200
|
|
|
98
201
|
function selectTarget(ctx: Ctx, target: string): void {
|
|
@@ -102,7 +205,7 @@ function selectTarget(ctx: Ctx, target: string): void {
|
|
|
102
205
|
ctx.targetFilterSet = true
|
|
103
206
|
}
|
|
104
207
|
|
|
105
|
-
function setModifier(ctx: Ctx, flag:
|
|
208
|
+
function setModifier(ctx: Ctx, flag: ScalarModifierFlag, value: string): void {
|
|
106
209
|
switch (flag) {
|
|
107
210
|
case "--claude-model":
|
|
108
211
|
ctx.claudeModel = value
|
|
@@ -120,20 +223,19 @@ function setModifier(ctx: Ctx, flag: ModifierFlag, value: string): void {
|
|
|
120
223
|
ctx.codexEffort = value
|
|
121
224
|
break
|
|
122
225
|
}
|
|
123
|
-
|
|
124
|
-
flags.add(flag)
|
|
125
|
-
ctx.modifierFlags = flags
|
|
226
|
+
markModifier(ctx, flag)
|
|
126
227
|
}
|
|
127
228
|
|
|
128
|
-
function
|
|
129
|
-
return
|
|
229
|
+
function isScalarModifierFlag(value: string): value is ScalarModifierFlag {
|
|
230
|
+
return SCALAR_MODIFIER_FLAGS[value as ScalarModifierFlag] === true
|
|
130
231
|
}
|
|
131
232
|
|
|
233
|
+
|
|
132
234
|
export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
133
235
|
const { err } = ctx.services.logger
|
|
134
236
|
for (let index = 0; index < args.length; index += 1) {
|
|
135
237
|
const arg = args[index] ?? ""
|
|
136
|
-
if (
|
|
238
|
+
if (isScalarModifierFlag(arg) && args[index + 1] === "") {
|
|
137
239
|
setModifier(ctx, arg, "")
|
|
138
240
|
index += 1
|
|
139
241
|
continue
|
|
@@ -190,6 +292,7 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
|
190
292
|
throw new ExitError(2)
|
|
191
293
|
case "--claude-permissive":
|
|
192
294
|
ctx.claudePermissive = true
|
|
295
|
+
markModifier(ctx, "--claude-permissive")
|
|
193
296
|
continue
|
|
194
297
|
case "--claude-plugin":
|
|
195
298
|
err(`--claude-plugin requires a value: --claude-plugin=<${KNOWN_CLAUDE_OPTIN_PLUGINS.join("|")}>`)
|
|
@@ -244,8 +347,12 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
|
244
347
|
throw new ExitError(2)
|
|
245
348
|
}
|
|
246
349
|
ctx.claudeCompactWindow = parsed
|
|
350
|
+
markModifier(ctx, "--claude-compact-window")
|
|
247
351
|
} else if (arg.startsWith("--claude-plugin=")) {
|
|
248
352
|
addClaudePlugin(ctx, arg.slice("--claude-plugin=".length))
|
|
353
|
+
} else if (arg.startsWith("--claude-permissive=")) {
|
|
354
|
+
err("--claude-permissive does not take a value")
|
|
355
|
+
throw new ExitError(2)
|
|
249
356
|
} else {
|
|
250
357
|
err(`Unknown arg: ${arg}`)
|
|
251
358
|
throw new ExitError(2)
|
|
@@ -266,53 +373,49 @@ function printCatalog(ctx: Ctx, catalog: string): void {
|
|
|
266
373
|
|
|
267
374
|
export function validateModifierFlags(ctx: Ctx): void {
|
|
268
375
|
const { err, warn } = ctx.services.logger
|
|
269
|
-
const supplied = (flag: ModifierFlag
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
376
|
+
const supplied = (flag: ModifierFlag): boolean =>
|
|
377
|
+
MODIFIER_METADATA[flag].hasValue(ctx) || ctx.modifierFlags?.has(flag) === true
|
|
378
|
+
|
|
379
|
+
for (const flag of Object.keys(MODIFIER_METADATA) as Array<ModifierFlag>) {
|
|
380
|
+
const metadata = MODIFIER_METADATA[flag]
|
|
381
|
+
const targetSelected = metadata.target === "claude" ? ctx.syncClaude : ctx.syncCodex
|
|
382
|
+
if (!targetSelected && supplied(flag)) {
|
|
383
|
+
warn(metadata.ignoredWarning)
|
|
384
|
+
metadata.clear(ctx)
|
|
385
|
+
ctx.modifierFlags?.delete(flag)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (supplied("--claude-model")) {
|
|
390
|
+
if (!validateClaudeModel(ctx, ctx.claudeModel)) {
|
|
276
391
|
printModels(ctx, "claude")
|
|
277
392
|
err(`Invalid Claude model '${ctx.claudeModel}' — use an alias above or a full claude-* ID`)
|
|
278
393
|
throw new ExitError(2)
|
|
279
394
|
}
|
|
280
395
|
}
|
|
281
|
-
if (supplied("--claude-effort"
|
|
282
|
-
if (!ctx.
|
|
283
|
-
warn("--claude-effort ignored: claude target not selected")
|
|
284
|
-
ctx.claudeEffort = ""
|
|
285
|
-
} else if (!isEffortModifierValue("claude", ctx.claudeEffort)) {
|
|
396
|
+
if (supplied("--claude-effort")) {
|
|
397
|
+
if (!isEffortModifierValue("claude", ctx.claudeEffort)) {
|
|
286
398
|
printCatalog(ctx, effortCatalog("claude"))
|
|
287
399
|
err(`Invalid Claude effort '${ctx.claudeEffort}' — valid: ${effortValueGrammar("claude")}`)
|
|
288
400
|
throw new ExitError(2)
|
|
289
401
|
}
|
|
290
402
|
}
|
|
291
|
-
if (supplied("--claude-advisor"
|
|
292
|
-
if (!ctx.
|
|
293
|
-
warn("--claude-advisor ignored: claude target not selected")
|
|
294
|
-
ctx.claudeAdvisor = ""
|
|
295
|
-
} else if (!CLAUDE_ADVISOR_STATES.some((state) => state === ctx.claudeAdvisor)) {
|
|
403
|
+
if (supplied("--claude-advisor")) {
|
|
404
|
+
if (!CLAUDE_ADVISOR_STATES.some((state) => state === ctx.claudeAdvisor)) {
|
|
296
405
|
printCatalog(ctx, advisorCatalog())
|
|
297
406
|
err(`Invalid Claude advisor state '${ctx.claudeAdvisor}' — valid: ${advisorValueGrammar()}`)
|
|
298
407
|
throw new ExitError(2)
|
|
299
408
|
}
|
|
300
409
|
}
|
|
301
|
-
if (supplied("--codex-model"
|
|
302
|
-
if (!ctx.
|
|
303
|
-
warn("--codex-model ignored: codex target not selected")
|
|
304
|
-
ctx.codexModel = ""
|
|
305
|
-
} else if (!validateCodexModel(ctx, ctx.codexModel)) {
|
|
410
|
+
if (supplied("--codex-model")) {
|
|
411
|
+
if (!validateCodexModel(ctx, ctx.codexModel)) {
|
|
306
412
|
printModels(ctx, "codex")
|
|
307
413
|
err(`Invalid Codex model '${ctx.codexModel}' — must match ^[A-Za-z0-9._-]+$`)
|
|
308
414
|
throw new ExitError(2)
|
|
309
415
|
}
|
|
310
416
|
}
|
|
311
|
-
if (supplied("--codex-effort"
|
|
312
|
-
if (!ctx.
|
|
313
|
-
warn("--codex-effort ignored: codex target not selected")
|
|
314
|
-
ctx.codexEffort = ""
|
|
315
|
-
} else if (!isEffortModifierValue("codex", ctx.codexEffort)) {
|
|
417
|
+
if (supplied("--codex-effort")) {
|
|
418
|
+
if (!isEffortModifierValue("codex", ctx.codexEffort)) {
|
|
316
419
|
printCatalog(ctx, effortCatalog("codex"))
|
|
317
420
|
err(`Invalid Codex effort '${ctx.codexEffort}' — valid: ${effortValueGrammar("codex")}`)
|
|
318
421
|
throw new ExitError(2)
|
|
@@ -36,8 +36,6 @@ export interface DependencyManager {
|
|
|
36
36
|
export interface Platform {
|
|
37
37
|
readonly raw: () => NodeJS.Platform
|
|
38
38
|
readonly name: () => PlatformName
|
|
39
|
-
readonly isLinux: () => boolean
|
|
40
|
-
readonly shellRcApplicable: () => boolean
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
export interface EngineServices {
|
|
@@ -53,9 +51,7 @@ export interface EngineServiceOptions {
|
|
|
53
51
|
/** Platform view over an injectable platform id. */
|
|
54
52
|
export const makePlatform = (pf: NodeJS.Platform = rawPlatform()): Platform => ({
|
|
55
53
|
raw: () => pf,
|
|
56
|
-
name: () => platformName(pf)
|
|
57
|
-
isLinux: () => pf === "linux",
|
|
58
|
-
shellRcApplicable: () => pf === "linux" || pf === "darwin"
|
|
54
|
+
name: () => platformName(pf)
|
|
59
55
|
})
|
|
60
56
|
|
|
61
57
|
/** DependencyManager whose hints default to the INJECTED platform, not the host. */
|
|
@@ -16,8 +16,9 @@ export function reconcileSettings(repo: Json, user: Json): Json {
|
|
|
16
16
|
* `unique` — i.e. codepoint-sorted and deduplicated, matching jq).
|
|
17
17
|
*/
|
|
18
18
|
export function mergeSettings(repo: Json, user: Json): Json {
|
|
19
|
-
const
|
|
20
|
-
if (!isObject(
|
|
19
|
+
const candidate = deepMerge(user, repo)
|
|
20
|
+
if (!isObject(candidate)) return candidate
|
|
21
|
+
const merged = { ...candidate }
|
|
21
22
|
const permissions = isObject(merged["permissions"]) ? merged["permissions"] : {}
|
|
22
23
|
merged["permissions"] = {
|
|
23
24
|
...permissions,
|