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