@klhapp/skillmux 1.10.0 → 1.11.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 +31 -0
- package/README.md +18 -18
- 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 +73 -33
- package/docs/concepts.md +10 -10
- package/docs/configuration.md +6 -4
- package/docs/deployment.md +1 -1
- package/docs/getting-started.md +17 -13
- package/docs/mcp-routing.md +1 -1
- package/docs/skill-management.md +11 -11
- package/docs/troubleshooting.md +4 -4
- package/package.json +1 -1
- package/src/adapters.ts +11 -11
- package/src/cli.ts +173 -63
- 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/project.ts +161 -44
- package/src/commands/report.ts +3 -3
- package/src/commands/shared.ts +7 -14
- package/src/commands/skill.ts +2 -1
- package/src/commands/target.ts +27 -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 +19 -19
- package/src/server.ts +1 -1
- package/src/init-clients.ts +0 -220
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,
|
|
@@ -476,7 +563,7 @@ alongside a stdio transport without opening the full HTTP surface.`,
|
|
|
476
563
|
usage:
|
|
477
564
|
skillmux index`,
|
|
478
565
|
|
|
479
|
-
sync: `sync: apply the manifest to native
|
|
566
|
+
sync: `sync: apply the manifest to native agent target directories
|
|
480
567
|
|
|
481
568
|
usage:
|
|
482
569
|
skillmux sync [--dry-run] [--restore-monolith] [--install-hook] [--yes] [--json]`,
|
|
@@ -484,37 +571,62 @@ usage:
|
|
|
484
571
|
init: `init: guided setup for native skill management
|
|
485
572
|
|
|
486
573
|
usage:
|
|
487
|
-
skillmux init [--
|
|
488
|
-
[--vault
|
|
489
|
-
[--
|
|
574
|
+
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
575
|
+
[--migrate-full-vault] [--show-mcp-setup] [--register-mcp]
|
|
576
|
+
[--no-instructions] [--no-sync]
|
|
490
577
|
[--interactive|--yes|--dry-run] [--json]
|
|
491
578
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
579
|
+
agents: ${SUPPORTED_AGENT_IDS.join(", ")}
|
|
580
|
+
|
|
581
|
+
Native pins and MCP are independent — skip both of the flags below for
|
|
582
|
+
native-only setup, and init writes no instruction files (the managed
|
|
583
|
+
block only teaches resolve_skill/fetch_skill, which are MCP tools).
|
|
584
|
+
--show-mcp-setup prints the MCP registration snippet to copy in yourself,
|
|
585
|
+
for any agent, and also writes the instruction block for every selected
|
|
586
|
+
agent. --register-mcp instead runs that agent's own CLI to register
|
|
587
|
+
skillmux automatically, but only for claude-code and codex (the only
|
|
588
|
+
agents with a verified registration command), and writes the instruction
|
|
589
|
+
block just for those; interactively, init asks about this only when
|
|
590
|
+
you've selected one of those two. --no-instructions forces instruction
|
|
591
|
+
writes off even when an MCP flag is set. A tool not in the agents list
|
|
592
|
+
above isn't supported by init yet — add it to SUPPORTED_AGENT_IDS rather
|
|
593
|
+
than guessing a directory. To adopt an arbitrary existing directory
|
|
594
|
+
directly, use "skillmux target add <name> --dir <dir>" instead of init.`,
|
|
495
595
|
|
|
496
596
|
project: `project: manage project-scoped skill pins and sync groups
|
|
497
597
|
|
|
498
598
|
usage:
|
|
499
599
|
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
500
|
-
[--
|
|
501
|
-
[--interactive|--yes|--dry-run] [--json]
|
|
600
|
+
[--agent <name>...] [--target <name>...] [--register-mcp]
|
|
601
|
+
[--no-sync] [--interactive|--yes|--dry-run] [--json]
|
|
502
602
|
skillmux project list
|
|
503
603
|
skillmux project show <group>
|
|
504
604
|
skillmux project add-path <group> [path] --yes
|
|
505
605
|
skillmux project remove-path <group> [path] --yes
|
|
506
606
|
skillmux project pin <group> <skill_id>... --yes
|
|
507
607
|
skillmux project unpin <group> <skill_id>... --yes
|
|
508
|
-
skillmux project attach <group> (--
|
|
509
|
-
skillmux project detach <group> (--
|
|
608
|
+
skillmux project attach <group> (--agent <id>... | --target <name>...) --yes
|
|
609
|
+
skillmux project detach <group> (--agent <id>... | --target <name>...) --yes
|
|
610
|
+
|
|
611
|
+
--register-mcp is the project-local counterpart to "skillmux init
|
|
612
|
+
--register-mcp": only for claude-code (the only agent whose own CLI has a
|
|
613
|
+
project MCP scope — codex's mcp add has no scope flag, so it's always
|
|
614
|
+
global). It runs "claude mcp add -s project" for this project directory,
|
|
615
|
+
which writes a committed .mcp.json shared with your team, and writes a
|
|
616
|
+
project-root CLAUDE.md with the resolve_skill/fetch_skill discovery
|
|
617
|
+
paragraph — same reasoning as init: no instruction file is written unless
|
|
618
|
+
MCP is actually being registered.`,
|
|
510
619
|
|
|
511
620
|
target: `target: manage native sync target directories
|
|
512
621
|
|
|
513
622
|
usage:
|
|
514
623
|
skillmux target list
|
|
515
624
|
skillmux target show <name>
|
|
516
|
-
skillmux target add <name> --dir <dir> --yes
|
|
517
|
-
skillmux target remove <name> --yes
|
|
625
|
+
skillmux target add <name> [--dir <dir>] --yes
|
|
626
|
+
skillmux target remove <name> --yes
|
|
627
|
+
|
|
628
|
+
--dir may be omitted when <name> is a built-in target with a deterministic
|
|
629
|
+
path: agent-skills, claude-code, codex. Any other <name> requires --dir.`,
|
|
518
630
|
|
|
519
631
|
core: `core: pin or unpin core-tier skills
|
|
520
632
|
|
|
@@ -558,7 +670,7 @@ usage:
|
|
|
558
670
|
|
|
559
671
|
usage:
|
|
560
672
|
skillmux eval [--json]
|
|
561
|
-
skillmux eval promote --since <window> [--
|
|
673
|
+
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
562
674
|
|
|
563
675
|
Accepts --context <name> / --server <url> to evaluate a remote deployment.`,
|
|
564
676
|
|
|
@@ -614,29 +726,27 @@ See docs/deployment.md for server deployment examples.`);
|
|
|
614
726
|
|
|
615
727
|
Setup:
|
|
616
728
|
skillmux config init --vault <path> --yes
|
|
617
|
-
skillmux init [--
|
|
618
|
-
[--vault <path>] [--core <skill_id>...]
|
|
729
|
+
skillmux init [--agent <name>...] [--vault <path>] [--core <skill_id>...]
|
|
619
730
|
[--migrate-full-vault] [--no-instructions] [--no-sync]
|
|
620
731
|
[--interactive|--yes|--dry-run] [--json]
|
|
621
732
|
skillmux project init [path] [--name <group>] [--skill <skill_id>...]
|
|
622
|
-
[--
|
|
733
|
+
[--agent <name>...] [--target <name>...] [--no-sync]
|
|
623
734
|
[--interactive|--yes|--dry-run] [--json]
|
|
624
735
|
skillmux project <list|show|add-path|remove-path|pin|unpin|attach|detach>
|
|
625
736
|
skillmux target <list|show|add|remove>
|
|
626
737
|
skillmux core <pin|unpin> <skill_id>... [--yes] [--dry-run] [--json]
|
|
627
738
|
skillmux skill which <skill_id> (local vault shadow resolution; unrelated to MCP routing)
|
|
628
739
|
|
|
629
|
-
Init
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
agent-skills, claude-code, codex, custom
|
|
740
|
+
Init agents:
|
|
741
|
+
${SUPPORTED_AGENT_IDS.join(", ")}
|
|
742
|
+
("skillmux init --show-mcp-setup" also prints the MCP registration
|
|
743
|
+
snippet, independent of which agents you select. A tool not in this
|
|
744
|
+
list isn't supported by init yet — see "skillmux init --help".)
|
|
635
745
|
|
|
636
746
|
Operations:
|
|
637
747
|
skillmux report [--context <name> | --server <url> | --db <path>] --since <window> [--json]
|
|
638
748
|
skillmux audit prune [--older-than <window>] [--dry-run] [--yes] [--json]
|
|
639
|
-
skillmux eval promote --since <window> [--
|
|
749
|
+
skillmux eval promote --since <window> [--out <path>] [--dry-run] [--yes] [--json]
|
|
640
750
|
skillmux outdated [--allow-local-source] [--json]
|
|
641
751
|
skillmux update [skill-id] [--yes] [--dry-run] [--force] [--allow-local-source] [--fail-on low|medium|high] [--json]
|
|
642
752
|
|
|
@@ -713,7 +823,7 @@ async function runIndex(): Promise<void> {
|
|
|
713
823
|
}
|
|
714
824
|
}
|
|
715
825
|
|
|
716
|
-
async function runEval(options: { isJson: boolean; adapter:
|
|
826
|
+
async function runEval(options: { isJson: boolean; adapter: ContextAdapter }): Promise<void> {
|
|
717
827
|
const config = await loadConfig();
|
|
718
828
|
configure({ config, clients: createClients(config) });
|
|
719
829
|
|
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]");
|
package/src/commands/config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { expandHome, migrateLegacyPaths, resolveConfigPath } from "../config";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ContextAdapter } from "../adapters";
|
|
3
3
|
import { type ResolvedContext } from "../context";
|
|
4
4
|
import { applyConfigInit, planConfigInit, type ConfigInitPlan } from "../setup";
|
|
5
|
-
import { emitSuccess, isInteractive,
|
|
5
|
+
import { emitSuccess, isInteractive, renderContextBanner, unknownSubcommandError } from "../output";
|
|
6
6
|
import { confirmAction } from "./shared";
|
|
7
7
|
import { isGlobalFlag } from "../global-flags";
|
|
8
8
|
function emitConfigInitOutcome(
|
|
@@ -38,10 +38,10 @@ function emitConfigInitOutcome(
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export async function handleConfigCommand(
|
|
41
|
-
adapter:
|
|
41
|
+
adapter: ContextAdapter,
|
|
42
42
|
sub: string,
|
|
43
43
|
args: string[],
|
|
44
|
-
ctx: {
|
|
44
|
+
ctx: { context: ResolvedContext; isJson: boolean; dryRun: boolean },
|
|
45
45
|
) {
|
|
46
46
|
if (sub === "init") {
|
|
47
47
|
let vaultPath: string | undefined;
|
|
@@ -127,8 +127,8 @@ export async function handleConfigCommand(
|
|
|
127
127
|
if (sub === "show") {
|
|
128
128
|
const withSources = args.includes("--sources");
|
|
129
129
|
const data = await adapter.getConfigShow();
|
|
130
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
131
|
-
|
|
130
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, data, () => {
|
|
131
|
+
renderContextBanner(ctx.context);
|
|
132
132
|
if (withSources) {
|
|
133
133
|
const policy =
|
|
134
134
|
data.effective.config?.environment_overrides === false
|
|
@@ -151,7 +151,7 @@ export async function handleConfigCommand(
|
|
|
151
151
|
if (!key) throw new Error("usage: skillmux config get <key>");
|
|
152
152
|
const val = await adapter.getConfigGet(key);
|
|
153
153
|
emitSuccess(
|
|
154
|
-
{ isJson: ctx.isJson,
|
|
154
|
+
{ isJson: ctx.isJson, context: ctx.context },
|
|
155
155
|
{ key, value: val },
|
|
156
156
|
() => {
|
|
157
157
|
console.log(
|
|
@@ -164,7 +164,7 @@ export async function handleConfigCommand(
|
|
|
164
164
|
|
|
165
165
|
if (sub === "validate") {
|
|
166
166
|
const res = await adapter.configValidate();
|
|
167
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
167
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, res, () => {
|
|
168
168
|
console.log(res.valid ? "configuration is valid" : "configuration is invalid");
|
|
169
169
|
});
|
|
170
170
|
return;
|
|
@@ -172,8 +172,8 @@ export async function handleConfigCommand(
|
|
|
172
172
|
|
|
173
173
|
if (sub === "diff") {
|
|
174
174
|
const res = await adapter.configDiff();
|
|
175
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
176
|
-
|
|
175
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, res, () => {
|
|
176
|
+
renderContextBanner(ctx.context);
|
|
177
177
|
console.log(JSON.stringify(res.diff, null, 2));
|
|
178
178
|
});
|
|
179
179
|
return;
|
|
@@ -186,8 +186,8 @@ export async function handleConfigCommand(
|
|
|
186
186
|
throw new Error("usage: skillmux config set <key> <value> [--dry-run]");
|
|
187
187
|
}
|
|
188
188
|
const res = await adapter.configSet(key, value, { dryRun: ctx.dryRun });
|
|
189
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
190
|
-
|
|
189
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, res, () => {
|
|
190
|
+
renderContextBanner(ctx.context);
|
|
191
191
|
const prefix = ctx.dryRun ? "[dry-run] " : "";
|
|
192
192
|
console.log(
|
|
193
193
|
`${prefix}${key}: ${JSON.stringify(res.prior_val)} -> ${JSON.stringify(res.resulting_val)}`,
|
|
@@ -201,8 +201,8 @@ export async function handleConfigCommand(
|
|
|
201
201
|
|
|
202
202
|
if (sub === "status") {
|
|
203
203
|
const res = await adapter.configStatus();
|
|
204
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
205
|
-
|
|
204
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, res, () => {
|
|
205
|
+
renderContextBanner(ctx.context);
|
|
206
206
|
console.log(`runtime: ${res.runtime}`);
|
|
207
207
|
console.log(`deployment runtime: ${res.deployment_runtime}`);
|
|
208
208
|
console.log(`image variant: ${res.image_variant ?? "none"}`);
|
|
@@ -212,5 +212,13 @@ export async function handleConfigCommand(
|
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
throw
|
|
215
|
+
throw unknownSubcommandError("config", sub, [
|
|
216
|
+
"init",
|
|
217
|
+
"show",
|
|
218
|
+
"get",
|
|
219
|
+
"set",
|
|
220
|
+
"validate",
|
|
221
|
+
"diff",
|
|
222
|
+
"status",
|
|
223
|
+
]);
|
|
216
224
|
}
|
package/src/commands/context.ts
CHANGED
|
@@ -9,18 +9,19 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
emitSuccess,
|
|
11
11
|
renderTable,
|
|
12
|
-
|
|
12
|
+
renderContextBanner,
|
|
13
|
+
unknownSubcommandError,
|
|
13
14
|
} from "../output";
|
|
14
15
|
|
|
15
16
|
export async function handleContextCommand(
|
|
16
17
|
sub: string,
|
|
17
18
|
args: string[],
|
|
18
|
-
ctx: {
|
|
19
|
+
ctx: { context: ResolvedContext; isJson: boolean },
|
|
19
20
|
) {
|
|
20
21
|
if (sub === "list") {
|
|
21
22
|
const contexts = await listContexts();
|
|
22
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
23
|
-
|
|
23
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, contexts, () => {
|
|
24
|
+
renderContextBanner(ctx.context);
|
|
24
25
|
renderTable(
|
|
25
26
|
[
|
|
26
27
|
{ key: "name", header: "NAME" },
|
|
@@ -40,8 +41,8 @@ export async function handleContextCommand(
|
|
|
40
41
|
|
|
41
42
|
if (sub === "current") {
|
|
42
43
|
const current = await getCurrentContext();
|
|
43
|
-
emitSuccess({ isJson: ctx.isJson,
|
|
44
|
-
|
|
44
|
+
emitSuccess({ isJson: ctx.isJson, context: ctx.context }, current, () => {
|
|
45
|
+
renderContextBanner(ctx.context);
|
|
45
46
|
console.log(`Current context: ${current.name} (${current.server})`);
|
|
46
47
|
});
|
|
47
48
|
return;
|
|
@@ -62,7 +63,7 @@ export async function handleContextCommand(
|
|
|
62
63
|
}
|
|
63
64
|
await addContext(name, { server, token_env: tokenEnv });
|
|
64
65
|
emitSuccess(
|
|
65
|
-
{ isJson: ctx.isJson,
|
|
66
|
+
{ isJson: ctx.isJson, context: ctx.context },
|
|
66
67
|
{ name, server, token_env: tokenEnv },
|
|
67
68
|
() => {
|
|
68
69
|
console.log(`Added context "${name}" -> ${server}`);
|
|
@@ -76,7 +77,7 @@ export async function handleContextCommand(
|
|
|
76
77
|
if (!name) throw new Error("usage: skillmux context use <name>");
|
|
77
78
|
await useContext(name);
|
|
78
79
|
emitSuccess(
|
|
79
|
-
{ isJson: ctx.isJson,
|
|
80
|
+
{ isJson: ctx.isJson, context: ctx.context },
|
|
80
81
|
{ default_context: name },
|
|
81
82
|
() => {
|
|
82
83
|
console.log(`Switched default context to "${name}"`);
|
|
@@ -90,7 +91,7 @@ export async function handleContextCommand(
|
|
|
90
91
|
if (!name) throw new Error("usage: skillmux context remove <name>");
|
|
91
92
|
await removeContext(name);
|
|
92
93
|
emitSuccess(
|
|
93
|
-
{ isJson: ctx.isJson,
|
|
94
|
+
{ isJson: ctx.isJson, context: ctx.context },
|
|
94
95
|
{ removed: name },
|
|
95
96
|
() => {
|
|
96
97
|
console.log(`Removed context "${name}"`);
|
|
@@ -99,5 +100,5 @@ export async function handleContextCommand(
|
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
throw
|
|
103
|
+
throw unknownSubcommandError("context", sub, ["add", "list", "current", "use", "remove"]);
|
|
103
104
|
}
|
package/src/commands/core.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expandHome } from "../config";
|
|
2
2
|
import { pinCore, unpinCore, validateManifest, writeManifestAtomic } from "../manifest";
|
|
3
|
-
import { emitSuccess } from "../output";
|
|
3
|
+
import { emitSuccess, unknownSubcommandError } from "../output";
|
|
4
4
|
import { confirmIfNeeded, loadManifestContext } from "./shared";
|
|
5
5
|
export async function runCore(
|
|
6
6
|
subCommand: string,
|
|
@@ -8,7 +8,7 @@ export async function runCore(
|
|
|
8
8
|
options: { isJson: boolean; dryRun: boolean },
|
|
9
9
|
): Promise<void> {
|
|
10
10
|
if (subCommand !== "pin" && subCommand !== "unpin") {
|
|
11
|
-
throw
|
|
11
|
+
throw unknownSubcommandError("core", subCommand, ["pin", "unpin"]);
|
|
12
12
|
}
|
|
13
13
|
const skillIds = args.filter((arg) => !arg.startsWith("-"));
|
|
14
14
|
if (skillIds.length === 0) {
|