docks-kit 0.17.1 → 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.
Files changed (83) hide show
  1. package/AGENTS.md +36 -32
  2. package/cli/docs/omp-models.md +88 -53
  3. package/cli/src/argv.ts +38 -469
  4. package/cli/src/argvSurface.ts +371 -0
  5. package/cli/src/argvValidate.ts +122 -0
  6. package/cli/src/commands/docs.ts +57 -42
  7. package/cli/src/commands/harnesses.ts +38 -45
  8. package/cli/src/commands/model.ts +48 -53
  9. package/cli/src/commands/models.ts +24 -26
  10. package/cli/src/commands/omp.ts +132 -112
  11. package/cli/src/commands/plugins.ts +20 -20
  12. package/cli/src/commands/skills.ts +22 -20
  13. package/cli/src/commands/status.ts +87 -80
  14. package/cli/src/commands/sync.ts +113 -87
  15. package/cli/src/commands/toolchain.ts +21 -21
  16. package/cli/src/commands/update.ts +139 -117
  17. package/cli/src/efforts.ts +35 -34
  18. package/cli/src/engine-native/DESIGN.md +30 -30
  19. package/cli/src/engine-native/bun.ts +77 -55
  20. package/cli/src/engine-native/claudeLsp.ts +154 -0
  21. package/cli/src/engine-native/claudeOptionalPlugins.ts +113 -0
  22. package/cli/src/engine-native/claudePluginPasses.ts +348 -0
  23. package/cli/src/engine-native/claudeRemovals.ts +195 -0
  24. package/cli/src/engine-native/claudeRetired.ts +4 -4
  25. package/cli/src/engine-native/claudeRuntime.ts +94 -68
  26. package/cli/src/engine-native/claudeSettings.ts +174 -0
  27. package/cli/src/engine-native/claudeSettingsModifiers.ts +50 -54
  28. package/cli/src/engine-native/claudeSync.ts +176 -456
  29. package/cli/src/engine-native/codexConfig.ts +158 -0
  30. package/cli/src/engine-native/codexHooks.ts +91 -0
  31. package/cli/src/engine-native/codexPlugins.ts +290 -0
  32. package/cli/src/engine-native/codexStatus.ts +24 -0
  33. package/cli/src/engine-native/codexSync.ts +110 -568
  34. package/cli/src/engine-native/codexToml.ts +140 -138
  35. package/cli/src/engine-native/deps.ts +144 -107
  36. package/cli/src/engine-native/engineCtx.ts +126 -0
  37. package/cli/src/engine-native/exec.ts +98 -87
  38. package/cli/src/engine-native/failures.ts +3 -3
  39. package/cli/src/engine-native/harnesses.ts +62 -72
  40. package/cli/src/engine-native/index.ts +34 -315
  41. package/cli/src/engine-native/jq.ts +25 -24
  42. package/cli/src/engine-native/logger.ts +98 -100
  43. package/cli/src/engine-native/models.ts +50 -45
  44. package/cli/src/engine-native/modes.ts +83 -80
  45. package/cli/src/engine-native/ompFileDeploy.ts +111 -0
  46. package/cli/src/engine-native/ompMarketplace.ts +136 -0
  47. package/cli/src/engine-native/ompOverlay.ts +67 -64
  48. package/cli/src/engine-native/ompPaths.ts +49 -38
  49. package/cli/src/engine-native/ompPlugins.ts +170 -0
  50. package/cli/src/engine-native/ompSync.ts +64 -408
  51. package/cli/src/engine-native/ompYaml.ts +43 -41
  52. package/cli/src/engine-native/os/darwin.ts +5 -5
  53. package/cli/src/engine-native/os/index.ts +15 -15
  54. package/cli/src/engine-native/os/linux.ts +5 -5
  55. package/cli/src/engine-native/os/posix.ts +28 -24
  56. package/cli/src/engine-native/os/targets.ts +17 -17
  57. package/cli/src/engine-native/os/types.ts +31 -31
  58. package/cli/src/engine-native/os/windows.ts +61 -61
  59. package/cli/src/engine-native/parseArgs.ts +122 -372
  60. package/cli/src/engine-native/parseHelp.ts +66 -0
  61. package/cli/src/engine-native/parseModifiers.ts +222 -0
  62. package/cli/src/engine-native/services.ts +34 -34
  63. package/cli/src/engine-native/settings.ts +15 -15
  64. package/cli/src/engine-native/sharedTypes.d.ts +46 -0
  65. package/cli/src/engine-native/skillsInstall.ts +105 -0
  66. package/cli/src/engine-native/skillsLinks.ts +203 -0
  67. package/cli/src/engine-native/skillsManifest.ts +16 -0
  68. package/cli/src/engine-native/skillsPrune.ts +108 -0
  69. package/cli/src/engine-native/skillsSync.ts +28 -362
  70. package/cli/src/engine-native/syncConcurrency.ts +55 -0
  71. package/cli/src/engine-native/syncDispatch.ts +141 -0
  72. package/cli/src/engine-native/toolchain.ts +65 -62
  73. package/cli/src/engine.ts +65 -57
  74. package/cli/src/generated/sotPayload.ts +7 -7
  75. package/cli/src/kitHome.ts +44 -40
  76. package/cli/src/main.ts +63 -56
  77. package/cli/src/manifests.ts +53 -54
  78. package/cli/src/md.d.ts +2 -2
  79. package/cli/src/payload.ts +9 -9
  80. package/cli/src/services.ts +21 -13
  81. package/cli/tsconfig.json +4 -1
  82. package/package.json +9 -2
  83. package/cli/src/engine-native/claudePlugins.ts +0 -510
