@klhapp/skillmux 1.12.0 → 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.
Files changed (103) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +18 -9
  3. package/bin/skillmux.js +89 -0
  4. package/docs/README.md +4 -4
  5. package/docs/cli.md +14 -4
  6. package/docs/concepts.md +2 -2
  7. package/docs/configuration.md +18 -3
  8. package/docs/deployment.md +3 -3
  9. package/docs/getting-started.md +32 -7
  10. package/docs/releasing.md +17 -2
  11. package/docs/sdd/cli-remote-target-parity/schema-bucket-b.yaml +297 -0
  12. package/docs/sdd/cli-remote-target-parity/spec-bucket-b.md +95 -0
  13. package/docs/sdd/cli-remote-target-parity/spec.md +111 -0
  14. package/docs/sdd/cli-remote-target-parity/think.md +183 -0
  15. package/docs/sdd/cli-surface-consistency/think.md +149 -0
  16. package/docs/sdd/config-authority-rerank-resilience/plan.md +338 -0
  17. package/docs/sdd/config-authority-rerank-resilience/review.md +124 -0
  18. package/docs/sdd/config-authority-rerank-resilience/spec.md +78 -0
  19. package/docs/sdd/feat-packaging-deployment/review.md +95 -0
  20. package/docs/sdd/fetch-outcome-flywheel/review.md +103 -0
  21. package/docs/sdd/fetch-outcome-flywheel/schema.json +347 -0
  22. package/docs/sdd/fetch-outcome-flywheel/spec.md +185 -0
  23. package/docs/sdd/npm-native-binaries/review.md +103 -0
  24. package/docs/sdd/npm-native-binaries/spec.md +148 -0
  25. package/docs/sdd/ranked-shortlist-2/pr3-calibration-removal.md +56 -0
  26. package/docs/sdd/refactor-builtin-target-paths/spec.md +41 -0
  27. package/docs/sdd/runtime-resource-hardening/review.md +86 -0
  28. package/docs/sdd/runtime-resource-hardening/spec.md +43 -0
  29. package/docs/sdd/security-hardening-phase/think.md +129 -0
  30. package/docs/sdd/security-observability/review.md +96 -0
  31. package/docs/sdd/security-observability/spec.md +55 -0
  32. package/docs/sdd/skill-provenance-update/review.md +152 -0
  33. package/docs/sdd/skill-provenance-update/schema.json +193 -0
  34. package/docs/sdd/skill-provenance-update/spec.md +134 -0
  35. package/docs/sdd/supply-chain-hardening/review.md +50 -0
  36. package/docs/sdd/supply-chain-hardening/spec.md +42 -0
  37. package/docs/sdd/target-marker-rehome/spec.md +33 -0
  38. package/docs/troubleshooting.md +18 -2
  39. package/package.json +15 -8
  40. package/src/adapters.ts +0 -438
  41. package/src/audit.ts +0 -21
  42. package/src/cli.ts +0 -928
  43. package/src/clients.ts +0 -368
  44. package/src/commands/audit.ts +0 -85
  45. package/src/commands/config.ts +0 -224
  46. package/src/commands/context.ts +0 -104
  47. package/src/commands/core.ts +0 -56
  48. package/src/commands/doctor.ts +0 -97
  49. package/src/commands/eval.ts +0 -88
  50. package/src/commands/init.ts +0 -672
  51. package/src/commands/install.ts +0 -155
  52. package/src/commands/local-vault.ts +0 -60
  53. package/src/commands/models.ts +0 -10
  54. package/src/commands/outdated.ts +0 -115
  55. package/src/commands/project.ts +0 -580
  56. package/src/commands/report.ts +0 -66
  57. package/src/commands/scan.ts +0 -71
  58. package/src/commands/shared.ts +0 -38
  59. package/src/commands/skill.ts +0 -33
  60. package/src/commands/sync.ts +0 -233
  61. package/src/commands/target.ts +0 -225
  62. package/src/commands/update.ts +0 -277
  63. package/src/completions.ts +0 -196
  64. package/src/concurrency-limiter.ts +0 -61
  65. package/src/config-service.ts +0 -398
  66. package/src/config-watcher.ts +0 -253
  67. package/src/config.ts +0 -583
  68. package/src/context.ts +0 -188
  69. package/src/db-audit.ts +0 -286
  70. package/src/db-index.ts +0 -238
  71. package/src/db.ts +0 -3
  72. package/src/deployment.ts +0 -39
  73. package/src/doctor.ts +0 -205
  74. package/src/eval.ts +0 -302
  75. package/src/global-flags.ts +0 -46
  76. package/src/init-agents.ts +0 -329
  77. package/src/init-instructions.ts +0 -192
  78. package/src/init.ts +0 -390
  79. package/src/install.ts +0 -232
  80. package/src/lifecycle.ts +0 -51
  81. package/src/logger.ts +0 -26
  82. package/src/manifest.ts +0 -359
  83. package/src/mcp-registration.ts +0 -89
  84. package/src/metrics.ts +0 -121
  85. package/src/models.ts +0 -20
  86. package/src/output.ts +0 -214
  87. package/src/project-setup.ts +0 -36
  88. package/src/prompts.ts +0 -124
  89. package/src/provenance.ts +0 -99
  90. package/src/rate-limiter.ts +0 -137
  91. package/src/readiness.ts +0 -30
  92. package/src/redact.ts +0 -52
  93. package/src/router-core.ts +0 -666
  94. package/src/rrf.ts +0 -31
  95. package/src/scan.ts +0 -300
  96. package/src/server.ts +0 -930
  97. package/src/setup.ts +0 -145
  98. package/src/snapshot.ts +0 -135
  99. package/src/stats.ts +0 -340
  100. package/src/sync.ts +0 -482
  101. package/src/toml-writer.ts +0 -51
  102. package/src/types.ts +0 -212
  103. package/src/vault.ts +0 -207
