@klhapp/skillmux 1.11.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +20 -10
- package/bin/skillmux.js +89 -0
- package/docs/README.md +4 -4
- package/docs/cli.md +15 -5
- package/docs/concepts.md +4 -3
- package/docs/configuration.md +22 -5
- package/docs/deployment.md +3 -3
- package/docs/getting-started.md +32 -7
- package/docs/releasing.md +17 -2
- package/docs/sdd/cli-remote-target-parity/schema-bucket-b.yaml +297 -0
- package/docs/sdd/cli-remote-target-parity/spec-bucket-b.md +95 -0
- package/docs/sdd/cli-remote-target-parity/spec.md +111 -0
- package/docs/sdd/cli-remote-target-parity/think.md +183 -0
- package/docs/sdd/cli-surface-consistency/think.md +149 -0
- package/docs/sdd/config-authority-rerank-resilience/plan.md +338 -0
- package/docs/sdd/config-authority-rerank-resilience/review.md +124 -0
- package/docs/sdd/config-authority-rerank-resilience/spec.md +78 -0
- package/docs/sdd/feat-packaging-deployment/review.md +95 -0
- package/docs/sdd/fetch-outcome-flywheel/review.md +103 -0
- package/docs/sdd/fetch-outcome-flywheel/schema.json +347 -0
- package/docs/sdd/fetch-outcome-flywheel/spec.md +185 -0
- package/docs/sdd/npm-native-binaries/review.md +103 -0
- package/docs/sdd/npm-native-binaries/spec.md +148 -0
- package/docs/sdd/ranked-shortlist-2/pr3-calibration-removal.md +56 -0
- package/docs/sdd/refactor-builtin-target-paths/spec.md +41 -0
- package/docs/sdd/runtime-resource-hardening/review.md +86 -0
- package/docs/sdd/runtime-resource-hardening/spec.md +43 -0
- package/docs/sdd/security-hardening-phase/think.md +129 -0
- package/docs/sdd/security-observability/review.md +96 -0
- package/docs/sdd/security-observability/spec.md +55 -0
- package/docs/sdd/skill-provenance-update/review.md +152 -0
- package/docs/sdd/skill-provenance-update/schema.json +193 -0
- package/docs/sdd/skill-provenance-update/spec.md +134 -0
- package/docs/sdd/supply-chain-hardening/review.md +50 -0
- package/docs/sdd/supply-chain-hardening/spec.md +42 -0
- package/docs/sdd/target-marker-rehome/spec.md +33 -0
- package/docs/skill-management.md +1 -1
- package/docs/troubleshooting.md +18 -2
- package/package.json +15 -8
- package/src/adapters.ts +0 -438
- package/src/audit.ts +0 -21
- package/src/cli.ts +0 -928
- package/src/clients.ts +0 -368
- package/src/commands/audit.ts +0 -85
- package/src/commands/config.ts +0 -224
- package/src/commands/context.ts +0 -104
- package/src/commands/core.ts +0 -56
- package/src/commands/doctor.ts +0 -97
- package/src/commands/eval.ts +0 -88
- package/src/commands/init.ts +0 -672
- package/src/commands/install.ts +0 -155
- package/src/commands/local-vault.ts +0 -60
- package/src/commands/models.ts +0 -10
- package/src/commands/outdated.ts +0 -115
- package/src/commands/project.ts +0 -580
- package/src/commands/report.ts +0 -66
- package/src/commands/scan.ts +0 -71
- package/src/commands/shared.ts +0 -38
- package/src/commands/skill.ts +0 -33
- package/src/commands/sync.ts +0 -233
- package/src/commands/target.ts +0 -225
- package/src/commands/update.ts +0 -277
- package/src/completions.ts +0 -196
- package/src/concurrency-limiter.ts +0 -61
- package/src/config-service.ts +0 -398
- package/src/config-watcher.ts +0 -253
- package/src/config.ts +0 -583
- package/src/context.ts +0 -188
- package/src/db-audit.ts +0 -286
- package/src/db-index.ts +0 -238
- package/src/db.ts +0 -3
- package/src/deployment.ts +0 -39
- package/src/doctor.ts +0 -205
- package/src/eval.ts +0 -302
- package/src/global-flags.ts +0 -46
- package/src/init-agents.ts +0 -329
- package/src/init-instructions.ts +0 -192
- package/src/init.ts +0 -389
- package/src/install.ts +0 -232
- package/src/lifecycle.ts +0 -51
- package/src/logger.ts +0 -26
- package/src/manifest.ts +0 -352
- package/src/mcp-registration.ts +0 -89
- package/src/metrics.ts +0 -121
- package/src/models.ts +0 -20
- package/src/output.ts +0 -214
- package/src/project-setup.ts +0 -36
- package/src/prompts.ts +0 -124
- package/src/provenance.ts +0 -99
- package/src/rate-limiter.ts +0 -137
- package/src/readiness.ts +0 -30
- package/src/redact.ts +0 -52
- package/src/router-core.ts +0 -666
- package/src/rrf.ts +0 -31
- package/src/scan.ts +0 -300
- package/src/server.ts +0 -930
- package/src/setup.ts +0 -145
- package/src/snapshot.ts +0 -135
- package/src/stats.ts +0 -340
- package/src/sync.ts +0 -482
- package/src/toml-writer.ts +0 -51
- package/src/types.ts +0 -212
- package/src/vault.ts +0 -207
package/src/cli.ts
DELETED
|
@@ -1,928 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
3
|
-
import { lstatSync, mkdirSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
import { createClients } from "./clients";
|
|
6
|
-
import { loadConfig } from "./config";
|
|
7
|
-
import { openAudit } from "./db";
|
|
8
|
-
import { getEffectiveConfig } from "./config-service";
|
|
9
|
-
import { buildRedactor } from "./redact";
|
|
10
|
-
import { evalVault } from "./eval";
|
|
11
|
-
import { runOutdated } from "./commands/outdated";
|
|
12
|
-
import { runUpdate } from "./commands/update";
|
|
13
|
-
import { serializeManifest } from "./manifest";
|
|
14
|
-
|
|
15
|
-
import { backfillEmbeddings, configure, rebuildIndex } from "./router-core";
|
|
16
|
-
import { type StatsResponse } from "./stats";
|
|
17
|
-
import { scanVault } from "./vault";
|
|
18
|
-
|
|
19
|
-
import { resolveContext, type ResolvedContext } from "./context";
|
|
20
|
-
import { createContextAdapter, isLoopbackHost, type ContextAdapter } from "./adapters";
|
|
21
|
-
import {
|
|
22
|
-
emitSuccess,
|
|
23
|
-
CliError,
|
|
24
|
-
formatJsonEnvelope,
|
|
25
|
-
mapExitCode,
|
|
26
|
-
red,
|
|
27
|
-
suggestCorrection,
|
|
28
|
-
warn,
|
|
29
|
-
} from "./output";
|
|
30
|
-
import { generateCompletions, type ShellType } from "./completions";
|
|
31
|
-
import { SUPPORTED_AGENT_IDS } from "./init-agents";
|
|
32
|
-
import { runAudit } from "./commands/audit";
|
|
33
|
-
import { handleConfigCommand } from "./commands/config";
|
|
34
|
-
import { handleContextCommand } from "./commands/context";
|
|
35
|
-
import { runDoctor } from "./commands/doctor";
|
|
36
|
-
import { runEvalPromote } from "./commands/eval";
|
|
37
|
-
import { runInstall } from "./commands/install";
|
|
38
|
-
import { runCore } from "./commands/core";
|
|
39
|
-
import { runLocalVaultInit } from "./commands/local-vault";
|
|
40
|
-
import { runModelDownload } from "./commands/models";
|
|
41
|
-
import { runProject } from "./commands/project";
|
|
42
|
-
import { runReport } from "./commands/report";
|
|
43
|
-
import { runScan } from "./commands/scan";
|
|
44
|
-
import { runSkill } from "./commands/skill";
|
|
45
|
-
import { runTarget } from "./commands/target";
|
|
46
|
-
import { runSync } from "./commands/sync";
|
|
47
|
-
import { runInit } from "./commands/init";
|
|
48
|
-
|
|
49
|
-
export const KNOWN_COMMANDS = [
|
|
50
|
-
"context",
|
|
51
|
-
"config",
|
|
52
|
-
"completions",
|
|
53
|
-
"serve",
|
|
54
|
-
"index",
|
|
55
|
-
"sync",
|
|
56
|
-
"init",
|
|
57
|
-
"project",
|
|
58
|
-
"target",
|
|
59
|
-
"core",
|
|
60
|
-
"report",
|
|
61
|
-
"audit",
|
|
62
|
-
"scan",
|
|
63
|
-
"install",
|
|
64
|
-
"outdated",
|
|
65
|
-
"update",
|
|
66
|
-
"eval",
|
|
67
|
-
"doctor",
|
|
68
|
-
"models",
|
|
69
|
-
"skill",
|
|
70
|
-
"local-vault",
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Declared context support for every command in KNOWN_COMMANDS — the single
|
|
75
|
-
* source of truth getLocalOnlyCommand() enforces against. A command missing
|
|
76
|
-
* from here, or misclassified, is a bug: see tests/cli-context-support.test.ts,
|
|
77
|
-
* which fails the build rather than letting a command silently drift out of
|
|
78
|
-
* sync the way `config init` did (it was never rejected for a remote context,
|
|
79
|
-
* nor actually remote-capable — it just silently ran local logic that choked
|
|
80
|
-
* on an unrecognized --context/--server flag with a confusing error).
|
|
81
|
-
*
|
|
82
|
-
* - "local-only": operates on this machine's vault/filesystem/agents only;
|
|
83
|
-
* a remote context is rejected outright.
|
|
84
|
-
* - "remote-capable": routed through ContextAdapter — same command, backed by
|
|
85
|
-
* LocalAdapter or RemoteAdapter depending on the resolved context.
|
|
86
|
-
* - "context-agnostic": the resolved context isn't used to decide behavior at
|
|
87
|
-
* all (context management is inherently local; completions never touch
|
|
88
|
-
* vault/server state).
|
|
89
|
-
*
|
|
90
|
-
* Subcommand-level exceptions within an otherwise-classified command (e.g.
|
|
91
|
-
* `config init`, which bootstraps *this machine's* config file and so is
|
|
92
|
-
* local-only despite `config` overall being remote-capable) are handled in
|
|
93
|
-
* getLocalOnlyCommand() itself, not in this top-level map.
|
|
94
|
-
*/
|
|
95
|
-
export type CommandContextSupport = "local-only" | "remote-capable" | "context-agnostic";
|
|
96
|
-
|
|
97
|
-
export const COMMAND_CONTEXT_SUPPORT: Record<string, CommandContextSupport> = {
|
|
98
|
-
context: "context-agnostic",
|
|
99
|
-
config: "remote-capable",
|
|
100
|
-
completions: "context-agnostic",
|
|
101
|
-
serve: "local-only",
|
|
102
|
-
index: "local-only",
|
|
103
|
-
sync: "local-only",
|
|
104
|
-
init: "local-only",
|
|
105
|
-
project: "local-only",
|
|
106
|
-
target: "local-only",
|
|
107
|
-
core: "local-only",
|
|
108
|
-
report: "remote-capable",
|
|
109
|
-
audit: "remote-capable",
|
|
110
|
-
scan: "local-only",
|
|
111
|
-
install: "local-only",
|
|
112
|
-
outdated: "local-only",
|
|
113
|
-
update: "local-only",
|
|
114
|
-
eval: "remote-capable",
|
|
115
|
-
doctor: "remote-capable",
|
|
116
|
-
models: "local-only",
|
|
117
|
-
skill: "local-only",
|
|
118
|
-
"local-vault": "local-only",
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const LOCAL_ONLY_COMMANDS = new Set(
|
|
122
|
-
Object.entries(COMMAND_CONTEXT_SUPPORT)
|
|
123
|
-
.filter(([, support]) => support === "local-only")
|
|
124
|
-
.map(([command]) => command),
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
export function getLocalOnlyCommand(command: string, subCommand: string): string | null {
|
|
128
|
-
if (command === "skill" && (subCommand === "which" || !subCommand)) {
|
|
129
|
-
return "skill which";
|
|
130
|
-
}
|
|
131
|
-
if (command === "config" && subCommand === "init") {
|
|
132
|
-
return "config init";
|
|
133
|
-
}
|
|
134
|
-
if (LOCAL_ONLY_COMMANDS.has(command)) {
|
|
135
|
-
return command;
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Why a local-only command can't take a remote context, keyed by the exact
|
|
142
|
-
* string getLocalOnlyCommand() returns. Drives the guidance sentence
|
|
143
|
-
* remoteContextUnsupported() appends, so the rejection points somewhere
|
|
144
|
-
* useful instead of just saying no.
|
|
145
|
-
*/
|
|
146
|
-
type LocalOnlyReason = "vault-content" | "native-delivery" | "local-runtime" | "local-config";
|
|
147
|
-
|
|
148
|
-
const LOCAL_ONLY_REASON: Record<string, LocalOnlyReason> = {
|
|
149
|
-
install: "vault-content",
|
|
150
|
-
update: "vault-content",
|
|
151
|
-
outdated: "vault-content",
|
|
152
|
-
scan: "vault-content",
|
|
153
|
-
init: "native-delivery",
|
|
154
|
-
sync: "native-delivery",
|
|
155
|
-
target: "native-delivery",
|
|
156
|
-
core: "native-delivery",
|
|
157
|
-
project: "native-delivery",
|
|
158
|
-
"local-vault": "native-delivery",
|
|
159
|
-
"skill which": "native-delivery",
|
|
160
|
-
serve: "local-runtime",
|
|
161
|
-
models: "local-runtime",
|
|
162
|
-
index: "local-runtime",
|
|
163
|
-
"config init": "local-config",
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const LOCAL_ONLY_GUIDANCE: Record<LocalOnlyReason, string> = {
|
|
167
|
-
"vault-content":
|
|
168
|
-
"To change a remote deployment's vault contents, update its git-backed source and redeploy or pull on that host — skillmux doesn't replicate vault checkouts over the network.",
|
|
169
|
-
"native-delivery":
|
|
170
|
-
"This manages skill delivery into agent directories on the machine you run it from; there's no remote equivalent — run it on the machine that owns those directories.",
|
|
171
|
-
"local-runtime":
|
|
172
|
-
"This operates on the local runtime process on the machine you run it from.",
|
|
173
|
-
"local-config":
|
|
174
|
-
"This bootstraps this machine's own config file. To inspect or change a remote deployment's configuration, use \"skillmux config show/set --context <name>\" instead.",
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
function remoteContextUnsupported(rejectedCommand: string): CliError {
|
|
178
|
-
const reason = LOCAL_ONLY_REASON[rejectedCommand];
|
|
179
|
-
const guidance = reason ? ` ${LOCAL_ONLY_GUIDANCE[reason]}` : "";
|
|
180
|
-
return new CliError(
|
|
181
|
-
`\`${rejectedCommand}\` operates on the local vault only; --context/--server isn't supported here.${guidance}`,
|
|
182
|
-
2,
|
|
183
|
-
"REMOTE_CONTEXT_UNSUPPORTED",
|
|
184
|
-
{
|
|
185
|
-
rejected_command: rejectedCommand,
|
|
186
|
-
...(reason ? { reason } : {}),
|
|
187
|
-
},
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function isDockerHostManagementCommand(command: string, subCommand: string): boolean {
|
|
192
|
-
if (
|
|
193
|
-
[
|
|
194
|
-
"init",
|
|
195
|
-
"sync",
|
|
196
|
-
"install",
|
|
197
|
-
"outdated",
|
|
198
|
-
"update",
|
|
199
|
-
"project",
|
|
200
|
-
"target",
|
|
201
|
-
"core",
|
|
202
|
-
"local-vault",
|
|
203
|
-
"models",
|
|
204
|
-
"context",
|
|
205
|
-
].includes(command)
|
|
206
|
-
) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// eval promote only touches the mounted state_dir, unlike bare `eval`
|
|
211
|
-
// (vault ranking evaluation), which needs an embeddings client and the vault.
|
|
212
|
-
if (command === "eval" && subCommand !== "promote") return true;
|
|
213
|
-
|
|
214
|
-
return command === "config" && ["init", "set"].includes(subCommand);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function containerCommandUnsupported(command: string, subCommand: string): CliError {
|
|
218
|
-
const rejectedCommand = [command, subCommand].filter(Boolean).join(" ");
|
|
219
|
-
const recommendedHostCommand = `skillmux ${rejectedCommand}`;
|
|
220
|
-
const guide = "docs/deployment.md";
|
|
221
|
-
const documentation =
|
|
222
|
-
"https://github.com/klhq/skillmux/blob/main/docs/deployment.md#container-command-contract";
|
|
223
|
-
return new CliError(
|
|
224
|
-
`\`skillmux ${rejectedCommand}\` manages host agent directories and cannot run in the Skillmux server image.\n\n` +
|
|
225
|
-
"Install the host CLI:\n" +
|
|
226
|
-
" bun add -g @klhapp/skillmux\n\n" +
|
|
227
|
-
"Then run:\n" +
|
|
228
|
-
` ${recommendedHostCommand}\n\n` +
|
|
229
|
-
`See ${guide} for server deployment examples.`,
|
|
230
|
-
2,
|
|
231
|
-
"CONTAINER_COMMAND_UNSUPPORTED",
|
|
232
|
-
{
|
|
233
|
-
// `command` remains for automation written against the first Docker
|
|
234
|
-
// boundary release. `rejected_command` is the explicit contract name.
|
|
235
|
-
command: rejectedCommand,
|
|
236
|
-
rejected_command: rejectedCommand,
|
|
237
|
-
recommended_host_command: recommendedHostCommand,
|
|
238
|
-
guide,
|
|
239
|
-
documentation,
|
|
240
|
-
},
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async function main() {
|
|
245
|
-
const rawArgv = Bun.argv.slice(2);
|
|
246
|
-
|
|
247
|
-
let isJson = process.env.SKILLMUX_JSON === "true";
|
|
248
|
-
let allowInsecure = false;
|
|
249
|
-
let isVerbose = false;
|
|
250
|
-
let flagContext: string | undefined;
|
|
251
|
-
let flagServer: string | undefined;
|
|
252
|
-
let isDryRun = false;
|
|
253
|
-
// Global flags do not form part of the command identity reported to users.
|
|
254
|
-
// In particular, `init --json` should recommend `skillmux init`, not a
|
|
255
|
-
// redundant JSON-only host command.
|
|
256
|
-
const subCommand = rawArgv[1]?.startsWith("-") ? "" : rawArgv[1] ?? "";
|
|
257
|
-
const commandArgs = rawArgv.slice(2);
|
|
258
|
-
|
|
259
|
-
const command = rawArgv[0];
|
|
260
|
-
if (command === "--version" || command === "-V") {
|
|
261
|
-
console.log(packageJson.version);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!command || command === "--help" || command === "-h") {
|
|
266
|
-
printHelp();
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Parse global flags for context/config
|
|
271
|
-
for (let i = 0; i < rawArgv.length; i++) {
|
|
272
|
-
const arg = rawArgv[i];
|
|
273
|
-
if (arg === "--json") isJson = true;
|
|
274
|
-
else if (arg === "--allow-insecure") allowInsecure = true;
|
|
275
|
-
else if (arg === "--verbose") isVerbose = true;
|
|
276
|
-
else if (arg === "--dry-run") isDryRun = true;
|
|
277
|
-
else if (arg === "--context") flagContext = rawArgv[++i];
|
|
278
|
-
else if (arg === "--server") flagServer = rawArgv[++i];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
let resolvedContext: ResolvedContext = { type: "local", name: "local" };
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
process.env.RUNNING_IN_DOCKER === "true" &&
|
|
285
|
-
isDockerHostManagementCommand(command, subCommand)
|
|
286
|
-
) {
|
|
287
|
-
await handleError(containerCommandUnsupported(command, subCommand), {
|
|
288
|
-
context: resolvedContext,
|
|
289
|
-
isJson,
|
|
290
|
-
isVerbose,
|
|
291
|
-
});
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
(rawArgv.includes("--help") || rawArgv.includes("-h")) &&
|
|
297
|
-
printCommandHelp(command)
|
|
298
|
-
) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
resolvedContext = await resolveContext({
|
|
304
|
-
context: flagContext,
|
|
305
|
-
server: flagServer,
|
|
306
|
-
});
|
|
307
|
-
} catch (err: any) {
|
|
308
|
-
await handleError(err, { context: resolvedContext, isJson, isVerbose });
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const localOnlyCommand = getLocalOnlyCommand(command, subCommand);
|
|
313
|
-
if (localOnlyCommand && resolvedContext.type === "remote") {
|
|
314
|
-
await handleError(remoteContextUnsupported(localOnlyCommand), {
|
|
315
|
-
context: resolvedContext,
|
|
316
|
-
isJson,
|
|
317
|
-
isVerbose,
|
|
318
|
-
});
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const adapter = createContextAdapter(resolvedContext, { allowInsecure });
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
switch (command) {
|
|
326
|
-
case "context":
|
|
327
|
-
await handleContextCommand(subCommand, commandArgs, {
|
|
328
|
-
context: resolvedContext,
|
|
329
|
-
isJson,
|
|
330
|
-
});
|
|
331
|
-
break;
|
|
332
|
-
case "config":
|
|
333
|
-
await handleConfigCommand(adapter, subCommand, commandArgs, {
|
|
334
|
-
context: resolvedContext,
|
|
335
|
-
isJson,
|
|
336
|
-
dryRun: isDryRun,
|
|
337
|
-
});
|
|
338
|
-
break;
|
|
339
|
-
case "calibrate":
|
|
340
|
-
throw new Error(
|
|
341
|
-
'skillmux calibrate was removed. Threshold calibration was removed; use "skillmux eval" for ranking evaluation.',
|
|
342
|
-
);
|
|
343
|
-
case "completions":
|
|
344
|
-
await handleCompletionsCommand(subCommand);
|
|
345
|
-
break;
|
|
346
|
-
case "serve": {
|
|
347
|
-
const { startServer } = await import("./server");
|
|
348
|
-
const { transport, port, statsPort } = parseServeArgs(rawArgv.slice(1));
|
|
349
|
-
const handle = await startServer({ transport, port, statsPort });
|
|
350
|
-
let stopping = false;
|
|
351
|
-
const shutdown = async () => {
|
|
352
|
-
if (stopping) return;
|
|
353
|
-
stopping = true;
|
|
354
|
-
const timeout = setTimeout(() => process.exit(1), 10_000);
|
|
355
|
-
timeout.unref();
|
|
356
|
-
await handle.stop();
|
|
357
|
-
clearTimeout(timeout);
|
|
358
|
-
process.exit(0);
|
|
359
|
-
};
|
|
360
|
-
process.once("SIGTERM", shutdown);
|
|
361
|
-
process.once("SIGINT", shutdown);
|
|
362
|
-
if (transport === "stdio") {
|
|
363
|
-
process.stdin.on("close", shutdown);
|
|
364
|
-
process.stdin.on("end", shutdown);
|
|
365
|
-
}
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
case "index":
|
|
369
|
-
await runIndex();
|
|
370
|
-
break;
|
|
371
|
-
case "sync":
|
|
372
|
-
await runSync(rawArgv.slice(1));
|
|
373
|
-
break;
|
|
374
|
-
case "init":
|
|
375
|
-
await runInit(rawArgv.slice(1), { isJson, dryRun: isDryRun });
|
|
376
|
-
break;
|
|
377
|
-
case "project":
|
|
378
|
-
await runProject(subCommand, commandArgs, {
|
|
379
|
-
isJson,
|
|
380
|
-
dryRun: isDryRun,
|
|
381
|
-
sync: runSync,
|
|
382
|
-
});
|
|
383
|
-
break;
|
|
384
|
-
case "target":
|
|
385
|
-
await runTarget(subCommand, commandArgs, { isJson, dryRun: isDryRun });
|
|
386
|
-
break;
|
|
387
|
-
case "core":
|
|
388
|
-
await runCore(subCommand, commandArgs, { isJson, dryRun: isDryRun });
|
|
389
|
-
break;
|
|
390
|
-
case "report":
|
|
391
|
-
await runReport(rawArgv.slice(1), {
|
|
392
|
-
isJson,
|
|
393
|
-
context: resolvedContext,
|
|
394
|
-
allowInsecure,
|
|
395
|
-
adapter,
|
|
396
|
-
});
|
|
397
|
-
break;
|
|
398
|
-
case "audit":
|
|
399
|
-
await runAudit(subCommand, commandArgs, {
|
|
400
|
-
isJson,
|
|
401
|
-
dryRun: isDryRun,
|
|
402
|
-
context: resolvedContext,
|
|
403
|
-
adapter,
|
|
404
|
-
});
|
|
405
|
-
break;
|
|
406
|
-
case "scan":
|
|
407
|
-
await runScan(rawArgv.slice(1), { isJson });
|
|
408
|
-
break;
|
|
409
|
-
case "install":
|
|
410
|
-
await runInstall(rawArgv.slice(1), { isJson });
|
|
411
|
-
break;
|
|
412
|
-
case "outdated":
|
|
413
|
-
await runOutdated(rawArgv.slice(1), { isJson });
|
|
414
|
-
break;
|
|
415
|
-
case "update":
|
|
416
|
-
await runUpdate(rawArgv.slice(1), { isJson });
|
|
417
|
-
break;
|
|
418
|
-
case "eval":
|
|
419
|
-
if (subCommand === "promote") {
|
|
420
|
-
await runEvalPromote(commandArgs, { isJson, dryRun: isDryRun, adapter });
|
|
421
|
-
} else if (subCommand === "") {
|
|
422
|
-
await runEval({ isJson, adapter });
|
|
423
|
-
} else {
|
|
424
|
-
throw new Error(`usage: skillmux eval [promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]]`);
|
|
425
|
-
}
|
|
426
|
-
break;
|
|
427
|
-
case "doctor":
|
|
428
|
-
await runDoctor({
|
|
429
|
-
isJson,
|
|
430
|
-
context: resolvedContext,
|
|
431
|
-
adapter,
|
|
432
|
-
args: rawArgv.slice(1),
|
|
433
|
-
});
|
|
434
|
-
break;
|
|
435
|
-
case "which":
|
|
436
|
-
throw new Error(
|
|
437
|
-
`skillmux which is removed - use "skillmux skill which ${subCommand || "<skill_id>"}" instead`,
|
|
438
|
-
);
|
|
439
|
-
case "skill":
|
|
440
|
-
await runSkill(subCommand, commandArgs);
|
|
441
|
-
break;
|
|
442
|
-
case "manifest":
|
|
443
|
-
throw new Error(
|
|
444
|
-
`skillmux manifest is removed - use "skillmux core ${subCommand || "pin|unpin"}" for [core] skills, or "skillmux project ${subCommand || "pin|unpin"} <group>" for [project.*] skills`,
|
|
445
|
-
);
|
|
446
|
-
case "local-vault":
|
|
447
|
-
if (subCommand !== "init")
|
|
448
|
-
throw new Error("usage: skillmux local-vault init <path>");
|
|
449
|
-
await runLocalVaultInit(commandArgs, { isJson, dryRun: isDryRun });
|
|
450
|
-
break;
|
|
451
|
-
case "models":
|
|
452
|
-
if (subCommand !== "download")
|
|
453
|
-
throw new Error("usage: skillmux models download");
|
|
454
|
-
await runModelDownload({ isJson });
|
|
455
|
-
break;
|
|
456
|
-
default: {
|
|
457
|
-
const suggestion = suggestCorrection(command, KNOWN_COMMANDS);
|
|
458
|
-
const msg = suggestion
|
|
459
|
-
? `Unknown command "${command}". Did you mean "${suggestion}"?`
|
|
460
|
-
: `usage: skillmux <serve|index|sync|init|project|target|core pin/unpin|report|audit prune|scan|install|outdated|update|eval|doctor|skill which|local-vault init|config show|models download>`;
|
|
461
|
-
throw new Error(msg);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
} catch (err: any) {
|
|
465
|
-
await handleError(err, { context: resolvedContext, isJson, isVerbose });
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
async function handleCompletionsCommand(shell: string) {
|
|
472
|
-
if (shell !== "bash" && shell !== "zsh" && shell !== "fish") {
|
|
473
|
-
throw new Error("usage: skillmux completions <bash|zsh|fish>");
|
|
474
|
-
}
|
|
475
|
-
console.log(generateCompletions(shell as ShellType));
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
async function handleError(
|
|
479
|
-
err: any,
|
|
480
|
-
opts: { context: ResolvedContext; isJson: boolean; isVerbose: boolean },
|
|
481
|
-
) {
|
|
482
|
-
const code = mapExitCode(err);
|
|
483
|
-
process.exitCode = code;
|
|
484
|
-
|
|
485
|
-
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
486
|
-
// Best-effort: a broken config must not suppress the original error report,
|
|
487
|
-
// so fall back to the URL-credential-only layer of buildRedactor(undefined)
|
|
488
|
-
// rather than let a config-load failure mask the real failure.
|
|
489
|
-
let redact: (text: string) => string;
|
|
490
|
-
try {
|
|
491
|
-
const { effective } = await getEffectiveConfig();
|
|
492
|
-
redact = buildRedactor(effective);
|
|
493
|
-
} catch {
|
|
494
|
-
redact = buildRedactor(undefined);
|
|
495
|
-
}
|
|
496
|
-
const msg = redact(rawMsg);
|
|
497
|
-
|
|
498
|
-
if (opts.isJson) {
|
|
499
|
-
const env = formatJsonEnvelope({
|
|
500
|
-
ok: false,
|
|
501
|
-
context: opts.context,
|
|
502
|
-
error: {
|
|
503
|
-
code: err instanceof CliError ? err.code : `EXIT_${code}`,
|
|
504
|
-
message: msg,
|
|
505
|
-
details: err instanceof CliError ? err.details : undefined,
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
console.log(JSON.stringify(env));
|
|
509
|
-
} else {
|
|
510
|
-
console.error(
|
|
511
|
-
red(
|
|
512
|
-
msg.startsWith("usage:") ||
|
|
513
|
-
msg.startsWith("Unknown") ||
|
|
514
|
-
msg.startsWith("error:")
|
|
515
|
-
? msg
|
|
516
|
-
: `error: ${msg}`,
|
|
517
|
-
),
|
|
518
|
-
);
|
|
519
|
-
if (opts.isVerbose && err instanceof Error && err.stack) {
|
|
520
|
-
console.error(redact(err.stack));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const COMMAND_HELP: Record<string, string> = {
|
|
526
|
-
context: `context: manage named CLI contexts for remote administration
|
|
527
|
-
|
|
528
|
-
usage:
|
|
529
|
-
skillmux context list
|
|
530
|
-
skillmux context current
|
|
531
|
-
skillmux context add <name> --server <url> [--token-env <env_name>]
|
|
532
|
-
skillmux context use <name>
|
|
533
|
-
skillmux context remove <name>`,
|
|
534
|
-
|
|
535
|
-
config: `config: inspect or update server/machine configuration
|
|
536
|
-
|
|
537
|
-
usage:
|
|
538
|
-
skillmux config init --vault <path> --yes
|
|
539
|
-
skillmux config show [--sources]
|
|
540
|
-
skillmux config get <key>
|
|
541
|
-
skillmux config set <key> <value> [--dry-run]
|
|
542
|
-
skillmux config validate
|
|
543
|
-
skillmux config diff
|
|
544
|
-
skillmux config status
|
|
545
|
-
|
|
546
|
-
config init bootstraps this machine's config file from a populated vault. It
|
|
547
|
-
is not a prerequisite for anything: "skillmux init --vault <path>" runs the
|
|
548
|
-
same bootstrap when no config exists, so reach for config init only when you
|
|
549
|
-
are setting up the config without the guided init.
|
|
550
|
-
|
|
551
|
-
Accepts --context <name> / --server <url> to target a remote deployment.`,
|
|
552
|
-
|
|
553
|
-
completions: `completions: generate a shell completion script
|
|
554
|
-
|
|
555
|
-
usage:
|
|
556
|
-
skillmux completions <bash|zsh|fish>`,
|
|
557
|
-
|
|
558
|
-
serve: `serve: start the MCP server
|
|
559
|
-
|
|
560
|
-
usage:
|
|
561
|
-
skillmux serve [--transport stdio|http] [--port <port>] [--stats-port <port>]
|
|
562
|
-
|
|
563
|
-
--transport defaults to stdio. --stats-port exposes GET /health and GET /stats
|
|
564
|
-
alongside a stdio transport without opening the full HTTP surface.`,
|
|
565
|
-
|
|
566
|
-
index: `index: rebuild the local retrieval index and backfill embeddings
|
|
567
|
-
|
|
568
|
-
usage:
|
|
569
|
-
skillmux index`,
|
|
570
|
-
|
|
571
|
-
sync: `sync: apply the manifest to native agent target directories
|
|
572
|
-
|
|
573
|
-
usage:
|
|
574
|
-
skillmux sync [--dry-run] [--restore-monolith] [--install-hook] [--yes] [--json]
|
|
575
|
-
|
|
576
|
-
--dry-run prints what would change without writing. --yes approves creating
|
|
577
|
-
a target directory that does not exist yet; without it, an unseen directory
|
|
578
|
-
is skipped rather than created.
|
|
579
|
-
|
|
580
|
-
--install-hook installs a git post-merge hook in the vault checkout so a
|
|
581
|
-
"git pull" re-syncs the targets automatically.
|
|
582
|
-
|
|
583
|
-
--restore-monolith undoes managed-pin delivery for a target: instead of
|
|
584
|
-
individual pinned skills, the target directory is replaced by a single
|
|
585
|
-
symlink to the whole vault. It refuses to touch a directory skillmux does
|
|
586
|
-
not own, one carrying a local_vault marker, or one whose marker points at a
|
|
587
|
-
different vault.`,
|
|
588
|
-
|
|
589
|
-
init: `init: guided setup for native skill management
|
|
590
|
-
|
|
591
|
-
usage:
|
|
592
|
-
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
593
|
-
[--migrate-full-vault] [--show-mcp-setup] [--register-mcp]
|
|
594
|
-
[--no-instructions] [--no-sync]
|
|
595
|
-
[--interactive|--yes|--dry-run] [--json]
|
|
596
|
-
|
|
597
|
-
agents: ${SUPPORTED_AGENT_IDS.join(", ")}
|
|
598
|
-
|
|
599
|
-
Native pins and MCP are independent — skip both of the flags below for
|
|
600
|
-
native-only setup, and init writes no instruction files (the managed
|
|
601
|
-
block only teaches resolve_skill/fetch_skill, which are MCP tools).
|
|
602
|
-
--show-mcp-setup prints the MCP registration snippet to copy in yourself,
|
|
603
|
-
for any agent, and also writes the instruction block for every selected
|
|
604
|
-
agent. --register-mcp instead runs that agent's own CLI to register
|
|
605
|
-
skillmux automatically, but only for claude-code and codex (the only
|
|
606
|
-
agents with a verified registration command), and writes the instruction
|
|
607
|
-
block just for those; interactively, init asks about this only when
|
|
608
|
-
you've selected one of those two. --no-instructions forces instruction
|
|
609
|
-
writes off even when an MCP flag is set. A tool not in the agents list
|
|
610
|
-
above isn't supported by init yet — add it to SUPPORTED_AGENT_IDS rather
|
|
611
|
-
than guessing a directory. To adopt an arbitrary existing directory
|
|
612
|
-
directly, use "skillmux target add <name> --dir <dir>" instead of init.`,
|
|
613
|
-
|
|
614
|
-
project: `project: manage project-scoped skill pins and sync groups
|
|
615
|
-
|
|
616
|
-
usage:
|
|
617
|
-
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
618
|
-
[--agent <name>...] [--target <name>...] [--register-mcp]
|
|
619
|
-
[--no-sync] [--interactive|--yes|--dry-run] [--json]
|
|
620
|
-
skillmux project list
|
|
621
|
-
skillmux project show <group>
|
|
622
|
-
skillmux project add-path <group> [path] --yes
|
|
623
|
-
skillmux project remove-path <group> [path] --yes
|
|
624
|
-
skillmux project pin <group> <skill_id>... --yes
|
|
625
|
-
skillmux project unpin <group> <skill_id>... --yes
|
|
626
|
-
skillmux project attach <group> (--agent <id>... | --target <name>...) --yes
|
|
627
|
-
skillmux project detach <group> (--agent <id>... | --target <name>...) --yes
|
|
628
|
-
|
|
629
|
-
--agent and --target both name sync targets, and either may be repeated.
|
|
630
|
-
--target <name> names a target directly, including a custom one created by
|
|
631
|
-
"skillmux target add". --agent <id> is shorthand for "whatever target that
|
|
632
|
-
agent maps to", resolved from the targets init already configured, so it
|
|
633
|
-
fails if that agent was never set up or maps to no target at all (goose and
|
|
634
|
-
hermes use full-vault delivery and have none). Several agents can share one
|
|
635
|
-
target, so attaching two agents that map to the same directory attaches it
|
|
636
|
-
once.
|
|
637
|
-
|
|
638
|
-
--register-mcp is the project-local counterpart to "skillmux init
|
|
639
|
-
--register-mcp": only for claude-code (the only agent whose own CLI has a
|
|
640
|
-
project MCP scope — codex's mcp add has no scope flag, so it's always
|
|
641
|
-
global). It runs "claude mcp add -s project" for this project directory,
|
|
642
|
-
which writes a committed .mcp.json shared with your team, and writes a
|
|
643
|
-
project-root CLAUDE.md with the resolve_skill/fetch_skill discovery
|
|
644
|
-
paragraph — same reasoning as init: no instruction file is written unless
|
|
645
|
-
MCP is actually being registered.`,
|
|
646
|
-
|
|
647
|
-
target: `target: manage native sync target directories
|
|
648
|
-
|
|
649
|
-
usage:
|
|
650
|
-
skillmux target list
|
|
651
|
-
skillmux target show <name>
|
|
652
|
-
skillmux target add <name> [--dir <dir>] --yes
|
|
653
|
-
skillmux target remove <name> --yes
|
|
654
|
-
skillmux target rehome <name> --yes
|
|
655
|
-
skillmux target migrate --yes
|
|
656
|
-
|
|
657
|
-
--dir may be omitted when <name> is a built-in target with a deterministic
|
|
658
|
-
path: agent-skills, claude-code, codex. Any other <name> requires --dir.
|
|
659
|
-
|
|
660
|
-
A target is a directory, not a product. Several agents can map to the same
|
|
661
|
-
one (opencode, github-copilot and windsurf all share agent-skills), which is
|
|
662
|
-
why "skillmux project attach" accepts --agent as shorthand for the target
|
|
663
|
-
that agent resolves to.`,
|
|
664
|
-
|
|
665
|
-
core: `core: pin or unpin core-tier skills
|
|
666
|
-
|
|
667
|
-
usage:
|
|
668
|
-
skillmux core pin <skill_id>... --yes
|
|
669
|
-
skillmux core unpin <skill_id>... --yes`,
|
|
670
|
-
|
|
671
|
-
report: `report: show routing/fetch-outcome audit statistics
|
|
672
|
-
|
|
673
|
-
usage:
|
|
674
|
-
skillmux report [--context <name> | --server <url> | --db <path>] --since <window> [--json]`,
|
|
675
|
-
|
|
676
|
-
audit: `audit: prune the audit database
|
|
677
|
-
|
|
678
|
-
usage:
|
|
679
|
-
skillmux audit prune [--older-than <window>] [--dry-run] [--yes] [--json]
|
|
680
|
-
|
|
681
|
-
Accepts --context <name> / --server <url> to prune a remote deployment's audit db.`,
|
|
682
|
-
|
|
683
|
-
scan: `scan: check the vault for install-time or integrity issues
|
|
684
|
-
|
|
685
|
-
usage:
|
|
686
|
-
skillmux scan [path] [--fail-on low|medium|high|none] [--json]
|
|
687
|
-
|
|
688
|
-
Scans [path], or the configured vault when omitted. Reporting only: it
|
|
689
|
-
exits 0 whatever it finds unless --fail-on names a severity, which is why
|
|
690
|
-
it has no default threshold while install and update default to high.
|
|
691
|
-
|
|
692
|
-
--format text|json is deprecated: it emits JSON outside the standard
|
|
693
|
-
envelope. Use --json instead; --format will be removed in a future 1.x
|
|
694
|
-
release.`,
|
|
695
|
-
|
|
696
|
-
install: `install: install a skill from a git source
|
|
697
|
-
|
|
698
|
-
usage:
|
|
699
|
-
skillmux install <repo>[/path] [--yes] [--force] [--fail-on low|medium|high|none] [--dry-run] [--allow-local-source] [--json]
|
|
700
|
-
|
|
701
|
-
--yes approves writing the skill into the vault. Without it an interactive
|
|
702
|
-
run asks first, and a non-interactive one (no TTY, or --json) fails rather
|
|
703
|
-
than installing unattended, matching "skillmux update".
|
|
704
|
-
|
|
705
|
-
The fetched skill is scanned before it is written to the vault. --fail-on
|
|
706
|
-
sets the severity that aborts the install and defaults to high; pass
|
|
707
|
-
--fail-on none to install despite findings. A lower threshold is stricter:
|
|
708
|
-
low aborts on low, medium and high.
|
|
709
|
-
|
|
710
|
-
--force overwrites a skill that already exists in the vault instead of
|
|
711
|
-
refusing. --dry-run reports where the skill would land without writing.
|
|
712
|
-
--allow-local-source permits a file:// or local path source, which is
|
|
713
|
-
otherwise rejected.`,
|
|
714
|
-
|
|
715
|
-
outdated: `outdated: list installed skills with a newer upstream version
|
|
716
|
-
|
|
717
|
-
usage:
|
|
718
|
-
skillmux outdated [--allow-local-source] [--json]
|
|
719
|
-
|
|
720
|
-
Read-only: it reports what "skillmux update" would change and writes
|
|
721
|
-
nothing. --allow-local-source includes skills installed from a local or
|
|
722
|
-
file:// source, which are skipped by default because their upstream is a
|
|
723
|
-
path on this machine rather than a shared remote.`,
|
|
724
|
-
|
|
725
|
-
update: `update: update one or all skills to their latest source version
|
|
726
|
-
|
|
727
|
-
usage:
|
|
728
|
-
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high|none] [--json]
|
|
729
|
-
|
|
730
|
-
Updates every installed skill, or just <skill-id>. --yes is required to
|
|
731
|
-
apply non-interactively. --dry-run prints the plan without writing.
|
|
732
|
-
|
|
733
|
-
--fail-on works exactly as it does for install and defaults to high, so a
|
|
734
|
-
skill whose new version carries a high-severity finding is skipped rather
|
|
735
|
-
than updated; --fail-on none restores the old permissive behavior.
|
|
736
|
-
|
|
737
|
-
--force updates a skill whose local content no longer matches the hash
|
|
738
|
-
recorded at install time, which otherwise blocks the update to avoid
|
|
739
|
-
discarding local edits. --allow-local-source permits local/file:// sources.`,
|
|
740
|
-
|
|
741
|
-
eval: `eval: run retrieval evaluation against the holdout set
|
|
742
|
-
|
|
743
|
-
usage:
|
|
744
|
-
skillmux eval [--json]
|
|
745
|
-
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
746
|
-
|
|
747
|
-
Accepts --context <name> / --server <url> to evaluate a remote deployment.`,
|
|
748
|
-
|
|
749
|
-
doctor: `doctor: check server/environment readiness
|
|
750
|
-
|
|
751
|
-
usage:
|
|
752
|
-
skillmux doctor [--json]
|
|
753
|
-
|
|
754
|
-
Accepts --context <name> / --server <url> to check a remote deployment.`,
|
|
755
|
-
|
|
756
|
-
skill: `skill: inspect local vault skill resolution
|
|
757
|
-
|
|
758
|
-
usage:
|
|
759
|
-
skillmux skill which <skill_id> (local vault shadow resolution; unrelated to MCP routing)`,
|
|
760
|
-
|
|
761
|
-
"local-vault": `local-vault: register an additional local vault checkout
|
|
762
|
-
|
|
763
|
-
usage:
|
|
764
|
-
skillmux local-vault init <path> --yes`,
|
|
765
|
-
|
|
766
|
-
models: `models: manage local embedding model downloads
|
|
767
|
-
|
|
768
|
-
usage:
|
|
769
|
-
skillmux models download`,
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
function printCommandHelp(command: string): boolean {
|
|
773
|
-
const help = COMMAND_HELP[command];
|
|
774
|
-
if (!help) return false;
|
|
775
|
-
console.log(help);
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function printHelp(): void {
|
|
780
|
-
if (process.env.RUNNING_IN_DOCKER === "true") {
|
|
781
|
-
console.log(`Skillmux server image
|
|
782
|
-
|
|
783
|
-
Default:
|
|
784
|
-
serve --transport http
|
|
785
|
-
|
|
786
|
-
Supported commands:
|
|
787
|
-
serve, index, doctor, report, audit prune, eval promote, scan, skill which
|
|
788
|
-
config show|get|validate|diff|status
|
|
789
|
-
|
|
790
|
-
Native skill management:
|
|
791
|
-
Install the Skillmux CLI on the host for init, install, pinning, and sync.
|
|
792
|
-
|
|
793
|
-
See docs/deployment.md for server deployment examples.`);
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
console.log(`usage: skillmux <command> [options]
|
|
798
|
-
|
|
799
|
-
Setup:
|
|
800
|
-
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
801
|
-
[--migrate-full-vault] [--show-mcp-setup] [--register-mcp]
|
|
802
|
-
[--no-instructions] [--no-sync]
|
|
803
|
-
[--interactive|--yes|--dry-run] [--json]
|
|
804
|
-
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
805
|
-
[--agent <name>...] [--target <name>...] [--no-sync]
|
|
806
|
-
[--interactive|--yes|--dry-run] [--json]
|
|
807
|
-
skillmux project <list|show|add-path|remove-path|pin|unpin|attach|detach>
|
|
808
|
-
skillmux target <list|show|add|remove|rehome|migrate> (a target is a directory sync writes into)
|
|
809
|
-
skillmux core <pin|unpin> <skill_id>... [--yes] [--dry-run] [--json]
|
|
810
|
-
skillmux skill which <skill_id> (local vault shadow resolution; unrelated to MCP routing)
|
|
811
|
-
skillmux config init --vault <path> --yes
|
|
812
|
-
(bootstraps this machine's config on its own; not a
|
|
813
|
-
prerequisite, since "skillmux init --vault" does the same)
|
|
814
|
-
|
|
815
|
-
Init agents:
|
|
816
|
-
${SUPPORTED_AGENT_IDS.join(", ")}
|
|
817
|
-
("skillmux init --show-mcp-setup" also prints the MCP registration
|
|
818
|
-
snippet, independent of which agents you select. A tool not in this
|
|
819
|
-
list isn't supported by init yet — see "skillmux init --help".)
|
|
820
|
-
|
|
821
|
-
Operations:
|
|
822
|
-
skillmux report [--context <name> | --server <url> | --db <path>] --since <window> [--json]
|
|
823
|
-
skillmux audit prune [--older-than <window>] [--dry-run] [--yes] [--json]
|
|
824
|
-
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
825
|
-
skillmux outdated [--allow-local-source] [--json]
|
|
826
|
-
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high|none] [--json]
|
|
827
|
-
|
|
828
|
-
Commands:
|
|
829
|
-
serve, index, sync, init, project, target, core, report, audit, scan, install, outdated, update,
|
|
830
|
-
eval, doctor, skill, local-vault, config, models, context, completions
|
|
831
|
-
|
|
832
|
-
Run "skillmux <command> --help" for a command's full usage.`);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// ---------------------------------------------------------------------------
|
|
836
|
-
// Implementation of commands: serve, index, sync, init, report, scan, install, eval, doctor, models
|
|
837
|
-
// ---------------------------------------------------------------------------
|
|
838
|
-
|
|
839
|
-
type Transport = "stdio" | "http";
|
|
840
|
-
|
|
841
|
-
function parseServeArgs(args: string[]): {
|
|
842
|
-
transport: Transport;
|
|
843
|
-
port?: number;
|
|
844
|
-
statsPort?: number;
|
|
845
|
-
} {
|
|
846
|
-
let transport: Transport = "stdio";
|
|
847
|
-
let port: number | undefined;
|
|
848
|
-
let statsPort: number | undefined;
|
|
849
|
-
for (let i = 0; i < args.length; i++) {
|
|
850
|
-
const option = args[i];
|
|
851
|
-
const value = args[i + 1];
|
|
852
|
-
if (option === "--transport") {
|
|
853
|
-
if (value !== "stdio" && value !== "http") {
|
|
854
|
-
throw new Error("--transport must be stdio or http");
|
|
855
|
-
}
|
|
856
|
-
transport = value;
|
|
857
|
-
i++;
|
|
858
|
-
} else if (option === "--port") {
|
|
859
|
-
const parsed = Number(value);
|
|
860
|
-
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {
|
|
861
|
-
throw new Error("--port must be an integer between 0 and 65535");
|
|
862
|
-
}
|
|
863
|
-
port = parsed;
|
|
864
|
-
i++;
|
|
865
|
-
} else if (option === "--stats-port") {
|
|
866
|
-
const parsed = Number(value);
|
|
867
|
-
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {
|
|
868
|
-
throw new Error("--stats-port must be an integer between 0 and 65535");
|
|
869
|
-
}
|
|
870
|
-
statsPort = parsed;
|
|
871
|
-
i++;
|
|
872
|
-
} else {
|
|
873
|
-
throw new Error(`unknown serve option: ${option}`);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
return { transport, port, statsPort };
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
async function runIndex(): Promise<void> {
|
|
880
|
-
const config = await loadConfig();
|
|
881
|
-
configure({ config, clients: createClients(config) });
|
|
882
|
-
const report = await rebuildIndex((skillId, error) => {
|
|
883
|
-
warn(`keeping previous index entry for ${skillId}: ${error}`);
|
|
884
|
-
});
|
|
885
|
-
const retainedNote =
|
|
886
|
-
report.retained.length > 0
|
|
887
|
-
? ` (${report.retained.length} retained after parse errors)`
|
|
888
|
-
: "";
|
|
889
|
-
console.log(`indexed ${report.indexed} skills${retainedNote}`);
|
|
890
|
-
|
|
891
|
-
try {
|
|
892
|
-
const backfilled = await backfillEmbeddings();
|
|
893
|
-
console.log(`embeddings: ${backfilled} backfilled`);
|
|
894
|
-
} catch {
|
|
895
|
-
console.log(
|
|
896
|
-
"embeddings: skipped (endpoint unreachable; lexical-only recall until next index)",
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
async function runEval(options: { isJson: boolean; adapter: ContextAdapter }): Promise<void> {
|
|
902
|
-
const config = await loadConfig();
|
|
903
|
-
configure({ config, clients: createClients(config) });
|
|
904
|
-
|
|
905
|
-
const report = await options.adapter.evalRun().catch((error: unknown) => {
|
|
906
|
-
throw new Error(
|
|
907
|
-
`eval requires an embeddings client (local model or a configured remote endpoint): ${String(error)}`,
|
|
908
|
-
);
|
|
909
|
-
});
|
|
910
|
-
emitSuccess({ isJson: options.isJson }, report, () => {
|
|
911
|
-
console.log(`holdout queries: ${report.queries}`);
|
|
912
|
-
console.log(`judged queries: ${report.judged_queries}`);
|
|
913
|
-
console.log(`unjudged queries: ${report.unjudged_queries}`);
|
|
914
|
-
console.log(`lexical recall@5: ${report.lexical.recall_at_5.toFixed(3)}`);
|
|
915
|
-
console.log(`lexical recall@10: ${report.lexical.recall_at_10.toFixed(3)}`);
|
|
916
|
-
console.log(`lexical MRR: ${report.lexical.mrr.toFixed(3)}`);
|
|
917
|
-
console.log(`lexical nDCG@10: ${report.lexical.ndcg_at_10.toFixed(3)}`);
|
|
918
|
-
console.log(`hybrid recall@5: ${report.hybrid.recall_at_5.toFixed(3)}`);
|
|
919
|
-
console.log(`hybrid recall@10: ${report.hybrid.recall_at_10.toFixed(3)}`);
|
|
920
|
-
console.log(`hybrid MRR: ${report.hybrid.mrr.toFixed(3)}`);
|
|
921
|
-
console.log(`hybrid nDCG@10: ${report.hybrid.ndcg_at_10.toFixed(3)}`);
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (import.meta.main) {
|
|
927
|
-
await main();
|
|
928
|
-
}
|