@@ -5,437 +5,93 @@
5
5
  * PI_CODING_AGENT_DIR, and XDG roots each move them.
6
6
  * Resolution stays within the environment and filesystem probes so no omp
7
7
  * subcommand runs under ctx.dryRun.
8
+ *
9
+ * Split: file deploy lives in `ompFileDeploy.ts`, the marketplace pass in
10
+ * `ompMarketplace.ts`, and the plugin pass in `ompPlugins.ts`. This module
11
+ * keeps the orchestration plus the summary surface.
8
12
  */
9
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"
10
- import { basename, isAbsolute, resolve } from "node:path"
13
+ import { isAbsolute, resolve } from "node:path";
11
14
 
12
- import { payloadDisplayPath, payloadText, type PayloadPath } from "../payload"
13
- import { bunBootstrap } from "./bun"
14
- import { p, spawnProcess, type AsyncProcessResult } from "./exec"
15
- import { recordFailure } from "./failures"
16
- import type { Ctx } from "./index"
17
- import { isObject, parseJson } from "./jq"
18
- import { ompPaths } from "./ompPaths"
19
- import { mergeOmpConfig, mergeOmpModels } from "./ompYaml"
20
- import { field } from "./toolchain"
21
-
22
- const MARKETPLACE_NAME = "docks"
23
- const MARKETPLACE_SOURCE = "https://github.com/DocksDocks/docks.git"
24
- const MARKETPLACE_PLUGIN_IDS = ["docks@docks", "plan-lifecycle@docks"] as const
25
- type OmpTextPayloadPath = Extract<PayloadPath, `SoT/.omp/${string}`>
15
+ import { bunBootstrap } from "./bun";
16
+ import { p } from "./exec";
17
+ import type { Ctx } from "./index";
18
+ import { ompPaths } from "./ompPaths";
19
+ import { mergeOmpConfig, mergeOmpModels } from "./ompYaml";
20
+ import { ensureDirectory, syncMergedYaml, syncWholeFile } from "./ompFileDeploy";
21
+ import { syncMarketplace } from "./ompMarketplace";
22
+ import { syncPlugins } from "./ompPlugins";
26
23
 
27
24
  export interface OmpState {
28
- readonly pluginsInstalled: number
25
+ readonly pluginsInstalled: number;
29
26
  }
30
27
 
