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.
- package/AGENTS.md +36 -32
- package/cli/docs/omp-models.md +88 -53
- 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 +132 -112
- 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 +67 -64
- 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
|
@@ -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 {
|
|
10
|
-
import { basename, isAbsolute, resolve } from "node:path"
|
|
13
|
+
import { isAbsolute, resolve } from "node:path";
|
|
11
14
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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({
|
|
433
|
-
|
|
434
|
-
|
|
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(
|
|
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
|
+
});
|