@dadado/agent-kit-cli 5.15.0 → 5.16.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 (71) hide show
  1. package/dashboard/dashboard-data.mjs +69 -65
  2. package/dashboard/lib/guards.d.mts +5 -0
  3. package/dashboard/lib/guards.mjs +31 -0
  4. package/dashboard/lib/identity-probe.mjs +56 -0
  5. package/dashboard/lib/pipeline-cache.mjs +58 -0
  6. package/dashboard/lib/processes.mjs +71 -0
  7. package/dashboard/serve.mjs +43 -1
  8. package/dashboard/start-broadcast.mjs +2 -12
  9. package/dashboard/start.mjs +2 -13
  10. package/dist/add-7UMSGVWC.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2YU2H7I6.js +42 -0
  13. package/dist/chunk-4IWG5CVE.js +214 -0
  14. package/dist/chunk-5OXPNVCF.js +106 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-6QV2BFGA.js +341 -0
  17. package/dist/chunk-77V75A7O.js +39 -0
  18. package/dist/chunk-7HDQEK6T.js +26 -0
  19. package/dist/chunk-CBCW5R5J.js +484 -0
  20. package/dist/chunk-CGTE7DHN.js +410 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-F6C3GFKV.js +57 -0
  23. package/dist/chunk-HFUH3HVS.js +282 -0
  24. package/dist/chunk-HS7LVVLO.js +74 -0
  25. package/dist/chunk-IHROBTNM.js +135 -0
  26. package/dist/chunk-KD2MYBB6.js +213 -0
  27. package/dist/chunk-KI6ZHBPV.js +332 -0
  28. package/dist/chunk-MAPOQEA2.js +43 -0
  29. package/dist/chunk-NQ7IQ5XT.js +26 -0
  30. package/dist/chunk-OUQZC744.js +1315 -0
  31. package/dist/chunk-OZ4QV6LM.js +1078 -0
  32. package/dist/chunk-PCCQUWE5.js +817 -0
  33. package/dist/chunk-PNFF6OU3.js +273 -0
  34. package/dist/chunk-QTON2GQX.js +203 -0
  35. package/dist/chunk-TFZ2JUXH.js +1081 -0
  36. package/dist/chunk-VGZBARQC.js +318 -0
  37. package/dist/chunk-VS2JD2XC.js +1453 -0
  38. package/dist/chunk-VY7MTPJ2.js +205 -0
  39. package/dist/chunk-WW3YZKHW.js +165 -0
  40. package/dist/chunk-XWPO3VYQ.js +53 -0
  41. package/dist/chunk-XZZAVHH4.js +503 -0
  42. package/dist/chunk-YGBBHKBE.js +82 -0
  43. package/dist/chunk-ZB43YMHW.js +20 -0
  44. package/dist/chunk-ZCTSUQRR.js +874 -0
  45. package/dist/chunk-ZXKZBTXE.js +17 -0
  46. package/dist/contribute-YKK6YCSI.js +367 -0
  47. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  48. package/dist/dashboard-YSNUGBFY.js +21 -0
  49. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  50. package/dist/diff-YTR7VOAN.js +74 -0
  51. package/dist/doctor-TA2FWREU.js +497 -0
  52. package/dist/dogfood-KKFVRWT6.js +366 -0
  53. package/dist/guard-EHXF76ID.js +530 -0
  54. package/dist/handoff-3UI6VKVG.js +302 -0
  55. package/dist/hook-MUV6Y7UW.js +1074 -0
  56. package/dist/index.js +62 -14068
  57. package/dist/init-TTTKGC4C.js +127 -0
  58. package/dist/install-ZMFJBPOI.js +34 -0
  59. package/dist/mission-control-LJACEVVN.js +89 -0
  60. package/dist/monitors-QV2IS3MP.js +176 -0
  61. package/dist/plan-index-JMC7Z4DW.js +44 -0
  62. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  63. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  64. package/dist/run-I3BFGULT.js +243 -0
  65. package/dist/run-plan-FO7CL4M3.js +118 -0
  66. package/dist/scan-NI2SWJ2E.js +33 -0
  67. package/dist/setup-global-JUVMJXJ5.js +340 -0
  68. package/dist/status-MVCUNCWX.js +138 -0
  69. package/dist/update-Q4EBIRUB.js +260 -0
  70. package/dist/validate-V2CW7J4B.js +201 -0
  71. package/package.json +1 -1