31
28
  export async function ompSync(ctx: Ctx): Promise<OmpState> {
32
- await bunBootstrap(ctx, ctx.services)
33
-
34
- const paths = ompPaths({ home: ctx.home, env: process.env, platform: ctx.services.platform.raw() })
35
- const agentDir = paths.agentDir
36
- if (!ctx.dryRun) ensureDirectory(agentDir)
29
+ await bunBootstrap(ctx, ctx.services);
37
30
 
38
- syncWholeFile(ctx, "SoT/.omp/AGENTS.md", p(agentDir, "AGENTS.md"), "omp AGENTS.md already in sync", "omp AGENTS.md synced")
39
- syncWholeFile(ctx, "SoT/.omp/mcp.json", p(agentDir, "mcp.json"), "omp mcp.json already in sync", "omp mcp.json synced")
40
- syncMergedYaml(ctx, "SoT/.omp/config.yml", p(agentDir, "config.yml"), mergeOmpConfig)
41
- syncMergedYaml(ctx, "SoT/.omp/models.yml", p(agentDir, "models.yml"), mergeOmpModels)
31
+ const paths = ompPaths({
32
+ home: ctx.home,
33
+ env: process.env,
34
+ platform: ctx.services.platform.raw(),
35
+ });
36
+ const agentDir = paths.agentDir;
37
+ if (!ctx.dryRun) ensureDirectory(agentDir);
42
38
 
43
- const intercomRootSetting = process.env["PI_CODING_AGENT_DIR"]
44
- const intercomRoot = intercomRootSetting !== undefined && intercomRootSetting !== ""
45
- ? isAbsolute(intercomRootSetting)
46
- ? intercomRootSetting
47
- : resolve(process.cwd(), intercomRootSetting)
48
- : p(ctx.home, ".pi", "agent")
49
- const intercomDir = p(intercomRoot, "intercom")
50
- if (!ctx.dryRun) ensureDirectory(intercomDir)
39
+ syncWholeFile(
40
+ ctx,
41
+ "SoT/.omp/AGENTS.md",
42
+ p(agentDir, "AGENTS.md"),
43
+ "omp AGENTS.md already in sync",
44
+ "omp AGENTS.md synced",
45
+ );
46
+ syncWholeFile(
47
+ ctx,
48
+ "SoT/.omp/mcp.json",
49
+ p(agentDir, "mcp.json"),
50
+ "omp mcp.json already in sync",
51
+ "omp mcp.json synced",
52
+ );
53
+ syncMergedYaml(ctx, "SoT/.omp/config.yml", p(agentDir, "config.yml"), mergeOmpConfig);
54
+ syncMergedYaml(ctx, "SoT/.omp/models.yml", p(agentDir, "models.yml"), mergeOmpModels);
55
+
56
+ const intercomRootSetting = process.env["PI_CODING_AGENT_DIR"];
57
+ const intercomRoot =
58
+ intercomRootSetting !== undefined && intercomRootSetting !== ""
59
+ ? isAbsolute(intercomRootSetting)
60
+ ? intercomRootSetting
61
+ : resolve(process.cwd(), intercomRootSetting)
62
+ : p(ctx.home, ".pi", "agent");
63
+ const intercomDir = p(intercomRoot, "intercom");
64
+ if (!ctx.dryRun) ensureDirectory(intercomDir);
51
65
  syncWholeFile(
52
66
  ctx,
53
67
  "SoT/.omp/intercom.json",
54
68
  p(intercomDir, "config.json"),
55
69
  "omp intercom configuration already in sync",
56
- "omp intercom configuration synced"
57
- )
58
-
59
- const legacyRegistryFile = paths.dataRoot === paths.configRoot
60
- ? undefined
61
- : p(paths.configRoot, "marketplaces.json")
62
- await syncMarketplace(ctx, p(paths.dataRoot, "marketplaces.json"), legacyRegistryFile)
63
- const pluginsInstalled = await syncPlugins(ctx)
64
- return { pluginsInstalled }
65
- }
66
-
67
- // ------------------------------------------------------------ file modes ----
68
-
69
- /**
70
- * `mkdirSync` and `writeFileSync` apply their `mode` only when they create the
71
- * path, so an existing world-readable directory or file would keep its mode.
72
- * The explicit chmod reproduces `install -d -m 0700` / `install -m 0600`.
73
- */
74
- function ensureDirectory(directory: string): void {
75
- mkdirSync(directory, { recursive: true, mode: 0o700 })
76
- chmodSync(directory, 0o700)
77
- }
78
-
79
- function writePrivateFile(target: string, content: string): void {
80
- writeFileSync(target, content, { mode: 0o600 })
81
- chmodSync(target, 0o600)
82
- }
83
-
84
- function backupPrivateFile(target: string): void {
85
- copyFileSync(target, `${target}.bak`)
86
- chmodSync(`${target}.bak`, 0o600)
87
- }
88
-
89
- // --------------------------------------------------------------- config ----
90
-
91
- function syncWholeFile(
92
- ctx: Ctx,
93
- sourcePath: OmpTextPayloadPath,
94
- target: string,
95
- alreadyMessage: string,
96
- syncedMessage: string
97
- ): void {
98
- const { change, echo, verbose } = ctx.services.logger
99
- const source = payloadDisplayPath(sourcePath)
100
- const content = payloadText(sourcePath)
101
-
102
- if (ctx.dryRun) {
103
- echo(`[dry-run] cp ${source} -> ${target}`)
104
- return
105
- }
106
-
107
- if (existsSync(target) && readFileSync(target, "utf8") === content) {
108
- verbose(alreadyMessage)
109
- return
110
- }
111
- if (existsSync(target)) backupPrivateFile(target)
112
- writePrivateFile(target, content)
113
- change(syncedMessage)
114
- ctx.nextStepTriggers.ompRestart = true
115
- }
116
-
117
- function syncMergedYaml(
118
- ctx: Ctx,
119
- sourcePath: OmpTextPayloadPath,
120
- target: string,
121
- merge: (sotText: string, deployedText: string) => string
122
- ): void {
123
- const { change, echo, verbose } = ctx.services.logger
124
- const source = payloadDisplayPath(sourcePath)
125
- const sotText = payloadText(sourcePath)
126
- const name = basename(sourcePath)
127
-
128
- if (!existsSync(target)) {
129
- if (ctx.dryRun) {
130
- echo(`[dry-run] cp ${source} -> ${target}`)
131
- return
132
- }
133
- writePrivateFile(target, sotText)
134
- change(`omp ${name} installed`)
135
- ctx.nextStepTriggers.ompRestart = true
136
- return
137
- }
138
-
139
- const deployedText = readFileSync(target, "utf8")
140
- const merged = merge(sotText, deployedText)
141
- if (merged === deployedText) {
142
- verbose(`omp ${name} already in sync`)
143
- return
144
- }
145
- if (ctx.dryRun) {
146
- echo(`[dry-run] merge ${source} -> ${target} (backup at ${target}.bak)`)
147
- return
148
- }
149
-
150
- backupPrivateFile(target)
151
- writePrivateFile(`${target}.tmp`, merged)
152
- renameSync(`${target}.tmp`, target)
153
- chmodSync(target, 0o600)
154
- change(`omp ${name} merged (backup at ${name}.bak)`)
155
- ctx.nextStepTriggers.ompRestart = true
156
- }
157
-
158
- // ---------------------------------------------------------- marketplace ----
159
-
160
- function registryHasDocks(registryFile: string): boolean {
161
- if (!existsSync(registryFile)) return false
162
-
163
- let registryText: string
164
- try {
165
- registryText = readFileSync(registryFile, "utf8")
166
- } catch {
167
- return false
168
- }
169
- const registry = parseJson(registryText)
170
- if (registry === undefined) return false
171
- if (isObject(registry) && Object.hasOwn(registry, MARKETPLACE_NAME)) return true
172
-
173
- const entries = Array.isArray(registry)
174
- ? registry
175
- : isObject(registry) && Array.isArray(registry["marketplaces"])
176
- ? registry["marketplaces"]
177
- : []
178
- return entries.some((entry) => isObject(entry) && entry["name"] === MARKETPLACE_NAME)
179
- }
180
-
181
- function firstOutputLine(result: AsyncProcessResult): string {
182
- const output = `${result.stdout}${result.stderr}`
183
- return output.split("\n")[0] || "unknown error"
184
- }
185
-
186
- /**
187
- * Three states, because upstream `getMarketplacesRegistryPath` copies a legacy
188
- * `configRoot` registry forward the first time it resolves an XDG data root:
189
- * the active registry lists docks, only the legacy registry lists it, or
190
- * neither does. In the middle state the kit never copies user data itself; it
191
- * takes the refresh path, whose own resolution inside omp performs that
192
- * adoption and leaves the active registry present.
193
- */
194
- async function syncMarketplace(ctx: Ctx, registryFile: string, legacyRegistryFile?: string): Promise<void> {
195
- const { change, clearProgress, echo, progress, verbose } = ctx.services.logger
196
- const registered = registryHasDocks(registryFile)
197
- const adoptable = !registered && legacyRegistryFile !== undefined && registryHasDocks(legacyRegistryFile)
198
-
199
- if (ctx.dryRun) {
200
- if (adoptable) {
201
- echo(
202
- ctx.skipPluginRefresh === true
203
- ? "[dry-run] omp plugin marketplace list"
204
- : `[dry-run] omp plugin marketplace update ${MARKETPLACE_NAME}`
205
- )
206
- } else if (registered) verbose("omp docks marketplace already registered")
207
- else echo(`[dry-run] omp plugin marketplace add ${MARKETPLACE_SOURCE}`)
208
- return
209
- }
210
-
211
- // syncPlugins owns the single skip message for both missing CLIs, and it runs
212
- // right after this pass. Without omp no marketplace command can run at all,
213
- // and without git a marketplace clone cannot resolve, so both are deliberate
214
- // skips: return silently instead of spawning and recording a failure.
215
- if (ctx.services.deps.probe("omp").state === "missing") return
216
- if (ctx.services.deps.probe("git").state === "missing") return
217
-
218
- if (!registered && !adoptable) {
219
- progress("Registering omp docks marketplace...")
220
- const result = await spawnProcess("omp", ["plugin", "marketplace", "add", MARKETPLACE_SOURCE], {
221
- stdio: ["ignore", "pipe", "pipe"]
222
- })
223
- clearProgress()
224
- if (result.error === undefined && result.exitCode === 0) {
225
- change("omp docks marketplace registered")
226
- } else {
227
- recordFailure(
228
- ctx,
229
- `omp docks marketplace registration failed: ${firstOutputLine(result)}; run manually: omp plugin marketplace add ${MARKETPLACE_SOURCE}`
230
- )
231
- }
232
- return
233
- }
234
-
235
- if (ctx.skipPluginRefresh === true) {
236
- if (!adoptable) {
237
- verbose("omp docks marketplace already registered; refresh-only update skipped")
238
- return
239
- }
240
-
241
- // Adoption only needs omp to resolve the registry path. `marketplace list`
242
- // does that and fetches nothing, so it stays inside the flag's contract
243
- // while leaving the active registry present.
244
- progress("Adopting omp docks marketplace registry...")
245
- const listed = await spawnProcess("omp", ["plugin", "marketplace", "list"], {
246
- stdio: ["ignore", "ignore", "pipe"]
247
- })
248
- clearProgress()
249
- if (listed.error === undefined && listed.exitCode === 0) {
250
- change("omp docks marketplace registry adopted; refresh-only update skipped")
251
- } else {
252
- recordFailure(
253
- ctx,
254
- `omp docks marketplace adoption failed: ${firstOutputLine(listed)}; run manually: omp plugin marketplace list`
255
- )
256
- }
257
- return
258
- }
259
-
260
- progress("Updating omp docks marketplace...")
261
- const result = await spawnProcess("omp", ["plugin", "marketplace", "update", MARKETPLACE_NAME], {
262
- stdio: ["ignore", "pipe", "pipe"]
263
- })
264
- clearProgress()
265
- if (result.error === undefined && result.exitCode === 0) {
266
- verbose("omp docks marketplace refreshed")
267
- } else {
268
- recordFailure(
269
- ctx,
270
- `omp docks marketplace update failed: ${firstOutputLine(result)}; run manually: omp plugin marketplace update ${MARKETPLACE_NAME}`
271
- )
272
- }
273
- }
274
-
275
- // -------------------------------------------------------------- plugins ----
276
-
277
- interface InstalledPlugins {
278
- readonly marketplace: Set<string>
279
- readonly npm: Map<string, string>
280
- }
281
-
282
- async function installedPluginIdsFromCli(): Promise<InstalledPlugins | undefined> {
283
- const result = await spawnProcess("omp", ["plugin", "list", "--json"], {
284
- stdio: ["ignore", "pipe", "ignore"]
285
- })
286
- if (result.error !== undefined || result.exitCode !== 0) return undefined
287
-
288
- const value = parseJson(result.stdout)
289
- if (
290
- value === undefined ||
291
- !isObject(value) ||
292
- !Array.isArray(value["marketplace"]) ||
293
- !Array.isArray(value["npm"])
294
- ) {
295
- return undefined
296
- }
297
-
298
- // `omp plugin list --json` reports marketplace rows as
299
- // `{ id: "<plugin>@<marketplace>", scope, entries: [...] }` - the composite id
300
- // is already the token `omp plugin install/upgrade` takes. omp emits one row
301
- // per scope holding the plugin, so a match found only in a `project` row
302
- // means the user scope is empty and `upgrade --scope user` would fail; only a
303
- // `user` row counts as installed for this pipeline.
304
- const marketplace = new Set<string>()
305
- for (const row of value["marketplace"]) {
306
- if (!isObject(row) || typeof row["id"] !== "string" || row["scope"] !== "user") continue
307
- marketplace.add(row["id"])
308
- }
309
-
310
- const npm = new Map<string, string>()
311
- for (const row of value["npm"]) {
312
- if (!isObject(row) || typeof row["name"] !== "string" || typeof row["version"] !== "string") continue
313
- npm.set(row["name"], row["version"])
314
- }
315
- return { marketplace, npm }
316
- }
317
-
318
- async function runPluginCommand(
319
- ctx: Ctx,
320
- plugin: string,
321
- args: ReadonlyArray<string>
322
- ): Promise<boolean> {
323
- const { clearProgress, progress } = ctx.services.logger
324
- progress(`Updating omp plugin ${plugin}...`)
325
- const result = await spawnProcess("omp", args, { stdio: ["ignore", "pipe", "pipe"] })
326
- clearProgress()
327
- if (result.error === undefined && result.exitCode === 0) return true
328
-
329
- recordFailure(
330
- ctx,
331
- `omp plugin operation failed for ${plugin}: ${firstOutputLine(result)}; run manually: omp ${args.join(" ")}`
332
- )
333
- return false
334
- }
335
-
336
- async function syncPlugins(ctx: Ctx): Promise<number> {
337
- const { change, clearProgress, echo, progress, verbose, warn } = ctx.services.logger
338
-
339
- if (ctx.dryRun) {
340
- const piIntercomPin = field("pi-intercom", "verified")
341
- for (const pluginId of MARKETPLACE_PLUGIN_IDS) {
342
- echo(`[dry-run] omp plugin install --scope user ${pluginId}`)
343
- }
344
- if (piIntercomPin === "") {
345
- warn("pi-intercom install skipped because SoT/toolchain.json has no verified pi-intercom pin")
346
- } else {
347
- echo(`[dry-run] omp install pi-intercom@${piIntercomPin}`)
348
- }
349
- return 0
350
- }
351
-
352
- if (ctx.services.deps.probe("omp").state === "missing") {
353
- ctx.services.deps.warnMissing(
354
- "omp",
355
- ctx.services.logger,
356
- "deployed omp config only — marketplace and plugin passes skipped; re-run sync after installing"
357
- )
358
- return 0
359
- }
360
- if (ctx.services.deps.probe("git").state === "missing") {
361
- ctx.services.deps.warnMissing(
362
- "git",
363
- ctx.services.logger,
364
- "plugin marketplaces are git repos — omp plugin refresh skipped; re-run sync after installing"
365
- )
366
- return 0
367
- }
368
- const piIntercomPin = field("pi-intercom", "verified")
369
-
370
- progress("Checking installed omp plugins...")
371
- const installed = await installedPluginIdsFromCli()
372
- clearProgress()
373
- if (installed === undefined) {
374
- warn("omp plugin inventory unavailable — falling back to the full refresh path")
375
- }
376
-
377
- let pluginsInstalled = installed === undefined
378
- ? 0
379
- : MARKETPLACE_PLUGIN_IDS.filter((pluginId) => installed.marketplace.has(pluginId)).length +
380
- (piIntercomPin !== "" && installed.npm.has("pi-intercom") ? 1 : 0)
381
- let operationsSucceeded = 0
382
-
383
- for (const pluginId of MARKETPLACE_PLUGIN_IDS) {
384
- const present = installed?.marketplace.has(pluginId) === true
385
- if (present && ctx.skipPluginRefresh === true) {
386
- verbose(`omp plugin ${pluginId} already installed; refresh-only update skipped`)
387
- continue
388
- }
389
-
390
- const args = present
391
- ? ["plugin", "upgrade", "--scope", "user", pluginId]
392
- : ["plugin", "install", "--scope", "user", pluginId]
393
- if (await runPluginCommand(ctx, pluginId, args)) {
394
- operationsSucceeded++
395
- if (!present) pluginsInstalled++
396
- }
397
- }
398
-
399
- if (piIntercomPin === "") {
400
- warn("pi-intercom install skipped because SoT/toolchain.json has no verified pi-intercom pin")
401
- } else {
402
- const installedVersion = installed?.npm.get("pi-intercom")
403
- const present = installedVersion !== undefined
404
- if (installedVersion === piIntercomPin) {
405
- verbose(`omp npm plugin pi-intercom already installed at ${piIntercomPin}`)
406
- } else if (present && ctx.skipPluginRefresh === true) {
407
- verbose("omp npm plugin pi-intercom already installed; refresh-only update skipped")
408
- } else {
409
- const args = present
410
- ? ["install", "--force", `pi-intercom@${piIntercomPin}`]
411
- : ["install", `pi-intercom@${piIntercomPin}`]
412
- if (await runPluginCommand(ctx, "pi-intercom", args)) {
413
- operationsSucceeded++
414
- if (!present) pluginsInstalled++
415
- }
416
- }
417
- }
70
+ "omp intercom configuration synced",
71
+ );
418
72
 