@@ -1,155 +0,0 @@
1
- import { rmSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { expandHome, loadConfig } from "../config";
4
- import {
5
- assertHostAllowed,
6
- cloneToTemp,
7
- deriveRepoName,
8
- installIntoVault,
9
- isLocalFileUrl,
10
- resolveCloneCommit,
11
- resolveRepoSource,
12
- resolveSkillDir,
13
- validateSkillCandidate,
14
- } from "../install";
15
- import { emitSuccess } from "../output";
16
- import { hashSkillContent, writeSkillOrigin } from "../provenance";
17
- import {
18
- FAIL_ON_USAGE,
19
- parseFailOn,
20
- renderScanText,
21
- resolveMutatingFailOn,
22
- scanExitCode,
23
- type FailOnOption,
24
- } from "../scan";
25
- import { isGlobalFlag } from "../global-flags";
26
- import { confirmIfNeeded } from "./shared";
27
-
28
- function parseInstallArgs(args: string[]): {
29
- repo?: string;
30
- force: boolean;
31
- dryRun: boolean;
32
- failOn?: FailOnOption;
33
- allowLocalSource: boolean;
34
- yes: boolean;
35
- } {
36
- let repo: string | undefined;
37
- let force = false;
38
- let dryRun = false;
39
- let failOn: FailOnOption | undefined;
40
- let allowLocalSource = false;
41
- let yes = false;
42
- for (let i = 0; i < args.length; i++) {
43
- const option = args[i];
44
- if (option === "--force") force = true;
45
- else if (option === "--yes") yes = true;
46
- else if (option === "--dry-run") dryRun = true;
47
- else if (option === "--allow-local-source") allowLocalSource = true;
48
- else if (option === "--fail-on") {
49
- failOn = parseFailOn(args[++i]);
50
- } else if (isGlobalFlag(option, "--json", "--verbose")) {
51
- // handled globally by main()'s isJson/isVerbose flags; recognized here so they aren't rejected
52
- } else if (option?.startsWith("--")) {
53
- throw new Error(`unknown install option: ${option}`);
54
- } else if (repo !== undefined) {
55
- throw new Error("skillmux install accepts at most one <repo> argument");
56
- } else {
57
- repo = option;
58
- }
59
- }
60
- return { repo, force, dryRun, failOn, allowLocalSource, yes };
61
- }
62
-
63
- export async function runInstall(
64
- args: string[],
65
- options: { isJson: boolean },
66
- ): Promise<void> {
67
- const { repo, force, dryRun, failOn, allowLocalSource, yes } = parseInstallArgs(args);
68
- if (!repo) {
69
- throw new Error(
70
- `usage: skillmux install <repo>[/path] [--yes] [--force] [--fail-on ${FAIL_ON_USAGE}] [--dry-run] [--allow-local-source] [--json]`,
71
- );
72
- }
73
-
74
- const source = resolveRepoSource(repo);
75
- if (!allowLocalSource && isLocalFileUrl(source.url)) {
76
- throw new Error(
77
- `"${repo}" is a local (file://) source — pass --allow-local-source to install from it`,
78
- );
79
- }
80
- const config = await loadConfig();
81
- assertHostAllowed(source.url, config.egress?.allowed_hosts);
82
- const cloneDir = await cloneToTemp(source.url);
83
- try {
84
- const resolved = resolveSkillDir(
85
- cloneDir,
86
- deriveRepoName(source.url),
87
- source.skillPath,
88
- );
89
- const { findings } = await validateSkillCandidate(
90
- resolved.skillId,
91
- resolved.dir,
92
- );
93
- if (!options.isJson) console.log(renderScanText({ scanned: 1, findings }));
94
-
95
- // Defaults to blocking on a high-severity finding. Printing the findings
96
- // and installing anyway is the wrong default for the command that pulls
97
- // remote content into the vault.
98
- const effectiveFailOn = resolveMutatingFailOn(failOn);
99
- if (scanExitCode(findings, effectiveFailOn) !== 0) {
100
- process.exitCode = 1;
101
- console.error(
102
- `aborting install: a finding met the --fail-on ${effectiveFailOn} threshold` +
103
- (failOn === undefined ? " (the default)" : "") +
104
- `; re-run with --fail-on none to install anyway`,
105
- );
106
- return;
107
- }
108
-
109
- const vaultPath = expandHome(config.vault_path);
110
- if (dryRun) {
111
- const plannedPath = join(vaultPath, resolved.skillId);
112
- emitSuccess(
113
- { isJson: options.isJson },
114
- { skill_id: resolved.skillId, would_install_at: plannedPath },
115
- () =>
116
- console.log(
117
- `dry-run: would install "${resolved.skillId}" into ${plannedPath}`,
118
- ),
119
- );
120
- return;
121
- }
122
-
123
- // Confirm after the scan, so an interactive user decides with the findings
124
- // already on screen, and after the --dry-run return, which writes nothing.
125
- const proceed = await confirmIfNeeded({
126
- confirmed: yes,
127
- isJson: options.isJson,
128
- prompt: `install:\n ${resolved.skillId} <- ${source.url}\n?`,
129
- nonInteractiveError: "skillmux install requires --yes when run non-interactively",
130
- });
131
- if (!proceed) return;
132
-
133
- const commit = resolveCloneCommit(cloneDir);
134
- const targetDir = installIntoVault(
135
- vaultPath,
136
- resolved.skillId,
137
- resolved.dir,
138
- force,
139
- );
140
- writeSkillOrigin(targetDir, {
141
- source_url: source.url,
142
- skill_path: source.skillPath,
143
- commit,
144
- installed_at: new Date().toISOString(),
145
- content_hash: hashSkillContent(targetDir),
146
- });
147
- emitSuccess(
148
- { isJson: options.isJson },
149
- { skill_id: resolved.skillId, installed_at: targetDir },
150
- () => console.log(`installed "${resolved.skillId}" into ${targetDir}`),
151
- );
152
- } finally {
153
- rmSync(cloneDir, { recursive: true, force: true });
154
- }
155
- }
@@ -1,60 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { expandHome, loadConfig } from "../config";
4
- import { emitSuccess } from "../output";
5
- import { writeLocalVaultMarker } from "../sync";
6
- import { confirmIfNeeded } from "./shared";
7
-
8
- export async function runLocalVaultInit(
9
- args: string[],
10
- options: { isJson: boolean; dryRun: boolean },
11
- ): Promise<void> {
12
- const path = args[0];
13
- if (!path) throw new Error("usage: skillmux local-vault init <path> --yes");
14
- const expanded = expandHome(path);
15
- const config = await loadConfig();
16
- const localVaultPaths = config.local_vault_paths.map(expandHome);
17
- if (!localVaultPaths.includes(expanded)) {
18
- throw new Error(
19
- `"${path}" is not one of the configured local_vault_paths — add it to config.toml first`,
20
- );
21
- }
22
- if (!existsSync(expanded)) throw new Error(`"${path}" does not exist`);
23
- const markerPath = join(expanded, ".skillmux");
24
- if (options.dryRun) {
25
- emitSuccess(
26
- { isJson: options.isJson },
27
- {
28
- marker_path: markerPath,
29
- vault_path: expandHome(config.vault_path),
30
- },
31
- () =>
32
- console.log(
33
- `local-vault init: ${markerPath} (role: local_vault, vault_path: ${expandHome(config.vault_path)}) (dry-run)`,
34
- ),
35
- );
36
- return;
37
- }
38
- if (
39
- !(await confirmIfNeeded({
40
- confirmed: args.includes("--yes"),
41
- isJson: options.isJson,
42
- prompt: `mark ${expanded} as a local_vault (role: local_vault, vault_path: ${expandHome(config.vault_path)})?`,
43
- nonInteractiveError:
44
- "skillmux local-vault init requires --yes when run non-interactively",
45
- }))
46
- )
47
- return;
48
- writeLocalVaultMarker(expanded, expandHome(config.vault_path));
49
- emitSuccess(
50
- { isJson: options.isJson },
51
- {
52
- marker_path: markerPath,
53
- vault_path: expandHome(config.vault_path),
54
- },
55
- () =>
56
- console.log(
57
- `wrote ${markerPath} (role: local_vault, vault_path: ${expandHome(config.vault_path)})`,
58
- ),
59
- );
60
- }
@@ -1,10 +0,0 @@
1
- import { loadConfig } from "../config";
2
- import { downloadLocalModels } from "../models";
3
- import { emitSuccess } from "../output";
4
-
5
- export async function runModelDownload(options: { isJson: boolean }): Promise<void> {
6
- const cacheDir = await downloadLocalModels(await loadConfig());
7
- emitSuccess({ isJson: options.isJson }, { cache_dir: cacheDir }, () =>
8
- console.log(`models ready in ${cacheDir}`),
9
- );
10
- }
@@ -1,115 +0,0 @@
1
- import { readdirSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { expandHome, loadConfig } from "../config";
4
- import { assertHostAllowed, isLocalFileUrl, remoteHeadCommit } from "../install";
5
- import { emitSuccess } from "../output";
6
- import { readSkillOrigin } from "../provenance";
7
- import { SKILL_ID_PATTERN } from "../vault";
8
- import { isGlobalFlag } from "../global-flags";
9
-
10
- export interface OutdatedCheckResult {
11
- skill_id: string;
12
- source_url: string;
13
- recorded_commit: string;
14
- remote_commit: string | null;
15
- status: "up_to_date" | "outdated" | "check_failed" | "local_source_skipped";
16
- reason: string | null;
17
- }
18
-
19
- function vaultSkillIds(vaultPath: string): string[] {
20
- return readdirSync(vaultPath, { withFileTypes: true })
21
- .filter((entry) => entry.isDirectory() && SKILL_ID_PATTERN.test(entry.name))
22
- .map((entry) => entry.name)
23
- .sort();
24
- }
25
-
26
- export async function checkOutdated(
27
- vaultPath: string,
28
- options: { allowLocalSource?: boolean; allowedHosts?: string[] } = {},
29
- ): Promise<OutdatedCheckResult[]> {
30
- const results: OutdatedCheckResult[] = [];
31
- for (const skillId of vaultSkillIds(vaultPath)) {
32
- let origin: ReturnType<typeof readSkillOrigin>;
33
- try {
34
- origin = readSkillOrigin(join(vaultPath, skillId));
35
- } catch (error) {
36
- // A corrupt or unreadable sidecar is this skill's problem alone — never
37
- // let it abort the check for every other skill in the vault (AC3/AC4's
38
- // per-skill isolation applies to local parse failures too, not just
39
- // unreachable remotes).
40
- results.push({
41
- skill_id: skillId,
42
- source_url: "",
43
- recorded_commit: "",
44
- remote_commit: null,
45
- status: "check_failed",
46
- reason: `.skillmux-origin: ${error instanceof Error ? error.message : String(error)}`,
47
- });
48
- continue;
49
- }
50
- if (!origin) continue;
51
-
52
- if (!options.allowLocalSource && isLocalFileUrl(origin.source_url)) {
53
- results.push({
54
- skill_id: skillId,
55
- source_url: origin.source_url,
56
- recorded_commit: origin.commit,
57
- remote_commit: null,
58
- status: "local_source_skipped",
59
- reason: "source_url is a local file:// path — skipped by default; pass --allow-local-source to check it",
60
- });
61
- continue;
62
- }
63
-
64
- let remoteCommit: string | null = null;
65
- let status: OutdatedCheckResult["status"];
66
- let reason: string | null = null;
67
- try {
68
- assertHostAllowed(origin.source_url, options.allowedHosts);
69
- remoteCommit = await remoteHeadCommit(origin.source_url);
70
- status = remoteCommit === origin.commit ? "up_to_date" : "outdated";
71
- } catch (error) {
72
- status = "check_failed";
73
- reason = error instanceof Error ? error.message : String(error);
74
- }
75
-
76
- results.push({
77
- skill_id: skillId,
78
- source_url: origin.source_url,
79
- recorded_commit: origin.commit,
80
- remote_commit: remoteCommit,
81
- status,
82
- reason,
83
- });
84
- }
85
- return results;
86
- }
87
-
88
- export async function runOutdated(args: string[], options: { isJson: boolean }): Promise<void> {
89
- let allowLocalSource = false;
90
- for (const arg of args) {
91
- if (isGlobalFlag(arg, "--json")) continue;
92
- if (arg === "--allow-local-source") {
93
- allowLocalSource = true;
94
- continue;
95
- }
96
- throw new Error(`unknown outdated option: ${arg}`);
97
- }
98
-
99
- const config = await loadConfig();
100
- const vaultPath = expandHome(config.vault_path);
101
- const skills = await checkOutdated(vaultPath, { allowLocalSource, allowedHosts: config.egress?.allowed_hosts });
102
- const checksFailed = skills.filter((s) => s.status === "check_failed").length;
103
- process.exitCode = checksFailed > 0 ? 1 : 0;
104
-
105
- emitSuccess({ isJson: options.isJson }, { skills, checks_failed: checksFailed }, () => {
106
- if (skills.length === 0) {
107
- console.log("outdated: no vault skills carry provenance");
108
- return;
109
- }
110
- for (const s of skills) {
111
- const suffix = s.status === "check_failed" ? ` — ${s.reason}` : "";
112
- console.log(`[${s.status}] ${s.skill_id}${suffix}`);
113
- }
114
- });
115
- }