@@ -0,0 +1,1078 @@
1
+ import {
2
+ NON_INTERACTIVE_ROOT_ARGS,
3
+ installL0,
4
+ syncFromManifest
5
+ } from "./chunk-5OXPNVCF.js";
6
+ import {
7
+ SkippedSymlinkError,
8
+ formatPersonalizationSkippedSymlinks,
9
+ logApplyStats
10
+ } from "./chunk-HS7LVVLO.js";
11
+ import {
12
+ REGISTRY_CLI_ARGS,
13
+ resolveRegistryFromCli
14
+ } from "./chunk-XWPO3VYQ.js";
15
+ import {
16
+ executeSafeReadinessFixes
17
+ } from "./chunk-KI6ZHBPV.js";
18
+ import {
19
+ writeReadinessSnapshot
20
+ } from "./chunk-ZXKZBTXE.js";
21
+ import {
22
+ writeClaudeSessionStartHook
23
+ } from "./chunk-CGTE7DHN.js";
24
+ import {
25
+ shouldUseWelcomeColor,
26
+ withCliProgress
27
+ } from "./chunk-QTON2GQX.js";
28
+ import {
29
+ createReadinessReport
30
+ } from "./chunk-PNFF6OU3.js";
31
+ import {
32
+ REPOSITORY_PROFILE_REL,
33
+ detectIde,
34
+ runScanner
35
+ } from "./chunk-ZCTSUQRR.js";
36
+ import {
37
+ GIT_PLATFORM_META
38
+ } from "./chunk-F6C3GFKV.js";
39
+ import {
40
+ logger
41
+ } from "./chunk-ZB43YMHW.js";
42
+ import {
43
+ RootRefusedError,
44
+ classifyInstallError,
45
+ confirmProjectRoot,
46
+ isNonInteractive,
47
+ npxPinned,
48
+ pathCliStatus,
49
+ syncPathCliToRuntime
50
+ } from "./chunk-VGZBARQC.js";
51
+ import {
52
+ assessEnvironment
53
+ } from "./chunk-4IWG5CVE.js";
54
+ import {
55
+ warnIfRunningCliBehindNpm
56
+ } from "./chunk-XZZAVHH4.js";
57
+ import {
58
+ DOMAIN_PACK_IDS,
59
+ allPacks,
60
+ allSkills,
61
+ buildManifest,
62
+ contentHash,
63
+ emptyStats,
64
+ installPack,
65
+ installSkill,
66
+ isRealWriteTargetContained,
67
+ loadAgentKitManifest,
68
+ loadManagedHashLedger,
69
+ loadPackManifest,
70
+ loadRegistry,
71
+ mergeStats,
72
+ packMemberFileTargets,
73
+ resolveContained,
74
+ resolveProtectedGlobs,
75
+ saveManagedHashLedger,
76
+ saveManifest,
77
+ shouldPreserveCustomizedOverlay,
78
+ skillTargetDir
79
+ } from "./chunk-VS2JD2XC.js";
80
+ import {
81
+ KIT_VERSION,
82
+ pinnedCliSpec
83
+ } from "./chunk-7HDQEK6T.js";
84
+ import {
85
+ ensureDir,
86
+ fileExists,
87
+ writeJson
88
+ } from "./chunk-77V75A7O.js";
89
+
90
+ // src/commands/install.ts
91
+ import path5 from "path";
92
+ import { defineCommand } from "citty";
93
+ import { bold, cyan, green, options as koloristOptions } from "kolorist";
94
+
95
+ // src/generator/personalization.ts
96
+ import { readFile as readFile2, writeFile as writeFile4 } from "fs/promises";
97
+ import path4 from "path";
98
+
99
+ // src/generator/claude-command-adapters.ts
100
+ import { readFile, readdir, writeFile } from "fs/promises";
101
+ import path from "path";
102
+ var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
103
+ var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
104
+ var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
105
+ function parseCommandFrontmatter(name, raw) {
106
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
107
+ if (!match) return null;
108
+ const body = match[1] ?? "";
109
+ const descMatch = /^description:\s*(.+)$/m.exec(body);
110
+ if (!descMatch) return null;
111
+ const description = (descMatch[1] ?? "").trim();
112
+ if (!description) return null;
113
+ return { name, description };
114
+ }
115
+ var CLAUDE_HITL_FALLBACK = `Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else print one line \`HITL_GATE: <ask-id> | <label 1> | <label 2> | ...\` immediately followed by the same labels as one numbered list, one list per message, and WAIT for the answer (ask-id and labels: \`.cursor/skills/core/hitl-gates/SKILL.md\`). A headless reply arrives as \`HITL_REPLY: <ask-id> | operator reply <n> | <label>\`; cite that line as the Ask provenance.`;
116
+ var MODEL_INVOCATION_DISABLED = /* @__PURE__ */ new Set([
117
+ "git-prod",
118
+ "kit-prod",
119
+ "backlog-delete",
120
+ "backlog-cancel",
121
+ "archive-plan",
122
+ "hotfix",
123
+ "run-plan-all"
124
+ ]);
125
+ function renderClaudeCommandAdapter(command) {
126
+ const invocation = MODEL_INVOCATION_DISABLED.has(command.name) ? "disable-model-invocation: true\n" : "";
127
+ return `---
128
+ description: ${command.description}
129
+ ${invocation}---
130
+
131
+ Read \`.cursor/commands/${command.name}.md\` now and follow that contract exactly \u2014 it is the source of truth for /${command.name}; this file is only a thin adapter for Claude Code.
132
+
133
+ Adapter rules (Claude Code CLI):
134
+ - ${CLAUDE_HITL_FALLBACK}
135
+ - Skip or cancel means stop.
136
+ - Never \`/git-prod\` without an explicit operator yes.
137
+ - Do not clone Cursor hooks or invent behavior beyond the SoT file.
138
+ `;
139
+ }
140
+ async function discoverInstalledCommands(rootDir) {
141
+ const dir = path.join(rootDir, CURSOR_COMMANDS_DIR_REL);
142
+ let entries;
143
+ try {
144
+ entries = (await readdir(dir)).filter((f) => f.endsWith(".md"));
145
+ } catch {
146
+ return [];
147
+ }
148
+ const commands = [];
149
+ for (const file of entries.sort()) {
150
+ const name = file.slice(0, -3);
151
+ if (RESERVED_ADAPTER_NAMES.has(name)) continue;
152
+ try {
153
+ const raw = await readFile(path.join(dir, file), "utf8");
154
+ const parsed = parseCommandFrontmatter(name, raw);
155
+ if (parsed) commands.push(parsed);
156
+ } catch {
157
+ }
158
+ }
159
+ return commands;
160
+ }
161
+ async function generateClaudeCommandAdapters(rootDir) {
162
+ const commands = await discoverInstalledCommands(rootDir);
163
+ if (commands.length === 0) return [];
164
+ const ledger = await loadManagedHashLedger(rootDir);
165
+ const results = [];
166
+ let ledgerDirty = false;
167
+ for (const command of commands) {
168
+ const relPath = path.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
169
+ const rendered = renderClaudeCommandAdapter(command);
170
+ const abs = resolveContained(rootDir, relPath);
171
+ if (!await isRealWriteTargetContained(rootDir, abs)) {
172
+ results.push({ relativePath: relPath, status: "skipped-symlink" });
173
+ continue;
174
+ }
175
+ if (!await fileExists(abs)) {
176
+ await ensureDir(path.dirname(abs));
177
+ await writeFile(abs, rendered, "utf8");
178
+ ledger.hashes[relPath] = contentHash(rendered);
179
+ ledgerDirty = true;
180
+ results.push({ relativePath: relPath, status: "applied" });
181
+ continue;
182
+ }
183
+ const localContent = await readFile(abs, "utf8");
184
+ if (localContent === rendered) {
185
+ if (ledger.hashes[relPath] !== contentHash(rendered)) {
186
+ ledger.hashes[relPath] = contentHash(rendered);
187
+ ledgerDirty = true;
188
+ }
189
+ results.push({ relativePath: relPath, status: "unchanged" });
190
+ continue;
191
+ }
192
+ if (shouldPreserveCustomizedOverlay(localContent, ledger.hashes[relPath])) {
193
+ results.push({ relativePath: relPath, status: "preserved-customized" });
194
+ continue;
195
+ }
196
+ await writeFile(abs, rendered, "utf8");
197
+ ledger.hashes[relPath] = contentHash(rendered);
198
+ ledgerDirty = true;
199
+ results.push({ relativePath: relPath, status: "refreshed" });
200
+ }
201
+ if (ledgerDirty) await saveManagedHashLedger(rootDir, ledger);
202
+ return results;
203
+ }
204
+
205
+ // src/generator/claude-kit-load.ts
206
+ import { writeFile as writeFile2 } from "fs/promises";
207
+ import path2 from "path";
208
+ var CLAUDE_MD_REL = "CLAUDE.md";
209
+ var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
210
+ function renderClaudeMd() {
211
+ return `# Agent Kit (Claude Code)
212
+
213
+ This repository uses Agent Kit / Mission Control. Before rediscovering the tree, read the shared sources of truth (paths below are pointers; do not treat this file as a second rulebook).
214
+
215
+ ## Read first
216
+
217
+ 1. \`AGENTS.md\` - cross-IDE contract
218
+ 2. \`.cursor/project-context.md\` - verified repository facts (derived; prefer code, tests, SHAs when docs conflict)
219
+ 3. \`.cursor/HANDOFF.md\` - if present: active plan, next to-do, queue fields
220
+ 4. \`.cursor/commands/\` - slash catalog (Cursor). Follow the same HITL contracts here.
221
+
222
+ Mid-session refresh: \`/agent-kit\`.
223
+
224
+ ## HITL
225
+
226
+ ${CLAUDE_HITL_FALLBACK} Skip or cancel means stop. Never \`/git-prod\` without an explicit operator yes.
227
+
228
+ ## Commit messages
229
+
230
+ Never append \`Co-Authored-By: Claude ...\` or \`Claude-Session: https://claude.ai/code/...\` (or any session-ID trailer) to commit messages, PR descriptions, or PR bodies in this repo. This overrides the harness's default git-commit template. Plain Conventional Commits messages only.
231
+
232
+ ## Non-goals
233
+
234
+ Do not clone other Cursor hooks (Claude gets only the SessionStart context and PreToolUse(Bash) guard hooks); never \`/git-prod\` from a headless tick.
235
+ `;
236
+ }
237
+ function renderAgentKitCommand() {
238
+ return `---
239
+ description: Load Agent Kit session context (HANDOFF, project-context, commands). Manual refresh only.
240
+ disable-model-invocation: true
241
+ ---
242
+
243
+ Read these files if they exist. Prefer a one-shot ASCII Mission Control snapshot over a plain HANDOFF paraphrase.
244
+
245
+ 1. Run \`agent-kit mission-control --once\` (or \`npx @dadado/agent-kit-cli mission-control --once\`) in the project root and paste the stdout frame as the snapshot. Do not start a live loop: Claude Code cannot sustain one across turns.
246
+ 2. If that command is missing or fails, fall back to reading:
247
+ - \`AGENTS.md\`
248
+ - \`.cursor/project-context.md\`
249
+ - \`.cursor/HANDOFF.md\`
250
+ - The plan file named in HANDOFF \`- **Plan:**\` under \`.cursor/plans/\`
251
+
252
+ If HANDOFF is missing, say so and point at \`/agent-kit-onboard\` or \`/start-project\` rather than inventing a plan.
253
+
254
+ HITL: numbered-list fallback for Ask questions labels. Never \`/git-prod\` from this skill.
255
+
256
+ Non-goals: not audits / \`/plan-external-review\`, not a second tick dialect, not Cursor hook clones beyond the SessionStart and PreToolUse(Bash) adapters, not a continuous TUI loop.
257
+ `;
258
+ }
259
+ async function writeUnlessExists(rootDir, relativePath, content) {
260
+ const target = path2.join(rootDir, relativePath);
261
+ if (await fileExists(target)) {
262
+ return { relativePath, status: "skipped-customized" };
263
+ }
264
+ await ensureDir(path2.dirname(target));
265
+ await writeFile2(target, content, "utf8");
266
+ return { relativePath, status: "applied" };
267
+ }
268
+ async function generateClaudeKitLoadArtifacts(rootDir) {
269
+ return Promise.all([
270
+ writeUnlessExists(rootDir, CLAUDE_MD_REL, renderClaudeMd()),
271
+ writeUnlessExists(rootDir, AGENT_KIT_COMMAND_REL, renderAgentKitCommand())
272
+ ]);
273
+ }
274
+
275
+ // src/generator/vscode.ts
276
+ import { writeFile as writeFile3 } from "fs/promises";
277
+ import path3 from "path";
278
+
279
+ // src/generator/platform.ts
280
+ function gitProviderLabel(profile) {
281
+ const p = profile.git.provider;
282
+ if (!p || p === "other") return "Git";
283
+ const labels = {
284
+ github: "GitHub",
285
+ gitlab: "GitLab",
286
+ bitbucket: "Bitbucket",
287
+ "azure-devops": "Azure DevOps",
288
+ gitea: "Gitea"
289
+ };
290
+ return labels[p] ?? "Git";
291
+ }
292
+ function prTerminology(profile) {
293
+ const p = profile.git.provider;
294
+ if (!p || p === "other") return "PR/MR";
295
+ return GIT_PLATFORM_META[p]?.prTerm ?? "PR/MR";
296
+ }
297
+
298
+ // src/generator/vscode.ts
299
+ async function generateVSCodeArtifacts(profile) {
300
+ const results = [];
301
+ const vscodeDir = path3.join(profile.rootDir, ".vscode");
302
+ const githubDir = path3.join(profile.rootDir, ".github");
303
+ await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
304
+ const settingsPath = path3.join(vscodeDir, "settings.json");
305
+ if (await fileExists(settingsPath)) {
306
+ results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
307
+ } else {
308
+ await writeFile3(
309
+ settingsPath,
310
+ `${JSON.stringify(
311
+ {
312
+ "editor.formatOnSave": true,
313
+ "editor.codeActionsOnSave": {
314
+ "source.fixAll": "explicit"
315
+ },
316
+ "files.autoSave": "afterDelay"
317
+ },
318
+ null,
319
+ 2
320
+ )}
321
+ `,
322
+ "utf8"
323
+ );
324
+ results.push({ relativePath: ".vscode/settings.json", status: "applied" });
325
+ }
326
+ const provider = gitProviderLabel(profile);
327
+ const prTerm = prTerminology(profile);
328
+ const copilotPath = path3.join(githubDir, "copilot-instructions.md");
329
+ if (await fileExists(copilotPath)) {
330
+ results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
331
+ } else {
332
+ await writeFile3(
333
+ copilotPath,
334
+ `# Copilot Instructions
335
+
336
+ - Keep code changes small and testable.
337
+ - Use Conventional Commits (feat:, fix:, docs:, etc.).
338
+ - Prefer security-safe defaults.
339
+ - Git platform: ${provider}. Always create a ${prTerm} for review.
340
+ `,
341
+ "utf8"
342
+ );
343
+ results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
344
+ }
345
+ if (profile.ide.plan === "vscode-pro") {
346
+ const securityPath = path3.join(vscodeDir, "security-review.agent.md");
347
+ if (await fileExists(securityPath)) {
348
+ results.push({
349
+ relativePath: ".vscode/security-review.agent.md",
350
+ status: "skipped-customized"
351
+ });
352
+ } else {
353
+ await writeFile3(
354
+ securityPath,
355
+ "# Security Review Agent\n\nSpecialized mode for security review.\n",
356
+ "utf8"
357
+ );
358
+ results.push({ relativePath: ".vscode/security-review.agent.md", status: "applied" });
359
+ }
360
+ }
361
+ return results;
362
+ }
363
+
364
+ // src/generator/personalization.ts
365
+ var PERSONALIZATION_CONTRACT_VERSION = 1;
366
+ var CONTEXT_PATH = ".cursor/project-context.md";
367
+ var AGENTS_PATH = "AGENTS.md";
368
+ var RESULT_PATH = ".cursor/context/personalization.json";
369
+ function uniqueEvidence(evidence) {
370
+ const seen = /* @__PURE__ */ new Set();
371
+ return evidence.filter((item) => {
372
+ const key = `${item.source}:${item.value}`;
373
+ if (seen.has(key)) return false;
374
+ seen.add(key);
375
+ return true;
376
+ });
377
+ }
378
+ function purposeEvidence(profile) {
379
+ if (profile.purpose.confidence === "low") return [];
380
+ return profile.purpose.evidence;
381
+ }
382
+ function contextEvidence(profile, pattern) {
383
+ return profile.context.sources.filter((item) => pattern.test(item.value));
384
+ }
385
+ function readinessEvidence(report, checkId) {
386
+ return report.pillars.flatMap((pillar) => pillar.checks).filter((check) => check.id === checkId).flatMap((check) => check.evidence);
387
+ }
388
+ function hasPurpose(profile, purposes) {
389
+ return purposes.some(
390
+ (purpose) => profile.purpose.value === purpose || profile.purpose.categories.includes(purpose)
391
+ );
392
+ }
393
+ function candidate(kind, id, evidence, status) {
394
+ const verified = uniqueEvidence(evidence);
395
+ return verified.length > 0 ? { kind, id, status, evidence: verified } : null;
396
+ }
397
+ function componentAvailable(index, item) {
398
+ if (item.kind === "skill") return allSkills(index).some((entry) => entry.id === item.id);
399
+ if (item.kind === "pack") return allPacks(index).some((entry) => entry.id === item.id);
400
+ if (item.kind === "agent" || item.kind === "command") {
401
+ return (index.artifacts ?? []).some(
402
+ (entry) => entry.kind === item.kind && entry.id === item.id
403
+ );
404
+ }
405
+ return true;
406
+ }
407
+ function buildPersonalizationPlan(profile, report, registry) {
408
+ const items = [];
409
+ const purpose = purposeEvidence(profile);
410
+ const packageEvidence = profile.stack.packageManagerEvidence ?? [];
411
+ const n8nEvidence = [
412
+ ...contextEvidence(profile, /(^|[/.-])n8n([/.-]|$)/i),
413
+ ...purpose.filter((item) => /n8n/i.test(item.value))
414
+ ];
415
+ const sqlEvidence = [
416
+ ...contextEvidence(profile, /(^|[/.-])(sql|schema|migration)([/.-]|$)/i),
417
+ ...purpose.filter((item) => /\bsql\b/i.test(item.value))
418
+ ];
419
+ const promptEvidence = contextEvidence(profile, /(^|[/.-])prompts?([/.-]|$)/i);
420
+ const ciEvidence = profile.infra.ciFiles.map((value) => ({ source: "file", value }));
421
+ const infraEvidence = [
422
+ ...ciEvidence,
423
+ ...profile.infra.infrastructureFiles.map((value) => ({ source: "file", value })),
424
+ ...profile.infra.deploymentFiles.map((value) => ({ source: "file", value }))
425
+ ];
426
+ if (hasPurpose(profile, ["documentation", "knowledge"])) {
427
+ items.push(candidate("skill", "docs-repo", purpose, "applied"));
428
+ }
429
+ if (profile.stack.language.toLowerCase() === "node" && packageEvidence.length > 0) {
430
+ items.push(candidate("skill", "cursor-skills-node", packageEvidence, "applied"));
431
+ }
432
+ if (n8nEvidence.length > 0) {
433
+ items.push(candidate("skill", "n8n-workflows", n8nEvidence, "applied"));
434
+ } else if (hasPurpose(profile, ["automation"])) {
435
+ items.push(candidate("skill", "n8n-workflows", purpose, "recommended-confirmation"));
436
+ }
437
+ if (sqlEvidence.length > 0) {
438
+ items.push(candidate("skill", "sql-postgres", sqlEvidence, "applied"));
439
+ } else if (profile.services.database?.toLowerCase().includes("postgres")) {
440
+ items.push(
441
+ candidate(
442
+ "skill",
443
+ "sql-postgres",
444
+ [{ source: "configuration", value: `database:${profile.services.database}` }],
445
+ "recommended-confirmation"
446
+ )
447
+ );
448
+ }
449
+ if (promptEvidence.length > 0) {
450
+ items.push(candidate("skill", "prompts-markdown", promptEvidence, "applied"));
451
+ }
452
+ if (infraEvidence.length > 0) {
453
+ items.push(candidate("pack", "devops", infraEvidence, "applied"));
454
+ }
455
+ if (profile.stack.testCommands.length > 0 && packageEvidence.length > 0) {
456
+ items.push(candidate("pack", "quality", packageEvidence, "applied"));
457
+ items.push(candidate("agent", "test-suites", packageEvidence, "applied"));
458
+ }
459
+ const pmTools = (profile.services.projectManagement ?? []).filter((tool) => tool !== "none");
460
+ if (pmTools.length > 0) {
461
+ items.push(
462
+ candidate(
463
+ "pack",
464
+ "project-management",
465
+ pmTools.map((tool) => ({ source: "configuration", value: `projectManagement:${tool}` })),
466
+ "recommended-confirmation"
467
+ )
468
+ );
469
+ }
470
+ const safetyEvidence = readinessEvidence(report, "safety.secrets").filter(
471
+ (item) => item.value.startsWith("tracked:")
472
+ );
473
+ if (safetyEvidence.length > 0) {
474
+ items.push(candidate("pack", "cybersec", safetyEvidence, "recommended-confirmation"));
475
+ }
476
+ items.push(
477
+ candidate(
478
+ "command",
479
+ "start-project",
480
+ readinessEvidence(report, "agent-kit.context"),
481
+ "applied"
482
+ )
483
+ );
484
+ const availableItems = items.filter((item) => item !== null);
485
+ return availableItems.map(
486
+ (item) => componentAvailable(registry, item) ? item : { ...item, status: "unavailable" }
487
+ ).sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
488
+ }
489
+ var INSTALLED_SKILL_STATUSES = /* @__PURE__ */ new Set(["applied", "skipped-customized"]);
490
+ function installedSkillItems(items, installedIds = []) {
491
+ const rows = [];
492
+ const seen = /* @__PURE__ */ new Set();
493
+ for (const item of items) {
494
+ if (item.kind !== "skill" || !INSTALLED_SKILL_STATUSES.has(item.status)) continue;
495
+ if (seen.has(item.id)) continue;
496
+ seen.add(item.id);
497
+ rows.push(item);
498
+ }
499
+ for (const id of installedIds) {
500
+ if (seen.has(id)) continue;
501
+ seen.add(id);
502
+ rows.push({
503
+ kind: "skill",
504
+ id,
505
+ status: "applied",
506
+ evidence: [{ source: "configuration", value: `.cursor/agent-kit.json skills[]:${id}` }]
507
+ });
508
+ }
509
+ return rows.sort((left, right) => left.id.localeCompare(right.id));
510
+ }
511
+ function relevantSkillTableRows(skills) {
512
+ if (skills.length === 0) {
513
+ return ["| (none yet) | No installed or project-owned skills detected | \u2014 |"];
514
+ }
515
+ return skills.map((skill) => {
516
+ const label = skill.path ?? skill.id;
517
+ const role = skill.status === "skipped-customized" ? "Already present (customized)" : "Installed by personalization";
518
+ const evidence = skill.path ?? skill.evidence[0]?.value ?? "personalization";
519
+ return `| ${label} | ${role} | ${evidence} |`;
520
+ });
521
+ }
522
+ function renderProjectContext(profile, skillItems = []) {
523
+ const sections = ["# Project Context", "", "Verified repository facts:"];
524
+ const purpose = purposeEvidence(profile);
525
+ if (purpose.length > 0 && profile.purpose.value !== "unknown") {
526
+ sections.push(`- Purpose: ${profile.purpose.value}.`);
527
+ }
528
+ const packageEvidence = profile.stack.packageManagerEvidence ?? [];
529
+ if (packageEvidence.length > 0) {
530
+ sections.push(`- Runtime family: ${profile.stack.language}.`);
531
+ if (profile.stack.packageManager) {
532
+ sections.push(`- Package manager: ${profile.stack.packageManager}.`);
533
+ }
534
+ }
535
+ if (profile.git.provider && (profile.git.providerEvidence?.length ?? 0) > 0) {
536
+ sections.push(`- Git provider: ${profile.git.provider}.`);
537
+ }
538
+ if (profile.infra.ci !== "none" && profile.infra.ciFiles.length > 0) {
539
+ sections.push(`- CI: ${profile.infra.ci}.`);
540
+ }
541
+ sections.push(
542
+ "",
543
+ "## Relevant skills",
544
+ "",
545
+ "Installed and project-owned skills for this repository. Prefer `.cursor/agent-kit.json` `skills[]` as the install index; project-only domain skills may live under `.cursor/skills/domain/`.",
546
+ "",
547
+ "| Skill / path | Role | Evidence |",
548
+ "|--------------|------|----------|",
549
+ ...relevantSkillTableRows(installedSkillItems(skillItems))
550
+ );
551
+ if (profile.context.sources.length > 0) {
552
+ sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
553
+ }
554
+ return `${sections.join("\n")}
555
+ `;
556
+ }
557
+ async function createOwnedFile(rootDir, relativePath, content, evidence) {
558
+ const target = path4.join(rootDir, relativePath);
559
+ if (await fileExists(target)) {
560
+ return {
561
+ kind: "file",
562
+ id: relativePath,
563
+ path: relativePath,
564
+ status: "skipped-customized",
565
+ evidence
566
+ };
567
+ }
568
+ await ensureDir(path4.dirname(target));
569
+ await writeFile4(target, content, "utf8");
570
+ return {
571
+ kind: "file",
572
+ id: relativePath,
573
+ path: relativePath,
574
+ status: "applied",
575
+ evidence
576
+ };
577
+ }
578
+ async function packTargets(registryRoot, packId) {
579
+ const manifest = await loadPackManifest(registryRoot, packId);
580
+ const targets = [];
581
+ for (const member of manifest.members) {
582
+ for (const { targetRel } of await packMemberFileTargets(registryRoot, member)) {
583
+ targets.push(targetRel);
584
+ }
585
+ }
586
+ return targets;
587
+ }
588
+ async function existingTargets(projectRoot, targets) {
589
+ const checks = await Promise.all(
590
+ targets.map(
591
+ async (target) => await fileExists(path4.join(projectRoot, target)) ? target : null
592
+ )
593
+ );
594
+ return checks.filter((target) => target !== null);
595
+ }
596
+ async function applyPersonalization(input) {
597
+ const planned = buildPersonalizationPlan(input.profile, input.report, input.registry);
598
+ const stats = emptyStats();
599
+ const componentResults = [];
600
+ const protectedPaths = new Set(input.manifest.protected ?? []);
601
+ const packs = new Set(input.manifest.packs ?? []);
602
+ const skills = new Set(input.manifest.skills ?? []);
603
+ const notRecorded = [];
604
+ for (const item of planned) {
605
+ if (item.status !== "applied") {
606
+ componentResults.push(item);
607
+ continue;
608
+ }
609
+ if (item.kind === "skill") {
610
+ const skill = allSkills(input.registry).find((entry) => entry.id === item.id);
611
+ if (!skill) {
612
+ componentResults.push({ ...item, status: "unavailable" });
613
+ continue;
614
+ }
615
+ const target = path4.posix.join(skillTargetDir(skill.path, skill.id), "SKILL.md");
616
+ if (await fileExists(path4.join(input.rootDir, target))) {
617
+ componentResults.push({ ...item, status: "skipped-customized", path: target });
618
+ protectedPaths.add(target);
619
+ continue;
620
+ }
621
+ const skillStats = await installSkill(input.registryRoot, input.rootDir, skill);
622
+ mergeStats(stats, skillStats);
623
+ if (skillStats.skippedSymlink.length > 0) {
624
+ notRecorded.push(item.id);
625
+ componentResults.push({ ...item, status: "unavailable", path: target });
626
+ continue;
627
+ }
628
+ skills.add(item.id);
629
+ componentResults.push({ ...item, path: target });
630
+ continue;
631
+ }
632
+ if (item.kind === "pack") {
633
+ const targets = await packTargets(input.registryRoot, item.id);
634
+ const customizedTargets = await existingTargets(input.rootDir, targets);
635
+ const packStats = await installPack(input.registryRoot, input.rootDir, item.id, {
636
+ protectedGlobs: customizedTargets
637
+ });
638
+ mergeStats(stats, packStats);
639
+ for (const target of customizedTargets) protectedPaths.add(target);
640
+ if (packStats.skippedSymlink.length > 0) {
641
+ notRecorded.push(item.id);
642
+ componentResults.push({ ...item, status: "unavailable" });
643
+ continue;
644
+ }
645
+ packs.add(item.id);
646
+ componentResults.push(
647
+ customizedTargets.length > 0 ? { ...item, status: "skipped-customized" } : item
648
+ );
649
+ continue;
650
+ }
651
+ componentResults.push(item);
652
+ }
653
+ const profileEvidence = uniqueEvidence([
654
+ ...purposeEvidence(input.profile),
655
+ ...input.profile.stack.packageManagerEvidence ?? [],
656
+ ...input.profile.git.providerEvidence ?? [],
657
+ ...input.profile.context.sources
658
+ ]);
659
+ const fileResults = await Promise.all([
660
+ createOwnedFile(
661
+ input.rootDir,
662
+ CONTEXT_PATH,
663
+ renderProjectContext(input.profile, installedSkillItems(componentResults, skills)),
664
+ profileEvidence
665
+ ),
666
+ createOwnedFile(
667
+ input.rootDir,
668
+ AGENTS_PATH,
669
+ "# Repository Agent Guidance\n\nUse `.cursor/project-context.md` for verified repository facts. Preserve existing project-owned guidance and request confirmation before adding optional integrations.\n",
670
+ profileEvidence
671
+ )
672
+ ]);
673
+ protectedPaths.add(CONTEXT_PATH);
674
+ protectedPaths.add(AGENTS_PATH);
675
+ const claudeResults = await generateClaudeKitLoadArtifacts(input.rootDir);
676
+ const claudeItems = claudeResults.map((artifact) => {
677
+ protectedPaths.add(artifact.relativePath);
678
+ return {
679
+ kind: "file",
680
+ id: artifact.relativePath,
681
+ path: artifact.relativePath,
682
+ status: artifact.status,
683
+ evidence: profileEvidence
684
+ };
685
+ });
686
+ const claudeCommandItems = [];
687
+ let claudeSessionStartInstructions;
688
+ if (input.claudeAdapters) {
689
+ const adapterResults = await generateClaudeCommandAdapters(input.rootDir);
690
+ for (const artifact of adapterResults) {
691
+ protectedPaths.add(artifact.relativePath);
692
+ claudeCommandItems.push({
693
+ kind: "file",
694
+ id: artifact.relativePath,
695
+ path: artifact.relativePath,
696
+ // Overlay statuses (applied/unchanged/refreshed/preserved-customized)
697
+ // fold onto the shared PersonalizationStatus union: anything written
698
+ // or already current reads as "applied"; a hand-edited adapter left
699
+ // alone reads as "skipped-customized" (same meaning as elsewhere in
700
+ // this file — never silently clobbered).
701
+ status: artifact.status === "skipped-symlink" ? "unavailable" : artifact.status === "preserved-customized" ? "skipped-customized" : "applied",
702
+ evidence: profileEvidence
703
+ });
704
+ if (artifact.status === "skipped-symlink") stats.skippedSymlink.push(artifact.relativePath);
705
+ }
706
+ const hookResult = await writeClaudeSessionStartHook(input.rootDir);
707
+ protectedPaths.add(hookResult.relativePath);
708
+ claudeCommandItems.push({
709
+ kind: "file",
710
+ id: hookResult.relativePath,
711
+ path: hookResult.relativePath,
712
+ // "unavailable" (existing .claude/settings.json unparseable) is a real
713
+ // PersonalizationStatus value already; every other hook status folds
714
+ // onto "applied" the same way the command-adapter statuses do above.
715
+ status: hookResult.status === "unavailable" || hookResult.status === "skipped-symlink" ? "unavailable" : "applied",
716
+ evidence: profileEvidence
717
+ });
718
+ if (hookResult.status === "skipped-symlink") stats.skippedSymlink.push(hookResult.relativePath);
719
+ if (hookResult.ledgerPath) {
720
+ protectedPaths.add(hookResult.ledgerPath);
721
+ claudeCommandItems.push({
722
+ kind: "file",
723
+ id: hookResult.ledgerPath,
724
+ path: hookResult.ledgerPath,
725
+ status: "applied",
726
+ evidence: profileEvidence
727
+ });
728
+ }
729
+ if (hookResult.status === "unavailable" && hookResult.instructions) {
730
+ claudeSessionStartInstructions = hookResult.instructions;
731
+ }
732
+ }
733
+ const ideDetection = await detectIde(input.rootDir);
734
+ if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
735
+ const git = {
736
+ providerKind: input.profile.git.providerKind ?? "unknown",
737
+ providerConfidence: input.profile.git.providerConfidence ?? "low",
738
+ providerEvidence: input.profile.git.providerEvidence ?? [],
739
+ remotes: input.profile.git.remotes ?? [],
740
+ mode: input.profile.git.mode ?? "none",
741
+ workflow: input.profile.git.workflow ?? "unknown",
742
+ isDirty: input.profile.git.isDirty ?? false,
743
+ hasLocalStaging: input.profile.git.hasLocalStaging ?? false,
744
+ hasRemoteStaging: input.profile.git.hasRemoteStaging ?? false,
745
+ provider: input.profile.git.provider,
746
+ remoteUrl: input.profile.git.remoteUrl,
747
+ remoteName: input.profile.git.remoteName,
748
+ currentBranch: input.profile.git.currentBranch,
749
+ defaultBranch: input.profile.git.defaultBranch
750
+ };
751
+ const projectProfile = {
752
+ rootDir: input.rootDir,
753
+ stack: input.profile.stack,
754
+ git,
755
+ ide: ideDetection,
756
+ infra: input.profile.infra,
757
+ services: input.profile.services
758
+ };
759
+ const vscodeResults = await generateVSCodeArtifacts(projectProfile);
760
+ const ideEvidence = [
761
+ { source: "derived", value: `ide:${ideDetection.ide}` }
762
+ ];
763
+ for (const artifact of vscodeResults) {
764
+ protectedPaths.add(artifact.relativePath);
765
+ componentResults.push({
766
+ kind: "file",
767
+ id: artifact.relativePath,
768
+ path: artifact.relativePath,
769
+ status: artifact.status,
770
+ evidence: ideEvidence
771
+ });
772
+ }
773
+ }
774
+ const result = {
775
+ contractVersion: PERSONALIZATION_CONTRACT_VERSION,
776
+ generatorVersion: input.generatorVersion,
777
+ repositoryFingerprint: input.report.repositoryFingerprint,
778
+ items: [...fileResults, ...claudeItems, ...claudeCommandItems, ...componentResults],
779
+ protectedPaths: [...protectedPaths].sort(),
780
+ ...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
781
+ };
782
+ await writeJson(path4.join(input.rootDir, RESULT_PATH), result);
783
+ return {
784
+ result,
785
+ stats,
786
+ notRecorded,
787
+ manifest: {
788
+ ...input.manifest,
789
+ packs: [...packs].sort(),
790
+ skills: [...skills].sort(),
791
+ protected: result.protectedPaths,
792
+ personalization: {
793
+ contractVersion: PERSONALIZATION_CONTRACT_VERSION,
794
+ generatorVersion: input.generatorVersion,
795
+ origin: "repository-profile",
796
+ resultPath: RESULT_PATH
797
+ }
798
+ }
799
+ };
800
+ }
801
+ async function readRepositoryProfile(rootDir) {
802
+ const target = path4.join(rootDir, REPOSITORY_PROFILE_REL);
803
+ if (!await fileExists(target)) return null;
804
+ return JSON.parse(await readFile2(target, "utf8"));
805
+ }
806
+
807
+ // src/commands/install.ts
808
+ function parsePackList(raw) {
809
+ if (!raw?.trim()) return [];
810
+ return [
811
+ ...new Set(
812
+ raw.split(",").map((s) => s.trim()).filter(Boolean)
813
+ )
814
+ ];
815
+ }
816
+ function nextStepAfterInstall(pendingActions) {
817
+ return pendingActions > 0 ? "Next: run /agent-kit-onboard in Cursor to resolve the first pending action." : "Next: run /start-project in Cursor when you have a deliverable.";
818
+ }
819
+ function printReadinessNarrative(result) {
820
+ const { summary, pendingActions } = result.readiness;
821
+ const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
822
+ console.log("\nRepository readiness");
823
+ console.log(
824
+ ` ready: ${summary.ready}, choices: ${summary.needs_choice}, manual: ${summary.manual}, blocked: ${summary.blocked}`
825
+ );
826
+ console.log(` safe fixes applied: ${fixed}`);
827
+ console.log(` pending actions: ${pendingActions.length}`);
828
+ console.log(nextStepAfterInstall(pendingActions.length));
829
+ }
830
+ function paint(fn, text) {
831
+ const prevEnabled = koloristOptions.enabled;
832
+ const prevSupportLevel = koloristOptions.supportLevel;
833
+ koloristOptions.enabled = true;
834
+ koloristOptions.supportLevel = 3;
835
+ try {
836
+ return fn(text);
837
+ } finally {
838
+ koloristOptions.enabled = prevEnabled;
839
+ koloristOptions.supportLevel = prevSupportLevel;
840
+ }
841
+ }
842
+ function printInstallEpilogue(env, options = {}) {
843
+ const print = options.print ?? ((line) => console.log(line));
844
+ const color = options.color ?? shouldUseWelcomeColor();
845
+ const runtimeVersion = options.runtimeVersion ?? KIT_VERSION;
846
+ const status = pathCliStatus(env, runtimeVersion);
847
+ if (status === "current") {
848
+ const line = `PATH \`agent-kit\` is v${env.binVersion ?? runtimeVersion}. Run it directly, e.g. \`agent-kit doctor\`.`;
849
+ print(color ? paint(green, line) : line);
850
+ return;
851
+ }
852
+ if (status === "behind" || status === "unknown") {
853
+ const line = `Keep using ${npxPinned(runtimeVersion, "<subcommand>")} until PATH is v${runtimeVersion}.`;
854
+ print(color ? paint(green, line) : line);
855
+ return;
856
+ }
857
+ const divider = "\u2500".repeat(60);
858
+ const body = [
859
+ "You ran this through npx, so a bare `agent-kit` isn't on PATH yet.",
860
+ 'If you try `agent-kit <subcommand>` next, you will see "command not',
861
+ 'found". Pick one:',
862
+ "",
863
+ " 1. Keep using npx (works right now, no action needed)",
864
+ ` ${npxPinned(runtimeVersion, "<subcommand>")}`,
865
+ "",
866
+ " 2. Put a bare `agent-kit` on PATH",
867
+ ` ${npxPinned(runtimeVersion, "setup-global")}`,
868
+ " (fixes a root-owned npm prefix if that's the blocker, or just installs)",
869
+ "",
870
+ " 3. Manual steps",
871
+ " See docs/getting-started.md (Troubleshooting npm failures), or:",
872
+ " mkdir -p ~/.npm-global",
873
+ ' npm config set prefix "~/.npm-global"',
874
+ ' export PATH="~/.npm-global/bin:$PATH"',
875
+ ` npm i -g ${pinnedCliSpec(runtimeVersion)}`
876
+ ];
877
+ print(color ? paint(cyan, divider) : divider);
878
+ const heading = "Heads up: a bare `agent-kit` command won't work yet";
879
+ print(color ? paint(bold, paint(cyan, heading)) : heading);
880
+ for (const line of body) print(line);
881
+ print(color ? paint(cyan, divider) : divider);
882
+ }
883
+ async function printPostInstallSummary(result) {
884
+ printReadinessNarrative(result);
885
+ const env = await assessEnvironment();
886
+ const sync = await syncPathCliToRuntime({ runtimeVersion: KIT_VERSION, env });
887
+ for (const line of sync.lines) console.log(line);
888
+ printInstallEpilogue(sync.env, { runtimeVersion: KIT_VERSION });
889
+ }
890
+ async function performInstall(options) {
891
+ const projectRoot = path5.resolve(options.cwd);
892
+ const packs = parsePackList(options.pack);
893
+ const existing = await loadAgentKitManifest(projectRoot, { onWarning: logger.warn });
894
+ const registry = await resolveRegistryFromCli({
895
+ cwd: projectRoot,
896
+ registry: options.registry,
897
+ url: options.url,
898
+ ref: options.ref,
899
+ refresh: options.refresh,
900
+ manifest: existing
901
+ });
902
+ try {
903
+ const draft = buildManifest({
904
+ version: KIT_VERSION,
905
+ profile: options.profile ?? existing?.profile ?? "default",
906
+ packs: packs.length > 0 ? packs : existing?.packs,
907
+ skills: existing?.skills,
908
+ protected: existing?.protected,
909
+ personalization: existing?.personalization,
910
+ registryUrl: registry.url ?? existing?.registry?.url,
911
+ registryRef: registry.ref ?? existing?.registry?.ref
912
+ });
913
+ if (existing?.unknownFields) draft.unknownFields = existing.unknownFields;
914
+ const stats = (draft.packs?.length ?? 0) > 0 || (draft.skills?.length ?? 0) > 0 ? await syncFromManifest(registry.root, projectRoot, draft) : await installL0(registry.root, projectRoot, resolveProtectedGlobs(draft));
915
+ if (stats.skippedSymlink.length > 0) throw new SkippedSymlinkError(stats);
916
+ const manifestPath = await saveManifest(projectRoot, draft);
917
+ const readinessExecution = await executeSafeReadinessFixes(projectRoot, {
918
+ generatorVersion: KIT_VERSION
919
+ });
920
+ let readiness = readinessExecution.after;
921
+ let claudeSessionStartInstructions;
922
+ const profile = await readRepositoryProfile(projectRoot);
923
+ if (profile) {
924
+ const registryIndex = await loadRegistry(registry.root);
925
+ const personalization = await applyPersonalization({
926
+ rootDir: projectRoot,
927
+ registryRoot: registry.root,
928
+ profile,
929
+ report: readinessExecution.after,
930
+ registry: registryIndex,
931
+ manifest: draft,
932
+ generatorVersion: KIT_VERSION,
933
+ claudeAdapters: options.claudeAdapters
934
+ });
935
+ await saveManifest(projectRoot, personalization.manifest);
936
+ claudeSessionStartInstructions = personalization.result.claudeSessionStartInstructions;
937
+ readiness = createReadinessReport(await runScanner(projectRoot), {
938
+ generatorVersion: KIT_VERSION
939
+ });
940
+ readiness.appliedSafeFixes = readinessExecution.after.appliedSafeFixes;
941
+ if (personalization.stats.skippedSymlink.length > 0) {
942
+ await writeReadinessSnapshot(projectRoot, readiness);
943
+ throw new SkippedSymlinkError(
944
+ mergeStats(stats, personalization.stats),
945
+ formatPersonalizationSkippedSymlinks(
946
+ personalization.stats.skippedSymlink,
947
+ personalization.notRecorded
948
+ ),
949
+ claudeSessionStartInstructions
950
+ );
951
+ }
952
+ }
953
+ await writeReadinessSnapshot(projectRoot, readiness);
954
+ return {
955
+ projectRoot,
956
+ manifestPath,
957
+ stats,
958
+ readiness,
959
+ safeChanges: readinessExecution.changes,
960
+ ...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
961
+ };
962
+ } finally {
963
+ await registry.unlock?.();
964
+ }
965
+ }
966
+ var installCommand = defineCommand({
967
+ meta: {
968
+ name: "install",
969
+ description: "Bootstrap L0 (+ optional packs) and write .cursor/agent-kit.json."
970
+ },
971
+ args: {
972
+ profile: {
973
+ type: "positional",
974
+ description: "Install profile name stored in the manifest (default: default)",
975
+ required: false
976
+ },
977
+ pack: {
978
+ type: "string",
979
+ description: `Comma-separated L1 pack ids (e.g. cybersec,devops). Known: ${DOMAIN_PACK_IDS.join(", ")}`
980
+ },
981
+ ...NON_INTERACTIVE_ROOT_ARGS,
982
+ claude: {
983
+ type: "boolean",
984
+ description: "Opt-in: generate .claude/commands/*.md thin pointer adapters for the installed .cursor/commands set, and merge a SessionStart context hook into .claude/settings.json (default install behavior is unchanged without this flag)",
985
+ default: false
986
+ },
987
+ cwd: {
988
+ type: "string",
989
+ default: process.cwd()
990
+ },
991
+ ...REGISTRY_CLI_ARGS
992
+ },
993
+ async run({ args }) {
994
+ const nonInteractive = args.yes || isNonInteractive();
995
+ if (nonInteractive) {
996
+ logger.info("Non-interactive mode: skipping prompts, using defaults.");
997
+ }
998
+ let projectRoot;
999
+ try {
1000
+ projectRoot = await confirmProjectRoot(args.cwd, {
1001
+ nonInteractive,
1002
+ command: "install",
1003
+ forceRoot: args["force-root"]
1004
+ });
1005
+ } catch (err) {
1006
+ if (err instanceof RootRefusedError) {
1007
+ logger.error(err.message);
1008
+ if (err.recovery) console.error(`
1009
+ ${err.recovery}
1010
+ `);
1011
+ process.exitCode = 1;
1012
+ return;
1013
+ }
1014
+ throw err;
1015
+ }
1016
+ logger.info(`Installing into: ${projectRoot}`);
1017
+ logger.info(`CLI v${KIT_VERSION}`);
1018
+ await warnIfRunningCliBehindNpm(projectRoot, { warn: (message) => logger.warn(message) });
1019
+ const packs = parsePackList(args.pack);
1020
+ for (const id of packs) {
1021
+ if (!DOMAIN_PACK_IDS.includes(id)) {
1022
+ logger.warn(`Pack '${id}' is not in the known L1 list; will still try registry.`);
1023
+ }
1024
+ }
1025
+ try {
1026
+ const result = await withCliProgress(
1027
+ "install",
1028
+ () => performInstall({
1029
+ cwd: projectRoot,
1030
+ profile: args.profile,
1031
+ pack: args.pack,
1032
+ registry: args.registry,
1033
+ url: args.url,
1034
+ ref: args.ref,
1035
+ refresh: args.refresh,
1036
+ claudeAdapters: args.claude
1037
+ })
1038
+ );
1039
+ logApplyStats(result.stats);
1040
+ logger.success(`Manifest written: ${result.manifestPath}`);
1041
+ logger.success("Readiness snapshot written: .cursor/context/readiness.json");
1042
+ await printPostInstallSummary(result);
1043
+ if (result.claudeSessionStartInstructions) {
1044
+ logger.warn("Could not merge the Claude Code SessionStart hook automatically:");
1045
+ console.log(`
1046
+ ${result.claudeSessionStartInstructions}
1047
+ `);
1048
+ }
1049
+ } catch (err) {
1050
+ if (err instanceof SkippedSymlinkError) {
1051
+ logApplyStats(err.stats);
1052
+ logger.error(err.message);
1053
+ if (err.claudeSessionStartInstructions) {
1054
+ logger.warn("Could not merge the Claude Code SessionStart hook automatically:");
1055
+ console.log(`
1056
+ ${err.claudeSessionStartInstructions}
1057
+ `);
1058
+ }
1059
+ process.exitCode = 1;
1060
+ return;
1061
+ }
1062
+ const hint = classifyInstallError(err);
1063
+ logger.error(hint.message);
1064
+ console.error(`
1065
+ ${hint.recovery}
1066
+ `);
1067
+ process.exitCode = 1;
1068
+ return;
1069
+ }
1070
+ }
1071
+ });
1072
+
1073
+ export {
1074
+ nextStepAfterInstall,
1075
+ printInstallEpilogue,
1076
+ performInstall,
1077
+ installCommand
1078
+ };