419
- if (operationsSucceeded > 0) {
420
- change(`omp plugins synced (plugins: ~${operationsSucceeded})`)
421
- ctx.nextStepTriggers.ompRestart = true
422
- } else {
423
- verbose("omp plugins already in sync")
424
- }
425
- return pluginsInstalled
73
+ const legacyRegistryFile =
74
+ paths.dataRoot === paths.configRoot ? undefined : p(paths.configRoot, "marketplaces.json");
75
+ await syncMarketplace(ctx, p(paths.dataRoot, "marketplaces.json"), legacyRegistryFile);
76
+ const pluginsInstalled = await syncPlugins(ctx);
77
+ return { pluginsInstalled };
426
78
  }
427
79
 
428
80
  // -------------------------------------------------------------- summary ----
429
81
 
430
82
  export function ompSummary(ctx: Ctx, state: OmpState): void {
431
- const { echo } = ctx.services.logger
432
- const agentDir = ompPaths({ home: ctx.home, env: process.env, platform: ctx.services.platform.raw() }).agentDir
433
- echo(`omp: ${agentDir}`)
434
- if (!ctx.dryRun) echo(`omp plugins: ${state.pluginsInstalled} installed`)
83
+ const { echo } = ctx.services.logger;
84
+ const agentDir = ompPaths({
85
+ home: ctx.home,
86
+ env: process.env,
87
+ platform: ctx.services.platform.raw(),
88
+ }).agentDir;
89
+ echo(`omp: ${agentDir}`);
90
+ if (!ctx.dryRun) echo(`omp plugins: ${state.pluginsInstalled} installed`);
435
91
  }
