@klhapp/skillmux 1.10.0 → 1.11.1
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 +44 -0
- package/README.md +26 -19
- package/docs/README.md +3 -3
- package/docs/assets/architecture-dark.svg +39 -32
- package/docs/assets/architecture-light.svg +25 -18
- package/docs/cli.md +80 -33
- package/docs/concepts.md +15 -11
- package/docs/configuration.md +6 -4
- package/docs/deployment.md +1 -1
- package/docs/getting-started.md +21 -13
- package/docs/mcp-routing.md +1 -1
- package/docs/skill-management.md +39 -19
- package/docs/troubleshooting.md +4 -4
- package/package.json +1 -1
- package/src/adapters.ts +11 -11
- package/src/cli.ts +255 -72
- package/src/commands/audit.ts +2 -2
- package/src/commands/config.ts +23 -15
- package/src/commands/context.ts +11 -10
- package/src/commands/core.ts +2 -2
- package/src/commands/doctor.ts +31 -10
- package/src/commands/eval.ts +14 -4
- package/src/commands/init.ts +175 -124
- package/src/commands/install.ts +36 -13
- package/src/commands/project.ts +177 -44
- package/src/commands/report.ts +3 -3
- package/src/commands/scan.ts +18 -8
- package/src/commands/shared.ts +7 -14
- package/src/commands/skill.ts +2 -1
- package/src/commands/target.ts +27 -9
- package/src/commands/update.ts +16 -9
- package/src/completions.ts +41 -15
- package/src/config-service.ts +3 -3
- package/src/init-agents.ts +329 -0
- package/src/init-instructions.ts +47 -28
- package/src/mcp-registration.ts +89 -0
- package/src/output.ts +53 -16
- package/src/prompts.ts +75 -20
- package/src/scan.ts +53 -19
- package/src/server.ts +1 -1
- package/src/init-clients.ts +0 -220
package/src/adapters.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface Capabilities {
|
|
|
28
28
|
restart_required_keys: string[];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export interface
|
|
31
|
+
export interface ContextAdapterOptions {
|
|
32
32
|
configPath?: string;
|
|
33
33
|
allowInsecure?: boolean;
|
|
34
34
|
clients?: Clients;
|
|
@@ -46,10 +46,10 @@ export interface AuditPruneResult extends PruneResult {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Context adapter: `local` = this CLI process has the Skillmux runtime (vault, index,
|
|
50
50
|
* audit db, embeddings/reranker clients) in-process; `remote` = thin network client to an external process.
|
|
51
51
|
*/
|
|
52
|
-
export interface
|
|
52
|
+
export interface ContextAdapter {
|
|
53
53
|
getCapabilities(): Promise<Capabilities>;
|
|
54
54
|
getConfigShow(): Promise<{ effective: Config; sources: Record<string, string>; active_revision: string }>;
|
|
55
55
|
getConfigGet(key: string): Promise<unknown>;
|
|
@@ -74,11 +74,11 @@ export function isLoopbackHost(hostname: string): boolean {
|
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export class LocalAdapter implements
|
|
77
|
+
export class LocalAdapter implements ContextAdapter {
|
|
78
78
|
private configPath: string;
|
|
79
79
|
private clients?: Clients;
|
|
80
80
|
|
|
81
|
-
constructor(opts?:
|
|
81
|
+
constructor(opts?: ContextAdapterOptions) {
|
|
82
82
|
this.configPath = resolveConfigPath(opts?.configPath);
|
|
83
83
|
this.clients = opts?.clients;
|
|
84
84
|
}
|
|
@@ -132,7 +132,7 @@ export class LocalAdapter implements TargetAdapter {
|
|
|
132
132
|
return setDottedKey(key, rawValStr, {
|
|
133
133
|
configPath: this.configPath,
|
|
134
134
|
dryRun: opts?.dryRun,
|
|
135
|
-
|
|
135
|
+
contextName: "local",
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -210,12 +210,12 @@ export class LocalAdapter implements TargetAdapter {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
export class RemoteAdapter implements
|
|
213
|
+
export class RemoteAdapter implements ContextAdapter {
|
|
214
214
|
private serverUrl: string;
|
|
215
215
|
private tokenEnv?: string;
|
|
216
216
|
private allowInsecure: boolean;
|
|
217
217
|
|
|
218
|
-
constructor(target: { server: string; token_env?: string }, opts?:
|
|
218
|
+
constructor(target: { server: string; token_env?: string }, opts?: ContextAdapterOptions) {
|
|
219
219
|
this.serverUrl = target.server.replace(/\/$/, "");
|
|
220
220
|
this.tokenEnv = target.token_env;
|
|
221
221
|
this.allowInsecure = opts?.allowInsecure ?? false;
|
|
@@ -429,10 +429,10 @@ export class RemoteAdapter implements TargetAdapter {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
export function
|
|
433
|
-
if (
|
|
432
|
+
export function createContextAdapter(context: ResolvedContext, opts?: ContextAdapterOptions): ContextAdapter {
|
|
433
|
+
if (context.type === "local") {
|
|
434
434
|
return new LocalAdapter(opts);
|
|
435
435
|
} else {
|
|
436
|
-
return new RemoteAdapter({ server:
|
|
436
|
+
return new RemoteAdapter({ server: context.server, token_env: context.token_env }, opts);
|
|
437
437
|
}
|
|
438
438
|
}
|
package/src/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { type StatsResponse } from "./stats";
|
|
|
17
17
|
import { scanVault } from "./vault";
|
|
18
18
|
|
|
19
19
|
import { resolveContext, type ResolvedContext } from "./context";
|
|
20
|
-
import {
|
|
20
|
+
import { createContextAdapter, isLoopbackHost, type ContextAdapter } from "./adapters";
|
|
21
21
|
import {
|
|
22
22
|
emitSuccess,
|
|
23
23
|
CliError,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
warn,
|
|
29
29
|
} from "./output";
|
|
30
30
|
import { generateCompletions, type ShellType } from "./completions";
|
|
31
|
+
import { SUPPORTED_AGENT_IDS } from "./init-agents";
|
|
31
32
|
import { runAudit } from "./commands/audit";
|
|
32
33
|
import { handleConfigCommand } from "./commands/config";
|
|
33
34
|
import { handleContextCommand } from "./commands/context";
|
|
@@ -45,7 +46,7 @@ import { runTarget } from "./commands/target";
|
|
|
45
46
|
import { runSync } from "./commands/sync";
|
|
46
47
|
import { runInit } from "./commands/init";
|
|
47
48
|
|
|
48
|
-
const KNOWN_COMMANDS = [
|
|
49
|
+
export const KNOWN_COMMANDS = [
|
|
49
50
|
"context",
|
|
50
51
|
"config",
|
|
51
52
|
"completions",
|
|
@@ -69,39 +70,120 @@ const KNOWN_COMMANDS = [
|
|
|
69
70
|
"local-vault",
|
|
70
71
|
];
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
};
|
|
87
120
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
92
128
|
if (command === "skill" && (subCommand === "which" || !subCommand)) {
|
|
93
129
|
return "skill which";
|
|
94
130
|
}
|
|
131
|
+
if (command === "config" && subCommand === "init") {
|
|
132
|
+
return "config init";
|
|
133
|
+
}
|
|
134
|
+
if (LOCAL_ONLY_COMMANDS.has(command)) {
|
|
135
|
+
return command;
|
|
136
|
+
}
|
|
95
137
|
return null;
|
|
96
138
|
}
|
|
97
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
|
+
|
|
98
177
|
function remoteContextUnsupported(rejectedCommand: string): CliError {
|
|
178
|
+
const reason = LOCAL_ONLY_REASON[rejectedCommand];
|
|
179
|
+
const guidance = reason ? ` ${LOCAL_ONLY_GUIDANCE[reason]}` : "";
|
|
99
180
|
return new CliError(
|
|
100
|
-
`\`${rejectedCommand}\` operates on the local vault only; --context/--server isn't supported here`,
|
|
181
|
+
`\`${rejectedCommand}\` operates on the local vault only; --context/--server isn't supported here.${guidance}`,
|
|
101
182
|
2,
|
|
102
183
|
"REMOTE_CONTEXT_UNSUPPORTED",
|
|
103
184
|
{
|
|
104
185
|
rejected_command: rejectedCommand,
|
|
186
|
+
...(reason ? { reason } : {}),
|
|
105
187
|
},
|
|
106
188
|
);
|
|
107
189
|
}
|
|
@@ -196,14 +278,14 @@ async function main() {
|
|
|
196
278
|
else if (arg === "--server") flagServer = rawArgv[++i];
|
|
197
279
|
}
|
|
198
280
|
|
|
199
|
-
let
|
|
281
|
+
let resolvedContext: ResolvedContext = { type: "local", name: "local" };
|
|
200
282
|
|
|
201
283
|
if (
|
|
202
284
|
process.env.RUNNING_IN_DOCKER === "true" &&
|
|
203
285
|
isDockerHostManagementCommand(command, subCommand)
|
|
204
286
|
) {
|
|
205
287
|
await handleError(containerCommandUnsupported(command, subCommand), {
|
|
206
|
-
|
|
288
|
+
context: resolvedContext,
|
|
207
289
|
isJson,
|
|
208
290
|
isVerbose,
|
|
209
291
|
});
|
|
@@ -218,38 +300,38 @@ async function main() {
|
|
|
218
300
|
}
|
|
219
301
|
|
|
220
302
|
try {
|
|
221
|
-
|
|
303
|
+
resolvedContext = await resolveContext({
|
|
222
304
|
context: flagContext,
|
|
223
305
|
server: flagServer,
|
|
224
306
|
});
|
|
225
307
|
} catch (err: any) {
|
|
226
|
-
await handleError(err, {
|
|
308
|
+
await handleError(err, { context: resolvedContext, isJson, isVerbose });
|
|
227
309
|
return;
|
|
228
310
|
}
|
|
229
311
|
|
|
230
312
|
const localOnlyCommand = getLocalOnlyCommand(command, subCommand);
|
|
231
|
-
if (localOnlyCommand &&
|
|
313
|
+
if (localOnlyCommand && resolvedContext.type === "remote") {
|
|
232
314
|
await handleError(remoteContextUnsupported(localOnlyCommand), {
|
|
233
|
-
|
|
315
|
+
context: resolvedContext,
|
|
234
316
|
isJson,
|
|
235
317
|
isVerbose,
|
|
236
318
|
});
|
|
237
319
|
return;
|
|
238
320
|
}
|
|
239
321
|
|
|
240
|
-
const adapter =
|
|
322
|
+
const adapter = createContextAdapter(resolvedContext, { allowInsecure });
|
|
241
323
|
|
|
242
324
|
try {
|
|
243
325
|
switch (command) {
|
|
244
326
|
case "context":
|
|
245
327
|
await handleContextCommand(subCommand, commandArgs, {
|
|
246
|
-
|
|
328
|
+
context: resolvedContext,
|
|
247
329
|
isJson,
|
|
248
330
|
});
|
|
249
331
|
break;
|
|
250
332
|
case "config":
|
|
251
333
|
await handleConfigCommand(adapter, subCommand, commandArgs, {
|
|
252
|
-
|
|
334
|
+
context: resolvedContext,
|
|
253
335
|
isJson,
|
|
254
336
|
dryRun: isDryRun,
|
|
255
337
|
});
|
|
@@ -308,7 +390,7 @@ async function main() {
|
|
|
308
390
|
case "report":
|
|
309
391
|
await runReport(rawArgv.slice(1), {
|
|
310
392
|
isJson,
|
|
311
|
-
|
|
393
|
+
context: resolvedContext,
|
|
312
394
|
allowInsecure,
|
|
313
395
|
adapter,
|
|
314
396
|
});
|
|
@@ -317,7 +399,7 @@ async function main() {
|
|
|
317
399
|
await runAudit(subCommand, commandArgs, {
|
|
318
400
|
isJson,
|
|
319
401
|
dryRun: isDryRun,
|
|
320
|
-
|
|
402
|
+
context: resolvedContext,
|
|
321
403
|
adapter,
|
|
322
404
|
});
|
|
323
405
|
break;
|
|
@@ -339,11 +421,16 @@ async function main() {
|
|
|
339
421
|
} else if (subCommand === "") {
|
|
340
422
|
await runEval({ isJson, adapter });
|
|
341
423
|
} else {
|
|
342
|
-
throw new Error(`usage: skillmux eval [promote --since <window> [--
|
|
424
|
+
throw new Error(`usage: skillmux eval [promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]]`);
|
|
343
425
|
}
|
|
344
426
|
break;
|
|
345
427
|
case "doctor":
|
|
346
|
-
await runDoctor({
|
|
428
|
+
await runDoctor({
|
|
429
|
+
isJson,
|
|
430
|
+
context: resolvedContext,
|
|
431
|
+
adapter,
|
|
432
|
+
args: rawArgv.slice(1),
|
|
433
|
+
});
|
|
347
434
|
break;
|
|
348
435
|
case "which":
|
|
349
436
|
throw new Error(
|
|
@@ -375,7 +462,7 @@ async function main() {
|
|
|
375
462
|
}
|
|
376
463
|
}
|
|
377
464
|
} catch (err: any) {
|
|
378
|
-
await handleError(err, {
|
|
465
|
+
await handleError(err, { context: resolvedContext, isJson, isVerbose });
|
|
379
466
|
}
|
|
380
467
|
}
|
|
381
468
|
|
|
@@ -390,7 +477,7 @@ async function handleCompletionsCommand(shell: string) {
|
|
|
390
477
|
|
|
391
478
|
async function handleError(
|
|
392
479
|
err: any,
|
|
393
|
-
opts: {
|
|
480
|
+
opts: { context: ResolvedContext; isJson: boolean; isVerbose: boolean },
|
|
394
481
|
) {
|
|
395
482
|
const code = mapExitCode(err);
|
|
396
483
|
process.exitCode = code;
|
|
@@ -411,7 +498,7 @@ async function handleError(
|
|
|
411
498
|
if (opts.isJson) {
|
|
412
499
|
const env = formatJsonEnvelope({
|
|
413
500
|
ok: false,
|
|
414
|
-
|
|
501
|
+
context: opts.context,
|
|
415
502
|
error: {
|
|
416
503
|
code: err instanceof CliError ? err.code : `EXIT_${code}`,
|
|
417
504
|
message: msg,
|
|
@@ -456,6 +543,11 @@ usage:
|
|
|
456
543
|
skillmux config diff
|
|
457
544
|
skillmux config status
|
|
458
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
|
+
|
|
459
551
|
Accepts --context <name> / --server <url> to target a remote deployment.`,
|
|
460
552
|
|
|
461
553
|
completions: `completions: generate a shell completion script
|
|
@@ -476,45 +568,97 @@ alongside a stdio transport without opening the full HTTP surface.`,
|
|
|
476
568
|
usage:
|
|
477
569
|
skillmux index`,
|
|
478
570
|
|
|
479
|
-
sync: `sync: apply the manifest to native
|
|
571
|
+
sync: `sync: apply the manifest to native agent target directories
|
|
480
572
|
|
|
481
573
|
usage:
|
|
482
|
-
skillmux sync [--dry-run] [--restore-monolith] [--install-hook] [--yes] [--json]
|
|
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.`,
|
|
483
588
|
|
|
484
589
|
init: `init: guided setup for native skill management
|
|
485
590
|
|
|
486
591
|
usage:
|
|
487
|
-
skillmux init [--
|
|
488
|
-
[--vault
|
|
489
|
-
[--
|
|
592
|
+
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
593
|
+
[--migrate-full-vault] [--show-mcp-setup] [--register-mcp]
|
|
594
|
+
[--no-instructions] [--no-sync]
|
|
490
595
|
[--interactive|--yes|--dry-run] [--json]
|
|
491
596
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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.`,
|
|
495
613
|
|
|
496
614
|
project: `project: manage project-scoped skill pins and sync groups
|
|
497
615
|
|
|
498
616
|
usage:
|
|
499
617
|
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
500
|
-
[--
|
|
501
|
-
[--interactive|--yes|--dry-run] [--json]
|
|
618
|
+
[--agent <name>...] [--target <name>...] [--register-mcp]
|
|
619
|
+
[--no-sync] [--interactive|--yes|--dry-run] [--json]
|
|
502
620
|
skillmux project list
|
|
503
621
|
skillmux project show <group>
|
|
504
622
|
skillmux project add-path <group> [path] --yes
|
|
505
623
|
skillmux project remove-path <group> [path] --yes
|
|
506
624
|
skillmux project pin <group> <skill_id>... --yes
|
|
507
625
|
skillmux project unpin <group> <skill_id>... --yes
|
|
508
|
-
skillmux project attach <group> (--
|
|
509
|
-
skillmux project detach <group> (--
|
|
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.`,
|
|
510
646
|
|
|
511
647
|
target: `target: manage native sync target directories
|
|
512
648
|
|
|
513
649
|
usage:
|
|
514
650
|
skillmux target list
|
|
515
651
|
skillmux target show <name>
|
|
516
|
-
skillmux target add <name> --dir <dir> --yes
|
|
517
|
-
skillmux target remove <name> --yes
|
|
652
|
+
skillmux target add <name> [--dir <dir>] --yes
|
|
653
|
+
skillmux target remove <name> --yes
|
|
654
|
+
|
|
655
|
+
--dir may be omitted when <name> is a built-in target with a deterministic
|
|
656
|
+
path: agent-skills, claude-code, codex. Any other <name> requires --dir.
|
|
657
|
+
|
|
658
|
+
A target is a directory, not a product. Several agents can map to the same
|
|
659
|
+
one (opencode, github-copilot and windsurf all share agent-skills), which is
|
|
660
|
+
why "skillmux project attach" accepts --agent as shorthand for the target
|
|
661
|
+
that agent resolves to.`,
|
|
518
662
|
|
|
519
663
|
core: `core: pin or unpin core-tier skills
|
|
520
664
|
|
|
@@ -537,28 +681,66 @@ Accepts --context <name> / --server <url> to prune a remote deployment's audit d
|
|
|
537
681
|
scan: `scan: check the vault for install-time or integrity issues
|
|
538
682
|
|
|
539
683
|
usage:
|
|
540
|
-
skillmux scan [path] [--
|
|
684
|
+
skillmux scan [path] [--fail-on low|medium|high|none] [--json]
|
|
685
|
+
|
|
686
|
+
Scans [path], or the configured vault when omitted. Reporting only: it
|
|
687
|
+
exits 0 whatever it finds unless --fail-on names a severity, which is why
|
|
688
|
+
it has no default threshold while install and update default to high.
|
|
689
|
+
|
|
690
|
+
--format text|json is deprecated: it emits JSON outside the standard
|
|
691
|
+
envelope. Use --json instead; --format will be removed in a future 1.x
|
|
692
|
+
release.`,
|
|
541
693
|
|
|
542
694
|
install: `install: install a skill from a git source
|
|
543
695
|
|
|
544
696
|
usage:
|
|
545
|
-
skillmux install <repo>[/path] [--force] [--fail-on low|medium|high] [--dry-run] [--allow-local-source] [--json]
|
|
697
|
+
skillmux install <repo>[/path] [--yes] [--force] [--fail-on low|medium|high|none] [--dry-run] [--allow-local-source] [--json]
|
|
698
|
+
|
|
699
|
+
--yes approves writing the skill into the vault. Without it an interactive
|
|
700
|
+
run asks first, and a non-interactive one (no TTY, or --json) fails rather
|
|
701
|
+
than installing unattended, matching "skillmux update".
|
|
702
|
+
|
|
703
|
+
The fetched skill is scanned before it is written to the vault. --fail-on
|
|
704
|
+
sets the severity that aborts the install and defaults to high; pass
|
|
705
|
+
--fail-on none to install despite findings. A lower threshold is stricter:
|
|
706
|
+
low aborts on low, medium and high.
|
|
707
|
+
|
|
708
|
+
--force overwrites a skill that already exists in the vault instead of
|
|
709
|
+
refusing. --dry-run reports where the skill would land without writing.
|
|
710
|
+
--allow-local-source permits a file:// or local path source, which is
|
|
711
|
+
otherwise rejected.`,
|
|
546
712
|
|
|
547
713
|
outdated: `outdated: list installed skills with a newer upstream version
|
|
548
714
|
|
|
549
715
|
usage:
|
|
550
|
-
skillmux outdated [--allow-local-source] [--json]
|
|
716
|
+
skillmux outdated [--allow-local-source] [--json]
|
|
717
|
+
|
|
718
|
+
Read-only: it reports what "skillmux update" would change and writes
|
|
719
|
+
nothing. --allow-local-source includes skills installed from a local or
|
|
720
|
+
file:// source, which are skipped by default because their upstream is a
|
|
721
|
+
path on this machine rather than a shared remote.`,
|
|
551
722
|
|
|
552
723
|
update: `update: update one or all skills to their latest source version
|
|
553
724
|
|
|
554
725
|
usage:
|
|
555
|
-
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high] [--json]
|
|
726
|
+
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high|none] [--json]
|
|
727
|
+
|
|
728
|
+
Updates every installed skill, or just <skill-id>. --yes is required to
|
|
729
|
+
apply non-interactively. --dry-run prints the plan without writing.
|
|
730
|
+
|
|
731
|
+
--fail-on works exactly as it does for install and defaults to high, so a
|
|
732
|
+
skill whose new version carries a high-severity finding is skipped rather
|
|
733
|
+
than updated; --fail-on none restores the old permissive behavior.
|
|
734
|
+
|
|
735
|
+
--force updates a skill whose local content no longer matches the hash
|
|
736
|
+
recorded at install time, which otherwise blocks the update to avoid
|
|
737
|
+
discarding local edits. --allow-local-source permits local/file:// sources.`,
|
|
556
738
|
|
|
557
739
|
eval: `eval: run retrieval evaluation against the holdout set
|
|
558
740
|
|
|
559
741
|
usage:
|
|
560
742
|
skillmux eval [--json]
|
|
561
|
-
skillmux eval promote --since <window> [--
|
|
743
|
+
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
562
744
|
|
|
563
745
|
Accepts --context <name> / --server <url> to evaluate a remote deployment.`,
|
|
564
746
|
|
|
@@ -613,32 +795,33 @@ See docs/deployment.md for server deployment examples.`);
|
|
|
613
795
|
console.log(`usage: skillmux <command> [options]
|
|
614
796
|
|
|
615
797
|
Setup:
|
|
616
|
-
skillmux
|
|
617
|
-
|
|
618
|
-
[--
|
|
619
|
-
[--migrate-full-vault] [--no-instructions] [--no-sync]
|
|
798
|
+
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
799
|
+
[--migrate-full-vault] [--show-mcp-setup] [--register-mcp]
|
|
800
|
+
[--no-instructions] [--no-sync]
|
|
620
801
|
[--interactive|--yes|--dry-run] [--json]
|
|
621
802
|
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
622
|
-
[--
|
|
803
|
+
[--agent <name>...] [--target <name>...] [--no-sync]
|
|
623
804
|
[--interactive|--yes|--dry-run] [--json]
|
|
624
805
|
skillmux project <list|show|add-path|remove-path|pin|unpin|attach|detach>
|
|
625
|
-
skillmux target <list|show|add|remove>
|
|
806
|
+
skillmux target <list|show|add|remove> (a target is a directory sync writes into)
|
|
626
807
|
skillmux core <pin|unpin> <skill_id>... [--yes] [--dry-run] [--json]
|
|
627
808
|
skillmux skill which <skill_id> (local vault shadow resolution; unrelated to MCP routing)
|
|
809
|
+
skillmux config init --vault <path> --yes
|
|
810
|
+
(bootstraps this machine's config on its own; not a
|
|
811
|
+
prerequisite, since "skillmux init --vault" does the same)
|
|
628
812
|
|
|
629
|
-
Init
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
agent-skills, claude-code, codex, custom
|
|
813
|
+
Init agents:
|
|
814
|
+
${SUPPORTED_AGENT_IDS.join(", ")}
|
|
815
|
+
("skillmux init --show-mcp-setup" also prints the MCP registration
|
|
816
|
+
snippet, independent of which agents you select. A tool not in this
|
|
817
|
+
list isn't supported by init yet — see "skillmux init --help".)
|
|
635
818
|
|
|
636
819
|
Operations:
|
|
637
820
|
skillmux report [--context <name> | --server <url> | --db <path>] --since <window> [--json]
|
|
638
821
|
skillmux audit prune [--older-than <window>] [--dry-run] [--yes] [--json]
|
|
639
|
-
skillmux eval promote --since <window> [--
|
|
822
|
+
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
640
823
|
skillmux outdated [--allow-local-source] [--json]
|
|
641
|
-
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high] [--json]
|
|
824
|
+
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high|none] [--json]
|
|
642
825
|
|
|
643
826
|
Commands:
|
|
644
827
|
serve, index, sync, init, project, target, core, report, audit, scan, install, outdated, update,
|
|
@@ -713,7 +896,7 @@ async function runIndex(): Promise<void> {
|
|
|
713
896
|
}
|
|
714
897
|
}
|
|
715
898
|
|
|
716
|
-
async function runEval(options: { isJson: boolean; adapter:
|
|
899
|
+
async function runEval(options: { isJson: boolean; adapter: ContextAdapter }): Promise<void> {
|
|
717
900
|
const config = await loadConfig();
|
|
718
901
|
configure({ config, clients: createClients(config) });
|
|
719
902
|
|
package/src/commands/audit.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { emitSuccess } from "../output";
|
|
2
2
|
import { confirmIfNeeded } from "./shared";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ContextAdapter } from "../adapters";
|
|
4
4
|
import type { ResolvedContext } from "../context";
|
|
5
5
|
import { isGlobalFlag, isGlobalFlagWithValue } from "../global-flags";
|
|
6
6
|
|
|
7
7
|
export async function runAudit(
|
|
8
8
|
subCommand: string,
|
|
9
9
|
args: string[],
|
|
10
|
-
options: { isJson: boolean; dryRun: boolean;
|
|
10
|
+
options: { isJson: boolean; dryRun: boolean; context: ResolvedContext; adapter: ContextAdapter },
|
|
11
11
|
): Promise<void> {
|
|
12
12
|
if (subCommand !== "prune") {
|
|
13
13
|
throw new Error("usage: skillmux audit prune [--older-than <window>] [--dry-run] [--yes] [--json]");
|