docks-kit 0.17.0 → 0.17.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 +37 -33
- package/README.md +1 -0
- package/cli/docs/omp-models.md +114 -58
- package/cli/src/argv.ts +38 -469
- package/cli/src/argvSurface.ts +371 -0
- package/cli/src/argvValidate.ts +122 -0
- package/cli/src/commands/docs.ts +57 -42
- package/cli/src/commands/harnesses.ts +38 -45
- package/cli/src/commands/model.ts +48 -53
- package/cli/src/commands/models.ts +24 -26
- package/cli/src/commands/omp.ts +178 -88
- package/cli/src/commands/plugins.ts +20 -20
- package/cli/src/commands/skills.ts +22 -20
- package/cli/src/commands/status.ts +87 -80
- package/cli/src/commands/sync.ts +113 -87
- package/cli/src/commands/toolchain.ts +21 -21
- package/cli/src/commands/update.ts +139 -117
- package/cli/src/efforts.ts +35 -34
- package/cli/src/engine-native/DESIGN.md +30 -30
- package/cli/src/engine-native/bun.ts +77 -55
- package/cli/src/engine-native/claudeLsp.ts +154 -0
- package/cli/src/engine-native/claudeOptionalPlugins.ts +113 -0
- package/cli/src/engine-native/claudePluginPasses.ts +348 -0
- package/cli/src/engine-native/claudeRemovals.ts +195 -0
- package/cli/src/engine-native/claudeRetired.ts +4 -4
- package/cli/src/engine-native/claudeRuntime.ts +94 -68
- package/cli/src/engine-native/claudeSettings.ts +174 -0
- package/cli/src/engine-native/claudeSettingsModifiers.ts +50 -54
- package/cli/src/engine-native/claudeSync.ts +176 -456
- package/cli/src/engine-native/codexConfig.ts +158 -0
- package/cli/src/engine-native/codexHooks.ts +91 -0
- package/cli/src/engine-native/codexPlugins.ts +290 -0
- package/cli/src/engine-native/codexStatus.ts +24 -0
- package/cli/src/engine-native/codexSync.ts +110 -568
- package/cli/src/engine-native/codexToml.ts +140 -138
- package/cli/src/engine-native/deps.ts +144 -107
- package/cli/src/engine-native/engineCtx.ts +126 -0
- package/cli/src/engine-native/exec.ts +98 -87
- package/cli/src/engine-native/failures.ts +3 -3
- package/cli/src/engine-native/harnesses.ts +62 -72
- package/cli/src/engine-native/index.ts +34 -315
- package/cli/src/engine-native/jq.ts +25 -24
- package/cli/src/engine-native/logger.ts +98 -100
- package/cli/src/engine-native/models.ts +50 -45
- package/cli/src/engine-native/modes.ts +83 -80
- package/cli/src/engine-native/ompFileDeploy.ts +111 -0
- package/cli/src/engine-native/ompMarketplace.ts +136 -0
- package/cli/src/engine-native/ompOverlay.ts +93 -53
- package/cli/src/engine-native/ompPaths.ts +49 -38
- package/cli/src/engine-native/ompPlugins.ts +170 -0
- package/cli/src/engine-native/ompSync.ts +64 -408
- package/cli/src/engine-native/ompYaml.ts +43 -41
- package/cli/src/engine-native/os/darwin.ts +5 -5
- package/cli/src/engine-native/os/index.ts +15 -15
- package/cli/src/engine-native/os/linux.ts +5 -5
- package/cli/src/engine-native/os/posix.ts +28 -24
- package/cli/src/engine-native/os/targets.ts +17 -17
- package/cli/src/engine-native/os/types.ts +31 -31
- package/cli/src/engine-native/os/windows.ts +61 -61
- package/cli/src/engine-native/parseArgs.ts +122 -372
- package/cli/src/engine-native/parseHelp.ts +66 -0
- package/cli/src/engine-native/parseModifiers.ts +222 -0
- package/cli/src/engine-native/services.ts +34 -34
- package/cli/src/engine-native/settings.ts +15 -15
- package/cli/src/engine-native/sharedTypes.d.ts +46 -0
- package/cli/src/engine-native/skillsInstall.ts +105 -0
- package/cli/src/engine-native/skillsLinks.ts +203 -0
- package/cli/src/engine-native/skillsManifest.ts +16 -0
- package/cli/src/engine-native/skillsPrune.ts +108 -0
- package/cli/src/engine-native/skillsSync.ts +28 -362
- package/cli/src/engine-native/syncConcurrency.ts +55 -0
- package/cli/src/engine-native/syncDispatch.ts +141 -0
- package/cli/src/engine-native/toolchain.ts +65 -62
- package/cli/src/engine.ts +65 -57
- package/cli/src/generated/sotPayload.ts +7 -7
- package/cli/src/kitHome.ts +44 -40
- package/cli/src/main.ts +63 -56
- package/cli/src/manifests.ts +53 -54
- package/cli/src/md.d.ts +2 -2
- package/cli/src/payload.ts +9 -9
- package/cli/src/services.ts +21 -13
- package/cli/tsconfig.json +4 -1
- package/package.json +9 -2
- package/cli/src/engine-native/claudePlugins.ts +0 -510
|
@@ -4,341 +4,60 @@
|
|
|
4
4
|
* Golden suites drive it through main.ts's harness-private `native-raw`
|
|
5
5
|
* channel, which bypasses @effect/cli so tests see the internal argv
|
|
6
6
|
* vocabulary directly.
|
|
7
|
+
*
|
|
8
|
+
* Split: run context lives in `engineCtx.ts`, scheduling in
|
|
9
|
+
* `syncConcurrency.ts`, and pipeline dispatch in `syncDispatch.ts`. This
|
|
10
|
+
* module keeps the entry routing and re-exports the public surface so
|
|
11
|
+
* existing import paths keep working.
|
|
7
12
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| "--claude-effort"
|
|
26
|
-
| "--claude-advisor"
|
|
27
|
-
| "--claude-compact-window"
|
|
28
|
-
| "--claude-permissive"
|
|
29
|
-
| "--claude-plugin"
|
|
30
|
-
| "--codex-model"
|
|
31
|
-
| "--codex-effort"
|
|
32
|
-
|
|
33
|
-
export type SyncConcurrency = 1 | 2 | 3
|
|
34
|
-
export type SyncTask<T> = () => Promise<T>
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Run input-ordered tasks with bounded overlap. Once one task rejects, queued
|
|
38
|
-
* tasks stay queued while already-started tasks drain; the earliest rejection
|
|
39
|
-
* in input order is then propagated.
|
|
40
|
-
*/
|
|
41
|
-
export async function runBounded<T>(
|
|
42
|
-
tasks: ReadonlyArray<SyncTask<T>>,
|
|
43
|
-
concurrency: SyncConcurrency
|
|
44
|
-
): Promise<Array<T>> {
|
|
45
|
-
const results = new Array<T>(tasks.length)
|
|
46
|
-
const failures = new Map<number, unknown>()
|
|
47
|
-
let nextIndex = 0
|
|
48
|
-
let stopped = false
|
|
49
|
-
|
|
50
|
-
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, async () => {
|
|
51
|
-
for (;;) {
|
|
52
|
-
if (stopped || nextIndex >= tasks.length) return
|
|
53
|
-
const index = nextIndex
|
|
54
|
-
nextIndex += 1
|
|
55
|
-
try {
|
|
56
|
-
results[index] = await tasks[index]!()
|
|
57
|
-
} catch (error) {
|
|
58
|
-
failures.set(index, error)
|
|
59
|
-
stopped = true
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
await Promise.all(workers)
|
|
64
|
-
|
|
65
|
-
for (let index = 0; index < tasks.length; index += 1) {
|
|
66
|
-
if (failures.has(index)) throw failures.get(index)
|
|
67
|
-
}
|
|
68
|
-
return results
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function syncConcurrencyForManifest(
|
|
72
|
-
configured: SyncConcurrency,
|
|
73
|
-
manifest: string,
|
|
74
|
-
claudeSelected: boolean,
|
|
75
|
-
skillsSelected: boolean
|
|
76
|
-
): SyncConcurrency {
|
|
77
|
-
if (!claudeSelected || !skillsSelected || normalizeManifest(manifest).length === 0) return configured
|
|
78
|
-
return 1
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface Ctx {
|
|
82
|
-
readonly repoDir: string
|
|
83
|
-
readonly home: string
|
|
84
|
-
readonly agentsDir: string
|
|
85
|
-
readonly interactive: boolean
|
|
86
|
-
dryRun: boolean
|
|
87
|
-
verbose: boolean
|
|
88
|
-
skipBubblewrap: boolean
|
|
89
|
-
skipPluginRefresh?: boolean
|
|
90
|
-
reconcile: boolean
|
|
91
|
-
prune: boolean
|
|
92
|
-
claudeCompactWindow: string
|
|
93
|
-
claudePermissive: boolean
|
|
94
|
-
claudePlugins: Array<string>
|
|
95
|
-
claudeModel: string
|
|
96
|
-
claudeEffort: string
|
|
97
|
-
claudeAdvisor: string
|
|
98
|
-
codexModel: string
|
|
99
|
-
codexEffort: string
|
|
100
|
-
/** Distinguishes an explicitly empty modifier from an option that was not supplied. */
|
|
101
|
-
modifierFlags?: Set<ModifierFlag>
|
|
102
|
-
/** Injected capability seam (logger/deps/platform) — see services.ts. */
|
|
103
|
-
readonly services: EngineServices
|
|
104
|
-
syncConcurrency: SyncConcurrency
|
|
105
|
-
terminalLease?: TerminalLease
|
|
106
|
-
bunRuntime?: Promise<BunRuntimeState>
|
|
107
|
-
targetFilterSet: boolean
|
|
108
|
-
syncClaude: boolean
|
|
109
|
-
syncCodex: boolean
|
|
110
|
-
syncAgents: boolean
|
|
111
|
-
syncOmp: boolean
|
|
112
|
-
/** Per-run next-step triggers (Output Policy): advice prints only when its trigger changed or --verbose. */
|
|
113
|
-
readonly nextStepTriggers: {
|
|
114
|
-
claudePlugins: boolean
|
|
115
|
-
claudeRestart: boolean
|
|
116
|
-
codexRestart: boolean
|
|
117
|
-
skillsRestart: boolean
|
|
118
|
-
ompRestart: boolean
|
|
119
|
-
}
|
|
120
|
-
/** Harness-CLI operations that failed this run; a non-empty list fails the sync. */
|
|
121
|
-
readonly failures: Array<string>
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Globals default from env using the historical ${VAR:-default} contract. */
|
|
125
|
-
function makeCtx(services: EngineServices): Ctx {
|
|
126
|
-
const env = process.env
|
|
127
|
-
const home = engineHome(env)
|
|
128
|
-
const compactWindowSource = env["CLAUDE_COMPACT_WINDOW"] ?? ""
|
|
129
|
-
const claudeCompactWindow = compactWindowSource === "" ? "" : parseCompactWindow(compactWindowSource)
|
|
130
|
-
if (claudeCompactWindow === undefined) {
|
|
131
|
-
services.logger.err("CLAUDE_COMPACT_WINDOW expects a token count (e.g. 680000 or 680k)")
|
|
132
|
-
throw new ExitError(2)
|
|
133
|
-
}
|
|
134
|
-
const claudePlugins = (env["CLAUDE_PLUGINS"] ?? "")
|
|
135
|
-
.split(" ")
|
|
136
|
-
.filter((plugin) => plugin !== "")
|
|
137
|
-
.map((plugin) => parseClaudePlugin(plugin, services.logger.err))
|
|
138
|
-
return {
|
|
139
|
-
repoDir: kitHome(),
|
|
140
|
-
home,
|
|
141
|
-
agentsDir: env["AGENTS_DIR"] !== undefined && env["AGENTS_DIR"] !== "" ? env["AGENTS_DIR"] : p(home, ".agents"),
|
|
142
|
-
interactive:
|
|
143
|
-
env["DOCKS_KIT_INTERACTIVE"] === "1" ||
|
|
144
|
-
(env["DOCKS_KIT_INTERACTIVE"] !== "0" &&
|
|
145
|
-
process.stdout.isTTY === true &&
|
|
146
|
-
process.stdin.isTTY === true),
|
|
147
|
-
dryRun: env["DRY_RUN"] === "1",
|
|
148
|
-
verbose: env["DOCKS_KIT_VERBOSE"] === "1",
|
|
149
|
-
skipBubblewrap: env["SKIP_BUBBLEWRAP"] === "1",
|
|
150
|
-
skipPluginRefresh: false,
|
|
151
|
-
reconcile: env["RECONCILE"] === "1",
|
|
152
|
-
prune: env["PRUNE"] === "1",
|
|
153
|
-
claudeCompactWindow,
|
|
154
|
-
claudePermissive: env["CLAUDE_PERMISSIVE"] === "1",
|
|
155
|
-
claudePlugins,
|
|
156
|
-
claudeModel: env["CLAUDE_MODEL"] ?? "",
|
|
157
|
-
claudeEffort: "",
|
|
158
|
-
claudeAdvisor: "",
|
|
159
|
-
codexModel: env["CODEX_MODEL"] ?? "",
|
|
160
|
-
codexEffort: "",
|
|
161
|
-
modifierFlags: new Set(),
|
|
162
|
-
syncConcurrency: 3,
|
|
163
|
-
services,
|
|
164
|
-
targetFilterSet: false,
|
|
165
|
-
syncClaude: false,
|
|
166
|
-
syncCodex: false,
|
|
167
|
-
syncAgents: false,
|
|
168
|
-
syncOmp: false,
|
|
169
|
-
nextStepTriggers: {
|
|
170
|
-
claudePlugins: false,
|
|
171
|
-
claudeRestart: false,
|
|
172
|
-
codexRestart: false,
|
|
173
|
-
skillsRestart: false,
|
|
174
|
-
ompRestart: false
|
|
175
|
-
},
|
|
176
|
-
failures: []
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function engineSync(ctx: Ctx, args: ReadonlyArray<string>): Promise<number> {
|
|
181
|
-
const { acquireTerminal, echo, err } = ctx.services.logger
|
|
182
|
-
parseArgs(ctx, args)
|
|
183
|
-
validateModifierFlags(ctx)
|
|
184
|
-
|
|
185
|
-
const configuredConcurrency = process.env["DOCKS_KIT_SYNC_CONCURRENCY"]
|
|
186
|
-
if (configuredConcurrency === undefined || configuredConcurrency === "") {
|
|
187
|
-
ctx.syncConcurrency = 3
|
|
188
|
-
} else if (
|
|
189
|
-
configuredConcurrency === "1" ||
|
|
190
|
-
configuredConcurrency === "2" ||
|
|
191
|
-
configuredConcurrency === "3"
|
|
192
|
-
) {
|
|
193
|
-
ctx.syncConcurrency = Number(configuredConcurrency) as SyncConcurrency
|
|
194
|
-
} else {
|
|
195
|
-
err("DOCKS_KIT_SYNC_CONCURRENCY must be 1, 2, or 3")
|
|
196
|
-
throw new ExitError(2)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
type PipelineResult =
|
|
200
|
-
| { readonly kind: "claude"; readonly runtime: ClaudeRuntimeState }
|
|
201
|
-
| { readonly kind: "codex" }
|
|
202
|
-
| { readonly kind: "skills"; readonly state: SkillsState }
|
|
203
|
-
| { readonly kind: "omp"; readonly state: OmpState }
|
|
204
|
-
interface SelectedPipeline {
|
|
205
|
-
readonly name: string
|
|
206
|
-
readonly run: SyncTask<PipelineResult>
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const selected: Array<SelectedPipeline> = []
|
|
210
|
-
if (ctx.syncClaude) {
|
|
211
|
-
selected.push({
|
|
212
|
-
name: "Claude",
|
|
213
|
-
run: async () => ({ kind: "claude", runtime: await claudeSync(ctx) })
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
if (ctx.syncCodex) {
|
|
217
|
-
selected.push({
|
|
218
|
-
name: "Codex",
|
|
219
|
-
run: async () => {
|
|
220
|
-
await codexSync(ctx)
|
|
221
|
-
return { kind: "codex" }
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
if (ctx.syncAgents) {
|
|
226
|
-
selected.push({
|
|
227
|
-
name: "skills",
|
|
228
|
-
run: async () => ({ kind: "skills", state: await skillsSync(ctx) })
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
if (ctx.syncOmp) {
|
|
232
|
-
selected.push({
|
|
233
|
-
name: "omp",
|
|
234
|
-
run: async () => ({ kind: "omp", state: await ompSync(ctx) })
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// A populated skills manifest deploys with `-a claude-code codex`, and symlink healing also writes into Claude's tree.
|
|
239
|
-
ctx.syncConcurrency = syncConcurrencyForManifest(
|
|
240
|
-
ctx.syncConcurrency,
|
|
241
|
-
payloadText("SoT/.agents/skills.txt"),
|
|
242
|
-
ctx.syncClaude,
|
|
243
|
-
ctx.syncAgents
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
const remaining = new Set(selected.map(({ name }) => name))
|
|
247
|
-
const lease = acquireTerminal(`Syncing ${[...remaining].join(", ")}...`)
|
|
248
|
-
ctx.terminalLease = lease
|
|
249
|
-
let results: Array<PipelineResult>
|
|
250
|
-
try {
|
|
251
|
-
const tasks = selected.map(
|
|
252
|
-
({ name, run }): SyncTask<PipelineResult> =>
|
|
253
|
-
async () => {
|
|
254
|
-
try {
|
|
255
|
-
return await run()
|
|
256
|
-
} finally {
|
|
257
|
-
remaining.delete(name)
|
|
258
|
-
if (remaining.size > 0) lease.update(`Syncing ${[...remaining].join(", ")}...`)
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
)
|
|
262
|
-
results = await runBounded(tasks, ctx.syncConcurrency)
|
|
263
|
-
} finally {
|
|
264
|
-
lease.release()
|
|
265
|
-
ctx.terminalLease = undefined
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const claudeRan = ctx.syncClaude
|
|
269
|
-
const codexRan = ctx.syncCodex
|
|
270
|
-
let claudeRuntime: ClaudeRuntimeState | undefined
|
|
271
|
-
let skillsState: SkillsState | undefined
|
|
272
|
-
let ompState: OmpState | undefined
|
|
273
|
-
for (const result of results) {
|
|
274
|
-
if (result.kind === "claude") claudeRuntime = result.runtime
|
|
275
|
-
else if (result.kind === "skills") skillsState = result.state
|
|
276
|
-
else if (result.kind === "omp") ompState = result.state
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
echo("")
|
|
280
|
-
echo("--- Sync complete ---")
|
|
281
|
-
echo(`Repo: ${ctx.repoDir}`)
|
|
282
|
-
if (claudeRuntime !== undefined) claudeSummary(ctx, claudeRuntime)
|
|
283
|
-
if (codexRan) codexSummary(ctx)
|
|
284
|
-
if (skillsState !== undefined) skillsSummary(ctx, skillsState)
|
|
285
|
-
if (ompState !== undefined) ompSummary(ctx, ompState)
|
|
286
|
-
|
|
287
|
-
const advice = [
|
|
288
|
-
...(claudeRan ? claudeNextSteps(ctx) : []),
|
|
289
|
-
...(codexRan ? codexNextSteps(ctx) : []),
|
|
290
|
-
...(skillsState !== undefined ? skillsNextSteps(ctx) : []),
|
|
291
|
-
...(ompState !== undefined ? ompNextSteps(ctx) : [])
|
|
292
|
-
]
|
|
293
|
-
if (advice.length > 0) {
|
|
294
|
-
echo("")
|
|
295
|
-
for (const line of advice) echo(line)
|
|
296
|
-
}
|
|
297
|
-
if (ctx.failures.length > 0) {
|
|
298
|
-
echo("")
|
|
299
|
-
echo("--- Failures ---")
|
|
300
|
-
for (const failure of ctx.failures) echo(`- ${failure}`)
|
|
301
|
-
return 1
|
|
302
|
-
}
|
|
303
|
-
return 0
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
export async function runEngineNative(argv: ReadonlyArray<string>, services?: EngineServices): Promise<number> {
|
|
308
|
-
let ctx!: Ctx
|
|
309
|
-
const baseServices = services ?? makeEngineServices()
|
|
310
|
-
const baseLogger = baseServices.logger
|
|
13
|
+
import { makeCtx, type Ctx } from "./engineCtx";
|
|
14
|
+
import { modeModel, modeToolchain } from "./modes";
|
|
15
|
+
import { ExitError } from "./parseArgs";
|
|
16
|
+
import { makeEngineServices, type EngineServices, type Logger } from "./services";
|
|
17
|
+
import { engineSync } from "./syncDispatch";
|
|
18
|
+
|
|
19
|
+
export type { Ctx, ModifierFlag } from "./engineCtx";
|
|
20
|
+
export { runBounded, syncConcurrencyForManifest } from "./syncConcurrency";
|
|
21
|
+
export type { SyncConcurrency, SyncTask } from "./syncConcurrency";
|
|
22
|
+
|
|
23
|
+
export async function runEngineNative(
|
|
24
|
+
argv: ReadonlyArray<string>,
|
|
25
|
+
services?: EngineServices,
|
|
26
|
+
): Promise<number> {
|
|
27
|
+
let ctx!: Ctx;
|
|
28
|
+
const baseServices = services ?? makeEngineServices();
|
|
29
|
+
const baseLogger = baseServices.logger;
|
|
311
30
|
const logger: Logger = {
|
|
312
31
|
change: (msg) => baseLogger.change(msg),
|
|
313
32
|
progress: (msg) => baseLogger.progress(msg),
|
|
314
33
|
clearProgress: () => baseLogger.clearProgress(),
|
|
315
34
|
verbose: (msg) => {
|
|
316
|
-
if (ctx.verbose) baseLogger.verbose(msg)
|
|
35
|
+
if (ctx.verbose) baseLogger.verbose(msg);
|
|
317
36
|
},
|
|
318
37
|
warn: (msg) => baseLogger.warn(msg),
|
|
319
38
|
err: (msg) => baseLogger.err(msg),
|
|
320
39
|
echo: (line) => baseLogger.echo(line),
|
|
321
|
-
acquireTerminal: (message) => baseLogger.acquireTerminal(message)
|
|
322
|
-
}
|
|
40
|
+
acquireTerminal: (message) => baseLogger.acquireTerminal(message),
|
|
41
|
+
};
|
|
323
42
|
const runServices: EngineServices = {
|
|
324
43
|
logger,
|
|
325
44
|
deps: baseServices.deps,
|
|
326
|
-
platform: baseServices.platform
|
|
327
|
-
}
|
|
45
|
+
platform: baseServices.platform,
|
|
46
|
+
};
|
|
328
47
|
try {
|
|
329
|
-
ctx = makeCtx(runServices)
|
|
48
|
+
ctx = makeCtx(runServices);
|
|
330
49
|
switch (argv[0]) {
|
|
331
50
|
case "model":
|
|
332
|
-
return modeModel(ctx, argv.slice(1))
|
|
51
|
+
return modeModel(ctx, argv.slice(1));
|
|
333
52
|
case "toolchain":
|
|
334
|
-
return await modeToolchain(ctx, argv.slice(1))
|
|
53
|
+
return await modeToolchain(ctx, argv.slice(1));
|
|
335
54
|
case "sync":
|
|
336
|
-
return await engineSync(ctx, argv.slice(1))
|
|
55
|
+
return await engineSync(ctx, argv.slice(1));
|
|
337
56
|
default:
|
|
338
|
-
return await engineSync(ctx, argv)
|
|
57
|
+
return await engineSync(ctx, argv);
|
|
339
58
|
}
|
|
340
59
|
} catch (e) {
|
|
341
|
-
if (e instanceof ExitError) return e.code
|
|
342
|
-
throw e
|
|
60
|
+
if (e instanceof ExitError) return e.code;
|
|
61
|
+
throw e;
|
|
343
62
|
}
|
|
344
63
|
}
|
|
@@ -11,60 +11,61 @@
|
|
|
11
11
|
* - output formatting: 2-space indent, UTF-8 raw, trailing newline.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import type { JsonObject } from "./sharedTypes";
|
|
15
16
|
|
|
16
|
-
export type Json = null | boolean | number | string | Array<Json> | { [key: string]: Json }
|
|
17
|
+
export type Json = null | boolean | number | string | Array<Json> | { [key: string]: Json };
|
|
17
18
|
|
|
18
|
-
export function isObject(v: Json): v is
|
|
19
|
-
return typeof v === "object" && v !== null && !Array.isArray(v)
|
|
19
|
+
export function isObject(v: Json): v is JsonObject {
|
|
20
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/** jq `$left * $right`. */
|
|
23
24
|
export function deepMerge(left: Json, right: Json): Json {
|
|
24
|
-
if (!isObject(left) || !isObject(right)) return right
|
|
25
|
-
const out:
|
|
25
|
+
if (!isObject(left) || !isObject(right)) return right;
|
|
26
|
+
const out: JsonObject = { ...left };
|
|
26
27
|
for (const [k, v] of Object.entries(right)) {
|
|
27
|
-
const lv = out[k]
|
|
28
|
-
out[k] = lv !== undefined && isObject(lv) && isObject(v) ? deepMerge(lv, v) : v
|
|
28
|
+
const lv = out[k];
|
|
29
|
+
out[k] = lv !== undefined && isObject(lv) && isObject(v) ? deepMerge(lv, v) : v;
|
|
29
30
|
}
|
|
30
|
-
return out
|
|
31
|
+
return out;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/** jq `unique` over an array of strings: codepoint sort + dedup. */
|
|
34
35
|
export function uniqueStrings(arr: Array<string>): Array<string> {
|
|
35
|
-
return [...new Set(arr)].sort(compareCodepoints)
|
|
36
|
+
return [...new Set(arr)].sort(compareCodepoints);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function compareCodepoints(a: string, b: string): number {
|
|
39
|
-
const ia = a[Symbol.iterator]()
|
|
40
|
-
const ib = b[Symbol.iterator]()
|
|
40
|
+
const ia = a[Symbol.iterator]();
|
|
41
|
+
const ib = b[Symbol.iterator]();
|
|
41
42
|
for (;;) {
|
|
42
|
-
const ra = ia.next()
|
|
43
|
-
const rb = ib.next()
|
|
44
|
-
if (ra.done && rb.done) return 0
|
|
45
|
-
if (ra.done) return -1
|
|
46
|
-
if (rb.done) return 1
|
|
47
|
-
const ca = ra.value.codePointAt(0)
|
|
48
|
-
const cb = rb.value.codePointAt(0)
|
|
49
|
-
if (ca !== cb) return ca - cb
|
|
43
|
+
const ra = ia.next();
|
|
44
|
+
const rb = ib.next();
|
|
45
|
+
if (ra.done && rb.done) return 0;
|
|
46
|
+
if (ra.done) return -1;
|
|
47
|
+
if (rb.done) return 1;
|
|
48
|
+
const ca = ra.value.codePointAt(0)!;
|
|
49
|
+
const cb = rb.value.codePointAt(0)!;
|
|
50
|
+
if (ca !== cb) return ca - cb;
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
/** jq's default pretty-printer for the documents this kit writes. */
|
|
54
55
|
export function jqStringify(v: Json): string {
|
|
55
|
-
return `${JSON.stringify(v, null, 2)}\n
|
|
56
|
+
return `${JSON.stringify(v, null, 2)}\n`;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/** jq `empty` validation: parse or null. */
|
|
59
60
|
export function parseJson(text: string): Json | undefined {
|
|
60
61
|
try {
|
|
61
|
-
return JSON.parse(text) as Json
|
|
62
|
+
return JSON.parse(text) as Json;
|
|
62
63
|
} catch {
|
|
63
|
-
return undefined
|
|
64
|
+
return undefined;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/** Parse a JSON file with `parseJson` semantics; a missing file is `undefined`. */
|
|
68
69
|
export function readJsonFile(file: string): Json | undefined {
|
|
69
|
-
return existsSync(file) ? parseJson(readFileSync(file, "utf8")) : undefined
|
|
70
|
+
return existsSync(file) ? parseJson(readFileSync(file, "utf8")) : undefined;
|
|
70
71
|
}
|