436
92
 
437
93
  export function ompNextSteps(ctx: Ctx): Array<string> {
438
94
  return ctx.verbose || ctx.nextStepTriggers.ompRestart
439
95
  ? ["Restart omp to load any refreshed plugins, skills, or tools."]
440
- : []
96
+ : [];
441
97
  }
@@ -8,44 +8,44 @@
8
8
  * the kit-managed role chains. models.yml drops nothing; a user file may carry
9
9
  * provider credentials and custom models the kit never declares.
10
10
  */
11
- import { isMap, isScalar, parseDocument, type YAMLMap } from "yaml"
11
+ import { isMap, isScalar, parseDocument, type YAMLMap } from "yaml";
12
12
 
13
13
  /** Decides whether a deployed-only key at `path` is dropped instead of kept. */
14
- type DropDeployedKey = (path: ReadonlyArray<string>, key: string) => boolean
14
+ type DropDeployedKey = (path: ReadonlyArray<string>, key: string) => boolean;
15
15
 
16
- const fallbackChainsPath = ["retry", "fallbackChains"] as const
16
+ const fallbackChainsPath = ["retry", "fallbackChains"] as const;
17
17
 
18
18
  function stringKey(key: unknown): string | undefined {
19
- if (typeof key === "string") return key
20
- if (isScalar(key) && typeof key.value === "string") return key.value
21
- return undefined
19
+ if (typeof key === "string") return key;
20
+ if (isScalar(key) && typeof key.value === "string") return key.value;
21
+ return undefined;
22
22
  }
