@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/init.ts
DELETED
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
expandHome,
|
|
5
|
-
loadConfig,
|
|
6
|
-
migrateLegacyPaths,
|
|
7
|
-
resolveConfigPath,
|
|
8
|
-
} from "../config";
|
|
9
|
-
import {
|
|
10
|
-
applyInit,
|
|
11
|
-
deriveTargetName,
|
|
12
|
-
detectSurfaces,
|
|
13
|
-
planInitManifest,
|
|
14
|
-
printLastMile,
|
|
15
|
-
surfaceCandidates,
|
|
16
|
-
} from "../init";
|
|
17
|
-
import {
|
|
18
|
-
assessAgentReadiness,
|
|
19
|
-
detectInstalledAgents,
|
|
20
|
-
planAgentSurfaces,
|
|
21
|
-
SUPPORTED_AGENT_IDS,
|
|
22
|
-
type AgentId,
|
|
23
|
-
type ReadinessAxis,
|
|
24
|
-
} from "../init-agents";
|
|
25
|
-
import {
|
|
26
|
-
applyInstructionPlan,
|
|
27
|
-
planInstructionSetup,
|
|
28
|
-
rollbackInstructionPlan,
|
|
29
|
-
} from "../init-instructions";
|
|
30
|
-
import { parseManifest, resolveManifestPath } from "../manifest";
|
|
31
|
-
import {
|
|
32
|
-
MCP_REGISTRABLE_AGENTS,
|
|
33
|
-
registerMcpServer,
|
|
34
|
-
type McpRegistrationResult,
|
|
35
|
-
} from "../mcp-registration";
|
|
36
|
-
import { isInteractive } from "../output";
|
|
37
|
-
import { isGlobalFlag } from "../global-flags";
|
|
38
|
-
import {
|
|
39
|
-
parseCommaList,
|
|
40
|
-
promptMultiSelect,
|
|
41
|
-
promptText,
|
|
42
|
-
shouldUseWizard,
|
|
43
|
-
} from "../prompts";
|
|
44
|
-
import {
|
|
45
|
-
applyConfigInit,
|
|
46
|
-
inspectVault,
|
|
47
|
-
planConfigInit,
|
|
48
|
-
rollbackConfigInit,
|
|
49
|
-
type ConfigInitPlan,
|
|
50
|
-
} from "../setup";
|
|
51
|
-
import { configuredTargetForSurface } from "./project";
|
|
52
|
-
import { confirmAction } from "./shared";
|
|
53
|
-
import { runSync } from "./sync";
|
|
54
|
-
|
|
55
|
-
function parseInitArgs(args: string[]): {
|
|
56
|
-
agents: string[];
|
|
57
|
-
coreSkillIds: string[];
|
|
58
|
-
migrateFullVault: boolean;
|
|
59
|
-
showMcpSetup: boolean;
|
|
60
|
-
registerMcp: boolean;
|
|
61
|
-
skipInstructions: boolean;
|
|
62
|
-
sync: boolean;
|
|
63
|
-
vaultPath?: string;
|
|
64
|
-
yes: boolean;
|
|
65
|
-
} {
|
|
66
|
-
const agents: string[] = [];
|
|
67
|
-
const coreSkillIds: string[] = [];
|
|
68
|
-
let migrateFullVault = false;
|
|
69
|
-
let showMcpSetup = false;
|
|
70
|
-
let registerMcp = false;
|
|
71
|
-
let skipInstructions = false;
|
|
72
|
-
let sync = true;
|
|
73
|
-
let vaultPath: string | undefined;
|
|
74
|
-
let yes = false;
|
|
75
|
-
for (let i = 0; i < args.length; i++) {
|
|
76
|
-
const option = args[i];
|
|
77
|
-
if (option === "--target" || option === "--dir" || option === "--client") {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`${option} was removed; select a specific supported agent with --agent instead (see "skillmux init --help")`,
|
|
80
|
-
);
|
|
81
|
-
} else if (option === "--agent") {
|
|
82
|
-
const value = args[i + 1];
|
|
83
|
-
if (!value) throw new Error("--agent requires a name");
|
|
84
|
-
agents.push(value);
|
|
85
|
-
i++;
|
|
86
|
-
} else if (option === "--vault") {
|
|
87
|
-
const value = args[i + 1];
|
|
88
|
-
if (!value) throw new Error("--vault requires a path");
|
|
89
|
-
vaultPath = value;
|
|
90
|
-
i++;
|
|
91
|
-
} else if (option === "--core") {
|
|
92
|
-
const value = args[i + 1];
|
|
93
|
-
if (!value) throw new Error("--core requires a skill_id");
|
|
94
|
-
coreSkillIds.push(value);
|
|
95
|
-
i++;
|
|
96
|
-
} else if (
|
|
97
|
-
isGlobalFlag(option, "--dry-run", "--json") ||
|
|
98
|
-
option === "--interactive"
|
|
99
|
-
) {
|
|
100
|
-
continue;
|
|
101
|
-
} else if (option === "--migrate-full-vault") {
|
|
102
|
-
migrateFullVault = true;
|
|
103
|
-
} else if (option === "--show-mcp-setup") {
|
|
104
|
-
showMcpSetup = true;
|
|
105
|
-
} else if (option === "--register-mcp") {
|
|
106
|
-
registerMcp = true;
|
|
107
|
-
} else if (option === "--no-instructions") {
|
|
108
|
-
skipInstructions = true;
|
|
109
|
-
} else if (option === "--no-sync") {
|
|
110
|
-
sync = false;
|
|
111
|
-
} else if (option === "--yes") {
|
|
112
|
-
yes = true;
|
|
113
|
-
} else {
|
|
114
|
-
throw new Error(`unknown init option: ${option}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
agents,
|
|
119
|
-
coreSkillIds,
|
|
120
|
-
migrateFullVault,
|
|
121
|
-
showMcpSetup,
|
|
122
|
-
registerMcp,
|
|
123
|
-
skipInstructions,
|
|
124
|
-
sync,
|
|
125
|
-
vaultPath,
|
|
126
|
-
yes,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
interface InitJsonPayload {
|
|
131
|
-
command: "init";
|
|
132
|
-
phase: "plan" | "result";
|
|
133
|
-
dry_run: boolean;
|
|
134
|
-
applied: boolean;
|
|
135
|
-
plan: unknown;
|
|
136
|
-
result?: unknown;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* init predates the shared emitSuccess envelope and shipped its own top-level
|
|
141
|
-
* keys, which existing automation reads. Emit the documented envelope
|
|
142
|
-
* (`target`/`data`/`error`, see docs/cli.md) so generic consumers work, while
|
|
143
|
-
* keeping the original keys so nothing breaks. init's error path already goes
|
|
144
|
-
* through the shared handler and needs no bridging.
|
|
145
|
-
*
|
|
146
|
-
* DEPRECATED: the duplicated top-level `command`/`phase`/`dry_run`/`applied`/
|
|
147
|
-
* `plan`/`result` keys are a compatibility alias for `data`. They stay so
|
|
148
|
-
* existing automation keeps parsing, and no removal is planned. init is
|
|
149
|
-
* local-only, so `target` is always "local".
|
|
150
|
-
*/
|
|
151
|
-
function initJsonEnvelope(payload: InitJsonPayload): string {
|
|
152
|
-
return JSON.stringify({
|
|
153
|
-
schema_version: 1,
|
|
154
|
-
ok: true,
|
|
155
|
-
context: "local",
|
|
156
|
-
target: "local",
|
|
157
|
-
data: payload,
|
|
158
|
-
error: null,
|
|
159
|
-
...payload,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function runInit(
|
|
164
|
-
args: string[],
|
|
165
|
-
options: { isJson: boolean; dryRun: boolean },
|
|
166
|
-
): Promise<void> {
|
|
167
|
-
const {
|
|
168
|
-
agents: requestedAgents,
|
|
169
|
-
coreSkillIds,
|
|
170
|
-
migrateFullVault,
|
|
171
|
-
showMcpSetup,
|
|
172
|
-
registerMcp: requestedRegisterMcp,
|
|
173
|
-
skipInstructions,
|
|
174
|
-
sync,
|
|
175
|
-
vaultPath: requestedVaultPath,
|
|
176
|
-
yes,
|
|
177
|
-
} = parseInitArgs(args);
|
|
178
|
-
const guided = shouldUseWizard(args, {
|
|
179
|
-
interactive: isInteractive(),
|
|
180
|
-
json: options.isJson,
|
|
181
|
-
dryRun: options.dryRun,
|
|
182
|
-
});
|
|
183
|
-
migrateLegacyPaths();
|
|
184
|
-
const configPath = resolveConfigPath();
|
|
185
|
-
let configPlan: ConfigInitPlan | undefined;
|
|
186
|
-
let vaultPath: string;
|
|
187
|
-
if (!existsSync(configPath)) {
|
|
188
|
-
let bootstrapVaultPath = requestedVaultPath;
|
|
189
|
-
if (!bootstrapVaultPath) {
|
|
190
|
-
if (guided) {
|
|
191
|
-
bootstrapVaultPath = await promptText("Skill vault path", "~/skills");
|
|
192
|
-
} else if (!options.isJson && isInteractive()) {
|
|
193
|
-
bootstrapVaultPath = "~/skills";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (!bootstrapVaultPath) {
|
|
197
|
-
throw new Error(
|
|
198
|
-
`machine config does not exist: ${configPath}; re-run with --vault <path>`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
configPlan = planConfigInit(configPath, expandHome(bootstrapVaultPath));
|
|
202
|
-
vaultPath = configPlan.vaultPath;
|
|
203
|
-
if (!options.isJson) {
|
|
204
|
-
console.log(`config create: ${configPath} (vault: ${vaultPath})`);
|
|
205
|
-
}
|
|
206
|
-
} else {
|
|
207
|
-
const config = await loadConfig();
|
|
208
|
-
vaultPath = expandHome(config.vault_path);
|
|
209
|
-
if (requestedVaultPath && expandHome(requestedVaultPath) !== vaultPath) {
|
|
210
|
-
throw new Error(
|
|
211
|
-
`machine config already uses vault_path ${vaultPath}; --vault does not overwrite existing config`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const vaultHealth = inspectVault(vaultPath);
|
|
217
|
-
if (!vaultHealth.ok) {
|
|
218
|
-
throw new Error(vaultHealth.message);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let selectedAgents = requestedAgents;
|
|
222
|
-
if (guided) {
|
|
223
|
-
const detected = detectInstalledAgents({
|
|
224
|
-
codexHome: process.env.CODEX_HOME
|
|
225
|
-
? expandHome(process.env.CODEX_HOME)
|
|
226
|
-
: undefined,
|
|
227
|
-
});
|
|
228
|
-
const evidence = new Map(
|
|
229
|
-
detected.map((item) => [item.agent, item.evidence]),
|
|
230
|
-
);
|
|
231
|
-
selectedAgents = await promptMultiSelect(
|
|
232
|
-
"Which agents do you use?",
|
|
233
|
-
SUPPORTED_AGENT_IDS.map((agent) => ({
|
|
234
|
-
value: agent,
|
|
235
|
-
label: agent,
|
|
236
|
-
detail: evidence.has(agent)
|
|
237
|
-
? `detected: ${evidence.get(agent)}`
|
|
238
|
-
: undefined,
|
|
239
|
-
selected: evidence.has(agent) || requestedAgents.includes(agent),
|
|
240
|
-
})),
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
let selectedCoreSkillIds = coreSkillIds;
|
|
244
|
-
if (guided) {
|
|
245
|
-
selectedCoreSkillIds = parseCommaList(
|
|
246
|
-
await promptText(
|
|
247
|
-
"Core skill IDs to add, comma-separated",
|
|
248
|
-
coreSkillIds.join(","),
|
|
249
|
-
),
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Native pins and MCP registration are independent choices — this stays
|
|
254
|
-
// opt-in and only offered for agents whose own CLI we've verified, so a
|
|
255
|
-
// user who only wants native skill management sees nothing new here.
|
|
256
|
-
const registrableAgents = selectedAgents.filter((agent) =>
|
|
257
|
-
MCP_REGISTRABLE_AGENTS.includes(agent as AgentId),
|
|
258
|
-
) as AgentId[];
|
|
259
|
-
let registerMcp = requestedRegisterMcp;
|
|
260
|
-
if (guided && registrableAgents.length > 0) {
|
|
261
|
-
registerMcp = await confirmAction(
|
|
262
|
-
`Also register skillmux as an MCP server for ${registrableAgents.join(", ")}?`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const agentPlan = planAgentSurfaces(selectedAgents, {
|
|
267
|
-
codexHome: process.env.CODEX_HOME
|
|
268
|
-
? expandHome(process.env.CODEX_HOME)
|
|
269
|
-
: undefined,
|
|
270
|
-
});
|
|
271
|
-
// The managed instruction block only teaches an agent to call resolve_skill
|
|
272
|
-
// /fetch_skill (MCP tools) — writing it for an agent with no MCP registered
|
|
273
|
-
// or requested would tell that agent to call tools that don't exist. Only
|
|
274
|
-
// agents actually getting MCP this run (or whose snippet the user asked to
|
|
275
|
-
// see) get the block; native-only setups get no instruction writes at all.
|
|
276
|
-
const mcpInstructionAgentIds = agentPlan.agents
|
|
277
|
-
.map((agent) => agent.id)
|
|
278
|
-
.filter(
|
|
279
|
-
(agentId) =>
|
|
280
|
-
showMcpSetup || (registerMcp && registrableAgents.includes(agentId)),
|
|
281
|
-
);
|
|
282
|
-
const instructionPlan = planInstructionSetup(
|
|
283
|
-
skipInstructions ? [] : mcpInstructionAgentIds,
|
|
284
|
-
{
|
|
285
|
-
codexHome: process.env.CODEX_HOME
|
|
286
|
-
? expandHome(process.env.CODEX_HOME)
|
|
287
|
-
: undefined,
|
|
288
|
-
},
|
|
289
|
-
);
|
|
290
|
-
const instructionReadiness: Partial<Record<AgentId, ReadinessAxis>> = {};
|
|
291
|
-
for (const change of instructionPlan.changes) {
|
|
292
|
-
for (const agent of change.agents) {
|
|
293
|
-
instructionReadiness[agent] = {
|
|
294
|
-
status: change.status === "unchanged" ? "ready" : "planned",
|
|
295
|
-
detail: change.path,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
for (const manual of instructionPlan.manual) {
|
|
300
|
-
instructionReadiness[manual.agent] = {
|
|
301
|
-
status: "manual",
|
|
302
|
-
detail: manual.reason,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
for (const agent of agentPlan.agents) {
|
|
306
|
-
if (mcpInstructionAgentIds.includes(agent.id)) continue;
|
|
307
|
-
if (instructionReadiness[agent.id]) continue;
|
|
308
|
-
instructionReadiness[agent.id] = {
|
|
309
|
-
status: "not-applicable",
|
|
310
|
-
detail: "no MCP requested — see --show-mcp-setup / --register-mcp",
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
const existingManifestPath = resolveManifestPath(vaultPath);
|
|
314
|
-
const existingManifest = existingManifestPath
|
|
315
|
-
? parseManifest(await Bun.file(existingManifestPath).text())
|
|
316
|
-
: undefined;
|
|
317
|
-
const targetByPath = new Map<string, string>();
|
|
318
|
-
for (const surface of agentPlan.surfaces) {
|
|
319
|
-
targetByPath.set(
|
|
320
|
-
surface.path,
|
|
321
|
-
existingManifest
|
|
322
|
-
? (configuredTargetForSurface(existingManifest, surface) ??
|
|
323
|
-
surface.targetName)
|
|
324
|
-
: surface.targetName,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
const candidatePaths = [
|
|
328
|
-
...new Set([
|
|
329
|
-
...surfaceCandidates().map(expandHome),
|
|
330
|
-
...targetByPath.keys(),
|
|
331
|
-
]),
|
|
332
|
-
];
|
|
333
|
-
const candidates = detectSurfaces(candidatePaths, vaultPath);
|
|
334
|
-
if (!options.isJson) {
|
|
335
|
-
for (const candidate of candidates) {
|
|
336
|
-
const name =
|
|
337
|
-
targetByPath.get(candidate.path) ?? deriveTargetName(candidate.path);
|
|
338
|
-
if (candidate.state === "missing") {
|
|
339
|
-
console.log(`${name} (${candidate.path}): not found`);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if (candidate.state === "broken-symlink") {
|
|
343
|
-
console.log(`${name} (${candidate.path}): broken symlink`);
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (candidate.state === "full-vault") {
|
|
347
|
-
console.log(
|
|
348
|
-
`${name} (${candidate.path}): full-vault -> ${candidate.canonicalPath}`,
|
|
349
|
-
);
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
if (candidate.state === "external-symlink") {
|
|
353
|
-
console.log(
|
|
354
|
-
`${name} (${candidate.path}): external symlink -> ${candidate.canonicalPath}`,
|
|
355
|
-
);
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
if (candidate.state === "unsupported") {
|
|
359
|
-
console.log(
|
|
360
|
-
`${name} (${candidate.path}): unsupported filesystem entry`,
|
|
361
|
-
);
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
const kind = "real dir";
|
|
365
|
-
const marked = candidate.alreadyMarked
|
|
366
|
-
? ", already skillmux-managed"
|
|
367
|
-
: "";
|
|
368
|
-
console.log(
|
|
369
|
-
`${name} (${candidate.path}): ${kind}, ${candidate.skillCount} skills${marked}`,
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
for (const readiness of assessAgentReadiness(
|
|
373
|
-
agentPlan,
|
|
374
|
-
instructionReadiness,
|
|
375
|
-
)) {
|
|
376
|
-
console.log(`\n${readiness.agent} readiness:`);
|
|
377
|
-
console.log(
|
|
378
|
-
` skill surface: ${readiness.skillSurface.status} — ${readiness.skillSurface.detail}`,
|
|
379
|
-
);
|
|
380
|
-
console.log(
|
|
381
|
-
` MCP registration: ${readiness.mcpRegistration.status} — ${readiness.mcpRegistration.detail}`,
|
|
382
|
-
);
|
|
383
|
-
console.log(
|
|
384
|
-
` instructions: ${readiness.instructionSetup.status} — ${readiness.instructionSetup.detail}`,
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
for (const change of instructionPlan.changes) {
|
|
388
|
-
console.log(
|
|
389
|
-
`instructions ${change.status}: ${change.path} (${change.agents.join(", ")})`,
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
for (const manual of instructionPlan.manual) {
|
|
393
|
-
console.log(`instructions manual: ${manual.agent} — ${manual.reason}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const requestedTargets = [...new Set(targetByPath.values())];
|
|
398
|
-
const hasInstructionWrites = instructionPlan.changes.some(
|
|
399
|
-
(change) => change.status !== "unchanged",
|
|
400
|
-
);
|
|
401
|
-
const hasConfigWrite = configPlan?.action === "create";
|
|
402
|
-
const hasChanges = !(
|
|
403
|
-
requestedTargets.length === 0 &&
|
|
404
|
-
!hasInstructionWrites &&
|
|
405
|
-
selectedCoreSkillIds.length === 0 &&
|
|
406
|
-
!hasConfigWrite
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
const byName = new Map(
|
|
410
|
-
candidates
|
|
411
|
-
.filter(
|
|
412
|
-
(candidate) =>
|
|
413
|
-
candidate.deliveryMode === "managed-pins" ||
|
|
414
|
-
(migrateFullVault && candidate.state === "full-vault"),
|
|
415
|
-
)
|
|
416
|
-
.map(
|
|
417
|
-
(candidate) =>
|
|
418
|
-
[
|
|
419
|
-
targetByPath.get(candidate.path) ??
|
|
420
|
-
deriveTargetName(candidate.path),
|
|
421
|
-
candidate,
|
|
422
|
-
] as const,
|
|
423
|
-
),
|
|
424
|
-
);
|
|
425
|
-
const allCandidatesByName = new Map(
|
|
426
|
-
candidates.map(
|
|
427
|
-
(candidate) =>
|
|
428
|
-
[
|
|
429
|
-
targetByPath.get(candidate.path) ?? deriveTargetName(candidate.path),
|
|
430
|
-
candidate,
|
|
431
|
-
] as const,
|
|
432
|
-
),
|
|
433
|
-
);
|
|
434
|
-
for (const name of requestedTargets) {
|
|
435
|
-
if (!byName.has(name)) {
|
|
436
|
-
if (allCandidatesByName.get(name)?.state === "full-vault") {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`target "${name}" is a full-vault surface; re-run with --migrate-full-vault to convert it to managed pins`,
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
throw new Error(
|
|
442
|
-
`target "${name}" not among detected surfaces`,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const confirmedTargets = requestedTargets.map((name) => {
|
|
448
|
-
const candidate = byName.get(name)!;
|
|
449
|
-
return {
|
|
450
|
-
name,
|
|
451
|
-
dir: candidate.path,
|
|
452
|
-
...(candidate.state === "full-vault" ? { migrateFullVault: true } : {}),
|
|
453
|
-
};
|
|
454
|
-
});
|
|
455
|
-
const plannedManifest = planInitManifest(
|
|
456
|
-
vaultPath,
|
|
457
|
-
confirmedTargets,
|
|
458
|
-
selectedCoreSkillIds,
|
|
459
|
-
);
|
|
460
|
-
const serializedPlan = {
|
|
461
|
-
vault_path: vaultPath,
|
|
462
|
-
config: configPlan
|
|
463
|
-
? { path: configPlan.configPath, action: configPlan.action }
|
|
464
|
-
: { path: configPath, action: "preserve" },
|
|
465
|
-
agents: agentPlan.agents.map((agent) => agent.id),
|
|
466
|
-
targets: confirmedTargets,
|
|
467
|
-
core: plannedManifest.core.skills,
|
|
468
|
-
instructions: instructionPlan.changes.map(({ path, agents, status }) => ({
|
|
469
|
-
path,
|
|
470
|
-
agents,
|
|
471
|
-
status,
|
|
472
|
-
})),
|
|
473
|
-
manual: instructionPlan.manual,
|
|
474
|
-
register_mcp_for: registerMcp ? registrableAgents : [],
|
|
475
|
-
};
|
|
476
|
-
if (!hasChanges) {
|
|
477
|
-
if (options.isJson) {
|
|
478
|
-
console.log(
|
|
479
|
-
initJsonEnvelope({
|
|
480
|
-
command: "init",
|
|
481
|
-
phase: "plan",
|
|
482
|
-
dry_run: options.dryRun,
|
|
483
|
-
applied: false,
|
|
484
|
-
plan: serializedPlan,
|
|
485
|
-
}),
|
|
486
|
-
);
|
|
487
|
-
} else {
|
|
488
|
-
console.log("\nno managed-pins surface selected — nothing written.");
|
|
489
|
-
}
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
if (!options.isJson) {
|
|
493
|
-
for (const target of confirmedTargets.filter(
|
|
494
|
-
(target) => target.migrateFullVault,
|
|
495
|
-
)) {
|
|
496
|
-
console.log(
|
|
497
|
-
`full-vault migration ${target.name}: ${vaultHealth.skillCount} visible skills -> ` +
|
|
498
|
-
`${plannedManifest.core.skills.length} core ${plannedManifest.core.skills.length === 1 ? "skill" : "skills"} after sync`,
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (options.dryRun) {
|
|
503
|
-
if (options.isJson) {
|
|
504
|
-
console.log(
|
|
505
|
-
initJsonEnvelope({
|
|
506
|
-
command: "init",
|
|
507
|
-
phase: "plan",
|
|
508
|
-
dry_run: true,
|
|
509
|
-
applied: false,
|
|
510
|
-
plan: serializedPlan,
|
|
511
|
-
}),
|
|
512
|
-
);
|
|
513
|
-
} else {
|
|
514
|
-
console.log(
|
|
515
|
-
`\ndry-run: ${confirmedTargets.length} target(s), ` +
|
|
516
|
-
`${instructionPlan.changes.filter((change) => change.status !== "unchanged").length} instruction file(s), ` +
|
|
517
|
-
`core: ${plannedManifest.core.skills.join(", ") || "(unchanged)"}, ` +
|
|
518
|
-
`MCP registration: ${registerMcp ? registrableAgents.join(", ") || "(none)" : "(none)"}`,
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!yes) {
|
|
525
|
-
if (!options.isJson && isInteractive()) {
|
|
526
|
-
if (guided) {
|
|
527
|
-
console.log("\nReview");
|
|
528
|
-
console.log(` agents: ${selectedAgents.join(", ") || "(none)"}`);
|
|
529
|
-
console.log(
|
|
530
|
-
` targets: ${confirmedTargets.map((target) => `${target.name} -> ${target.dir}`).join(", ") || "(none)"}`,
|
|
531
|
-
);
|
|
532
|
-
console.log(
|
|
533
|
-
` instructions: ${instructionPlan.changes.filter((change) => change.status !== "unchanged").length} file(s)`,
|
|
534
|
-
);
|
|
535
|
-
console.log(
|
|
536
|
-
` core: ${plannedManifest.core.skills.join(", ") || "(none)"}`,
|
|
537
|
-
);
|
|
538
|
-
console.log(
|
|
539
|
-
` MCP registration: ${registerMcp ? registrableAgents.join(", ") || "(none)" : "(none)"}`,
|
|
540
|
-
);
|
|
541
|
-
console.log(` sync: ${sync ? "yes" : "no"}`);
|
|
542
|
-
if (!(await confirmAction("apply this setup plan?"))) {
|
|
543
|
-
console.log("init cancelled");
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
} else {
|
|
547
|
-
const prompts = [
|
|
548
|
-
...confirmedTargets.map(
|
|
549
|
-
(target) => `adopt ${target.name} at ${target.dir}?`,
|
|
550
|
-
),
|
|
551
|
-
...instructionPlan.changes
|
|
552
|
-
.filter((change) => change.status !== "unchanged")
|
|
553
|
-
.map(
|
|
554
|
-
(change) => `${change.status} instruction file ${change.path}?`,
|
|
555
|
-
),
|
|
556
|
-
...(hasConfigWrite ? [`create machine config ${configPath}?`] : []),
|
|
557
|
-
...(selectedCoreSkillIds.length > 0
|
|
558
|
-
? [`pin core skills: ${selectedCoreSkillIds.join(", ")}?`]
|
|
559
|
-
: []),
|
|
560
|
-
];
|
|
561
|
-
for (const prompt of prompts) {
|
|
562
|
-
if (!(await confirmAction(prompt))) {
|
|
563
|
-
console.log("init cancelled; nothing written");
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
throw new Error(
|
|
570
|
-
"skillmux init requires --yes before applying target, instruction, or core changes non-interactively",
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
let configCreated = false;
|
|
576
|
-
let instructionsApplied = false;
|
|
577
|
-
const applyAdditional = () => {
|
|
578
|
-
try {
|
|
579
|
-
if (configPlan?.action === "create") {
|
|
580
|
-
configCreated = applyConfigInit(configPlan) === "created";
|
|
581
|
-
}
|
|
582
|
-
if (hasInstructionWrites) {
|
|
583
|
-
applyInstructionPlan(instructionPlan);
|
|
584
|
-
instructionsApplied = true;
|
|
585
|
-
}
|
|
586
|
-
} catch (error) {
|
|
587
|
-
if (configCreated && configPlan) rollbackConfigInit(configPlan);
|
|
588
|
-
configCreated = false;
|
|
589
|
-
throw error;
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
const rollbackAdditional = () => {
|
|
593
|
-
if (instructionsApplied) rollbackInstructionPlan(instructionPlan);
|
|
594
|
-
if (configCreated && configPlan) rollbackConfigInit(configPlan);
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
if (confirmedTargets.length === 0 && selectedCoreSkillIds.length === 0) {
|
|
598
|
-
applyAdditional();
|
|
599
|
-
} else {
|
|
600
|
-
applyInit(
|
|
601
|
-
vaultPath,
|
|
602
|
-
confirmedTargets,
|
|
603
|
-
hasInstructionWrites || hasConfigWrite
|
|
604
|
-
? {
|
|
605
|
-
apply: applyAdditional,
|
|
606
|
-
rollback: rollbackAdditional,
|
|
607
|
-
}
|
|
608
|
-
: undefined,
|
|
609
|
-
selectedCoreSkillIds,
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Best-effort and outside the rollback above: this mutates another tool's
|
|
614
|
-
// own config, not skillmux's, so a registration failure is reported, never
|
|
615
|
-
// rolled back — the successful native setup above still stands either way.
|
|
616
|
-
const mcpRegistrations: McpRegistrationResult[] = [];
|
|
617
|
-
if (registerMcp) {
|
|
618
|
-
for (const agent of registrableAgents) {
|
|
619
|
-
mcpRegistrations.push(await registerMcpServer(agent));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (options.isJson) {
|
|
624
|
-
console.log(
|
|
625
|
-
initJsonEnvelope({
|
|
626
|
-
command: "init",
|
|
627
|
-
phase: "result",
|
|
628
|
-
dry_run: false,
|
|
629
|
-
applied: true,
|
|
630
|
-
plan: serializedPlan,
|
|
631
|
-
result: {
|
|
632
|
-
config_created: configCreated,
|
|
633
|
-
targets_adopted: confirmedTargets.map((target) => target.name),
|
|
634
|
-
instructions_changed: instructionPlan.changes
|
|
635
|
-
.filter((change) => change.status !== "unchanged")
|
|
636
|
-
.map((change) => change.path),
|
|
637
|
-
core: plannedManifest.core.skills,
|
|
638
|
-
mcp_registrations: mcpRegistrations,
|
|
639
|
-
},
|
|
640
|
-
}),
|
|
641
|
-
);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
if (configCreated) console.log(`created ${configPath}`);
|
|
645
|
-
if (confirmedTargets.length > 0) {
|
|
646
|
-
console.log(
|
|
647
|
-
`\nwrote ${join(vaultPath, "skillmux.toml")}, adopted: ${confirmedTargets.map((t) => t.name).join(", ")}`,
|
|
648
|
-
);
|
|
649
|
-
} else if (selectedCoreSkillIds.length > 0) {
|
|
650
|
-
console.log(`\nwrote ${join(vaultPath, "skillmux.toml")}`);
|
|
651
|
-
}
|
|
652
|
-
if (plannedManifest.core.skills.length === 0 && confirmedTargets.length > 0) {
|
|
653
|
-
console.log("next: skillmux core pin <skill_id> --yes");
|
|
654
|
-
}
|
|
655
|
-
if (!sync && confirmedTargets.length > 0) console.log("next: skillmux sync");
|
|
656
|
-
for (const registration of mcpRegistrations) {
|
|
657
|
-
console.log(
|
|
658
|
-
registration.ok
|
|
659
|
-
? `registered skillmux as an MCP server for ${registration.agent}`
|
|
660
|
-
: `failed to register skillmux as an MCP server for ${registration.agent}: ${registration.error}`,
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
if (selectedAgents.length === 0 || showMcpSetup) {
|
|
664
|
-
console.log(`\n${printLastMile()}`);
|
|
665
|
-
}
|
|
666
|
-
// Reaching this point already required approval above (--yes, or an accepted
|
|
667
|
-
// confirmAction naming these exact targets/dirs) — that approval covers whatever
|
|
668
|
-
// new target directories this init just adopted, so runSync's own new-target
|
|
669
|
-
// confirmation gate would just be a redundant (and non-interactively,
|
|
670
|
-
// silently-skipping) re-ask.
|
|
671
|
-
if (sync && confirmedTargets.length > 0) await runSync(["--yes"]);
|
|
672
|
-
}
|