@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/commands/update.ts
DELETED
|
@@ -1,277 +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
|
-
installIntoVault,
|
|
8
|
-
isLocalFileUrl,
|
|
9
|
-
remoteHeadCommit,
|
|
10
|
-
resolveCloneCommit,
|
|
11
|
-
resolveSkillDir,
|
|
12
|
-
validateSkillCandidate,
|
|
13
|
-
} from "../install";
|
|
14
|
-
import { emitSuccess } from "../output";
|
|
15
|
-
import { hashSkillContent, readSkillOrigin, writeSkillOrigin } from "../provenance";
|
|
16
|
-
import type { SkillOrigin } from "../provenance";
|
|
17
|
-
import {
|
|
18
|
-
FAIL_ON_USAGE,
|
|
19
|
-
parseFailOn,
|
|
20
|
-
resolveMutatingFailOn,
|
|
21
|
-
scanExitCode,
|
|
22
|
-
type FailOnOption,
|
|
23
|
-
type ScanFinding,
|
|
24
|
-
type ScanSeverity,
|
|
25
|
-
} from "../scan";
|
|
26
|
-
import { SKILL_ID_PATTERN } from "../vault";
|
|
27
|
-
import { confirmIfNeeded } from "./shared";
|
|
28
|
-
import { checkOutdated } from "./outdated";
|
|
29
|
-
import { isGlobalFlag } from "../global-flags";
|
|
30
|
-
|
|
31
|
-
type UpdateKind = "update" | "up_to_date" | "skip_drift" | "skip_scan_failed" | "skip_read_error";
|
|
32
|
-
|
|
33
|
-
interface UpdatePlanItem {
|
|
34
|
-
skillId: string;
|
|
35
|
-
oldCommit: string;
|
|
36
|
-
newCommit: string;
|
|
37
|
-
contentChanged: boolean;
|
|
38
|
-
kind: UpdateKind;
|
|
39
|
-
findings?: ScanFinding[];
|
|
40
|
-
reason?: string;
|
|
41
|
-
cloneDir: string | null;
|
|
42
|
-
fetchedDir: string | null;
|
|
43
|
-
origin: SkillOrigin;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function resolveCandidateOrigins(
|
|
47
|
-
vaultPath: string,
|
|
48
|
-
skillId: string | undefined,
|
|
49
|
-
allowLocalSource: boolean,
|
|
50
|
-
allowedHosts: string[] | undefined,
|
|
51
|
-
): Promise<{ skillId: string; origin: SkillOrigin }[]> {
|
|
52
|
-
if (skillId) {
|
|
53
|
-
// skillId (the CLI's positional <skill-id>) is joined straight into vaultPath
|
|
54
|
-
// below, and that join ultimately reaches installIntoVault's rmSync(recursive)
|
|
55
|
-
// + cpSync on the write path — a "../"-shaped value escapes the vault and lets
|
|
56
|
-
// `skillmux update` delete and overwrite an arbitrary directory on disk. Batch
|
|
57
|
-
// mode never hits this because checkOutdated only enumerates real vault entries
|
|
58
|
-
// (already SKILL_ID_PATTERN-filtered); the explicit single-skill path is the
|
|
59
|
-
// only one that takes this string directly from argv, so validate it here.
|
|
60
|
-
if (!SKILL_ID_PATTERN.test(skillId)) {
|
|
61
|
-
throw new Error(`invalid skill id "${skillId}": expected lowercase letters, digits, and hyphens only`);
|
|
62
|
-
}
|
|
63
|
-
let origin: SkillOrigin | null;
|
|
64
|
-
try {
|
|
65
|
-
origin = readSkillOrigin(join(vaultPath, skillId));
|
|
66
|
-
} catch (error) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`"${skillId}" has a corrupt .skillmux-origin sidecar: ${error instanceof Error ? error.message : String(error)}`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
if (!origin) {
|
|
72
|
-
throw new Error(`"${skillId}" has no origin recorded — was this skill installed via "skillmux install"?`);
|
|
73
|
-
}
|
|
74
|
-
if (!allowLocalSource && isLocalFileUrl(origin.source_url)) {
|
|
75
|
-
throw new Error(`"${skillId}" has a local (file://) source — pass --allow-local-source to update it`);
|
|
76
|
-
}
|
|
77
|
-
return [{ skillId, origin }];
|
|
78
|
-
}
|
|
79
|
-
const outdated = await checkOutdated(vaultPath, { allowLocalSource, allowedHosts });
|
|
80
|
-
return outdated
|
|
81
|
-
.filter((result) => result.status === "outdated")
|
|
82
|
-
.map((result) => ({ skillId: result.skill_id, origin: readSkillOrigin(join(vaultPath, result.skill_id))! }));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function buildPlan(
|
|
86
|
-
vaultPath: string,
|
|
87
|
-
candidates: { skillId: string; origin: SkillOrigin }[],
|
|
88
|
-
failOn: ScanSeverity | undefined,
|
|
89
|
-
force: boolean,
|
|
90
|
-
allowedHosts: string[] | undefined,
|
|
91
|
-
): Promise<UpdatePlanItem[]> {
|
|
92
|
-
const plan: UpdatePlanItem[] = [];
|
|
93
|
-
for (const { skillId, origin } of candidates) {
|
|
94
|
-
const skillDir = join(vaultPath, skillId);
|
|
95
|
-
let currentHash: string;
|
|
96
|
-
try {
|
|
97
|
-
currentHash = hashSkillContent(skillDir);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
// A skill whose on-disk content can't be safely read (e.g. SKILL.md was
|
|
100
|
-
// swapped for a symlink after install — same threat model as AC7's drift
|
|
101
|
-
// check) is this skill's problem alone, matching checkOutdated's per-skill
|
|
102
|
-
// isolation: never let it abort the batch for every other candidate.
|
|
103
|
-
plan.push({
|
|
104
|
-
skillId,
|
|
105
|
-
oldCommit: origin.commit,
|
|
106
|
-
newCommit: origin.commit,
|
|
107
|
-
contentChanged: false,
|
|
108
|
-
kind: "skip_read_error",
|
|
109
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
110
|
-
cloneDir: null,
|
|
111
|
-
fetchedDir: null,
|
|
112
|
-
origin,
|
|
113
|
-
});
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Drift is a purely local comparison — check it before fetching anything,
|
|
118
|
-
// so a drifted (and therefore skipped) skill never pays for a clone.
|
|
119
|
-
if (currentHash !== origin.content_hash && !force) {
|
|
120
|
-
plan.push({
|
|
121
|
-
skillId,
|
|
122
|
-
oldCommit: origin.commit,
|
|
123
|
-
newCommit: await remoteHeadCommit(origin.source_url),
|
|
124
|
-
contentChanged: false,
|
|
125
|
-
kind: "skip_drift",
|
|
126
|
-
cloneDir: null,
|
|
127
|
-
fetchedDir: null,
|
|
128
|
-
origin,
|
|
129
|
-
});
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
assertHostAllowed(origin.source_url, allowedHosts);
|
|
134
|
-
const cloneDir = await cloneToTemp(origin.source_url);
|
|
135
|
-
const resolved = resolveSkillDir(cloneDir, skillId, origin.skill_path);
|
|
136
|
-
const base = {
|
|
137
|
-
skillId,
|
|
138
|
-
oldCommit: origin.commit,
|
|
139
|
-
newCommit: resolveCloneCommit(cloneDir),
|
|
140
|
-
cloneDir,
|
|
141
|
-
fetchedDir: resolved.dir,
|
|
142
|
-
origin,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const { findings } = await validateSkillCandidate(skillId, resolved.dir);
|
|
146
|
-
if (scanExitCode(findings, failOn) !== 0) {
|
|
147
|
-
plan.push({ ...base, contentChanged: false, kind: "skip_scan_failed", findings });
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const contentChanged = hashSkillContent(resolved.dir) !== currentHash;
|
|
152
|
-
plan.push({
|
|
153
|
-
...base,
|
|
154
|
-
contentChanged,
|
|
155
|
-
kind: base.newCommit === origin.commit && !contentChanged ? "up_to_date" : "update",
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return plan;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function buildConfirmPrompt(toWrite: Pick<UpdatePlanItem, "skillId" | "origin">[]): string {
|
|
162
|
-
const lines = toWrite.map((item) => ` ${item.skillId} <- ${item.origin.source_url}`);
|
|
163
|
-
return `update:\n${lines.join("\n")}\n?`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function statusFor(kind: UpdateKind, dryRun: boolean): string {
|
|
167
|
-
switch (kind) {
|
|
168
|
-
case "update":
|
|
169
|
-
return dryRun ? "would_update" : "updated";
|
|
170
|
-
case "skip_drift":
|
|
171
|
-
return dryRun ? "would_skip_drift" : "skipped_drift";
|
|
172
|
-
case "skip_scan_failed":
|
|
173
|
-
return dryRun ? "would_skip_scan_failed" : "skipped_scan_failed";
|
|
174
|
-
case "skip_read_error":
|
|
175
|
-
return "skipped_read_error";
|
|
176
|
-
default:
|
|
177
|
-
return "up_to_date";
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function parseUpdateArgs(args: string[]): {
|
|
182
|
-
skillId?: string;
|
|
183
|
-
yes: boolean;
|
|
184
|
-
dryRun: boolean;
|
|
185
|
-
force: boolean;
|
|
186
|
-
failOn?: FailOnOption;
|
|
187
|
-
allowLocalSource: boolean;
|
|
188
|
-
} {
|
|
189
|
-
let skillId: string | undefined;
|
|
190
|
-
let yes = false;
|
|
191
|
-
let dryRun = false;
|
|
192
|
-
let force = false;
|
|
193
|
-
let failOn: FailOnOption | undefined;
|
|
194
|
-
let allowLocalSource = false;
|
|
195
|
-
for (let i = 0; i < args.length; i++) {
|
|
196
|
-
const arg = args[i];
|
|
197
|
-
if (arg === "--yes") yes = true;
|
|
198
|
-
else if (arg === "--dry-run") dryRun = true;
|
|
199
|
-
else if (arg === "--force") force = true;
|
|
200
|
-
else if (arg === "--allow-local-source") allowLocalSource = true;
|
|
201
|
-
else if (arg === "--fail-on") {
|
|
202
|
-
failOn = parseFailOn(args[++i]);
|
|
203
|
-
} else if (isGlobalFlag(arg, "--json")) {
|
|
204
|
-
// handled globally
|
|
205
|
-
} else if (arg?.startsWith("--")) {
|
|
206
|
-
throw new Error(`unknown update option: ${arg}`);
|
|
207
|
-
} else if (skillId !== undefined) {
|
|
208
|
-
throw new Error("skillmux update accepts at most one <skill-id> argument");
|
|
209
|
-
} else {
|
|
210
|
-
skillId = arg;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return { skillId, yes, dryRun, force, failOn, allowLocalSource };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export async function runUpdate(args: string[], options: { isJson: boolean }): Promise<void> {
|
|
217
|
-
const { skillId, yes, dryRun, force, failOn, allowLocalSource } = parseUpdateArgs(args);
|
|
218
|
-
const config = await loadConfig();
|
|
219
|
-
const vaultPath = expandHome(config.vault_path);
|
|
220
|
-
|
|
221
|
-
const candidates = await resolveCandidateOrigins(vaultPath, skillId, allowLocalSource, config.egress?.allowed_hosts);
|
|
222
|
-
// Same default as install: block on a high-severity finding unless the
|
|
223
|
-
// caller explicitly opts out with --fail-on none.
|
|
224
|
-
const effectiveFailOn = resolveMutatingFailOn(failOn);
|
|
225
|
-
const plan = await buildPlan(vaultPath, candidates, effectiveFailOn, force, config.egress?.allowed_hosts);
|
|
226
|
-
try {
|
|
227
|
-
const toWrite = plan.filter((item) => item.kind === "update");
|
|
228
|
-
|
|
229
|
-
if (!dryRun && toWrite.length > 0) {
|
|
230
|
-
const proceed = await confirmIfNeeded({
|
|
231
|
-
confirmed: yes,
|
|
232
|
-
isJson: options.isJson,
|
|
233
|
-
prompt: buildConfirmPrompt(toWrite),
|
|
234
|
-
nonInteractiveError: "skillmux update requires --yes when run non-interactively",
|
|
235
|
-
});
|
|
236
|
-
if (!proceed) return;
|
|
237
|
-
|
|
238
|
-
for (const item of toWrite) {
|
|
239
|
-
// toWrite is filtered to kind === "update", which is only ever set after
|
|
240
|
-
// a successful clone above, so fetchedDir is always populated here.
|
|
241
|
-
const targetDir = installIntoVault(vaultPath, item.skillId, item.fetchedDir as string, true);
|
|
242
|
-
writeSkillOrigin(targetDir, {
|
|
243
|
-
source_url: item.origin.source_url,
|
|
244
|
-
skill_path: item.origin.skill_path,
|
|
245
|
-
commit: item.newCommit,
|
|
246
|
-
installed_at: new Date().toISOString(),
|
|
247
|
-
content_hash: hashSkillContent(targetDir),
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const skills = plan.map((item) => ({
|
|
253
|
-
skill_id: item.skillId,
|
|
254
|
-
source_url: item.origin.source_url,
|
|
255
|
-
old_commit: item.oldCommit,
|
|
256
|
-
new_commit: item.newCommit,
|
|
257
|
-
content_changed: item.contentChanged,
|
|
258
|
-
status: statusFor(item.kind, dryRun),
|
|
259
|
-
...(item.findings ? { findings: item.findings } : {}),
|
|
260
|
-
...(item.reason ? { reason: item.reason } : {}),
|
|
261
|
-
}));
|
|
262
|
-
|
|
263
|
-
emitSuccess({ isJson: options.isJson }, { dry_run: dryRun, skills }, () => {
|
|
264
|
-
if (skills.length === 0) {
|
|
265
|
-
console.log("update: nothing to do");
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
for (const s of skills) {
|
|
269
|
-
console.log(`[${s.status}] ${s.skill_id}`);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
} finally {
|
|
273
|
-
for (const item of plan) {
|
|
274
|
-
if (item.cloneDir) rmSync(item.cloneDir, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
package/src/completions.ts
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { MANAGED_PINS_AGENT_IDS, SUPPORTED_AGENT_IDS } from "./init-agents";
|
|
2
|
-
|
|
3
|
-
export type ShellType = "bash" | "zsh" | "fish";
|
|
4
|
-
|
|
5
|
-
const TOP_LEVEL_COMMANDS: { name: string; description: string }[] = [
|
|
6
|
-
{ name: "context", description: "Manage connection contexts" },
|
|
7
|
-
{ name: "config", description: "Manage configuration" },
|
|
8
|
-
{ name: "serve", description: "Start MCP server" },
|
|
9
|
-
{ name: "index", description: "Rebuild local search index" },
|
|
10
|
-
{ name: "sync", description: "Synchronize vault skills" },
|
|
11
|
-
{ name: "init", description: "Configure this machine and its agents" },
|
|
12
|
-
{ name: "project", description: "Configure project-scoped skills" },
|
|
13
|
-
{ name: "target", description: "Manage advanced skill-delivery targets" },
|
|
14
|
-
{ name: "core", description: "Pin/unpin skills into [core]" },
|
|
15
|
-
{ name: "report", description: "Generate usage stats" },
|
|
16
|
-
{ name: "scan", description: "Audit skills for issues" },
|
|
17
|
-
{ name: "install", description: "Install skills into vault" },
|
|
18
|
-
{ name: "eval", description: "Evaluate search accuracy" },
|
|
19
|
-
{ name: "doctor", description: "Check runtime health" },
|
|
20
|
-
{ name: "skill", description: "Show which root resolves a skill_id" },
|
|
21
|
-
{ name: "local-vault", description: "Manage local_vault_paths discoverability markers" },
|
|
22
|
-
{ name: "models", description: "Manage local models" },
|
|
23
|
-
{ name: "completions", description: "Generate shell completions" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export function generateCompletions(shell: ShellType): string {
|
|
27
|
-
if (shell === "bash") {
|
|
28
|
-
const opts = [...TOP_LEVEL_COMMANDS.map((c) => c.name), "--context", "--server", "--json", "--allow-insecure", "--verbose", "--dry-run", "--help"].join(" ");
|
|
29
|
-
return `# bash completion for skillmux
|
|
30
|
-
_skillmux_completions() {
|
|
31
|
-
local cur prev opts
|
|
32
|
-
COMPREPLY=()
|
|
33
|
-
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
34
|
-
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
35
|
-
opts="${opts}"
|
|
36
|
-
|
|
37
|
-
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
38
|
-
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
39
|
-
return 0
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
case "$prev" in
|
|
43
|
-
context)
|
|
44
|
-
COMPREPLY=( $(compgen -W "add list current use remove" -- "$cur") )
|
|
45
|
-
;;
|
|
46
|
-
config)
|
|
47
|
-
COMPREPLY=( $(compgen -W "init show get validate diff set status" -- "$cur") )
|
|
48
|
-
;;
|
|
49
|
-
completions)
|
|
50
|
-
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
51
|
-
;;
|
|
52
|
-
core)
|
|
53
|
-
COMPREPLY=( $(compgen -W "pin unpin" -- "$cur") )
|
|
54
|
-
;;
|
|
55
|
-
skill)
|
|
56
|
-
COMPREPLY=( $(compgen -W "which" -- "$cur") )
|
|
57
|
-
;;
|
|
58
|
-
project)
|
|
59
|
-
COMPREPLY=( $(compgen -W "init list show add-path remove-path pin unpin attach detach" -- "$cur") )
|
|
60
|
-
;;
|
|
61
|
-
target)
|
|
62
|
-
COMPREPLY=( $(compgen -W "list show add remove" -- "$cur") )
|
|
63
|
-
;;
|
|
64
|
-
local-vault)
|
|
65
|
-
COMPREPLY=( $(compgen -W "init" -- "$cur") )
|
|
66
|
-
;;
|
|
67
|
-
eval)
|
|
68
|
-
COMPREPLY=( $(compgen -W "promote" -- "$cur") )
|
|
69
|
-
;;
|
|
70
|
-
--agent)
|
|
71
|
-
if [ "\${COMP_WORDS[1]}" = "project" ]; then
|
|
72
|
-
COMPREPLY=( $(compgen -W "${MANAGED_PINS_AGENT_IDS.join(" ")}" -- "$cur") )
|
|
73
|
-
else
|
|
74
|
-
COMPREPLY=( $(compgen -W "${SUPPORTED_AGENT_IDS.join(" ")}" -- "$cur") )
|
|
75
|
-
fi
|
|
76
|
-
;;
|
|
77
|
-
esac
|
|
78
|
-
if [ "\${COMP_WORDS[1]}" = "init" ] && [ "\${#COMPREPLY[@]}" -eq 0 ]; then
|
|
79
|
-
COMPREPLY=( $(compgen -W "--agent --vault --core --migrate-full-vault --show-mcp-setup --register-mcp --no-instructions --no-sync --interactive --yes --dry-run --json" -- "$cur") )
|
|
80
|
-
fi
|
|
81
|
-
if [ "\${COMP_WORDS[1]}" = "project" ] && [ "\${COMP_WORDS[2]}" = "init" ]; then
|
|
82
|
-
COMPREPLY=( $(compgen -W "--name --skill --agent --target --register-mcp --no-sync --interactive --yes --dry-run --json" -- "$cur") )
|
|
83
|
-
fi
|
|
84
|
-
if [ "\${COMP_WORDS[1]}" = "eval" ] && [ "\${COMP_WORDS[2]}" = "promote" ]; then
|
|
85
|
-
COMPREPLY=( $(compgen -W "--since --out --dry-run --yes --json" -- "$cur") )
|
|
86
|
-
fi
|
|
87
|
-
}
|
|
88
|
-
complete -F _skillmux_completions skillmux
|
|
89
|
-
`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (shell === "zsh") {
|
|
93
|
-
const commands = TOP_LEVEL_COMMANDS.map((c) => ` '${c.name}:${c.description}'`).join("\n");
|
|
94
|
-
return `#compdef skillmux
|
|
95
|
-
_skillmux() {
|
|
96
|
-
local -a commands
|
|
97
|
-
commands=(
|
|
98
|
-
${commands}
|
|
99
|
-
)
|
|
100
|
-
if (( CURRENT == 2 )); then
|
|
101
|
-
_describe -t commands 'skillmux command' commands
|
|
102
|
-
elif [[ "$words[2]" == "init" ]]; then
|
|
103
|
-
_arguments \\
|
|
104
|
-
'*--agent[select an agent]:agent:(${SUPPORTED_AGENT_IDS.join(" ")})' \\
|
|
105
|
-
'--vault[vault directory]:directory:_directories' \\
|
|
106
|
-
'*--core[seed a core skill]:skill id:' \\
|
|
107
|
-
'--migrate-full-vault[convert a full-vault symlink to managed pins]' \\
|
|
108
|
-
'--show-mcp-setup[also print the MCP registration snippet]' \\
|
|
109
|
-
'--register-mcp[register skillmux via the agent own CLI]' \\
|
|
110
|
-
'--no-instructions[skip managed instruction files]' \\
|
|
111
|
-
'--no-sync[save setup without synchronizing targets]' \\
|
|
112
|
-
'--interactive[force guided setup]' \\
|
|
113
|
-
'--yes[apply without prompts]' \\
|
|
114
|
-
'--dry-run[print the plan without writing]' \\
|
|
115
|
-
'--json[emit a JSON envelope]'
|
|
116
|
-
elif [[ "$words[2]" == "project" && "$words[3]" == "init" ]]; then
|
|
117
|
-
_arguments \\
|
|
118
|
-
'1:project directory:_directories' \\
|
|
119
|
-
'--name[project group name]:group:' \\
|
|
120
|
-
'*--skill[project skill]:skill id:' \\
|
|
121
|
-
'*--agent[select an agent]:agent:(${MANAGED_PINS_AGENT_IDS.join(" ")})' \\
|
|
122
|
-
'*--target[select an advanced target]:target:' \\
|
|
123
|
-
'--register-mcp[register a project-scoped MCP server for claude-code]' \\
|
|
124
|
-
'--no-sync[save setup without synchronizing targets]' \\
|
|
125
|
-
'--interactive[force guided setup]' \\
|
|
126
|
-
'--yes[apply without prompts]' \\
|
|
127
|
-
'--dry-run[print the plan without writing]' \\
|
|
128
|
-
'--json[emit a JSON envelope]'
|
|
129
|
-
elif [[ "$words[2]" == "project" && CURRENT == 3 ]]; then
|
|
130
|
-
_values 'project command' init list show add-path remove-path pin unpin attach detach
|
|
131
|
-
elif [[ "$words[2]" == "eval" && "$words[3]" == "promote" ]]; then
|
|
132
|
-
_arguments \
|
|
133
|
-
'--since[time window]:window:' \
|
|
134
|
-
'--out[output file]:file:_files' \
|
|
135
|
-
'--dry-run[print the plan without writing]' \
|
|
136
|
-
'--yes[apply without prompts]' \
|
|
137
|
-
'--json[emit a JSON envelope]'
|
|
138
|
-
elif [[ "$words[2]" == "eval" && CURRENT == 3 ]]; then
|
|
139
|
-
_values 'eval command' promote
|
|
140
|
-
elif [[ "$words[2]" == "target" && CURRENT == 3 ]]; then
|
|
141
|
-
_values 'target command' list show add remove rehome migrate
|
|
142
|
-
elif [[ "$words[2]" == "skill" && CURRENT == 3 ]]; then
|
|
143
|
-
_values 'skill command' which
|
|
144
|
-
elif [[ "$words[2]" == "core" && CURRENT == 3 ]]; then
|
|
145
|
-
_values 'core command' pin unpin
|
|
146
|
-
elif [[ "$words[2]" == "local-vault" && CURRENT == 3 ]]; then
|
|
147
|
-
_values 'local-vault command' init
|
|
148
|
-
fi
|
|
149
|
-
}
|
|
150
|
-
_skillmux "$@"
|
|
151
|
-
`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (shell === "fish") {
|
|
155
|
-
const topLevel = TOP_LEVEL_COMMANDS.map(
|
|
156
|
-
(c) => `complete -c skillmux -n "__fish_use_subcommand" -a ${c.name} -d "${c.description}"`,
|
|
157
|
-
).join("\n");
|
|
158
|
-
return `# fish completion for skillmux
|
|
159
|
-
complete -c skillmux -f
|
|
160
|
-
${topLevel}
|
|
161
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l agent -x -a "${SUPPORTED_AGENT_IDS.join(" ")}" -d "Select an agent"
|
|
162
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l vault -r -d "Vault directory"
|
|
163
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l core -x -d "Seed a core skill"
|
|
164
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l migrate-full-vault -d "Convert a full-vault symlink"
|
|
165
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l show-mcp-setup -d "Also print the MCP registration snippet"
|
|
166
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l register-mcp -d "Register skillmux via the agent's own CLI"
|
|
167
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l no-instructions -d "Skip managed instruction files"
|
|
168
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l no-sync -d "Save without synchronizing"
|
|
169
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l interactive -d "Force guided setup"
|
|
170
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l yes -d "Apply without prompts"
|
|
171
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l dry-run -d "Print the plan without writing"
|
|
172
|
-
complete -c skillmux -n "__fish_seen_subcommand_from init" -l json -d "Emit a JSON envelope"
|
|
173
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -a "init list show add-path remove-path pin unpin attach detach" -d "Manage projects"
|
|
174
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l name -x -d "Project group name"
|
|
175
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l skill -x -d "Project skill"
|
|
176
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l agent -x -a "${MANAGED_PINS_AGENT_IDS.join(" ")}" -d "Select an agent"
|
|
177
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l target -x -d "Select an advanced delivery target"
|
|
178
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l register-mcp -d "Register a project-scoped MCP server for claude-code"
|
|
179
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l no-sync -d "Save without synchronizing"
|
|
180
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l interactive -d "Force guided setup"
|
|
181
|
-
complete -c skillmux -n "__fish_seen_subcommand_from project" -l yes -d "Apply without prompts"
|
|
182
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval" -a "promote" -d "Promote correlated fetches into eval cases"
|
|
183
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from promote" -l since -x -d "Time window"
|
|
184
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from promote" -l out -r -d "Output file"
|
|
185
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from promote" -l dry-run -d "Print the plan without writing"
|
|
186
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from promote" -l yes -d "Apply without prompts"
|
|
187
|
-
complete -c skillmux -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from promote" -l json -d "Emit a JSON envelope"
|
|
188
|
-
complete -c skillmux -n "__fish_seen_subcommand_from target" -a "list show add remove rehome migrate" -d "Manage targets"
|
|
189
|
-
complete -c skillmux -n "__fish_seen_subcommand_from core" -a "pin unpin" -d "Manage [core] pins"
|
|
190
|
-
complete -c skillmux -n "__fish_seen_subcommand_from skill" -a "which" -d "Show which root resolves a skill_id"
|
|
191
|
-
complete -c skillmux -n "__fish_seen_subcommand_from local-vault" -a "init" -d "Initialize a local_vault_paths marker"
|
|
192
|
-
`;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
throw new Error(`Unsupported shell: ${shell}`);
|
|
196
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
export class ConcurrencyLimiter {
|
|
2
|
-
private inFlight = 0;
|
|
3
|
-
|
|
4
|
-
constructor(private readonly max: number) {}
|
|
5
|
-
|
|
6
|
-
tryAcquire(): boolean {
|
|
7
|
-
if (this.inFlight >= this.max) return false;
|
|
8
|
-
this.inFlight++;
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
release(): void {
|
|
13
|
-
this.inFlight = Math.max(0, this.inFlight - 1);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Wraps a response body so `release` fires when the stream actually finishes
|
|
19
|
-
* (fully drained or cancelled by a client disconnect) instead of as soon as
|
|
20
|
-
* the Response object is constructed. For a buffered body this happens almost
|
|
21
|
-
* immediately; for an open SSE stream it defers release until the connection
|
|
22
|
-
* really closes, so a concurrency limiter reflects true connection lifetime.
|
|
23
|
-
*/
|
|
24
|
-
export function releaseOnStreamClose(
|
|
25
|
-
body: ReadableStream<Uint8Array> | null,
|
|
26
|
-
release: () => void,
|
|
27
|
-
): ReadableStream<Uint8Array> | null {
|
|
28
|
-
if (!body) {
|
|
29
|
-
release();
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const reader = body.getReader();
|
|
34
|
-
let released = false;
|
|
35
|
-
const releaseOnce = () => {
|
|
36
|
-
if (released) return;
|
|
37
|
-
released = true;
|
|
38
|
-
release();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return new ReadableStream<Uint8Array>({
|
|
42
|
-
async pull(controller) {
|
|
43
|
-
try {
|
|
44
|
-
const { done, value } = await reader.read();
|
|
45
|
-
if (done) {
|
|
46
|
-
controller.close();
|
|
47
|
-
releaseOnce();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
controller.enqueue(value);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
controller.error(error);
|
|
53
|
-
releaseOnce();
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
cancel(reason) {
|
|
57
|
-
releaseOnce();
|
|
58
|
-
return reader.cancel(reason);
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|