23
23
 
24
24
  function keyName(key: unknown): string {
25
- return isScalar(key) ? String(key.value) : String(key)
25
+ return isScalar(key) ? String(key.value) : String(key);
26
26
  }
27
27
 
28
28
  const dropFallbackWildcard: DropDeployedKey = (path, key) =>
29
29
  path.length === fallbackChainsPath.length &&
30
30
  path[0] === fallbackChainsPath[0] &&
31
31
  path[1] === fallbackChainsPath[1] &&
32
- key.includes("/")
32
+ key.includes("/");
33
33
 
34
- const keepEveryKey: DropDeployedKey = () => false
34
+ const keepEveryKey: DropDeployedKey = () => false;
35
35
 
36
36
  function pruneMapping(mapping: YAMLMap, path: ReadonlyArray<string>, drop: DropDeployedKey): void {
37
37
  for (let index = mapping.items.length - 1; index >= 0; index--) {
38
- const pair = mapping.items[index]
39
- if (pair === undefined) continue
38
+ const pair = mapping.items[index];
39
+ if (pair === undefined) continue;
40
40
 
41
41
  if (drop(path, keyName(pair.key))) {
42
- mapping.items.splice(index, 1)
43
- continue
42
+ mapping.items.splice(index, 1);
43
+ continue;
44
44
  }
45
45
 
46
- const key = stringKey(pair.key)
46
+ const key = stringKey(pair.key);
47
47
  if (key !== undefined && isMap(pair.value)) {
48
- pruneMapping(pair.value, [...path, key], drop)
48
+ pruneMapping(pair.value, [...path, key], drop);
49
49
  }
50
50
  }
51
51
  }
@@ -54,29 +54,31 @@ function mergeMappings(
54
54
  sotMapping: YAMLMap,
55
55
  deployedMapping: YAMLMap,
56
56
  path: ReadonlyArray<string>,
57
- drop: DropDeployedKey
57
+ drop: DropDeployedKey,
58
58
  ): void {
59
59
  for (const sotPair of sotMapping.items) {
60
- const key = stringKey(sotPair.key)
61
- if (key === undefined) continue
60
+ const key = stringKey(sotPair.key);
61
+ if (key === undefined) continue;
62
62
 
63
- const deployedPair = deployedMapping.items.find((candidate) => stringKey(candidate.key) === key)
63
+ const deployedPair = deployedMapping.items.find(
64
+ (candidate) => stringKey(candidate.key) === key,
65
+ );
64
66
  if (deployedPair !== undefined && isMap(sotPair.value) && isMap(deployedPair.value)) {
65
- mergeMappings(sotPair.value, deployedPair.value, [...path, key], drop)
67
+ mergeMappings(sotPair.value, deployedPair.value, [...path, key], drop);
66
68
  }
67
69
  }
68
70
 
69
71
  for (const deployedPair of deployedMapping.items) {
70
- const key = stringKey(deployedPair.key)
72
+ const key = stringKey(deployedPair.key);
71
73
  const isManaged =
72
- key !== undefined && sotMapping.items.some((candidate) => stringKey(candidate.key) === key)
73
- if (isManaged) continue
74
+ key !== undefined && sotMapping.items.some((candidate) => stringKey(candidate.key) === key);
75
+ if (isManaged) continue;
74
76
 
75
- if (drop(path, keyName(deployedPair.key))) continue
77
+ if (drop(path, keyName(deployedPair.key))) continue;
76
78
  if (key !== undefined && isMap(deployedPair.value)) {
77
- pruneMapping(deployedPair.value, [...path, key], drop)
79
+ pruneMapping(deployedPair.value, [...path, key], drop);
78
80
  }
79
- sotMapping.items.push(deployedPair)
81
+ sotMapping.items.push(deployedPair);
80
82
  }
81
83
  }
82
84
 
@@ -84,45 +86,45 @@ function mergeYamlDocuments(
84
86
  sotText: string,
85
87
  deployedText: string,
86
88
  name: string,
87
- drop: DropDeployedKey
89
+ drop: DropDeployedKey,
88
90
  ): string {
89
- const deployedDoc = parseDocument(deployedText)
90
- const deployedError = deployedDoc.errors[0]
91
+ const deployedDoc = parseDocument(deployedText);
92
+ const deployedError = deployedDoc.errors[0];
91
93
  if (deployedError !== undefined) {
92
- throw new Error(`Invalid deployed omp ${name} YAML: ${deployedError.message}`)
94
+ throw new Error(`Invalid deployed omp ${name} YAML: ${deployedError.message}`);
93
95
  }
94
96
 
95
- const deployedContents = deployedDoc.contents
97
+ const deployedContents = deployedDoc.contents;
96
98
  if (
97
99
  deployedContents === null ||
98
100
  deployedContents === undefined ||
99
101
  (isScalar(deployedContents) && deployedContents.value === null)
100
102
  ) {
101
- return sotText
103
+ return sotText;
102
104
  }
103
105
  if (!isMap(deployedContents)) {
104
- throw new Error(`Deployed omp ${name} YAML root must be a mapping`)
106
+ throw new Error(`Deployed omp ${name} YAML root must be a mapping`);
105
107
  }
106
108
 
107
- const sotDoc = parseDocument(sotText)
108
- const sotError = sotDoc.errors[0]
109
+ const sotDoc = parseDocument(sotText);
110
+ const sotError = sotDoc.errors[0];
109
111
  if (sotError !== undefined) {
110
- throw new Error(`Invalid SoT omp ${name} YAML: ${sotError.message}`)
112
+ throw new Error(`Invalid SoT omp ${name} YAML: ${sotError.message}`);
111
113
  }
112
114
  if (!isMap(sotDoc.contents)) {
113
- throw new Error(`SoT omp ${name} YAML root must be a mapping`)
115
+ throw new Error(`SoT omp ${name} YAML root must be a mapping`);
114
116
  }
115
117
 
116
- mergeMappings(sotDoc.contents, deployedContents, [], drop)
117
- return sotDoc.toString()
118
+ mergeMappings(sotDoc.contents, deployedContents, [], drop);
119
+ return sotDoc.toString();
118
120
  }
119
121
 
120
122
  /** config.yml merge: keeps user-only keys, drops stale fallback-chain wildcards. */
121
123
  export function mergeOmpConfig(sotText: string, deployedText: string): string {
122
- return mergeYamlDocuments(sotText, deployedText, "config.yml", dropFallbackWildcard)
124
+ return mergeYamlDocuments(sotText, deployedText, "config.yml", dropFallbackWildcard);
123
125
  }
124
126
 
125
127
  /** models.yml merge: keeps every user-only key, including credentials and custom models. */
126
128
  export function mergeOmpModels(sotText: string, deployedText: string): string {
127
- return mergeYamlDocuments(sotText, deployedText, "models.yml", keepEveryKey)
129
+ return mergeYamlDocuments(sotText, deployedText, "models.yml", keepEveryKey);
128
130
  }
@@ -1,5 +1,5 @@
1
- import { posixHost } from "./posix"
2
- import type { HostOs } from "./types"
1
+ import { posixHost } from "./posix";
2
+ import type { HostOs } from "./types";
3
3
 
4
4
  export const darwin: HostOs = posixHost({
5
5
  id: "darwin",
@@ -9,6 +9,6 @@ export const darwin: HostOs = posixHost({
9
9
  git: "brew install git",
10
10
  jq: "brew install jq",
11
11
  curl: "brew install curl",
12
- ffplay: "brew install ffmpeg"
13
- }
14
- })
12
+ ffplay: "brew install ffmpeg",
13
+ },
14
+ });