@dadado/agent-kit-cli 4.8.9 → 5.0.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { defineCommand as defineCommand19, runMain } from "citty";
4
+ import { defineCommand as defineCommand19, runMain, showUsage } from "citty";
5
5
 
6
6
  // src/commands/add.ts
7
7
  import { defineCommand } from "citty";
@@ -69,53 +69,116 @@ import path3 from "path";
69
69
 
70
70
  // src/lifecycle/overlay-known-hashes.ts
71
71
  var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
72
+ "004d98f557942677264d053dcd8335f2bbcf19c5591123ec16a7a2697cb88932",
73
+ "05ee6ecde4215e33ace0ee152ffbf2cd885d2bd4b032900f0f0bb001d350ef0f",
72
74
  "082bdbc584be3f3d8aa6e9d7b9f4e076450f3ff48f9147a1deb557e31475036b",
75
+ "0b5fe4729c3c68654434837630d775b847c082418f25b0b2c581700fe84b797a",
76
+ "1106b279558b6a20f87951102f172a680976f3e6530855d5dad083e1f89b9ba3",
73
77
  "15566765ec95dad3bc3160f20ac910e01a13471df536e6d990f4b263700784a9",
74
78
  "1681e6cf7b80a67cd999b94b8b6e16eb8caed0e11b32905848ba733da956e902",
79
+ "17818e47e06349ee35c1d8e1cc16e8bcbbbcc89c664014e9096e1f08c8567807",
75
80
  "1974a5af0e4ba2bb0fc7e1872ef9ab70e323323ab216958c31731bcdd82c8241",
81
+ "1b384771cc6228215e31e6a9ab0943b13a5cdc463354a2641f1c0d85b4357e84",
82
+ "1b5f8c4197393dc9110d020f3b619b8502862adeb6522a6b9be4b49dd8794612",
83
+ "1c5c1af87951367ad04bb6fa5b1d6ebaaefbc0ee95989b624cabf406a1869907",
84
+ "1ded5c7b7eefc355fc1d17feb65ec472e35ed96076e47ce95f7bff5c1b84746c",
76
85
  "1e58e0d4f2e8459b95a40d1b1e72c5c85a87230cbde945840a84073fe3a20422",
77
86
  "285668d08f1b15d96286d27e29cc4a67e126e097fb8a940bdde33ccf25bd2bcd",
87
+ "2acdb148b07f3a4872b896da419af796fd3fefaace7ac79cdee0aab447004a83",
88
+ "2cd7fc018a384b0188737259ecf593ce25b4b75cc38b3e410063ac91745894b9",
89
+ "2f6cedfa61cb62126efd6b845bbf4d8d7913e39223d5f799ac09ef64c0489824",
90
+ "3204a7449f3031916a8d57b37144e5d8ded6c94aa5659493fa5cc2c7321f4eab",
91
+ "33982aea21a074d21a638850475ce1fefa7d043ec035de3855889cc68d60cf35",
92
+ "38675aec695e9d4e7f16e8fbe3facc3280f47fb875bf9ac50bd6837b92b7a7fa",
93
+ "3ee94e2441218025c085856679d17b4a4fe7a6e3d0274423abe20a422a5ab05d",
78
94
  "3f3354825ff06ff9f9ae1e29b71784fef4250020c23c1b490940cf06b2be0f28",
95
+ "46a1dd4a07a2190ab9fb09abc05a3962277209f8e0c28033bb41450301474c86",
96
+ "48a0d55e21132a305946b195db7f6b75da78d44a1d32166fb0c69bef4a5ffbe2",
97
+ "4b62a46c74e5fa6a80283f21e8e6711958fbcd577051dc73ff4050643a031475",
79
98
  "4bf23acbc9bd4e0f8468e61f4970e182600b72d9025d29e81ddbd83cb7b1710a",
80
99
  "4c5167d0ebde685f266dce32c516a7c43876e0520647b93f07f2e2147e6a7b2f",
81
100
  "4dc4efcd1b43c643d3889fb349df8d4964d0867c3a67eb67e2ec168fbb70fc75",
101
+ "4f133554d01cedc62280006f4cf6453e25a5cb41ef6dde4a43f9508cb36ef75d",
102
+ "4fe1e2bce8bd55a6decdd62c887a423cf85148b692f313aad4c777d0e50596b3",
82
103
  "4ff844cf789cef4b1e809a55ca006316cd964bcdb2557d91c916a08e30ad15f6",
83
104
  "533b41599c25698e5746166096a68fd4080efb5e16287b2a1ed5f73295cda446",
105
+ "54dda8addb74bc187ee23c31f8b7d17ad19e745a45169d29ffea094860eb18f2",
84
106
  "570c3f91fd31a4cc1e56e997c85feab5c6756f45fc73dc741afe573912111231",
107
+ "5cb36f5dab846b2551afd05eab92c14122853c147d7f02136d1c04e01e3c9def",
85
108
  "5d302d207521c53b9fa814ee0eba1376a5b2bfd2983c0373657c9fdf9884541b",
109
+ "5f5c125f7edf280029f214589ce3e088fc74bad717e30178405a3b537673c42f",
86
110
  "621bc6cecf7c710a97e93463ca9b8872a1fcab1325c358cc8be65b418eebb339",
111
+ "693c732658e31d6403cf59e85985518f24ab92bf7fcdc3c225e9a79e54597a43",
87
112
  "69715048da3963947aa8a39e7ac30ec01632fec862bfcb9559c46764b4833be0",
113
+ "6994595e3a62edb6c518006a6bece6848c7077977c041439cf7c822b89304a14",
88
114
  "6a42e76fc038ca69ddad18541b8df161770c58980fefaf9e5e80d87407074bec",
89
115
  "6a9af9e8a95dbb279347166c43dcee245ca673f9687841086b49911c54944c26",
90
116
  "6b66ed7747ea2d19cb457f5af656e3c07f9f563f7f32b3951fa4ae5f27f0ac46",
117
+ "6dede0588274f5b44d833bcc696ec4776eb978ed24a20efdcc9c38caf3df1576",
118
+ "71991a10972b8b956cae93c88517e31741c95e351814082df65e44f813590210",
119
+ "75e591bfde61ca1ca3ca1303cc9bc487197af2f713c68f346a46e94ca9432fd5",
91
120
  "76ae85bbc336c6f435d9653e8f254ae49774b4ebaecf7335697115933eb8797c",
121
+ "79878f0f6f59d1a0b65d94011c0cafe7b6f8e03e89f568aa638b8ba7a2c34076",
92
122
  "8108b202c829627c3c2df33575016ebe942df42acf4f1cc393900be3b82b402c",
123
+ "819b7bd3ba9e8bf12dcd4f81a76c98e48261b34c397f339b36d36eb364d9c3f4",
93
124
  "8619500b0c7024390d43e3aaefa2a2d731728d7c664aaf90e0acdd067f3397ce",
125
+ "875b0a8805280b2a2df43f2c42f9063fe0623a34fa19c3aa5d2ec48211f3ebae",
94
126
  "8a11c80b0fbf6c4aabf60eec05b8cca7dccf5bca472787a8afa3028fd8fbedfa",
95
127
  "8b25659727063013e8bf6d7925f815b2630c2919b21e6fdac354362bf122bfab",
96
128
  "8c7edbacc74b1431fbecb04c9857c0e7a9cb1578fc6ca6367be888dbcec3c98f",
129
+ "8ffc181bb152c68a3353309bfdc87c42bdb925b98675cdba959c0cdb90b2adea",
130
+ "91ce0c933e3c8ee81927b8224f944ec5ce5445098a2ad154773092e3541dbf69",
131
+ "9309af07a0a99a56143386b3ddd2a35fae29378fba3f6b4c9297a5d1c02a9724",
97
132
  "9439da963acbe5dd88a3aa4d6ba4fb297db666ce8f2c9d3917dbc6d6e517f9aa",
133
+ "989e7e1bdfb29dedf850405f28648810d008885196efe7ae38c6e7554c7ce45d",
98
134
  "98d96e25f67aa9c234cb0c422f0d701c22a04bfe184fb4eb68295003d429552b",
99
135
  "99031145609af46e7e0f315006bf633f47002eccece8eb7af8b5efd762313ade",
100
136
  "9a3cea681399b42ef930a0720802378fe1b06ee73370df9ede9d0309c3c60ab2",
137
+ "9ccf52fe53c6fa4d731650ab4928ce50e201fa34f3e174fee70e3d29867fef2a",
101
138
  "9d905aca0123f3018c81c579217104f1749d8da802f3837b2407fbad8f2d803f",
139
+ "9e324c52c1a9d1ceee738bccf47da92910aca9f7a5a21cc41738d9573c414830",
140
+ "a013a6a476160183d6457ff6c1c51ec228bdca359b11eb15cd9b7fc177a705db",
102
141
  "a08d04f8efa121ad498f96873778c22630c9172c07c216839e348cd0bb7c2292",
142
+ "a7cd7058bcb6fbd962c37317fa16893bc86a270d0164af5a0e6f3f026aa408ad",
143
+ "a833a7131cdfc003b45a48cb26c6ce33dc218c1814156047db5211a79192e0a0",
144
+ "ac2f1f7d9e68f9c98fc4fe5ca348b1a90cf484141bbd116db871d63bdcc3c7c3",
103
145
  "b5c7a10df90d209be07159b7ff9bfa24e869538a0b6026bb5ce79e1cdd5b2528",
146
+ "bd9fd1efacbd132c5dafc12cd0cf2b651a2b1fab7b2d7a9ba4a3e44128097f48",
147
+ "be964ff33a40280e13ce6071fb3e30ce87c374527efbb74c25c9d4534084a634",
104
148
  "c96d522be440fbb638dbe953cebf1bb31185c431ac2e14446369cfb7d155a951",
105
149
  "cf2bd11891b934484171fa705f470de284e3874402e1e487750735dbc7692857",
150
+ "d0e3bf82efbe32c09761689802ffea7838046dbdd4803b88ead5ec3dfc7efb7e",
106
151
  "d4659e4bb6fd6022559fc632b17db6a39007c04bc0ded59017a0dacc013632ed",
107
152
  "d484243167e13c34f9df7ff378baf2967f7cbfcd88170d4d32e745efbcae7480",
153
+ "d67ac8fa1edec6e50c82aeca064335e799e408d0e4bbc95e1407cf7463a61de3",
154
+ "d7ad53f06a41bcb2f2b998fd4a4c99209a0cf287cf0d0d4acbcc36c73dd46e4a",
108
155
  "d96aa15dde45147aa40db64684688a4b5c9ad660dcd16c55156b31933a42c37e",
109
156
  "d9ecd90f80ebc25db73ebed27c32596b9cfce518b3026a3a4d5787321ba6324a",
110
157
  "dd01d051dbab257e16609b02c983bad6a576ed8473781b4f1df9ef2eb5f85795",
158
+ "dd9ff2ff8aa7bcb2cdfc59092ad73227631c1c3619b495131f7faee65224cb17",
111
159
  "de6efabde6a1d69357926b88b5a4ed3eec19e11b82597cb999f5365f952305b8",
160
+ "debc0758b3e39294df85ccb7eab3455e93a2b8c608602d309a7b0ebdb6618805",
112
161
  "e03b5764b908f9ed5c592108fbc11d5962d9256fd129a63ab4a39c8efaa2ef3e",
113
162
  "e102e77f9f520f938d26861978249e4d85a8c8755d48c8e79b21fef9423325ee",
163
+ "e2d89f780c6ae5393dc05818f9e3e9a8bf6629eac05702eea8f9a25b578e3ff1",
164
+ "e2f1e9108fd450817ab42570db656b1e43b20b5ba703b14027ce540e1b8fe502",
165
+ "e37c62695b5c70451ef1a0c90b31b73a9c442ebc55fb3fc3f79c294b993b99ea",
166
+ "e3c32aec17e8f728cbd13162a123a9debffb8bbcec527a00b1c2df30a1a20080",
114
167
  "e511e04064ffb8c429b659d2ae2e0f223e8cc51039ae816753095a5f7a160595",
168
+ "e5682d4696e874d09f12a1d65891fee5971201a29c654c08f5d3b90fb9a6a0b7",
169
+ "e57437bf790fda1d43b895e2b5b0a7d2d57635c319ac0e2439ba2222a825bcde",
170
+ "e8bb214c3e551c44f379db13c5962ef20698b505b0227d283d3ae632e1bc3626",
171
+ "ea36e74c9d592980cba5c08d04ee70fc59a6cd01e87a50bbfae4f668557df4b4",
115
172
  "ebd41f8df764a14b3460016fafcfdde98c4c3a59a3c17f773451ef061fc42f95",
173
+ "ef181a4e2bd9f82f45628637b8d66925ffa20801558808f1b03ea72b7cb82a72",
116
174
  "f061432a2606049717c0cbf098ef07b1da4df2b136c4cb9cdaa7ea44c876bcd6",
117
175
  "f061d36a1180293519fbb560bbf659c621bd90885e995fca6eaf2eda9e418a50",
118
- "f28731a82f4b394a5765fdefe2d1b667810831f6dcea400ca62bc66e1d65371c"
176
+ "f28731a82f4b394a5765fdefe2d1b667810831f6dcea400ca62bc66e1d65371c",
177
+ "f46c108457e4fe12953dca75b409e06fca9cb264baffabe0c0d04c89af471e56",
178
+ "f6bc17efa746680a44bbc49be67ac3f924d9c306c6d937f9959c57a1d3c5b9af",
179
+ "f981764422d468567b5aff31148dc659aeee22cb13dc0ecd873d737deaf07372",
180
+ "fa306a0cdb0f40c817e32164cd03b946564a02b4b7f7c3f4f0b9513096584d28",
181
+ "fa5cf460eb314437081f7cea30dc8041c0bd6fc3f560a74bc2d7be1bf07384b0"
119
182
  ]);
120
183
 
121
184
  // src/lifecycle/paths.ts
@@ -475,11 +538,119 @@ function logApplyStats(stats) {
475
538
 
476
539
  // src/registry/resolve.ts
477
540
  import { execFile } from "child_process";
478
- import { createHash as createHash2 } from "crypto";
541
+ import { createHash as createHash2, randomUUID } from "crypto";
542
+ import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
479
543
  import { homedir } from "os";
480
544
  import path5 from "path";
481
545
  import { promisify } from "util";
482
546
  var execFileAsync = promisify(execFile);
547
+ var LOCK_TIMEOUT_MS = 6e4;
548
+ var LOCK_RETRY_MS = 200;
549
+ var LOCK_STALE_MS = 5 * 6e4;
550
+ var LOCK_REFRESH_MS = 1e4;
551
+ function lockOwnerPath(lockDir) {
552
+ return path5.join(lockDir, "owner.json");
553
+ }
554
+ function isProcessAlive(pid) {
555
+ try {
556
+ process.kill(pid, 0);
557
+ return true;
558
+ } catch {
559
+ return false;
560
+ }
561
+ }
562
+ async function readLockOwner(lockDir) {
563
+ try {
564
+ const raw = await readFile4(lockOwnerPath(lockDir), "utf8");
565
+ const parsed = JSON.parse(raw);
566
+ if (typeof parsed.pid === "number" && typeof parsed.uuid === "string") {
567
+ return parsed;
568
+ }
569
+ } catch {
570
+ }
571
+ return null;
572
+ }
573
+ async function writeLockOwner(lockDir, owner) {
574
+ const finalPath = lockOwnerPath(lockDir);
575
+ const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.tmp`);
576
+ await writeFile3(tmpPath, JSON.stringify(owner), "utf8");
577
+ await rename(tmpPath, finalPath);
578
+ }
579
+ async function refreshLockOwner(lockDir, uuid) {
580
+ const owner = await readLockOwner(lockDir);
581
+ if (!owner || owner.uuid !== uuid) return;
582
+ await writeLockOwner(lockDir, { ...owner, updatedAt: Date.now() });
583
+ }
584
+ async function releaseCacheLock(lockDir, uuid) {
585
+ const owner = await readLockOwner(lockDir);
586
+ if (!owner || owner.uuid !== uuid) {
587
+ return;
588
+ }
589
+ try {
590
+ await rm(lockDir, { recursive: true, force: true });
591
+ } catch {
592
+ }
593
+ }
594
+ async function tryReclaimStaleLock(lockDir) {
595
+ let owner;
596
+ let lockStat;
597
+ try {
598
+ lockStat = await stat(lockDir);
599
+ owner = await readLockOwner(lockDir);
600
+ } catch {
601
+ return false;
602
+ }
603
+ const now2 = Date.now();
604
+ if (owner) {
605
+ const ownerFresh = now2 - owner.updatedAt < LOCK_STALE_MS;
606
+ if (isProcessAlive(owner.pid) && ownerFresh) {
607
+ return false;
608
+ }
609
+ }
610
+ if (now2 - lockStat.mtimeMs > LOCK_STALE_MS) {
611
+ try {
612
+ await rm(lockDir, { recursive: true, force: true });
613
+ return true;
614
+ } catch {
615
+ }
616
+ }
617
+ return false;
618
+ }
619
+ async function acquireCacheLock(cacheDir) {
620
+ const lockDir = `${cacheDir}.lock`;
621
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
622
+ const uuid = randomUUID();
623
+ const owner = { pid: process.pid, uuid, updatedAt: Date.now() };
624
+ await mkdir4(path5.dirname(lockDir), { recursive: true });
625
+ while (Date.now() < deadline) {
626
+ try {
627
+ await mkdir4(lockDir, { recursive: false });
628
+ await writeLockOwner(lockDir, owner);
629
+ const refreshInterval = setInterval(() => {
630
+ refreshLockOwner(lockDir, uuid).catch(() => {
631
+ });
632
+ }, LOCK_REFRESH_MS);
633
+ return async () => {
634
+ clearInterval(refreshInterval);
635
+ await releaseCacheLock(lockDir, uuid);
636
+ };
637
+ } catch (err) {
638
+ const code = err.code;
639
+ if (code === "EEXIST") {
640
+ if (await tryReclaimStaleLock(lockDir)) {
641
+ continue;
642
+ }
643
+ await new Promise((r) => setTimeout(r, LOCK_RETRY_MS + Math.random() * 100));
644
+ continue;
645
+ }
646
+ if (code === "ENOENT") {
647
+ continue;
648
+ }
649
+ throw err;
650
+ }
651
+ }
652
+ throw new Error(`Timed out waiting for cache lock on ${cacheDir}. Another install may be stuck.`);
653
+ }
483
654
  var DEFAULT_REGISTRY_URL = "https://github.com/agent-kit-startup/agent-kit";
484
655
  var DEFAULT_REGISTRY_REF = "main";
485
656
  var SAFE_URL = /^https:\/\/[^\s]+$/;
@@ -504,15 +675,32 @@ async function hasRegistryIndex(root) {
504
675
  return fileExists(path5.join(root, "registry", "registry.json"));
505
676
  }
506
677
  async function cloneRegistry(url, ref, dest) {
507
- const { mkdir: mkdir6 } = await import("fs/promises");
508
- await mkdir6(path5.dirname(dest), { recursive: true });
678
+ const { mkdir: mkdir7 } = await import("fs/promises");
679
+ await mkdir7(path5.dirname(dest), { recursive: true });
509
680
  try {
510
681
  await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
511
682
  env: gitEnv()
512
683
  });
513
- } catch {
514
- await execFileAsync("git", ["clone", "--depth", "1", "--", url, dest], { env: gitEnv() });
515
- await execFileAsync("git", ["checkout", ref, "--"], { cwd: dest, env: gitEnv() });
684
+ } catch (firstErr) {
685
+ try {
686
+ await execFileAsync("git", ["clone", "--depth", "1", "--", url, dest], { env: gitEnv() });
687
+ await execFileAsync("git", ["checkout", ref, "--"], { cwd: dest, env: gitEnv() });
688
+ } catch (secondErr) {
689
+ const msg = secondErr instanceof Error ? secondErr.message : String(secondErr);
690
+ if (/403|Authentication|could not read Username/i.test(msg)) {
691
+ throw new Error(
692
+ `Registry clone failed (auth/access): ${url}@${ref}. Check git credentials or use --registry <local-path> for a local checkout.`
693
+ );
694
+ }
695
+ if (/Could not resolve host|Network is unreachable|Connection refused/i.test(msg)) {
696
+ throw new Error(
697
+ `Registry clone failed (network): ${url}@${ref}. Check network/proxy settings or use --registry <local-path>.`
698
+ );
699
+ }
700
+ throw new Error(
701
+ `Registry clone failed: ${url}@${ref}. ${msg}. Use --registry <local-path> if you have a local checkout.`
702
+ );
703
+ }
516
704
  }
517
705
  }
518
706
  async function refreshCache(cacheDir) {
@@ -556,15 +744,24 @@ async function resolveRegistryRoot(options) {
556
744
  const ref = options.registryRef ?? DEFAULT_REGISTRY_REF;
557
745
  assertSafeRegistrySource(url, ref);
558
746
  const dest = path5.join(homedir(), ".cache", "agent-kit", "registry", cacheKey(url, ref));
559
- if (await hasRegistryIndex(dest)) {
560
- await refreshCache(dest);
561
- return { root: dest, source: "remote-cache", url, ref };
562
- }
563
- await cloneRegistry(url, ref, dest);
564
- if (!await hasRegistryIndex(dest)) {
565
- throw new Error(`Cloned ${url}@${ref} but registry/registry.json is missing`);
747
+ const unlock = await acquireCacheLock(dest);
748
+ let ownershipTransferred = false;
749
+ try {
750
+ if (await hasRegistryIndex(dest)) {
751
+ await refreshCache(dest);
752
+ } else {
753
+ await cloneRegistry(url, ref, dest);
754
+ if (!await hasRegistryIndex(dest)) {
755
+ throw new Error(`Cloned ${url}@${ref} but registry/registry.json is missing`);
756
+ }
757
+ }
758
+ ownershipTransferred = true;
759
+ return { root: dest, source: "remote-cache", url, ref, unlock };
760
+ } finally {
761
+ if (!ownershipTransferred) {
762
+ await unlock();
763
+ }
566
764
  }
567
- return { root: dest, source: "remote-cache", url, ref };
568
765
  }
569
766
 
570
767
  // src/lifecycle/resolve-cli.ts
@@ -826,7 +1023,7 @@ async function findPack(rootDir, packId) {
826
1023
  }
827
1024
 
828
1025
  // src/registry/install.ts
829
- import { readFile as readFile4 } from "fs/promises";
1026
+ import { readFile as readFile5 } from "fs/promises";
830
1027
  import path8 from "path";
831
1028
  function targetForMember(member) {
832
1029
  switch (member.kind) {
@@ -945,7 +1142,7 @@ function packMemberTargets(member) {
945
1142
  var addCommand = defineCommand({
946
1143
  meta: {
947
1144
  name: "add",
948
- description: "Install a skill or L1 pack from the registry into the project."
1145
+ description: "Install a skill or L1 pack from the registry."
949
1146
  },
950
1147
  args: {
951
1148
  id: {
@@ -982,64 +1179,68 @@ var addCommand = defineCommand({
982
1179
  refresh: args.refresh,
983
1180
  manifest: existing
984
1181
  });
985
- const protectedGlobs = resolveProtectedGlobs(existing);
986
- const index = await loadRegistry(registry.root);
987
- const asSkill = allSkills(index).find((s) => s.id === args.id);
988
- const asPack = allPacks(index).find((p) => p.id === args.id);
989
- const base = existing ? { ...existing } : buildManifest({
990
- version: KIT_VERSION,
991
- profile: "default",
992
- registryUrl: registry.url,
993
- registryRef: registry.ref
994
- });
995
- const finish = async (kind, id) => {
996
- if (kind === "skill") base.skills = upsertIdList(base.skills, id);
997
- else base.packs = upsertIdList(base.packs, id);
998
- if (registry.url || registry.ref) {
999
- base.registry = {
1000
- url: registry.url ?? base.registry?.url,
1001
- ref: registry.ref ?? base.registry?.ref
1002
- };
1182
+ try {
1183
+ const protectedGlobs = resolveProtectedGlobs(existing);
1184
+ const index = await loadRegistry(registry.root);
1185
+ const asSkill = allSkills(index).find((s) => s.id === args.id);
1186
+ const asPack = allPacks(index).find((p) => p.id === args.id);
1187
+ const base = existing ? { ...existing } : buildManifest({
1188
+ version: KIT_VERSION,
1189
+ profile: "default",
1190
+ registryUrl: registry.url,
1191
+ registryRef: registry.ref
1192
+ });
1193
+ const finish = async (kind, id) => {
1194
+ if (kind === "skill") base.skills = upsertIdList(base.skills, id);
1195
+ else base.packs = upsertIdList(base.packs, id);
1196
+ if (registry.url || registry.ref) {
1197
+ base.registry = {
1198
+ url: registry.url ?? base.registry?.url,
1199
+ ref: registry.ref ?? base.registry?.ref
1200
+ };
1201
+ }
1202
+ base.version = KIT_VERSION;
1203
+ await saveManifest(args.cwd, base);
1204
+ };
1205
+ if (args.skill) {
1206
+ const skill = await findSkill(registry.root, args.id);
1207
+ const stats = await installSkill(registry.root, args.cwd, skill, { protectedGlobs });
1208
+ logApplyStats(stats);
1209
+ await finish("skill", skill.id);
1210
+ logger.success(`Skill '${skill.id}' added.`);
1211
+ return;
1003
1212
  }
1004
- base.version = KIT_VERSION;
1005
- await saveManifest(args.cwd, base);
1006
- };
1007
- if (args.skill) {
1008
- const skill = await findSkill(registry.root, args.id);
1009
- const stats = await installSkill(registry.root, args.cwd, skill, { protectedGlobs });
1010
- logApplyStats(stats);
1011
- await finish("skill", skill.id);
1012
- logger.success(`Skill '${skill.id}' added.`);
1013
- return;
1014
- }
1015
- if (args.pack) {
1016
- const pack = await findPack(registry.root, args.id);
1017
- const stats = await installPack(registry.root, args.cwd, pack.id, { protectedGlobs });
1018
- logApplyStats(stats);
1019
- await finish("pack", pack.id);
1020
- logger.success(`Pack '${pack.id}' added.`);
1021
- return;
1022
- }
1023
- if (asSkill && asPack) {
1024
- throw new Error(
1025
- `'${args.id}' is both a skill and a pack. Disambiguate: agent-kit add --skill ${args.id} | agent-kit add --pack ${args.id}`
1026
- );
1027
- }
1028
- if (asSkill) {
1029
- const stats = await installSkill(registry.root, args.cwd, asSkill, { protectedGlobs });
1030
- logApplyStats(stats);
1031
- await finish("skill", asSkill.id);
1032
- logger.success(`Skill '${asSkill.id}' added.`);
1033
- return;
1034
- }
1035
- if (asPack) {
1036
- const stats = await installPack(registry.root, args.cwd, asPack.id, { protectedGlobs });
1037
- logApplyStats(stats);
1038
- await finish("pack", asPack.id);
1039
- logger.success(`Pack '${asPack.id}' added.`);
1040
- return;
1213
+ if (args.pack) {
1214
+ const pack = await findPack(registry.root, args.id);
1215
+ const stats = await installPack(registry.root, args.cwd, pack.id, { protectedGlobs });
1216
+ logApplyStats(stats);
1217
+ await finish("pack", pack.id);
1218
+ logger.success(`Pack '${pack.id}' added.`);
1219
+ return;
1220
+ }
1221
+ if (asSkill && asPack) {
1222
+ throw new Error(
1223
+ `'${args.id}' is both a skill and a pack. Disambiguate: agent-kit add --skill ${args.id} | agent-kit add --pack ${args.id}`
1224
+ );
1225
+ }
1226
+ if (asSkill) {
1227
+ const stats = await installSkill(registry.root, args.cwd, asSkill, { protectedGlobs });
1228
+ logApplyStats(stats);
1229
+ await finish("skill", asSkill.id);
1230
+ logger.success(`Skill '${asSkill.id}' added.`);
1231
+ return;
1232
+ }
1233
+ if (asPack) {
1234
+ const stats = await installPack(registry.root, args.cwd, asPack.id, { protectedGlobs });
1235
+ logApplyStats(stats);
1236
+ await finish("pack", asPack.id);
1237
+ logger.success(`Pack '${asPack.id}' added.`);
1238
+ return;
1239
+ }
1240
+ throw new Error(`'${args.id}' not found as skill or pack in registry.`);
1241
+ } finally {
1242
+ await registry.unlock?.();
1041
1243
  }
1042
- throw new Error(`'${args.id}' not found as skill or pack in registry.`);
1043
1244
  }
1044
1245
  });
1045
1246
 
@@ -1047,7 +1248,7 @@ var addCommand = defineCommand({
1047
1248
  import { defineCommand as defineCommand2 } from "citty";
1048
1249
 
1049
1250
  // src/lifecycle/contribute.ts
1050
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1251
+ import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
1051
1252
  import path9 from "path";
1052
1253
 
1053
1254
  // src/lifecycle/contribute-gate.ts
@@ -1240,6 +1441,9 @@ var L0_ARTIFACTS = [
1240
1441
  source: ".cursor/commands/dogfood.md",
1241
1442
  target: ".cursor/commands/dogfood.md"
1242
1443
  },
1444
+ // Factory-only: `.cursor/commands/public-issue-triage.md` is intentionally
1445
+ // omitted from L0 (and excluded from public-sync). See ADR
1446
+ // 2026-08-05_factory-only-public-issue-triage-command.md.
1243
1447
  {
1244
1448
  source: ".cursor/commands/cursor-update-awareness.md",
1245
1449
  target: ".cursor/commands/cursor-update-awareness.md"
@@ -1338,7 +1542,7 @@ var L0_ARTIFACTS = [
1338
1542
  // src/lifecycle/contribute.ts
1339
1543
  async function readLocal(projectRoot, rel) {
1340
1544
  try {
1341
- return await readFile5(resolveContained(projectRoot, rel), "utf8");
1545
+ return await readFile6(resolveContained(projectRoot, rel), "utf8");
1342
1546
  } catch {
1343
1547
  return null;
1344
1548
  }
@@ -1414,7 +1618,7 @@ async function planContribute(options) {
1414
1618
  }
1415
1619
  if (kind === "drift") {
1416
1620
  try {
1417
- const upstream = await readFile5(
1621
+ const upstream = await readFile6(
1418
1622
  resolveContained(options.registryRoot, registryPath),
1419
1623
  "utf8"
1420
1624
  );
@@ -1480,10 +1684,10 @@ async function writeContributeToRegistry(projectRoot, registryRoot, accepted) {
1480
1684
  const written = [];
1481
1685
  for (const c of accepted) {
1482
1686
  if (!c.registryPath) continue;
1483
- const content = await readFile5(resolveContained(projectRoot, c.projectPath), "utf8");
1687
+ const content = await readFile6(resolveContained(projectRoot, c.projectPath), "utf8");
1484
1688
  const dest = resolveContained(registryRoot, c.registryPath);
1485
- await mkdir4(path9.dirname(dest), { recursive: true });
1486
- await writeFile3(dest, content, "utf8");
1689
+ await mkdir5(path9.dirname(dest), { recursive: true });
1690
+ await writeFile4(dest, content, "utf8");
1487
1691
  written.push(c.registryPath);
1488
1692
  }
1489
1693
  return written;
@@ -1554,57 +1758,61 @@ var contributeCommand = defineCommand2({
1554
1758
  refresh: args.refresh,
1555
1759
  manifest
1556
1760
  });
1557
- if (registry.source !== "flag" && args.write) {
1558
- logger.warn(
1559
- "contribute --write needs a local kit checkout. Pass --registry /path/to/agent-kit."
1560
- );
1561
- return;
1562
- }
1563
- logger.info(`Registry: ${registry.root} (${registry.source})`);
1564
- const plan = await planContribute({
1565
- registryRoot: registry.root,
1566
- projectRoot: args.cwd,
1567
- manifest,
1568
- extraPaths: parsePaths(args.path),
1569
- includeDrift: args.drift
1570
- });
1571
- console.log(
1572
- `Contribute plan: accepted=${plan.accepted.length} rejected=${plan.rejected.length} (drift scan=${args.drift})`
1573
- );
1574
- if (plan.candidates.length === 0) {
1575
- logger.success("Nothing to contribute (no drift / no --path).");
1576
- return;
1577
- }
1578
- for (const c of plan.candidates) {
1579
- const mark = c.gateOk ? "ok" : "FAIL";
1761
+ try {
1762
+ if (registry.source !== "flag" && args.write) {
1763
+ logger.warn(
1764
+ "contribute --write needs a local kit checkout. Pass --registry /path/to/agent-kit."
1765
+ );
1766
+ return;
1767
+ }
1768
+ logger.info(`Registry: ${registry.root} (${registry.source})`);
1769
+ const plan = await planContribute({
1770
+ registryRoot: registry.root,
1771
+ projectRoot: args.cwd,
1772
+ manifest,
1773
+ extraPaths: parsePaths(args.path),
1774
+ includeDrift: args.drift
1775
+ });
1580
1776
  console.log(
1581
- `${mark.padEnd(4)} ${c.kind.padEnd(5)} ${c.projectPath} \u2192 ${c.registryPath || "(unmapped)"}`
1777
+ `Contribute plan: accepted=${plan.accepted.length} rejected=${plan.rejected.length} (drift scan=${args.drift})`
1582
1778
  );
1583
- for (const issue of c.issues) {
1584
- console.log(` [${issue.code}] ${issue.message}`);
1779
+ if (plan.candidates.length === 0) {
1780
+ logger.success("Nothing to contribute (no drift / no --path).");
1781
+ return;
1585
1782
  }
1783
+ for (const c of plan.candidates) {
1784
+ const mark = c.gateOk ? "ok" : "FAIL";
1785
+ console.log(
1786
+ `${mark.padEnd(4)} ${c.kind.padEnd(5)} ${c.projectPath} \u2192 ${c.registryPath || "(unmapped)"}`
1787
+ );
1788
+ for (const issue of c.issues) {
1789
+ console.log(` [${issue.code}] ${issue.message}`);
1790
+ }
1791
+ }
1792
+ if (plan.accepted.length === 0) {
1793
+ logger.warn("No files passed the contribute gate.");
1794
+ return;
1795
+ }
1796
+ console.log("\n--- Suggested PR body ---\n");
1797
+ console.log(formatPrBody(plan.accepted));
1798
+ if (!args.write) {
1799
+ logger.info(
1800
+ "Dry run. Re-run with --write --registry /path/to/agent-kit to copy accepted files into the kit checkout, then open a PR (gh pr create --base main)."
1801
+ );
1802
+ return;
1803
+ }
1804
+ const written = await writeContributeToRegistry(args.cwd, registry.root, plan.accepted);
1805
+ logger.success(`Wrote ${written.length} file(s) into ${registry.root}`);
1806
+ for (const p of written) console.log(` + ${p}`);
1807
+ console.log("\nNext (HITL \u2014 review before push):");
1808
+ console.log(` cd ${registry.root}`);
1809
+ console.log(" git checkout -b contribute/<short-topic>");
1810
+ console.log(' git add <files> && git commit -m "feat: contribute <topic> from consumer"');
1811
+ console.log(" git push -u origin HEAD");
1812
+ console.log(' gh pr create --base main --title "feat: contribute <topic>" --body-file -');
1813
+ } finally {
1814
+ await registry.unlock?.();
1586
1815
  }
1587
- if (plan.accepted.length === 0) {
1588
- logger.warn("No files passed the contribute gate.");
1589
- return;
1590
- }
1591
- console.log("\n--- Suggested PR body ---\n");
1592
- console.log(formatPrBody(plan.accepted));
1593
- if (!args.write) {
1594
- logger.info(
1595
- "Dry run. Re-run with --write --registry /path/to/agent-kit to copy accepted files into the kit checkout, then open a PR (gh pr create --base main)."
1596
- );
1597
- return;
1598
- }
1599
- const written = await writeContributeToRegistry(args.cwd, registry.root, plan.accepted);
1600
- logger.success(`Wrote ${written.length} file(s) into ${registry.root}`);
1601
- for (const p of written) console.log(` + ${p}`);
1602
- console.log("\nNext (HITL \u2014 review before push):");
1603
- console.log(` cd ${registry.root}`);
1604
- console.log(" git checkout -b contribute/<short-topic>");
1605
- console.log(' git add <files> && git commit -m "feat: contribute <topic> from consumer"');
1606
- console.log(" git push -u origin HEAD");
1607
- console.log(' gh pr create --base main --title "feat: contribute <topic>" --body-file -');
1608
1816
  }
1609
1817
  });
1610
1818
 
@@ -1612,13 +1820,30 @@ var contributeCommand = defineCommand2({
1612
1820
  import { defineCommand as defineCommand3 } from "citty";
1613
1821
 
1614
1822
  // src/lifecycle/cursor-update-awareness.ts
1615
- import { readFile as readFile6 } from "fs/promises";
1823
+ import { access as access2, readFile as readFile7 } from "fs/promises";
1616
1824
  import path10 from "path";
1617
1825
  var DEFAULT_CURSOR_CHANGELOG_URL = "https://cursor.com/changelog";
1618
1826
  var CHANGELOG_FETCH_TIMEOUT_MS = 15e3;
1619
1827
  var CURSOR_VERSION_MAJOR_MAX = 20;
1620
1828
  var INVENTORY_REL = path10.join("docs", "cursor-native-audit.md");
1621
1829
  var FEATURES_REL = path10.join("docs", "cursor-3-features.md");
1830
+ async function resolveInventoryRoot(cwd) {
1831
+ let dir = path10.resolve(cwd);
1832
+ for (; ; ) {
1833
+ try {
1834
+ await access2(path10.join(dir, INVENTORY_REL));
1835
+ return dir;
1836
+ } catch {
1837
+ }
1838
+ const parent = path10.dirname(dir);
1839
+ if (parent === dir) break;
1840
+ dir = parent;
1841
+ }
1842
+ return null;
1843
+ }
1844
+ function missingInventoryMessage() {
1845
+ return `Missing inventory at ${INVENTORY_REL}. Run from the kit/repo root that contains that file, or pass --cwd <path>.`;
1846
+ }
1622
1847
  var DEFAULT_PREFS = {
1623
1848
  enabled: false,
1624
1849
  intervalDays: 7,
@@ -1786,8 +2011,6 @@ function baseResult(partial) {
1786
2011
  };
1787
2012
  }
1788
2013
  async function checkCursorUpdateAwareness(cwd, options = {}) {
1789
- const inventoryPath = path10.join(cwd, INVENTORY_REL);
1790
- const featuresPath = path10.join(cwd, FEATURES_REL);
1791
2014
  const prefs = readCursorUpdateCheckPrefs(await loadContextConfig(cwd));
1792
2015
  const changelogUrl = options.changelogUrl ?? prefs.changelogUrl;
1793
2016
  if (options.respectPrefs) {
@@ -1820,9 +2043,26 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
1820
2043
  });
1821
2044
  }
1822
2045
  }
2046
+ const inventoryRoot = await resolveInventoryRoot(cwd);
2047
+ if (!inventoryRoot) {
2048
+ return baseResult({
2049
+ status: "error",
2050
+ inventoryPath: INVENTORY_REL,
2051
+ featuresPath: FEATURES_REL,
2052
+ changelogUrl,
2053
+ latestCursorVersion: null,
2054
+ lastSeenCursorVersion: prefs.lastSeenCursorVersion,
2055
+ inventoryRefreshed: null,
2056
+ openActionIds: [],
2057
+ gaps: [],
2058
+ message: missingInventoryMessage()
2059
+ });
2060
+ }
2061
+ const inventoryPath = path10.join(inventoryRoot, INVENTORY_REL);
2062
+ const featuresPath = path10.join(inventoryRoot, FEATURES_REL);
1823
2063
  let inventoryMd;
1824
2064
  try {
1825
- inventoryMd = await readFile6(inventoryPath, "utf8");
2065
+ inventoryMd = await readFile7(inventoryPath, "utf8");
1826
2066
  } catch {
1827
2067
  return baseResult({
1828
2068
  status: "error",
@@ -1834,7 +2074,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
1834
2074
  inventoryRefreshed: null,
1835
2075
  openActionIds: [],
1836
2076
  gaps: [],
1837
- message: `Missing inventory at ${INVENTORY_REL}.`
2077
+ message: missingInventoryMessage()
1838
2078
  });
1839
2079
  }
1840
2080
  const inventoryRefreshed = parseInventoryRefreshed(inventoryMd);
@@ -1865,7 +2105,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
1865
2105
  }
1866
2106
  }
1867
2107
  try {
1868
- const featuresMd = await readFile6(featuresPath, "utf8");
2108
+ const featuresMd = await readFile7(featuresPath, "utf8");
1869
2109
  if (parseFeatureMapNames(featuresMd).length === 0) {
1870
2110
  gaps.push({
1871
2111
  id: "features-map-empty",
@@ -1947,7 +2187,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
1947
2187
  var cursorAwarenessCommand = defineCommand3({
1948
2188
  meta: {
1949
2189
  name: "cursor-awareness",
1950
- description: "Opt-in advisory: diff Cursor changelog / native-audit inventory for gaps (never apply, never Field Reports)"
2190
+ description: "Opt-in advisory: Cursor changelog vs native-audit gaps (never apply)."
1951
2191
  },
1952
2192
  args: {
1953
2193
  cwd: {
@@ -2009,17 +2249,124 @@ var cursorAwarenessCommand = defineCommand3({
2009
2249
  });
2010
2250
 
2011
2251
  // src/commands/dashboard-broadcast.ts
2012
- import { spawn as spawn2 } from "child_process";
2013
- import { access as access3 } from "fs/promises";
2252
+ import { spawn as spawn3 } from "child_process";
2253
+ import { access as access4 } from "fs/promises";
2014
2254
  import path12 from "path";
2015
2255
  import { defineCommand as defineCommand5 } from "citty";
2016
2256
 
2017
2257
  // src/commands/dashboard.ts
2018
- import { execFileSync, spawn } from "child_process";
2019
- import { access as access2 } from "fs/promises";
2258
+ import { execFileSync, spawn as spawn2 } from "child_process";
2259
+ import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
2260
+ import { access as access3 } from "fs/promises";
2020
2261
  import path11 from "path";
2021
2262
  import { fileURLToPath } from "url";
2022
2263
  import { defineCommand as defineCommand4 } from "citty";
2264
+
2265
+ // ../../dashboard/lib/guards.mjs
2266
+ import { randomBytes, timingSafeEqual } from "crypto";
2267
+ import { networkInterfaces } from "os";
2268
+ import { resolve } from "path";
2269
+ var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
2270
+ var CONTEXT_CONFIG_REL = ".cursor/context/config.json";
2271
+ var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
2272
+ var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
2273
+ var CONFIG_REVIEW_BACKENDS = Object.freeze(["claude"]);
2274
+ var CONFIG_REVIEW_MODES = Object.freeze(["paste", "autonomous"]);
2275
+ var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
2276
+ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
2277
+ const exists2 = fsHooks.existsSync;
2278
+ const realpath = fsHooks.realpathSync;
2279
+ const mkdir7 = fsHooks.mkdirSync;
2280
+ if (typeof repoRoot !== "string" || !repoRoot) {
2281
+ return { ok: false, error: "invalid repo root" };
2282
+ }
2283
+ const abs = resolve(repoRoot, CONTEXT_CONFIG_REL);
2284
+ const contextDir = resolve(repoRoot, ".cursor", "context");
2285
+ const absNorm = abs.replace(/\\/g, "/");
2286
+ if (!absNorm.endsWith("/.cursor/context/config.json")) {
2287
+ return { ok: false, error: "path escape" };
2288
+ }
2289
+ try {
2290
+ if (typeof mkdir7 === "function" && typeof exists2 === "function" && !exists2(contextDir)) {
2291
+ mkdir7(contextDir, { recursive: true });
2292
+ }
2293
+ if (typeof realpath === "function" && typeof exists2 === "function" && exists2(abs)) {
2294
+ const fileReal = String(realpath(abs)).replace(/\\/g, "/");
2295
+ const rootReal = String(realpath(repoRoot)).replace(/\\/g, "/");
2296
+ if (!fileReal.startsWith(`${rootReal}/`)) {
2297
+ return { ok: false, error: "path escape" };
2298
+ }
2299
+ if (!fileReal.endsWith("/.cursor/context/config.json")) {
2300
+ return { ok: false, error: "path escape" };
2301
+ }
2302
+ return { ok: true, path: realpath(abs) };
2303
+ }
2304
+ } catch {
2305
+ return { ok: false, error: "path escape" };
2306
+ }
2307
+ return { ok: true, path: abs };
2308
+ }
2309
+
2310
+ // ../../dashboard/lib/open-browser.mjs
2311
+ import { spawn, spawnSync } from "child_process";
2312
+ import { readFileSync } from "fs";
2313
+ import { platform as osPlatform } from "os";
2314
+ var OS_DEFAULT_TOKENS = /* @__PURE__ */ new Set(["", "default", "os", "ask"]);
2315
+ function isSafePreferredBrowser(value) {
2316
+ if (typeof value !== "string") return false;
2317
+ const s = value.trim();
2318
+ if (!s) return false;
2319
+ if (/[/\\]/.test(s)) return false;
2320
+ if (/[\0-\x1f\x7f]/.test(s)) return false;
2321
+ if (/[$`;&|<>(){}[\]!*?#~"'%^=,+]/.test(s)) return false;
2322
+ if (s.includes(":")) return false;
2323
+ if (/^-/.test(s)) return false;
2324
+ return true;
2325
+ }
2326
+ function normalizePreferredBrowser(value) {
2327
+ if (value == null) return null;
2328
+ const s = String(value).trim();
2329
+ if (!s || OS_DEFAULT_TOKENS.has(s.toLowerCase())) return null;
2330
+ if (!isSafePreferredBrowser(s)) return null;
2331
+ return s;
2332
+ }
2333
+ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
2334
+ const read = fsHooks.readFileSync ?? readFileSync;
2335
+ try {
2336
+ const raw = read(configPath, "utf8");
2337
+ const data = JSON.parse(raw);
2338
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
2339
+ const mc = data.missionControl;
2340
+ if (!mc || typeof mc !== "object" || Array.isArray(mc)) return null;
2341
+ return mc.preferredBrowser ?? null;
2342
+ } catch {
2343
+ return null;
2344
+ }
2345
+ }
2346
+
2347
+ // src/commands/dashboard.ts
2348
+ function readPreferredBrowserFromWorkspace(cwd, readFile22 = readFileSync2) {
2349
+ const resolved = resolveContextConfigPath(path11.resolve(cwd), {
2350
+ existsSync,
2351
+ realpathSync
2352
+ });
2353
+ if (!resolved.ok) return null;
2354
+ const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile22 });
2355
+ return normalizePreferredBrowser(value);
2356
+ }
2357
+ function applyDashboardOpenEnv(env, opts) {
2358
+ const next = { ...env };
2359
+ if (opts.noOpen) next.MISSION_CONTROL_NO_OPEN = "1";
2360
+ const flag = opts.browser?.trim();
2361
+ if (flag) {
2362
+ const safe = normalizePreferredBrowser(flag);
2363
+ if (safe) next.MISSION_CONTROL_PREFERRED_BROWSER = safe;
2364
+ } else if (!next.MISSION_CONTROL_PREFERRED_BROWSER && opts.cwd) {
2365
+ const fromConfig = readPreferredBrowserFromWorkspace(opts.cwd);
2366
+ if (fromConfig) next.MISSION_CONTROL_PREFERRED_BROWSER = fromConfig;
2367
+ }
2368
+ return next;
2369
+ }
2023
2370
  function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
2024
2371
  const here = path11.dirname(fileURLToPath(moduleUrl));
2025
2372
  return [
@@ -2034,7 +2381,7 @@ function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
2034
2381
  async function firstExisting(candidates) {
2035
2382
  for (const candidate2 of candidates) {
2036
2383
  try {
2037
- await access2(candidate2);
2384
+ await access3(candidate2);
2038
2385
  return path11.resolve(candidate2);
2039
2386
  } catch {
2040
2387
  }
@@ -2046,7 +2393,7 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
2046
2393
  for (; ; ) {
2047
2394
  const candidate2 = path11.join(dir, "dashboard", "start.mjs");
2048
2395
  try {
2049
- await access2(candidate2);
2396
+ await access3(candidate2);
2050
2397
  return candidate2;
2051
2398
  } catch {
2052
2399
  }
@@ -2059,14 +2406,14 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
2059
2406
  if (!base || typeof base !== "string" || !base.trim()) continue;
2060
2407
  const candidate2 = path11.join(path11.resolve(base.trim()), "dashboard", "start.mjs");
2061
2408
  try {
2062
- await access2(candidate2);
2409
+ await access3(candidate2);
2063
2410
  return candidate2;
2064
2411
  } catch {
2065
2412
  }
2066
2413
  }
2067
2414
  const sibling = path11.join(path11.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
2068
2415
  try {
2069
- await access2(sibling);
2416
+ await access3(sibling);
2070
2417
  return path11.resolve(sibling);
2071
2418
  } catch {
2072
2419
  }
@@ -2076,6 +2423,15 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
2076
2423
  }
2077
2424
  function resolveDashboardSnapshotRoot(cwd) {
2078
2425
  const abs = path11.resolve(cwd);
2426
+ let dir = abs;
2427
+ for (; ; ) {
2428
+ if (existsSync(path11.join(dir, ".cursor", "agent-kit.json"))) {
2429
+ return dir;
2430
+ }
2431
+ const parent = path11.dirname(dir);
2432
+ if (parent === dir) break;
2433
+ dir = parent;
2434
+ }
2079
2435
  try {
2080
2436
  const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
2081
2437
  encoding: "utf8",
@@ -2087,46 +2443,68 @@ function resolveDashboardSnapshotRoot(cwd) {
2087
2443
  return abs;
2088
2444
  }
2089
2445
  function runStartScript(startPath, env) {
2090
- return new Promise((resolve, reject) => {
2091
- const child = spawn(process.execPath, [startPath], {
2446
+ return new Promise((resolve2, reject) => {
2447
+ const child = spawn2(process.execPath, [startPath], {
2092
2448
  cwd: path11.dirname(path11.dirname(startPath)),
2093
2449
  env,
2094
2450
  stdio: "inherit"
2095
2451
  });
2096
2452
  child.on("error", reject);
2097
- child.on("close", (code) => resolve(code ?? 1));
2453
+ child.on("close", (code) => resolve2(code ?? 1));
2098
2454
  });
2099
2455
  }
2100
2456
  var dashboardCommand = defineCommand4({
2101
2457
  meta: {
2102
2458
  name: "dashboard",
2103
- description: "Start Mission Control for this workspace (stable per-root port) and open the panel URL."
2459
+ description: "Start Mission Control for this workspace (loopback; opens panel URL)."
2104
2460
  },
2105
2461
  args: {
2106
2462
  cwd: {
2107
2463
  type: "string",
2108
2464
  default: process.cwd(),
2109
- description: "Workspace to snapshot (git root preferred); also searched upward for dashboard/start.mjs"
2465
+ description: "Workspace to snapshot (nearest .cursor/agent-kit.json, else git root); also searched upward for dashboard/start.mjs"
2110
2466
  },
2111
2467
  "no-open": {
2112
2468
  type: "boolean",
2113
2469
  default: false,
2114
2470
  description: "Do not open a browser; only ensure the server is up and print the URL"
2471
+ },
2472
+ browser: {
2473
+ type: "string",
2474
+ description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
2115
2475
  }
2116
2476
  },
2117
2477
  async run({ args }) {
2118
2478
  const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
2119
2479
  const startPath = await findDashboardStart(args.cwd);
2120
2480
  if (!startPath) {
2121
- logger.error(
2122
- "No dashboard/start.mjs found. After a CLI publish that ships dashboard/ (Path C), reinstall @dadado/agent-kit-cli. Or set MISSION_CONTROL_KIT_ROOT / AGENT_KIT_HOME to an agent-kit checkout, place a sibling ../agent-kit tree, or run from that kit tree."
2481
+ logger.error("No dashboard/start.mjs found.");
2482
+ console.error(
2483
+ [
2484
+ "",
2485
+ "The dashboard runtime is not available in this workspace.",
2486
+ "L0 install provides the /dashboard command text but not the panel itself.",
2487
+ "",
2488
+ "Recovery (pick one):",
2489
+ " 1. Upgrade the CLI: npx @dadado/agent-kit-cli@latest dashboard",
2490
+ " (Path C ships dashboard/ from 4.8.2 onward)",
2491
+ " 2. Set an env var pointing to an agent-kit checkout:",
2492
+ " export MISSION_CONTROL_KIT_ROOT=/path/to/agent-kit",
2493
+ " agent-kit dashboard",
2494
+ " 3. Place an agent-kit sibling: ../agent-kit/dashboard/start.mjs",
2495
+ " 4. Run directly from a kit tree: node dashboard/start.mjs",
2496
+ "",
2497
+ "Works in Cursor, VS Code, and any Node.js terminal.",
2498
+ ""
2499
+ ].join("\n")
2123
2500
  );
2124
2501
  process.exitCode = 1;
2125
2502
  return;
2126
2503
  }
2127
- const env = { ...process.env };
2128
- env.MISSION_CONTROL_REPO_ROOT = snapshotRoot;
2129
- if (args["no-open"]) env.MISSION_CONTROL_NO_OPEN = "1";
2504
+ const env = applyDashboardOpenEnv(
2505
+ { ...process.env, MISSION_CONTROL_REPO_ROOT: snapshotRoot },
2506
+ { noOpen: Boolean(args["no-open"]), browser: args.browser, cwd: snapshotRoot }
2507
+ );
2130
2508
  const code = await runStartScript(startPath, env);
2131
2509
  if (code !== 0) process.exitCode = code;
2132
2510
  }
@@ -2138,7 +2516,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
2138
2516
  for (; ; ) {
2139
2517
  const candidate2 = path12.join(dir, "dashboard", "start-broadcast.mjs");
2140
2518
  try {
2141
- await access3(candidate2);
2519
+ await access4(candidate2);
2142
2520
  return candidate2;
2143
2521
  } catch {
2144
2522
  }
@@ -2151,7 +2529,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
2151
2529
  if (!base || typeof base !== "string" || !base.trim()) continue;
2152
2530
  const candidate2 = path12.join(path12.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
2153
2531
  try {
2154
- await access3(candidate2);
2532
+ await access4(candidate2);
2155
2533
  return candidate2;
2156
2534
  } catch {
2157
2535
  }
@@ -2164,7 +2542,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
2164
2542
  "start-broadcast.mjs"
2165
2543
  );
2166
2544
  try {
2167
- await access3(sibling);
2545
+ await access4(sibling);
2168
2546
  return path12.resolve(sibling);
2169
2547
  } catch {
2170
2548
  }
@@ -2173,7 +2551,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
2173
2551
  options.moduleUrl ?? import.meta.url
2174
2552
  )) {
2175
2553
  try {
2176
- await access3(candidate2);
2554
+ await access4(candidate2);
2177
2555
  return path12.resolve(candidate2);
2178
2556
  } catch {
2179
2557
  }
@@ -2181,14 +2559,14 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
2181
2559
  return null;
2182
2560
  }
2183
2561
  function runStartScript2(startPath, env) {
2184
- return new Promise((resolve, reject) => {
2185
- const child = spawn2(process.execPath, [startPath], {
2562
+ return new Promise((resolve2, reject) => {
2563
+ const child = spawn3(process.execPath, [startPath], {
2186
2564
  cwd: path12.dirname(path12.dirname(startPath)),
2187
2565
  env,
2188
2566
  stdio: "inherit"
2189
2567
  });
2190
2568
  child.on("error", reject);
2191
- child.on("close", (code) => resolve(code ?? 1));
2569
+ child.on("close", (code) => resolve2(code ?? 1));
2192
2570
  });
2193
2571
  }
2194
2572
  var dashboardBroadcastCommand = defineCommand5({
@@ -2205,7 +2583,11 @@ var dashboardBroadcastCommand = defineCommand5({
2205
2583
  "no-open": {
2206
2584
  type: "boolean",
2207
2585
  default: false,
2208
- description: "Do not open a browser; only ensure the server is up and print LAN URL + token"
2586
+ description: "Do not open a browser; only ensure the server is up and print Share URL (when masking is on) plus token / LAN lines"
2587
+ },
2588
+ browser: {
2589
+ type: "string",
2590
+ description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
2209
2591
  }
2210
2592
  },
2211
2593
  async run({ args }) {
@@ -2218,8 +2600,11 @@ var dashboardBroadcastCommand = defineCommand5({
2218
2600
  process.exitCode = 1;
2219
2601
  return;
2220
2602
  }
2221
- const env = { ...process.env };
2222
- if (args["no-open"]) env.MISSION_CONTROL_NO_OPEN = "1";
2603
+ const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
2604
+ const env = applyDashboardOpenEnv(
2605
+ { ...process.env },
2606
+ { noOpen: Boolean(args["no-open"]), browser: args.browser, cwd: snapshotRoot }
2607
+ );
2223
2608
  const code = await runStartScript2(startPath, env);
2224
2609
  if (code !== 0) process.exitCode = code;
2225
2610
  }
@@ -2229,11 +2614,11 @@ var dashboardBroadcastCommand = defineCommand5({
2229
2614
  import { defineCommand as defineCommand6 } from "citty";
2230
2615
 
2231
2616
  // src/lifecycle/diff.ts
2232
- import { readFile as readFile7 } from "fs/promises";
2617
+ import { readFile as readFile8 } from "fs/promises";
2233
2618
  import path13 from "path";
2234
2619
  async function readIfExists(abs) {
2235
2620
  try {
2236
- return await readFile7(abs, "utf8");
2621
+ return await readFile8(abs, "utf8");
2237
2622
  } catch {
2238
2623
  return null;
2239
2624
  }
@@ -2347,19 +2732,23 @@ var diffCommand = defineCommand6({
2347
2732
  refresh: args.refresh,
2348
2733
  manifest
2349
2734
  });
2350
- logger.info(`Registry: ${registry.root} (${registry.source})`);
2351
- const entries = await diffAgainstRegistry(registry.root, args.cwd, manifest);
2352
- const summary = summarizeDiff(entries);
2353
- console.log(
2354
- `Summary: match=${summary.match} drift=${summary.drift} missing-local=${summary["missing-local"]} missing-registry=${summary["missing-registry"]} protected=${summary.protected}`
2355
- );
2356
- const rows = args.all ? entries : entries.filter((e) => e.status !== "match");
2357
- if (rows.length === 0) {
2358
- logger.success("No drift (matches only).");
2359
- return;
2360
- }
2361
- for (const e of rows) {
2362
- console.log(`${e.status.padEnd(18)} ${e.path}`);
2735
+ try {
2736
+ logger.info(`Registry: ${registry.root} (${registry.source})`);
2737
+ const entries = await diffAgainstRegistry(registry.root, args.cwd, manifest);
2738
+ const summary = summarizeDiff(entries);
2739
+ console.log(
2740
+ `Summary: match=${summary.match} drift=${summary.drift} missing-local=${summary["missing-local"]} missing-registry=${summary["missing-registry"]} protected=${summary.protected}`
2741
+ );
2742
+ const rows = args.all ? entries : entries.filter((e) => e.status !== "match");
2743
+ if (rows.length === 0) {
2744
+ logger.success("No drift (matches only).");
2745
+ return;
2746
+ }
2747
+ for (const e of rows) {
2748
+ console.log(`${e.status.padEnd(18)} ${e.path}`);
2749
+ }
2750
+ } finally {
2751
+ await registry.unlock?.();
2363
2752
  }
2364
2753
  }
2365
2754
  });
@@ -2370,7 +2759,7 @@ import { defineCommand as defineCommand7 } from "citty";
2370
2759
 
2371
2760
  // src/invariants/hooks-health.ts
2372
2761
  import { execFile as execFile2 } from "child_process";
2373
- import { constants as constants2, access as access4, readFile as readFile8, readdir as readdir3, stat } from "fs/promises";
2762
+ import { constants as constants2, access as access5, readFile as readFile9, readdir as readdir3, stat as stat2 } from "fs/promises";
2374
2763
  import path14 from "path";
2375
2764
  import { promisify as promisify2 } from "util";
2376
2765
  var execFileAsync2 = promisify2(execFile2);
@@ -2384,7 +2773,7 @@ var EXPECTED_EVENTS = [
2384
2773
  ];
2385
2774
  async function exists(p) {
2386
2775
  try {
2387
- await access4(p);
2776
+ await access5(p);
2388
2777
  return true;
2389
2778
  } catch {
2390
2779
  return false;
@@ -2392,7 +2781,7 @@ async function exists(p) {
2392
2781
  }
2393
2782
  async function isExecutable(p) {
2394
2783
  try {
2395
- await access4(p, constants2.X_OK);
2784
+ await access5(p, constants2.X_OK);
2396
2785
  return true;
2397
2786
  } catch {
2398
2787
  return false;
@@ -2444,7 +2833,7 @@ async function assessGitHooksInstallDrift(rootDir) {
2444
2833
  const gitDir = path14.join(root, ".git");
2445
2834
  if (!await exists(gitDir)) return [];
2446
2835
  try {
2447
- const st = await stat(gitDir);
2836
+ const st = await stat2(gitDir);
2448
2837
  if (!st.isDirectory()) return [];
2449
2838
  } catch {
2450
2839
  return [];
@@ -2472,7 +2861,7 @@ async function assessGitHooksInstallDrift(rootDir) {
2472
2861
  continue;
2473
2862
  }
2474
2863
  try {
2475
- const [a, b] = await Promise.all([readFile8(canonical, "utf8"), readFile8(installed, "utf8")]);
2864
+ const [a, b] = await Promise.all([readFile9(canonical, "utf8"), readFile9(installed, "utf8")]);
2476
2865
  if (a !== b) {
2477
2866
  advisories.push(
2478
2867
  `git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
@@ -2505,7 +2894,7 @@ async function assessHooksHealth(rootDir) {
2505
2894
  }
2506
2895
  let parsed;
2507
2896
  try {
2508
- parsed = JSON.parse(await readFile8(hooksJsonAbs, "utf8"));
2897
+ parsed = JSON.parse(await readFile9(hooksJsonAbs, "utf8"));
2509
2898
  } catch {
2510
2899
  return {
2511
2900
  status: "degraded",
@@ -2548,7 +2937,7 @@ async function assessHooksHealth(rootDir) {
2548
2937
  continue;
2549
2938
  }
2550
2939
  try {
2551
- const st = await stat(abs);
2940
+ const st = await stat2(abs);
2552
2941
  if (!st.isFile()) {
2553
2942
  reasons.push(`adapter path is not a file: \`${rel}\``);
2554
2943
  continue;
@@ -2828,11 +3217,11 @@ function createReadinessReport(scan, options) {
2828
3217
  }
2829
3218
 
2830
3219
  // src/scanner/safe-fixes.ts
2831
- import { readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
3220
+ import { readFile as readFile12, writeFile as writeFile5 } from "fs/promises";
2832
3221
  import path22 from "path";
2833
3222
 
2834
3223
  // src/scanner/detect-repository.ts
2835
- import { readFile as readFile9 } from "fs/promises";
3224
+ import { readFile as readFile10 } from "fs/promises";
2836
3225
  import path15 from "path";
2837
3226
  var CONTEXT_PATHS = [
2838
3227
  ["README.md", "README"],
@@ -2938,7 +3327,7 @@ var REQUIRED_SECRET_PATTERNS = [
2938
3327
  async function detectSafety(rootDir, trackedFiles) {
2939
3328
  const gitignorePath = path15.join(rootDir, ".gitignore");
2940
3329
  const hasGitignore = await fileExists(gitignorePath);
2941
- const gitignore = hasGitignore ? await readFile9(gitignorePath, "utf8") : "";
3330
+ const gitignore = hasGitignore ? await readFile10(gitignorePath, "utf8") : "";
2942
3331
  const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
2943
3332
  const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
2944
3333
  (pattern) => lines.includes(pattern)
@@ -2956,7 +3345,7 @@ async function detectSafety(rootDir, trackedFiles) {
2956
3345
  ];
2957
3346
  const guardContents = await Promise.all(
2958
3347
  guardCandidates.map(
2959
- async (item) => await fileExists(path15.join(rootDir, item)) ? readFile9(path15.join(rootDir, item), "utf8") : ""
3348
+ async (item) => await fileExists(path15.join(rootDir, item)) ? readFile10(path15.join(rootDir, item), "utf8") : ""
2960
3349
  )
2961
3350
  );
2962
3351
  return {
@@ -3163,6 +3552,33 @@ async function detectIde(rootDir) {
3163
3552
  import path18 from "path";
3164
3553
 
3165
3554
  // src/types.ts
3555
+ var GIT_PLATFORM_META = {
3556
+ github: {
3557
+ cli: "gh",
3558
+ prTerm: "Pull Request",
3559
+ prCommand: "gh pr create",
3560
+ ciDefault: "github-actions"
3561
+ },
3562
+ gitlab: {
3563
+ cli: "glab",
3564
+ prTerm: "Merge Request",
3565
+ prCommand: "glab mr create",
3566
+ ciDefault: "gitlab-ci"
3567
+ },
3568
+ bitbucket: {
3569
+ cli: "bb",
3570
+ prTerm: "Pull Request",
3571
+ prCommand: "bb pr create",
3572
+ ciDefault: "bitbucket-pipelines"
3573
+ },
3574
+ "azure-devops": {
3575
+ cli: "az repos",
3576
+ prTerm: "Pull Request",
3577
+ prCommand: "az repos pr create",
3578
+ ciDefault: "azure-pipelines"
3579
+ },
3580
+ gitea: { cli: "tea", prTerm: "Pull Request", prCommand: "tea pr create", ciDefault: "none" }
3581
+ };
3166
3582
  var CI_PLATFORM_FILES = {
3167
3583
  "github-actions": ".github/workflows",
3168
3584
  "gitlab-ci": ".gitlab-ci.yml",
@@ -3231,7 +3647,7 @@ async function detectInfra(rootDir) {
3231
3647
  }
3232
3648
 
3233
3649
  // src/scanner/detect-services.ts
3234
- import { readFile as readFile10 } from "fs/promises";
3650
+ import { readFile as readFile11 } from "fs/promises";
3235
3651
  import path19 from "path";
3236
3652
  async function detectProjectManagement(rootDir) {
3237
3653
  const tools = [];
@@ -3242,7 +3658,7 @@ async function detectProjectManagement(rootDir) {
3242
3658
  for (const configPath of mcpConfigPaths) {
3243
3659
  if (!await fileExists(configPath)) continue;
3244
3660
  try {
3245
- const raw = await readFile10(configPath, "utf8");
3661
+ const raw = await readFile11(configPath, "utf8");
3246
3662
  const lower = raw.toLowerCase();
3247
3663
  if (lower.includes("clickup")) tools.push("clickup");
3248
3664
  if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
@@ -3625,10 +4041,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
3625
4041
  }
3626
4042
  const gitignoreRelativePath = ".gitignore";
3627
4043
  const gitignorePath = path22.join(beforeScan.rootDir, gitignoreRelativePath);
3628
- const existingGitignore = await fileExists(gitignorePath) ? await readFile11(gitignorePath, "utf8") : "";
4044
+ const existingGitignore = await fileExists(gitignorePath) ? await readFile12(gitignorePath, "utf8") : "";
3629
4045
  const mergedGitignore = mergeSecretIgnores(existingGitignore);
3630
4046
  const gitignoreChanged = mergedGitignore !== existingGitignore;
3631
- if (gitignoreChanged && !dryRun) await writeFile4(gitignorePath, mergedGitignore, "utf8");
4047
+ if (gitignoreChanged && !dryRun) await writeFile5(gitignorePath, mergedGitignore, "utf8");
3632
4048
  recordChange(
3633
4049
  changes,
3634
4050
  "merge-secret-ignores",
@@ -3753,7 +4169,7 @@ function printDoctorSummary(result) {
3753
4169
  var doctorCommand = defineCommand7({
3754
4170
  meta: {
3755
4171
  name: "doctor",
3756
- description: "Diagnose repository readiness and optionally apply safe local fixes."
4172
+ description: "Diagnose repository readiness; optional --fix-safe local repairs."
3757
4173
  },
3758
4174
  args: {
3759
4175
  cwd: {
@@ -4044,7 +4460,7 @@ async function detectCurrentBranch() {
4044
4460
  var guardCommand = defineCommand8({
4045
4461
  meta: {
4046
4462
  name: "guard",
4047
- description: "Mechanizable deny/annotate guards (shell, prompt). Hooks are thin adapters."
4463
+ description: "Deny/annotate guards (shell, prompt). Hooks are thin adapters."
4048
4464
  },
4049
4465
  subCommands: {
4050
4466
  shell: defineCommand8({
@@ -4107,8 +4523,8 @@ var guardCommand = defineCommand8({
4107
4523
  });
4108
4524
 
4109
4525
  // src/commands/handoff.ts
4110
- import { spawn as spawn3 } from "child_process";
4111
- import { readFile as readFile12, readdir as readdir4, writeFile as writeFile5 } from "fs/promises";
4526
+ import { spawn as spawn4 } from "child_process";
4527
+ import { readFile as readFile13, readdir as readdir4, writeFile as writeFile6 } from "fs/promises";
4112
4528
  import path25 from "path";
4113
4529
  import { defineCommand as defineCommand9 } from "citty";
4114
4530
  function parsePlanFrontmatter(raw) {
@@ -4129,13 +4545,13 @@ async function findActivePlan(plansDir) {
4129
4545
  if (!await fileExists(plansDir)) return null;
4130
4546
  const files = (await readdir4(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
4131
4547
  for (const file of files) {
4132
- const raw = await readFile12(path25.join(plansDir, file), "utf8");
4548
+ const raw = await readFile13(path25.join(plansDir, file), "utf8");
4133
4549
  const fm = parsePlanFrontmatter(raw);
4134
4550
  if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
4135
4551
  return { file, raw };
4136
4552
  }
4137
4553
  }
4138
- return files[0] ? { file: files[0], raw: await readFile12(path25.join(plansDir, files[0]), "utf8") } : null;
4554
+ return files[0] ? { file: files[0], raw: await readFile13(path25.join(plansDir, files[0]), "utf8") } : null;
4139
4555
  }
4140
4556
  function now() {
4141
4557
  return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
@@ -4218,19 +4634,19 @@ function printV3Guidance() {
4218
4634
  logger.info("Resume in IDE: /resume, summaries and transcripts.");
4219
4635
  }
4220
4636
  function runCursorHandoff(scriptPath, cwd) {
4221
- return new Promise((resolve, reject) => {
4222
- const child = spawn3("sh", [scriptPath, "handoff"], {
4637
+ return new Promise((resolve2, reject) => {
4638
+ const child = spawn4("sh", [scriptPath, "handoff"], {
4223
4639
  cwd,
4224
4640
  stdio: "inherit"
4225
4641
  });
4226
4642
  child.on("error", (err) => reject(err));
4227
- child.on("close", (code) => resolve(code ?? 1));
4643
+ child.on("close", (code) => resolve2(code ?? 1));
4228
4644
  });
4229
4645
  }
4230
4646
  var handoffCommand = defineCommand9({
4231
4647
  meta: {
4232
4648
  name: "handoff",
4233
- description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
4649
+ description: "Write .cursor/HANDOFF.md from the active plan (or cursor-handoff fallback)."
4234
4650
  },
4235
4651
  args: {
4236
4652
  cwd: {
@@ -4249,7 +4665,7 @@ var handoffCommand = defineCommand9({
4249
4665
  if (fm) {
4250
4666
  await ensureDir(path25.join(args.cwd, ".cursor"));
4251
4667
  const content = buildHandoff(plan.file, fm, profile);
4252
- await writeFile5(handoffPath, content, "utf8");
4668
+ await writeFile6(handoffPath, content, "utf8");
4253
4669
  logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
4254
4670
  logger.info(
4255
4671
  `Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
@@ -4305,8 +4721,8 @@ function buildPreCompactUserMessage(payload = {}) {
4305
4721
  }
4306
4722
 
4307
4723
  // src/hooks/session-start.ts
4308
- import { spawn as spawn4 } from "child_process";
4309
- import { access as access5, readFile as readFile13 } from "fs/promises";
4724
+ import { spawn as spawn5 } from "child_process";
4725
+ import { access as access6, readFile as readFile14 } from "fs/promises";
4310
4726
  import path26 from "path";
4311
4727
 
4312
4728
  // src/invariants/handoff-schema.ts
@@ -4381,7 +4797,7 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
4381
4797
  var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
4382
4798
  async function readTextLimited(filePath, limit = 60) {
4383
4799
  try {
4384
- const lines = (await readFile13(filePath, "utf8")).split(/\r?\n/);
4800
+ const lines = (await readFile14(filePath, "utf8")).split(/\r?\n/);
4385
4801
  return lines.slice(0, limit).join("\n").trim();
4386
4802
  } catch {
4387
4803
  return "";
@@ -4389,14 +4805,14 @@ async function readTextLimited(filePath, limit = 60) {
4389
4805
  }
4390
4806
  async function readFull(filePath) {
4391
4807
  try {
4392
- return await readFile13(filePath, "utf8");
4808
+ return await readFile14(filePath, "utf8");
4393
4809
  } catch {
4394
4810
  return "";
4395
4811
  }
4396
4812
  }
4397
4813
  async function fileExists2(p) {
4398
4814
  try {
4399
- await access5(p);
4815
+ await access6(p);
4400
4816
  return true;
4401
4817
  } catch {
4402
4818
  return false;
@@ -4405,22 +4821,55 @@ async function fileExists2(p) {
4405
4821
  function parseUnprocessedDogfoodItems(readmeText) {
4406
4822
  const items = [];
4407
4823
  let inSection = false;
4824
+ let sectionLevel = 0;
4408
4825
  for (const line of readmeText.split(/\r?\n/)) {
4409
- if (line.startsWith("### Unprocessed Files")) {
4826
+ const unprocessedMatch = /^(#{2,3})\s+Unprocessed Files\b/.exec(line);
4827
+ if (unprocessedMatch) {
4828
+ const hashes = unprocessedMatch[1];
4829
+ if (!hashes) continue;
4410
4830
  inSection = true;
4831
+ sectionLevel = hashes.length;
4411
4832
  continue;
4412
4833
  }
4413
4834
  if (!inSection) continue;
4414
- if (line.startsWith("### ")) break;
4415
- const stripped = line.trim();
4416
- if (!stripped.startsWith("- ")) continue;
4417
- const body = stripped.slice(2).trim();
4418
- const normalized = body.toLowerCase().replace(/[*_]/g, "").trim();
4419
- if (!normalized || NONE_PLACEHOLDERS.has(normalized)) continue;
4835
+ const headingMatch = /^(#{1,6})\s+/.exec(line);
4836
+ if (headingMatch) {
4837
+ if (/\bProcessed Files\b/.test(line)) break;
4838
+ const hashes = headingMatch[1];
4839
+ if (hashes && hashes.length <= sectionLevel) break;
4840
+ continue;
4841
+ }
4842
+ const body = extractUnprocessedDogfoodLine(line);
4843
+ if (!body) continue;
4420
4844
  items.push(body);
4421
4845
  }
4422
4846
  return items;
4423
4847
  }
4848
+ function extractUnprocessedDogfoodLine(line) {
4849
+ const stripped = line.trim();
4850
+ if (!stripped) return null;
4851
+ let raw = null;
4852
+ if (stripped.startsWith("- ")) {
4853
+ raw = stripped.slice(2).trim();
4854
+ } else {
4855
+ const numbered = /^(\d+)[.)]\s+(.+)$/.exec(stripped);
4856
+ if (numbered?.[2]) {
4857
+ raw = numbered[2].trim();
4858
+ } else if (stripped.startsWith("|")) {
4859
+ const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
4860
+ if (parts.length === 0) return null;
4861
+ if (parts.every((cell) => /^:?-+:?$/.test(cell))) return null;
4862
+ const first = parts[0] ?? "";
4863
+ const headerish = first.toLowerCase().replace(/[*_`]/g, "").trim();
4864
+ if (/^(note|file|entrada|title|name|item|path)$/.test(headerish)) return null;
4865
+ raw = first.trim();
4866
+ }
4867
+ }
4868
+ if (!raw) return null;
4869
+ const normalized = raw.toLowerCase().replace(/[*_]/g, "").trim();
4870
+ if (!normalized || NONE_PLACEHOLDERS.has(normalized)) return null;
4871
+ return raw;
4872
+ }
4424
4873
  async function l0Present(root) {
4425
4874
  const cursor = path26.join(root, ".cursor");
4426
4875
  return await fileExists2(path26.join(cursor, "agent-kit.json")) || await fileExists2(path26.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path26.join(cursor, "commands", "start-project.md"));
@@ -4465,7 +4914,7 @@ async function readinessSection(root) {
4465
4914
  const snapshotPath = path26.join(root, ".cursor", "context", "readiness.json");
4466
4915
  let data;
4467
4916
  try {
4468
- data = JSON.parse(await readFile13(snapshotPath, "utf8"));
4917
+ data = JSON.parse(await readFile14(snapshotPath, "utf8"));
4469
4918
  } catch {
4470
4919
  return null;
4471
4920
  }
@@ -4502,7 +4951,7 @@ async function dogfoodInboxSection(root) {
4502
4951
  for (const readme of candidateReadmes) {
4503
4952
  if (!await fileExists2(readme)) continue;
4504
4953
  try {
4505
- const text = await readFile13(readme, "utf8");
4954
+ const text = await readFile14(readme, "utf8");
4506
4955
  if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
4507
4956
  } catch {
4508
4957
  }
@@ -4512,7 +4961,7 @@ async function dogfoodInboxSection(root) {
4512
4961
  async function loadUpdateCheckPrefs(root) {
4513
4962
  try {
4514
4963
  const data = JSON.parse(
4515
- await readFile13(path26.join(root, ".cursor", "context", "config.json"), "utf8")
4964
+ await readFile14(path26.join(root, ".cursor", "context", "config.json"), "utf8")
4516
4965
  );
4517
4966
  const uc = data.updateCheck;
4518
4967
  if (!uc || typeof uc !== "object" || uc.enabled !== true) {
@@ -4524,8 +4973,8 @@ async function loadUpdateCheckPrefs(root) {
4524
4973
  }
4525
4974
  }
4526
4975
  function runUpdateCheckJson(root) {
4527
- return new Promise((resolve) => {
4528
- const child = spawn4(
4976
+ return new Promise((resolve2) => {
4977
+ const child = spawn5(
4529
4978
  process.execPath,
4530
4979
  [
4531
4980
  process.argv[1] ?? "",
@@ -4543,21 +4992,21 @@ function runUpdateCheckJson(root) {
4543
4992
  child.stdout?.on("data", (chunk) => {
4544
4993
  out += chunk.toString("utf8");
4545
4994
  });
4546
- child.on("error", () => resolve(null));
4995
+ child.on("error", () => resolve2(null));
4547
4996
  child.on("close", () => {
4548
4997
  try {
4549
4998
  const parsed = JSON.parse(out.trim());
4550
- resolve(parsed && typeof parsed === "object" ? parsed : null);
4999
+ resolve2(parsed && typeof parsed === "object" ? parsed : null);
4551
5000
  } catch {
4552
- resolve(null);
5001
+ resolve2(null);
4553
5002
  }
4554
5003
  });
4555
5004
  });
4556
5005
  }
4557
5006
  async function updateCheckSection(root) {
4558
5007
  if (await loadUpdateCheckPrefs(root) === null) return null;
4559
- const result = await new Promise((resolve) => {
4560
- const child = spawn4(
5008
+ const result = await new Promise((resolve2) => {
5009
+ const child = spawn5(
4561
5010
  "agent-kit",
4562
5011
  ["update", "--check", "--json", "--respect-prefs", "--stamp", "--cwd", root],
4563
5012
  { stdio: ["ignore", "pipe", "ignore"], timeout: 12e3, shell: false }
@@ -4567,17 +5016,17 @@ async function updateCheckSection(root) {
4567
5016
  out += chunk.toString("utf8");
4568
5017
  });
4569
5018
  child.on("error", () => {
4570
- void runUpdateCheckJson(root).then(resolve);
5019
+ void runUpdateCheckJson(root).then(resolve2);
4571
5020
  });
4572
5021
  child.on("close", (code) => {
4573
5022
  if (code !== 0 && !out.trim()) {
4574
- void runUpdateCheckJson(root).then(resolve);
5023
+ void runUpdateCheckJson(root).then(resolve2);
4575
5024
  return;
4576
5025
  }
4577
5026
  try {
4578
- resolve(JSON.parse(out.trim()));
5027
+ resolve2(JSON.parse(out.trim()));
4579
5028
  } catch {
4580
- void runUpdateCheckJson(root).then(resolve);
5029
+ void runUpdateCheckJson(root).then(resolve2);
4581
5030
  }
4582
5031
  });
4583
5032
  });
@@ -4590,7 +5039,7 @@ async function updateCheckSection(root) {
4590
5039
  async function loadCursorUpdateCheckPrefs(root) {
4591
5040
  try {
4592
5041
  const data = JSON.parse(
4593
- await readFile13(path26.join(root, ".cursor", "context", "config.json"), "utf8")
5042
+ await readFile14(path26.join(root, ".cursor", "context", "config.json"), "utf8")
4594
5043
  );
4595
5044
  const uc = data.cursorUpdateCheck;
4596
5045
  if (!uc || typeof uc !== "object" || uc.enabled !== true) {
@@ -4602,8 +5051,8 @@ async function loadCursorUpdateCheckPrefs(root) {
4602
5051
  }
4603
5052
  }
4604
5053
  function runCursorAwarenessJson(root) {
4605
- return new Promise((resolve) => {
4606
- const child = spawn4(
5054
+ return new Promise((resolve2) => {
5055
+ const child = spawn5(
4607
5056
  process.execPath,
4608
5057
  [
4609
5058
  process.argv[1] ?? "",
@@ -4621,13 +5070,13 @@ function runCursorAwarenessJson(root) {
4621
5070
  child.stdout?.on("data", (chunk) => {
4622
5071
  out += chunk.toString("utf8");
4623
5072
  });
4624
- child.on("error", () => resolve(null));
5073
+ child.on("error", () => resolve2(null));
4625
5074
  child.on("close", () => {
4626
5075
  try {
4627
5076
  const parsed = JSON.parse(out.trim());
4628
- resolve(parsed && typeof parsed === "object" ? parsed : null);
5077
+ resolve2(parsed && typeof parsed === "object" ? parsed : null);
4629
5078
  } catch {
4630
- resolve(null);
5079
+ resolve2(null);
4631
5080
  }
4632
5081
  });
4633
5082
  });
@@ -4642,15 +5091,15 @@ function shouldEmitCursorAwarenessNudge(result) {
4642
5091
  }
4643
5092
  async function cursorAwarenessSection(root, deps = {}) {
4644
5093
  if (await loadCursorUpdateCheckPrefs(root) === null) return null;
4645
- const spawnFn = deps.spawnFn ?? spawn4;
5094
+ const spawnFn = deps.spawnFn ?? spawn5;
4646
5095
  const runFallback = deps.runFallback ?? runCursorAwarenessJson;
4647
- const result = await new Promise((resolve) => {
5096
+ const result = await new Promise((resolve2) => {
4648
5097
  let settled = false;
4649
5098
  let fallbackStarted = false;
4650
5099
  const finish = (value) => {
4651
5100
  if (settled) return;
4652
5101
  settled = true;
4653
- resolve(value);
5102
+ resolve2(value);
4654
5103
  };
4655
5104
  const startFallback = () => {
4656
5105
  if (settled || fallbackStarted) return;
@@ -4739,7 +5188,7 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
4739
5188
  var hookCommand = defineCommand10({
4740
5189
  meta: {
4741
5190
  name: "hook",
4742
- description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT; thin hooks shell out here."
5191
+ description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT."
4743
5192
  },
4744
5193
  subCommands: {
4745
5194
  "session-start": defineCommand10({
@@ -4778,13 +5227,216 @@ var hookCommand = defineCommand10({
4778
5227
  import { intro, outro } from "@clack/prompts";
4779
5228
  import { defineCommand as defineCommand12 } from "citty";
4780
5229
 
5230
+ // src/utils/terminal.ts
5231
+ import { homedir as homedir2 } from "os";
5232
+ import path28 from "path";
5233
+ import { confirm, isCancel } from "@clack/prompts";
5234
+ function isNonInteractive() {
5235
+ if (process.env.CI === "true" || process.env.CI === "1") return true;
5236
+ if (process.env.AGENT_KIT_YES === "1") return true;
5237
+ return !process.stdin.isTTY;
5238
+ }
5239
+ async function validateProjectRoot(resolved) {
5240
+ const home = path28.resolve(homedir2());
5241
+ if (resolved === "/" || resolved === home) {
5242
+ return { ok: false, reason: `Refused to use ${resolved} as a project root.` };
5243
+ }
5244
+ const hasGit = await fileExists(path28.join(resolved, ".git"));
5245
+ const hasManifest = await fileExists(path28.join(resolved, ".cursor", "agent-kit.json"));
5246
+ if (!hasGit && !hasManifest) {
5247
+ return {
5248
+ ok: false,
5249
+ reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json. Run from a project directory or use --force-root.`
5250
+ };
5251
+ }
5252
+ return { ok: true };
5253
+ }
5254
+ async function confirmProjectRoot(cwd, opts) {
5255
+ const resolved = path28.resolve(cwd);
5256
+ if (opts.forceRoot) {
5257
+ return resolved;
5258
+ }
5259
+ const validation = await validateProjectRoot(resolved);
5260
+ if (!validation.ok) {
5261
+ if (opts.nonInteractive) {
5262
+ throw new RootRefusedError(resolved, validation.reason);
5263
+ }
5264
+ const ok2 = await confirm({
5265
+ message: `${validation.reason} Proceed anyway?`,
5266
+ initialValue: false
5267
+ });
5268
+ if (isCancel(ok2) || !ok2) {
5269
+ throw new RootRefusedError(resolved, validation.reason);
5270
+ }
5271
+ return resolved;
5272
+ }
5273
+ if (opts.nonInteractive) {
5274
+ return resolved;
5275
+ }
5276
+ const ok = await confirm({
5277
+ message: `${opts.command === "install" ? "Install" : "Update"} Agent Kit in: ${resolved}`,
5278
+ initialValue: true
5279
+ });
5280
+ if (isCancel(ok) || !ok) {
5281
+ throw new RootRefusedError(resolved);
5282
+ }
5283
+ return resolved;
5284
+ }
5285
+ var RootRefusedError = class extends Error {
5286
+ constructor(root, reason) {
5287
+ super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
5288
+ this.root = root;
5289
+ this.name = "RootRefusedError";
5290
+ }
5291
+ root;
5292
+ };
5293
+ function classifyInstallError(err) {
5294
+ const msg = err instanceof Error ? err.message : String(err);
5295
+ const code = err?.code;
5296
+ if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
5297
+ return {
5298
+ kind: "eperm",
5299
+ message: `Permission error: ${msg}`,
5300
+ recovery: [
5301
+ "The npm cache may have ownership drift (root-written files in a user dir).",
5302
+ "Recovery options:",
5303
+ " 1. npx --cache .npm-cache @dadado/agent-kit-cli install",
5304
+ " 2. npm cache clean --force && npx @dadado/agent-kit-cli install",
5305
+ " 3. Use Port B fallback: drag install.md into the Cursor chat"
5306
+ ].join("\n")
5307
+ };
5308
+ }
5309
+ if (/403|Forbidden|unauthorized|E401|ENEEDAUTH/.test(msg)) {
5310
+ return {
5311
+ kind: "registry-auth",
5312
+ message: `Registry access denied: ${msg}`,
5313
+ recovery: [
5314
+ "The npm registry returned 403/401 for the scoped package.",
5315
+ "Recovery options:",
5316
+ " 1. Check npm auth: npm whoami (login if needed: npm login)",
5317
+ " 2. If using a private registry, verify .npmrc scope config",
5318
+ " 3. Use Port B fallback: drag install.md into the Cursor chat"
5319
+ ].join("\n")
5320
+ };
5321
+ }
5322
+ if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED|ECONNRESET|EAI_AGAIN|fetch failed/.test(msg) || code === "ENOTFOUND" || code === "ETIMEDOUT") {
5323
+ return {
5324
+ kind: "network",
5325
+ message: `Network error: ${msg}`,
5326
+ recovery: [
5327
+ "Could not reach the registry or git remote.",
5328
+ "Recovery options:",
5329
+ " 1. Check network/proxy/VPN settings",
5330
+ " 2. Retry: npx @dadado/agent-kit-cli install",
5331
+ " 3. Use --registry <local-path> if you have a local checkout"
5332
+ ].join("\n")
5333
+ };
5334
+ }
5335
+ return {
5336
+ kind: "unknown",
5337
+ message: msg,
5338
+ recovery: "Unexpected error. Check the message above and retry, or use Port B fallback."
5339
+ };
5340
+ }
5341
+
4781
5342
  // src/commands/install.ts
4782
- import path30 from "path";
5343
+ import path32 from "path";
4783
5344
  import { defineCommand as defineCommand11 } from "citty";
4784
5345
 
4785
5346
  // src/generator/personalization.ts
4786
- import { readFile as readFile14, writeFile as writeFile6 } from "fs/promises";
4787
- import path28 from "path";
5347
+ import { readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
5348
+ import path30 from "path";
5349
+
5350
+ // src/generator/vscode.ts
5351
+ import { writeFile as writeFile7 } from "fs/promises";
5352
+ import path29 from "path";
5353
+
5354
+ // src/generator/platform.ts
5355
+ function gitProviderLabel(profile) {
5356
+ const p = profile.git.provider;
5357
+ if (!p || p === "other") return "Git";
5358
+ const labels = {
5359
+ github: "GitHub",
5360
+ gitlab: "GitLab",
5361
+ bitbucket: "Bitbucket",
5362
+ "azure-devops": "Azure DevOps",
5363
+ gitea: "Gitea"
5364
+ };
5365
+ return labels[p] ?? "Git";
5366
+ }
5367
+ function prTerminology(profile) {
5368
+ const p = profile.git.provider;
5369
+ if (!p || p === "other") return "PR/MR";
5370
+ return GIT_PLATFORM_META[p]?.prTerm ?? "PR/MR";
5371
+ }
5372
+
5373
+ // src/generator/vscode.ts
5374
+ async function generateVSCodeArtifacts(profile) {
5375
+ const results = [];
5376
+ const vscodeDir = path29.join(profile.rootDir, ".vscode");
5377
+ const githubDir = path29.join(profile.rootDir, ".github");
5378
+ await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
5379
+ const settingsPath = path29.join(vscodeDir, "settings.json");
5380
+ if (await fileExists(settingsPath)) {
5381
+ results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
5382
+ } else {
5383
+ await writeFile7(
5384
+ settingsPath,
5385
+ `${JSON.stringify(
5386
+ {
5387
+ "editor.formatOnSave": true,
5388
+ "editor.codeActionsOnSave": {
5389
+ "source.fixAll": "explicit"
5390
+ },
5391
+ "files.autoSave": "afterDelay"
5392
+ },
5393
+ null,
5394
+ 2
5395
+ )}
5396
+ `,
5397
+ "utf8"
5398
+ );
5399
+ results.push({ relativePath: ".vscode/settings.json", status: "applied" });
5400
+ }
5401
+ const provider = gitProviderLabel(profile);
5402
+ const prTerm = prTerminology(profile);
5403
+ const copilotPath = path29.join(githubDir, "copilot-instructions.md");
5404
+ if (await fileExists(copilotPath)) {
5405
+ results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
5406
+ } else {
5407
+ await writeFile7(
5408
+ copilotPath,
5409
+ `# Copilot Instructions
5410
+
5411
+ - Keep code changes small and testable.
5412
+ - Use Conventional Commits (feat:, fix:, docs:, etc.).
5413
+ - Prefer security-safe defaults.
5414
+ - Git platform: ${provider}. Always create a ${prTerm} for review.
5415
+ `,
5416
+ "utf8"
5417
+ );
5418
+ results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
5419
+ }
5420
+ if (profile.ide.plan === "vscode-pro") {
5421
+ const securityPath = path29.join(vscodeDir, "security-review.agent.md");
5422
+ if (await fileExists(securityPath)) {
5423
+ results.push({
5424
+ relativePath: ".vscode/security-review.agent.md",
5425
+ status: "skipped-customized"
5426
+ });
5427
+ } else {
5428
+ await writeFile7(
5429
+ securityPath,
5430
+ "# Security Review Agent\n\nSpecialized mode for security review.\n",
5431
+ "utf8"
5432
+ );
5433
+ results.push({ relativePath: ".vscode/security-review.agent.md", status: "applied" });
5434
+ }
5435
+ }
5436
+ return results;
5437
+ }
5438
+
5439
+ // src/generator/personalization.ts
4788
5440
  var PERSONALIZATION_CONTRACT_VERSION = 1;
4789
5441
  var CONTEXT_PATH = ".cursor/project-context.md";
4790
5442
  var AGENTS_PATH = "AGENTS.md";
@@ -4928,6 +5580,16 @@ function renderProjectContext(profile) {
4928
5580
  if (profile.infra.ci !== "none" && profile.infra.ciFiles.length > 0) {
4929
5581
  sections.push(`- CI: ${profile.infra.ci}.`);
4930
5582
  }
5583
+ sections.push(
5584
+ "",
5585
+ "## Relevant skills",
5586
+ "",
5587
+ "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/`.",
5588
+ "",
5589
+ "| Skill / path | Role | Evidence |",
5590
+ "|--------------|------|----------|",
5591
+ "| (none yet) | Add rows when `/agent-kit-onboard` scaffolds domain skills or personalization installs packs | \u2014 |"
5592
+ );
4931
5593
  if (profile.context.sources.length > 0) {
4932
5594
  sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
4933
5595
  }
@@ -4935,7 +5597,7 @@ function renderProjectContext(profile) {
4935
5597
  `;
4936
5598
  }
4937
5599
  async function createOwnedFile(rootDir, relativePath, content, evidence) {
4938
- const target = path28.join(rootDir, relativePath);
5600
+ const target = path30.join(rootDir, relativePath);
4939
5601
  if (await fileExists(target)) {
4940
5602
  return {
4941
5603
  kind: "file",
@@ -4945,8 +5607,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
4945
5607
  evidence
4946
5608
  };
4947
5609
  }
4948
- await ensureDir(path28.dirname(target));
4949
- await writeFile6(target, content, "utf8");
5610
+ await ensureDir(path30.dirname(target));
5611
+ await writeFile8(target, content, "utf8");
4950
5612
  return {
4951
5613
  kind: "file",
4952
5614
  id: relativePath,
@@ -4962,7 +5624,7 @@ async function packTargets(registryRoot, packId) {
4962
5624
  async function existingTargets(projectRoot, targets) {
4963
5625
  const checks = await Promise.all(
4964
5626
  targets.map(
4965
- async (target) => await fileExists(path28.join(projectRoot, target)) ? target : null
5627
+ async (target) => await fileExists(path30.join(projectRoot, target)) ? target : null
4966
5628
  )
4967
5629
  );
4968
5630
  return checks.filter((target) => target !== null);
@@ -4984,14 +5646,14 @@ async function applyPersonalization(input) {
4984
5646
  componentResults.push({ ...item, status: "unavailable" });
4985
5647
  continue;
4986
5648
  }
4987
- const target = path28.posix.join(
5649
+ const target = path30.posix.join(
4988
5650
  ".cursor",
4989
5651
  "skills",
4990
5652
  skill.path.includes("/core/") ? "core" : "community",
4991
5653
  skill.id,
4992
5654
  "SKILL.md"
4993
5655
  );
4994
- if (await fileExists(path28.join(input.rootDir, target))) {
5656
+ if (await fileExists(path30.join(input.rootDir, target))) {
4995
5657
  componentResults.push({ ...item, status: "skipped-customized", path: target });
4996
5658
  protectedPaths.add(target);
4997
5659
  continue;
@@ -5039,6 +5701,49 @@ async function applyPersonalization(input) {
5039
5701
  ]);
5040
5702
  protectedPaths.add(CONTEXT_PATH);
5041
5703
  protectedPaths.add(AGENTS_PATH);
5704
+ const ideDetection = await detectIde(input.rootDir);
5705
+ if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
5706
+ const git = {
5707
+ providerKind: input.profile.git.providerKind ?? "unknown",
5708
+ providerConfidence: input.profile.git.providerConfidence ?? "low",
5709
+ providerEvidence: input.profile.git.providerEvidence ?? [],
5710
+ remotes: input.profile.git.remotes ?? [],
5711
+ mode: input.profile.git.mode ?? "none",
5712
+ workflow: input.profile.git.workflow ?? "unknown",
5713
+ isDirty: input.profile.git.isDirty ?? false,
5714
+ hasLocalStaging: input.profile.git.hasLocalStaging ?? false,
5715
+ hasRemoteStaging: input.profile.git.hasRemoteStaging ?? false,
5716
+ provider: input.profile.git.provider,
5717
+ remoteUrl: input.profile.git.remoteUrl,
5718
+ remoteName: input.profile.git.remoteName,
5719
+ currentBranch: input.profile.git.currentBranch,
5720
+ defaultBranch: input.profile.git.defaultBranch
5721
+ };
5722
+ const projectProfile = {
5723
+ rootDir: input.rootDir,
5724
+ stack: input.profile.stack,
5725
+ git,
5726
+ ide: ideDetection,
5727
+ infra: input.profile.infra,
5728
+ services: input.profile.services,
5729
+ installHooks: false,
5730
+ selectedCoreComponents: []
5731
+ };
5732
+ const vscodeResults = await generateVSCodeArtifacts(projectProfile);
5733
+ const ideEvidence = [
5734
+ { source: "derived", value: `ide:${ideDetection.ide}` }
5735
+ ];
5736
+ for (const artifact of vscodeResults) {
5737
+ protectedPaths.add(artifact.relativePath);
5738
+ componentResults.push({
5739
+ kind: "file",
5740
+ id: artifact.relativePath,
5741
+ path: artifact.relativePath,
5742
+ status: artifact.status,
5743
+ evidence: ideEvidence
5744
+ });
5745
+ }
5746
+ }
5042
5747
  const result = {
5043
5748
  contractVersion: PERSONALIZATION_CONTRACT_VERSION,
5044
5749
  generatorVersion: input.generatorVersion,
@@ -5046,7 +5751,7 @@ async function applyPersonalization(input) {
5046
5751
  items: [...fileResults, ...componentResults],
5047
5752
  protectedPaths: [...protectedPaths].sort()
5048
5753
  };
5049
- await writeJson(path28.join(input.rootDir, RESULT_PATH), result);
5754
+ await writeJson(path30.join(input.rootDir, RESULT_PATH), result);
5050
5755
  return {
5051
5756
  result,
5052
5757
  manifest: {
@@ -5064,26 +5769,26 @@ async function applyPersonalization(input) {
5064
5769
  };
5065
5770
  }
5066
5771
  async function readRepositoryProfile(rootDir) {
5067
- const target = path28.join(rootDir, ".cursor/agent-kit.config.json");
5772
+ const target = path30.join(rootDir, ".cursor/agent-kit.config.json");
5068
5773
  if (!await fileExists(target)) return null;
5069
- return JSON.parse(await readFile14(target, "utf8"));
5774
+ return JSON.parse(await readFile15(target, "utf8"));
5070
5775
  }
5071
5776
 
5072
5777
  // src/lifecycle/onboard-migration.ts
5073
5778
  import { createHash as createHash4 } from "crypto";
5074
- import { readFile as readFile15, unlink } from "fs/promises";
5075
- import path29 from "path";
5779
+ import { readFile as readFile16, unlink } from "fs/promises";
5780
+ import path31 from "path";
5076
5781
  var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
5077
5782
  var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
5078
5783
  var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
5079
5784
  "b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
5080
5785
  ]);
5081
5786
  async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
5082
- const legacyPath = path29.join(projectRoot, LEGACY_ONBOARD_PATH);
5787
+ const legacyPath = path31.join(projectRoot, LEGACY_ONBOARD_PATH);
5083
5788
  if (!await fileExists(legacyPath)) return "absent";
5084
- const namespacedPath = path29.join(projectRoot, NAMESPACED_ONBOARD_PATH);
5789
+ const namespacedPath = path31.join(projectRoot, NAMESPACED_ONBOARD_PATH);
5085
5790
  if (!await fileExists(namespacedPath)) return "preserved-customized";
5086
- const content = await readFile15(legacyPath);
5791
+ const content = await readFile16(legacyPath);
5087
5792
  const hash = createHash4("sha256").update(content).digest("hex");
5088
5793
  if (!managedHashes.has(hash)) return "preserved-customized";
5089
5794
  await unlink(legacyPath);
@@ -5156,7 +5861,7 @@ function printReadinessNarrative(result) {
5156
5861
  );
5157
5862
  }
5158
5863
  async function performInstall(options) {
5159
- const projectRoot = path30.resolve(options.cwd);
5864
+ const projectRoot = path32.resolve(options.cwd);
5160
5865
  const packs = parsePackList(options.pack);
5161
5866
  const existing = await loadAgentKitManifest(projectRoot);
5162
5867
  const registry = await resolveRegistryFromCli({
@@ -5167,53 +5872,57 @@ async function performInstall(options) {
5167
5872
  refresh: options.refresh,
5168
5873
  manifest: existing
5169
5874
  });
5170
- const draft = buildManifest({
5171
- version: KIT_VERSION,
5172
- profile: options.profile ?? existing?.profile ?? "default",
5173
- packs: packs.length > 0 ? packs : existing?.packs,
5174
- skills: existing?.skills,
5175
- protected: existing?.protected,
5176
- personalization: existing?.personalization,
5177
- registryUrl: registry.url ?? existing?.registry?.url,
5178
- registryRef: registry.ref ?? existing?.registry?.ref
5179
- });
5180
- 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));
5181
- const manifestPath = await saveManifest(projectRoot, draft);
5182
- const readinessExecution = await executeSafeReadinessFixes(projectRoot, {
5183
- generatorVersion: KIT_VERSION
5184
- });
5185
- let readiness = readinessExecution.after;
5186
- const profile = await readRepositoryProfile(projectRoot);
5187
- if (profile) {
5188
- const registryIndex = await loadRegistry(registry.root);
5189
- const personalization = await applyPersonalization({
5190
- rootDir: projectRoot,
5191
- registryRoot: registry.root,
5192
- profile,
5193
- report: readinessExecution.after,
5194
- registry: registryIndex,
5195
- manifest: draft,
5196
- generatorVersion: KIT_VERSION
5875
+ try {
5876
+ const draft = buildManifest({
5877
+ version: KIT_VERSION,
5878
+ profile: options.profile ?? existing?.profile ?? "default",
5879
+ packs: packs.length > 0 ? packs : existing?.packs,
5880
+ skills: existing?.skills,
5881
+ protected: existing?.protected,
5882
+ personalization: existing?.personalization,
5883
+ registryUrl: registry.url ?? existing?.registry?.url,
5884
+ registryRef: registry.ref ?? existing?.registry?.ref
5197
5885
  });
5198
- await saveManifest(projectRoot, personalization.manifest);
5199
- readiness = createReadinessReport(await runScanner(projectRoot), {
5886
+ 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));
5887
+ const manifestPath = await saveManifest(projectRoot, draft);
5888
+ const readinessExecution = await executeSafeReadinessFixes(projectRoot, {
5200
5889
  generatorVersion: KIT_VERSION
5201
5890
  });
5202
- readiness.appliedSafeFixes = readinessExecution.after.appliedSafeFixes;
5891
+ let readiness = readinessExecution.after;
5892
+ const profile = await readRepositoryProfile(projectRoot);
5893
+ if (profile) {
5894
+ const registryIndex = await loadRegistry(registry.root);
5895
+ const personalization = await applyPersonalization({
5896
+ rootDir: projectRoot,
5897
+ registryRoot: registry.root,
5898
+ profile,
5899
+ report: readinessExecution.after,
5900
+ registry: registryIndex,
5901
+ manifest: draft,
5902
+ generatorVersion: KIT_VERSION
5903
+ });
5904
+ await saveManifest(projectRoot, personalization.manifest);
5905
+ readiness = createReadinessReport(await runScanner(projectRoot), {
5906
+ generatorVersion: KIT_VERSION
5907
+ });
5908
+ readiness.appliedSafeFixes = readinessExecution.after.appliedSafeFixes;
5909
+ }
5910
+ await writeReadinessSnapshot(projectRoot, readiness);
5911
+ return {
5912
+ projectRoot,
5913
+ manifestPath,
5914
+ stats,
5915
+ readiness,
5916
+ safeChanges: readinessExecution.changes
5917
+ };
5918
+ } finally {
5919
+ await registry.unlock?.();
5203
5920
  }
5204
- await writeReadinessSnapshot(projectRoot, readiness);
5205
- return {
5206
- projectRoot,
5207
- manifestPath,
5208
- stats,
5209
- readiness,
5210
- safeChanges: readinessExecution.changes
5211
- };
5212
5921
  }
5213
5922
  var installCommand = defineCommand11({
5214
5923
  meta: {
5215
5924
  name: "install",
5216
- description: "Bootstrap L0 (+ optional packs) from the registry and write agent-kit.json."
5925
+ description: "Bootstrap L0 (+ optional packs) and write .cursor/agent-kit.json."
5217
5926
  },
5218
5927
  args: {
5219
5928
  profile: {
@@ -5225,6 +5934,17 @@ var installCommand = defineCommand11({
5225
5934
  type: "string",
5226
5935
  description: `Comma-separated L1 pack ids (e.g. cybersec,devops). Known: ${DOMAIN_PACK_IDS.join(", ")}`
5227
5936
  },
5937
+ yes: {
5938
+ type: "boolean",
5939
+ alias: "y",
5940
+ description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
5941
+ default: false
5942
+ },
5943
+ "force-root": {
5944
+ type: "boolean",
5945
+ description: "Bypass the ambiguous-root guard (use with caution)",
5946
+ default: false
5947
+ },
5228
5948
  cwd: {
5229
5949
  type: "string",
5230
5950
  default: process.cwd()
@@ -5232,7 +5952,25 @@ var installCommand = defineCommand11({
5232
5952
  ...REGISTRY_CLI_ARGS
5233
5953
  },
5234
5954
  async run({ args }) {
5235
- const projectRoot = path30.resolve(args.cwd);
5955
+ const nonInteractive = args.yes || isNonInteractive();
5956
+ if (nonInteractive) {
5957
+ logger.info("Non-interactive mode: skipping prompts, using defaults.");
5958
+ }
5959
+ let projectRoot;
5960
+ try {
5961
+ projectRoot = await confirmProjectRoot(args.cwd, {
5962
+ nonInteractive,
5963
+ command: "install",
5964
+ forceRoot: args["force-root"]
5965
+ });
5966
+ } catch (err) {
5967
+ if (err instanceof RootRefusedError) {
5968
+ logger.error(err.message);
5969
+ process.exitCode = 1;
5970
+ return;
5971
+ }
5972
+ throw err;
5973
+ }
5236
5974
  logger.info(`Installing into: ${projectRoot}`);
5237
5975
  const packs = parsePackList(args.pack);
5238
5976
  for (const id of packs) {
@@ -5240,19 +5978,28 @@ var installCommand = defineCommand11({
5240
5978
  logger.warn(`Pack '${id}' is not in the known L1 list; will still try registry.`);
5241
5979
  }
5242
5980
  }
5243
- const result = await performInstall({
5244
- cwd: projectRoot,
5245
- profile: args.profile,
5246
- pack: args.pack,
5247
- registry: args.registry,
5248
- url: args.url,
5249
- ref: args.ref,
5250
- refresh: args.refresh
5251
- });
5252
- logApplyStats(result.stats);
5253
- logger.success(`Manifest written: ${result.manifestPath}`);
5254
- logger.success("Readiness snapshot written: .cursor/context/readiness.json");
5255
- printReadinessNarrative(result);
5981
+ try {
5982
+ const result = await performInstall({
5983
+ cwd: projectRoot,
5984
+ profile: args.profile,
5985
+ pack: args.pack,
5986
+ registry: args.registry,
5987
+ url: args.url,
5988
+ ref: args.ref,
5989
+ refresh: args.refresh
5990
+ });
5991
+ logApplyStats(result.stats);
5992
+ logger.success(`Manifest written: ${result.manifestPath}`);
5993
+ logger.success("Readiness snapshot written: .cursor/context/readiness.json");
5994
+ printReadinessNarrative(result);
5995
+ } catch (err) {
5996
+ const hint = classifyInstallError(err);
5997
+ logger.error(hint.message);
5998
+ console.error(`
5999
+ ${hint.recovery}
6000
+ `);
6001
+ process.exit(1);
6002
+ }
5256
6003
  }
5257
6004
  });
5258
6005
 
@@ -5263,7 +6010,7 @@ async function runInitCompatibility(cwd, installer = performInstall) {
5263
6010
  var initCommand = defineCommand12({
5264
6011
  meta: {
5265
6012
  name: "init",
5266
- description: "Guided compatibility entry point for install and repository readiness."
6013
+ description: "Guided setup entry (compat path to install + readiness)."
5267
6014
  },
5268
6015
  args: {
5269
6016
  cwd: {
@@ -5273,25 +6020,42 @@ var initCommand = defineCommand12({
5273
6020
  }
5274
6021
  },
5275
6022
  async run({ args }) {
5276
- intro(`agent-kit v${KIT_VERSION}`);
6023
+ const nonInteractive = isNonInteractive();
6024
+ if (!nonInteractive) {
6025
+ intro(`agent-kit v${KIT_VERSION}`);
6026
+ } else {
6027
+ logger.info(`agent-kit v${KIT_VERSION} (non-interactive mode)`);
6028
+ }
5277
6029
  logger.info("init now uses the canonical install and readiness workflow.");
5278
- const result = await runInitCompatibility(args.cwd);
5279
- const pending = result.readiness.pendingActions.length;
5280
- logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
5281
- outro(
5282
- pending > 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."
5283
- );
6030
+ try {
6031
+ const result = await runInitCompatibility(args.cwd);
6032
+ const pending = result.readiness.pendingActions.length;
6033
+ logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
6034
+ const nextStep = pending > 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.";
6035
+ if (!nonInteractive) {
6036
+ outro(nextStep);
6037
+ } else {
6038
+ logger.info(nextStep);
6039
+ }
6040
+ } catch (err) {
6041
+ const hint = classifyInstallError(err);
6042
+ logger.error(hint.message);
6043
+ console.error(`
6044
+ ${hint.recovery}
6045
+ `);
6046
+ process.exit(1);
6047
+ }
5284
6048
  }
5285
6049
  });
5286
6050
 
5287
6051
  // src/commands/monitors.ts
5288
- import path32 from "path";
6052
+ import path34 from "path";
5289
6053
  import { defineCommand as defineCommand13 } from "citty";
5290
6054
 
5291
6055
  // src/invariants/monitors-untriaged.ts
5292
6056
  import { execFile as execFile5 } from "child_process";
5293
- import { readFile as readFile16, readdir as readdir5, stat as stat2 } from "fs/promises";
5294
- import path31 from "path";
6057
+ import { readFile as readFile17, readdir as readdir5, stat as stat3 } from "fs/promises";
6058
+ import path33 from "path";
5295
6059
  import { promisify as promisify5 } from "util";
5296
6060
 
5297
6061
  // src/invariants/triage-heading.ts
@@ -5332,7 +6096,7 @@ async function gitFreshMonitorNames(rootDir) {
5332
6096
  for (const line of stdout.split("\n")) {
5333
6097
  if (!line.trim()) continue;
5334
6098
  const file = line.slice(3).trim().replace(/^.* -> /, "");
5335
- const base = path31.basename(file);
6099
+ const base = path33.basename(file);
5336
6100
  if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
5337
6101
  names.add(base);
5338
6102
  }
@@ -5355,15 +6119,15 @@ function monitorSlugFromName(fileName) {
5355
6119
  return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
5356
6120
  }
5357
6121
  async function selectUntriagedMonitors(rootDir) {
5358
- const root = path31.resolve(rootDir);
5359
- const memoryDir = path31.join(root, ".cursor", "memory");
6122
+ const root = path33.resolve(rootDir);
6123
+ const memoryDir = path33.join(root, ".cursor", "memory");
5360
6124
  const allNames = await listMonitorFiles(memoryDir);
5361
6125
  const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
5362
6126
  const byName = /* @__PURE__ */ new Map();
5363
6127
  for (const name of allNames) {
5364
- const abs = path31.join(memoryDir, name);
6128
+ const abs = path33.join(memoryDir, name);
5365
6129
  try {
5366
- const [content, st] = await Promise.all([readFile16(abs, "utf8"), stat2(abs)]);
6130
+ const [content, st] = await Promise.all([readFile17(abs, "utf8"), stat3(abs)]);
5367
6131
  byName.set(name, { content, mtimeMs: st.mtimeMs });
5368
6132
  } catch {
5369
6133
  }
@@ -5376,7 +6140,7 @@ async function selectUntriagedMonitors(rootDir) {
5376
6140
  const gitFreshSet = [...gitFresh].filter(untriaged).sort();
5377
6141
  let handoff = "";
5378
6142
  try {
5379
- handoff = await readFile16(path31.join(root, ".cursor", "HANDOFF.md"), "utf8");
6143
+ handoff = await readFile17(path33.join(root, ".cursor", "HANDOFF.md"), "utf8");
5380
6144
  } catch {
5381
6145
  handoff = "";
5382
6146
  }
@@ -5396,8 +6160,8 @@ async function selectUntriagedMonitors(rootDir) {
5396
6160
  const row = byName.get(name);
5397
6161
  if (!row) continue;
5398
6162
  entries.push({
5399
- path: path31.join(memoryDir, name),
5400
- relativePath: path31.relative(root, path31.join(memoryDir, name)).split(path31.sep).join("/"),
6163
+ path: path33.join(memoryDir, name),
6164
+ relativePath: path33.relative(root, path33.join(memoryDir, name)).split(path33.sep).join("/"),
5401
6165
  mtimeMs: row.mtimeMs,
5402
6166
  hasTriageHeading: false,
5403
6167
  hasOpenGaps: hasOpenGaps(row.content),
@@ -5419,7 +6183,7 @@ async function selectUntriagedMonitors(rootDir) {
5419
6183
  var monitorsCommand = defineCommand13({
5420
6184
  meta: {
5421
6185
  name: "monitors",
5422
- description: "Plan-monitor selection helpers (untriaged SoT for /plan-review-triage)"
6186
+ description: "Select untriaged plan monitors (--untriaged; SoT for chat triage)."
5423
6187
  },
5424
6188
  args: {
5425
6189
  cwd: {
@@ -5443,7 +6207,7 @@ var monitorsCommand = defineCommand13({
5443
6207
  process.exitCode = 2;
5444
6208
  return;
5445
6209
  }
5446
- const result = await selectUntriagedMonitors(path32.resolve(args.cwd));
6210
+ const result = await selectUntriagedMonitors(path34.resolve(args.cwd));
5447
6211
  if (args.json) {
5448
6212
  console.log(JSON.stringify(result, null, 2));
5449
6213
  return;
@@ -5459,11 +6223,11 @@ var monitorsCommand = defineCommand13({
5459
6223
  });
5460
6224
 
5461
6225
  // src/commands/run-plan.ts
5462
- import path37 from "path";
6226
+ import path39 from "path";
5463
6227
  import { defineCommand as defineCommand14 } from "citty";
5464
6228
 
5465
6229
  // src/plan-loop/backends.ts
5466
- import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
6230
+ import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
5467
6231
  import { createWriteStream } from "fs";
5468
6232
  async function which(bin) {
5469
6233
  try {
@@ -5474,9 +6238,9 @@ async function which(bin) {
5474
6238
  }
5475
6239
  }
5476
6240
  function spawnLogged(command, args, logPath) {
5477
- return new Promise((resolve, reject) => {
6241
+ return new Promise((resolve2, reject) => {
5478
6242
  const out = createWriteStream(logPath, { flags: "w" });
5479
- const child = spawn5(command, args, {
6243
+ const child = spawn6(command, args, {
5480
6244
  stdio: ["ignore", "pipe", "pipe"]
5481
6245
  });
5482
6246
  const onData = (chunk) => {
@@ -5490,7 +6254,7 @@ function spawnLogged(command, args, logPath) {
5490
6254
  reject(err);
5491
6255
  });
5492
6256
  child.on("close", (code) => {
5493
- out.end(() => resolve({ exitCode: code ?? 1 }));
6257
+ out.end(() => resolve2({ exitCode: code ?? 1 }));
5494
6258
  });
5495
6259
  });
5496
6260
  }
@@ -5544,14 +6308,14 @@ function listBackendIds() {
5544
6308
  }
5545
6309
 
5546
6310
  // src/plan-loop/run-loop.ts
5547
- import { mkdir as mkdir5, readFile as readFile19, rm, unlink as unlink2 } from "fs/promises";
5548
- import path36 from "path";
6311
+ import { mkdir as mkdir6, readFile as readFile20, rm as rm2, unlink as unlink2 } from "fs/promises";
6312
+ import path38 from "path";
5549
6313
 
5550
6314
  // src/plan-loop/external-review.ts
5551
- import { spawn as spawn6 } from "child_process";
5552
- import path33 from "path";
5553
- var CANONICAL_REL = path33.join(".cursor", "scripts", "plan-external-review.sh");
5554
- var FALLBACK_REL = path33.join("scripts", "plan-external-review.sh");
6315
+ import { spawn as spawn7 } from "child_process";
6316
+ import path35 from "path";
6317
+ var CANONICAL_REL = path35.join(".cursor", "scripts", "plan-external-review.sh");
6318
+ var FALLBACK_REL = path35.join("scripts", "plan-external-review.sh");
5555
6319
  function isPlanExhaustedReason(reason) {
5556
6320
  const r = reason.trim().toLowerCase();
5557
6321
  if (!r) return false;
@@ -5569,12 +6333,12 @@ function shouldArmExternalPlanReview(input) {
5569
6333
  return false;
5570
6334
  }
5571
6335
  async function armExternalPlanReview(root, options = {}) {
5572
- const spawnFn = options.spawnFn ?? spawn6;
6336
+ const spawnFn = options.spawnFn ?? spawn7;
5573
6337
  const existsFn = options.existsFn ?? fileExists;
5574
6338
  const log = options.log ?? ((line) => console.log(line));
5575
6339
  const force = options.force === true;
5576
- const canonicalPath = path33.join(root, CANONICAL_REL);
5577
- const fallbackPath = path33.join(root, FALLBACK_REL);
6340
+ const canonicalPath = path35.join(root, CANONICAL_REL);
6341
+ const fallbackPath = path35.join(root, FALLBACK_REL);
5578
6342
  let scriptPath = null;
5579
6343
  let scriptRel = CANONICAL_REL;
5580
6344
  if (await existsFn(canonicalPath)) {
@@ -5592,7 +6356,7 @@ async function armExternalPlanReview(root, options = {}) {
5592
6356
  }
5593
6357
  log("Plan exhausted: arming optional external plan review (opt-in handled by script)...");
5594
6358
  const args = force ? [scriptPath, "--force", "--print"] : [scriptPath, "--print"];
5595
- return new Promise((resolve) => {
6359
+ return new Promise((resolve2) => {
5596
6360
  const child = spawnFn("bash", args, {
5597
6361
  cwd: root,
5598
6362
  env: { ...process.env, AGENT_KIT_HEADLESS: "1" }
@@ -5613,7 +6377,7 @@ async function armExternalPlanReview(root, options = {}) {
5613
6377
  log(
5614
6378
  `tip: external plan review launcher failed (${err.message}). Manual: ${scriptRel} or /plan-external-review`
5615
6379
  );
5616
- resolve({ invoked: true, exitCode: null, output });
6380
+ resolve2({ invoked: true, exitCode: null, output });
5617
6381
  });
5618
6382
  child.on("close", (code) => {
5619
6383
  if (code !== 0 && code !== null) {
@@ -5621,13 +6385,13 @@ async function armExternalPlanReview(root, options = {}) {
5621
6385
  `tip: external plan review exited ${code} (ignored; does not fail the loop). Manual: ${scriptRel} or /plan-external-review`
5622
6386
  );
5623
6387
  }
5624
- resolve({ invoked: true, exitCode: code, output });
6388
+ resolve2({ invoked: true, exitCode: code, output });
5625
6389
  });
5626
6390
  });
5627
6391
  }
5628
6392
 
5629
6393
  // src/plan-loop/persona-banners.ts
5630
- import path34 from "path";
6394
+ import path36 from "path";
5631
6395
  import {
5632
6396
  blue,
5633
6397
  cyan as cyan2,
@@ -5663,7 +6427,7 @@ function resolveColor(name, fallback) {
5663
6427
  async function resolveCliPersonaId(root) {
5664
6428
  try {
5665
6429
  const cfg = await readJson(
5666
- path34.join(root, ".cursor", "context", "config.json")
6430
+ path36.join(root, ".cursor", "context", "config.json")
5667
6431
  );
5668
6432
  const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
5669
6433
  const id = modes?.[CLI_RUN_PLAN_MODE];
@@ -5674,7 +6438,7 @@ async function resolveCliPersonaId(root) {
5674
6438
  }
5675
6439
  async function loadPersonaPack(root, personaId) {
5676
6440
  try {
5677
- const personaPath = path34.join(root, "registry", "personas", "core", personaId, "persona.json");
6441
+ const personaPath = path36.join(root, "registry", "personas", "core", personaId, "persona.json");
5678
6442
  const pack = await readJson(personaPath);
5679
6443
  if (!pack || typeof pack.id !== "string") return null;
5680
6444
  return pack;
@@ -5723,8 +6487,8 @@ function createPersonaBannerPrinter(persona) {
5723
6487
  }
5724
6488
 
5725
6489
  // src/plan-loop/plan-state.ts
5726
- import { readFile as readFile17, readdir as readdir6 } from "fs/promises";
5727
- import path35 from "path";
6490
+ import { readFile as readFile18, readdir as readdir6 } from "fs/promises";
6491
+ import path37 from "path";
5728
6492
  function countPendingTodos(raw) {
5729
6493
  const lines = raw.split(/\r?\n/);
5730
6494
  let inFront = 0;
@@ -5752,14 +6516,14 @@ function countPendingTodos(raw) {
5752
6516
  async function findActivePlanFile(plansDir) {
5753
6517
  if (!await fileExists(plansDir)) return null;
5754
6518
  const files = (await readdir6(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
5755
- return files[0] ? path35.join(plansDir, files[0]) : null;
6519
+ return files[0] ? path37.join(plansDir, files[0]) : null;
5756
6520
  }
5757
6521
  async function readPlan(planPath) {
5758
- return readFile17(planPath, "utf8");
6522
+ return readFile18(planPath, "utf8");
5759
6523
  }
5760
6524
 
5761
6525
  // src/plan-loop/sentinel.ts
5762
- import { readFile as readFile18 } from "fs/promises";
6526
+ import { readFile as readFile19 } from "fs/promises";
5763
6527
  var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
5764
6528
  function takeFromText(text) {
5765
6529
  if (!text) return null;
@@ -5806,7 +6570,7 @@ function parseSentinelFromLog(content) {
5806
6570
  }
5807
6571
  async function parseSentinelFromLogFile(logPath) {
5808
6572
  try {
5809
- const content = await readFile18(logPath, "utf8");
6573
+ const content = await readFile19(logPath, "utf8");
5810
6574
  return parseSentinelFromLog(content);
5811
6575
  } catch {
5812
6576
  return { kind: "missing" };
@@ -5829,16 +6593,16 @@ function sleep(ms) {
5829
6593
  return new Promise((r) => setTimeout(r, ms));
5830
6594
  }
5831
6595
  async function runPlanLoop(opts) {
5832
- const plansDir = path36.join(opts.root, ".cursor", "plans");
5833
- const stopFile = path36.join(opts.root, ".cursor", "loop.stop");
5834
- const logDir = path36.join(opts.root, ".cursor", "loop-logs");
6596
+ const plansDir = path38.join(opts.root, ".cursor", "plans");
6597
+ const stopFile = path38.join(opts.root, ".cursor", "loop.stop");
6598
+ const logDir = path38.join(opts.root, ".cursor", "loop-logs");
5835
6599
  const planPath = await findActivePlanFile(plansDir);
5836
6600
  if (!planPath) {
5837
6601
  logger.error("No active plan in .cursor/plans/");
5838
6602
  return 1;
5839
6603
  }
5840
6604
  const pending = async () => countPendingTodos(await readPlan(planPath));
5841
- await mkdir5(logDir, { recursive: true });
6605
+ await mkdir6(logDir, { recursive: true });
5842
6606
  try {
5843
6607
  await unlink2(stopFile);
5844
6608
  } catch {
@@ -5852,7 +6616,7 @@ async function runPlanLoop(opts) {
5852
6616
  try {
5853
6617
  const persona = await loadCliRunPlanPersona(opts.root);
5854
6618
  const banners = createPersonaBannerPrinter(persona);
5855
- console.log(`Active plan: ${path36.basename(planPath)}`);
6619
+ console.log(`Active plan: ${path38.basename(planPath)}`);
5856
6620
  console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
5857
6621
  console.log(`Backend: ${opts.backend.id}`);
5858
6622
  if (persona) {
@@ -5884,7 +6648,7 @@ async function runPlanLoop(opts) {
5884
6648
  if (banners) banners.stop(msg);
5885
6649
  else console.log(msg);
5886
6650
  try {
5887
- await rm(stopFile, { force: true });
6651
+ await rm2(stopFile, { force: true });
5888
6652
  } catch {
5889
6653
  }
5890
6654
  break;
@@ -5895,8 +6659,8 @@ async function runPlanLoop(opts) {
5895
6659
  planExhausted = true;
5896
6660
  break;
5897
6661
  }
5898
- const logPath = path36.join(logDir, `tick-${stamp()}.log`);
5899
- const relLog = path36.relative(opts.root, logPath);
6662
+ const logPath = path38.join(logDir, `tick-${stamp()}.log`);
6663
+ const relLog = path38.relative(opts.root, logPath);
5900
6664
  console.log("");
5901
6665
  const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
5902
6666
  if (banners) banners.tickStart(tickLine);
@@ -5915,7 +6679,7 @@ async function runPlanLoop(opts) {
5915
6679
  return 1;
5916
6680
  }
5917
6681
  try {
5918
- const logText = await readFile19(logPath, "utf8");
6682
+ const logText = await readFile20(logPath, "utf8");
5919
6683
  if (logText.includes("Too many MCP tools")) {
5920
6684
  const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
5921
6685
  if (banners) banners.stop(msg);
@@ -5972,7 +6736,7 @@ async function runPlanLoop(opts) {
5972
6736
  const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
5973
6737
  if (banners) banners.phaseComplete(finishDetail);
5974
6738
  console.log(
5975
- `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path36.relative(opts.root, logDir)}/`
6739
+ `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path38.relative(opts.root, logDir)}/`
5976
6740
  );
5977
6741
  if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
5978
6742
  await armExternalPlanReview(opts.root);
@@ -5987,7 +6751,7 @@ async function runPlanLoop(opts) {
5987
6751
  var runPlanCommand = defineCommand14({
5988
6752
  meta: {
5989
6753
  name: "run-plan",
5990
- description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
6754
+ description: "Headless continuous plan runner (one fresh agent per tick). Never git-prod."
5991
6755
  },
5992
6756
  args: {
5993
6757
  cwd: {
@@ -6043,7 +6807,7 @@ var runPlanCommand = defineCommand14({
6043
6807
  return;
6044
6808
  }
6045
6809
  const code = await runPlanLoop({
6046
- root: path37.resolve(args.cwd),
6810
+ root: path39.resolve(args.cwd),
6047
6811
  maxTicks,
6048
6812
  sleepSeconds,
6049
6813
  model: args.model ? String(args.model) : void 0,
@@ -6059,7 +6823,7 @@ import { defineCommand as defineCommand15 } from "citty";
6059
6823
  var scanCommand = defineCommand15({
6060
6824
  meta: {
6061
6825
  name: "scan",
6062
- description: "Scan the current repository and print detected profile."
6826
+ description: "Scan the repository and print the detected profile."
6063
6827
  },
6064
6828
  args: {
6065
6829
  cwd: {
@@ -6077,7 +6841,7 @@ var scanCommand = defineCommand15({
6077
6841
  });
6078
6842
 
6079
6843
  // src/commands/status.ts
6080
- import path38 from "path";
6844
+ import path40 from "path";
6081
6845
  import { defineCommand as defineCommand16 } from "citty";
6082
6846
  function profileStatus(profile) {
6083
6847
  if (!profile) return { origin: "none", evidence: [], profile: null };
@@ -6094,7 +6858,7 @@ function profileStatus(profile) {
6094
6858
  var statusCommand = defineCommand16({
6095
6859
  meta: {
6096
6860
  name: "status",
6097
- description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
6861
+ description: "Show installed kit version, manifest, and optional profile."
6098
6862
  },
6099
6863
  args: {
6100
6864
  cwd: {
@@ -6108,11 +6872,11 @@ var statusCommand = defineCommand16({
6108
6872
  }
6109
6873
  },
6110
6874
  async run({ args }) {
6111
- const rootDir = path38.resolve(args.cwd);
6875
+ const rootDir = path40.resolve(args.cwd);
6112
6876
  const [manifest, rawProfile, scan] = await Promise.all([
6113
6877
  loadAgentKitManifest(rootDir),
6114
6878
  readJson(
6115
- path38.join(rootDir, ".cursor", "agent-kit.config.json")
6879
+ path40.join(rootDir, ".cursor", "agent-kit.config.json")
6116
6880
  ),
6117
6881
  runScanner(rootDir)
6118
6882
  ]);
@@ -6171,7 +6935,7 @@ import { defineCommand as defineCommand17 } from "citty";
6171
6935
 
6172
6936
  // src/lifecycle/check-updates.ts
6173
6937
  import { execFile as execFile6 } from "child_process";
6174
- import path39 from "path";
6938
+ import path41 from "path";
6175
6939
  import { promisify as promisify6 } from "util";
6176
6940
  var execFileAsync5 = promisify6(execFile6);
6177
6941
  var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
@@ -6262,11 +7026,11 @@ function intervalElapsed2(lastCheckedAt, intervalDays) {
6262
7026
  return Date.now() - last >= ms;
6263
7027
  }
6264
7028
  async function loadContextConfig2(cwd) {
6265
- const configPath = path39.join(cwd, ".cursor", "context", "config.json");
7029
+ const configPath = path41.join(cwd, ".cursor", "context", "config.json");
6266
7030
  return readJson(configPath);
6267
7031
  }
6268
7032
  async function stampLastCheckedAt(cwd) {
6269
- const configPath = path39.join(cwd, ".cursor", "context", "config.json");
7033
+ const configPath = path41.join(cwd, ".cursor", "context", "config.json");
6270
7034
  const existing = await loadContextConfig2(cwd) ?? {};
6271
7035
  const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
6272
7036
  existing.updateCheck = {
@@ -6410,7 +7174,7 @@ async function checkForUpdates(cwd, options = {}) {
6410
7174
  var updateCommand = defineCommand17({
6411
7175
  meta: {
6412
7176
  name: "update",
6413
- description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths. Use --check for notify-only."
7177
+ description: "Re-apply L0/packs/skills from the registry (never overwrites L3). --check = notify-only."
6414
7178
  },
6415
7179
  args: {
6416
7180
  cwd: {
@@ -6442,6 +7206,17 @@ var updateCommand = defineCommand17({
6442
7206
  description: "Seed the managed-hash ledger from current local overlay files before applying (factory/dogfood only; consumers should not use this)",
6443
7207
  default: false
6444
7208
  },
7209
+ yes: {
7210
+ type: "boolean",
7211
+ alias: "y",
7212
+ description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
7213
+ default: false
7214
+ },
7215
+ "force-root": {
7216
+ type: "boolean",
7217
+ description: "Bypass the ambiguous-root guard (use with caution)",
7218
+ default: false
7219
+ },
6445
7220
  ...REGISTRY_CLI_ARGS
6446
7221
  },
6447
7222
  async run({ args }) {
@@ -6463,48 +7238,68 @@ var updateCommand = defineCommand17({
6463
7238
  if (result.status === "error") process.exitCode = 2;
6464
7239
  return;
6465
7240
  }
6466
- const existing = await loadAgentKitManifest(args.cwd);
7241
+ const nonInteractive = args.yes || isNonInteractive();
7242
+ let projectRoot;
7243
+ try {
7244
+ projectRoot = await confirmProjectRoot(args.cwd, {
7245
+ nonInteractive,
7246
+ command: "update",
7247
+ forceRoot: args["force-root"]
7248
+ });
7249
+ } catch (err) {
7250
+ if (err instanceof RootRefusedError) {
7251
+ logger.error(err.message);
7252
+ process.exitCode = 1;
7253
+ return;
7254
+ }
7255
+ throw err;
7256
+ }
7257
+ const existing = await loadAgentKitManifest(projectRoot);
6467
7258
  if (!existing) {
6468
7259
  logger.warn("No .cursor/agent-kit.json \u2014 run agent-kit install first.");
6469
7260
  return;
6470
7261
  }
6471
7262
  const registry = await resolveRegistryFromCli({
6472
- cwd: args.cwd,
7263
+ cwd: projectRoot,
6473
7264
  registry: args.registry,
6474
7265
  url: args.url,
6475
7266
  ref: args.ref,
6476
7267
  refresh: args.refresh,
6477
7268
  manifest: existing
6478
7269
  });
6479
- logger.info(`Registry: ${registry.root} (${registry.source})`);
6480
- const next = buildManifest({
6481
- version: KIT_VERSION,
6482
- profile: existing.profile,
6483
- packs: existing.packs,
6484
- skills: existing.skills,
6485
- protected: existing.protected,
6486
- personalization: existing.personalization,
6487
- registryUrl: registry.url ?? existing.registry?.url,
6488
- registryRef: registry.ref ?? existing.registry?.ref
6489
- });
6490
- if (existing.overrides?.length) next.overrides = existing.overrides;
6491
- if (next.version === existing.version && existing.installedAt) {
6492
- next.installedAt = existing.installedAt;
6493
- }
6494
- if (args["seed-overlay"]) {
6495
- await seedManagedHashLedger(args.cwd);
6496
- logger.info("Seeded managed-hash ledger from current local overlay files.");
7270
+ try {
7271
+ logger.info(`Registry: ${registry.root} (${registry.source})`);
7272
+ const next = buildManifest({
7273
+ version: KIT_VERSION,
7274
+ profile: existing.profile,
7275
+ packs: existing.packs,
7276
+ skills: existing.skills,
7277
+ protected: existing.protected,
7278
+ personalization: existing.personalization,
7279
+ registryUrl: registry.url ?? existing.registry?.url,
7280
+ registryRef: registry.ref ?? existing.registry?.ref
7281
+ });
7282
+ if (existing.overrides?.length) next.overrides = existing.overrides;
7283
+ if (next.version === existing.version && existing.installedAt) {
7284
+ next.installedAt = existing.installedAt;
7285
+ }
7286
+ if (args["seed-overlay"]) {
7287
+ await seedManagedHashLedger(projectRoot);
7288
+ logger.info("Seeded managed-hash ledger from current local overlay files.");
7289
+ }
7290
+ const stats = await syncFromManifest(registry.root, projectRoot, next);
7291
+ await saveManifest(projectRoot, next);
7292
+ logApplyStats(stats);
7293
+ logger.success("Update complete (L3 protected paths left untouched).");
7294
+ } finally {
7295
+ await registry.unlock?.();
6497
7296
  }
6498
- const stats = await syncFromManifest(registry.root, args.cwd, next);
6499
- await saveManifest(args.cwd, next);
6500
- logApplyStats(stats);
6501
- logger.success("Update complete (L3 protected paths left untouched).");
6502
7297
  }
6503
7298
  });
6504
7299
 
6505
7300
  // src/commands/validate.ts
6506
- import { readFile as readFile20 } from "fs/promises";
6507
- import path40 from "path";
7301
+ import { readFile as readFile21 } from "fs/promises";
7302
+ import path42 from "path";
6508
7303
  import { defineCommand as defineCommand18 } from "citty";
6509
7304
 
6510
7305
  // src/invariants/plan-schema.ts
@@ -6551,9 +7346,9 @@ function validatePlanFrontmatterText(text) {
6551
7346
  // src/commands/validate.ts
6552
7347
  async function resolveEditedPath(cwd, explicit) {
6553
7348
  if (explicit) {
6554
- const filePath2 = path40.resolve(cwd, explicit);
7349
+ const filePath2 = path42.resolve(cwd, explicit);
6555
7350
  try {
6556
- return { filePath: filePath2, content: await readFile20(filePath2, "utf8") };
7351
+ return { filePath: filePath2, content: await readFile21(filePath2, "utf8") };
6557
7352
  } catch {
6558
7353
  return null;
6559
7354
  }
@@ -6561,9 +7356,9 @@ async function resolveEditedPath(cwd, explicit) {
6561
7356
  const payload = await readStdinJson();
6562
7357
  const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
6563
7358
  if (!rel) return null;
6564
- const filePath = path40.isAbsolute(rel) ? rel : path40.resolve(cwd, rel);
7359
+ const filePath = path42.isAbsolute(rel) ? rel : path42.resolve(cwd, rel);
6565
7360
  try {
6566
- return { filePath, content: await readFile20(filePath, "utf8") };
7361
+ return { filePath, content: await readFile21(filePath, "utf8") };
6567
7362
  } catch {
6568
7363
  return null;
6569
7364
  }
@@ -6578,7 +7373,7 @@ function isPlanPath(filePath) {
6578
7373
  var validateCommand = defineCommand18({
6579
7374
  meta: {
6580
7375
  name: "validate",
6581
- description: "Advisory validators for HANDOFF / plan frontmatter (afterFileEdit adapter)"
7376
+ description: "Advisory validators for HANDOFF / plan frontmatter (hook adapter)."
6582
7377
  },
6583
7378
  subCommands: {
6584
7379
  handoff: defineCommand18({
@@ -6591,10 +7386,10 @@ var validateCommand = defineCommand18({
6591
7386
  async run({ args }) {
6592
7387
  const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
6593
7388
  const fileArg = typeof args.file === "string" ? args.file : void 0;
6594
- const filePath = fileArg ? path40.resolve(cwd, fileArg) : path40.join(path40.resolve(cwd), ".cursor", "HANDOFF.md");
7389
+ const filePath = fileArg ? path42.resolve(cwd, fileArg) : path42.join(path42.resolve(cwd), ".cursor", "HANDOFF.md");
6595
7390
  let content = "";
6596
7391
  try {
6597
- content = await readFile20(filePath, "utf8");
7392
+ content = await readFile21(filePath, "utf8");
6598
7393
  } catch {
6599
7394
  console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
6600
7395
  return;
@@ -6618,10 +7413,10 @@ var validateCommand = defineCommand18({
6618
7413
  process.exitCode = 2;
6619
7414
  return;
6620
7415
  }
6621
- const filePath = path40.resolve(cwd, fileArg);
7416
+ const filePath = path42.resolve(cwd, fileArg);
6622
7417
  let content = "";
6623
7418
  try {
6624
- content = await readFile20(filePath, "utf8");
7419
+ content = await readFile21(filePath, "utf8");
6625
7420
  } catch {
6626
7421
  console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
6627
7422
  return;
@@ -6640,7 +7435,7 @@ var validateCommand = defineCommand18({
6640
7435
  },
6641
7436
  async run({ args }) {
6642
7437
  const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
6643
- const resolved = await resolveEditedPath(path40.resolve(cwd));
7438
+ const resolved = await resolveEditedPath(path42.resolve(cwd));
6644
7439
  if (!resolved) {
6645
7440
  console.log(JSON.stringify({}));
6646
7441
  return;
@@ -6682,11 +7477,204 @@ var validateCommand = defineCommand18({
6682
7477
  }
6683
7478
  });
6684
7479
 
7480
+ // src/welcome/help-groups.ts
7481
+ import { bold, gray as gray3, underline } from "kolorist";
7482
+
7483
+ // src/welcome/screen.ts
7484
+ import {
7485
+ blue as blue2,
7486
+ cyan as cyan3,
7487
+ gray as gray2,
7488
+ options as koloristOptions,
7489
+ lightCyan,
7490
+ trueColor,
7491
+ white as white2
7492
+ } from "kolorist";
7493
+ var HELMET_OUTLINE = "#e2e8f0";
7494
+ var KOLORIST_TRUECOLOR = 3;
7495
+ var HELMET_ASCII = [
7496
+ " ____",
7497
+ " .-' '-.",
7498
+ " / .--. \\",
7499
+ " | / \\ |",
7500
+ " | | () | |",
7501
+ " | \\ / |",
7502
+ " \\ '--' /",
7503
+ " '-.____.-'",
7504
+ " /_/ \\_\\"
7505
+ ];
7506
+ function hexToRgb(hex) {
7507
+ const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
7508
+ const hexBody = m?.[1];
7509
+ if (!hexBody) throw new Error(`invalid hex color: ${hex}`);
7510
+ const n = Number.parseInt(hexBody, 16);
7511
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
7512
+ }
7513
+ function outlineAnsi(line) {
7514
+ const [r, g, b] = hexToRgb(HELMET_OUTLINE);
7515
+ return trueColor(r, g, b)(line);
7516
+ }
7517
+ function withKoloristColor(fn) {
7518
+ const prevEnabled = koloristOptions.enabled;
7519
+ const prevLevel = koloristOptions.supportLevel;
7520
+ koloristOptions.enabled = true;
7521
+ koloristOptions.supportLevel = KOLORIST_TRUECOLOR;
7522
+ try {
7523
+ return fn();
7524
+ } finally {
7525
+ koloristOptions.enabled = prevEnabled;
7526
+ koloristOptions.supportLevel = prevLevel;
7527
+ }
7528
+ }
7529
+ function hasCliSubcommand(rawArgs) {
7530
+ return Boolean(rawArgs?.some((arg) => !arg.startsWith("-")));
7531
+ }
7532
+ function shouldUseWelcomeColor(opts = {}) {
7533
+ if (opts.color === false) return false;
7534
+ if (process.env.NO_COLOR) return false;
7535
+ if (process.env.NODE_DISABLE_COLORS) return false;
7536
+ if (process.env.FORCE_COLOR === "0") return false;
7537
+ if (process.env.CI != null && process.env.CI !== "") return false;
7538
+ if (opts.color === true) return true;
7539
+ const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
7540
+ return tty;
7541
+ }
7542
+ function renderHelmetAscii(color) {
7543
+ if (!color) return HELMET_ASCII.join("\n");
7544
+ return withKoloristColor(
7545
+ () => HELMET_ASCII.map((line, i) => {
7546
+ if (i <= 1 || i >= HELMET_ASCII.length - 1) return outlineAnsi(line);
7547
+ if (i === 4) return lightCyan(line);
7548
+ return blue2(line);
7549
+ }).join("\n")
7550
+ );
7551
+ }
7552
+ var WELCOME_UTILITY_HINTS = [
7553
+ { cmd: "agent-kit --help", hint: "grouped commands" },
7554
+ { cmd: "agent-kit doctor", hint: "repository readiness" },
7555
+ { cmd: "agent-kit status", hint: "installed kit version" },
7556
+ { cmd: "agent-kit dashboard", hint: "Mission Control panel" },
7557
+ { cmd: "agent-kit init", hint: "guided install entry" }
7558
+ ];
7559
+ function renderWelcomeScreen(opts = {}) {
7560
+ const version = opts.version ?? KIT_VERSION;
7561
+ const color = shouldUseWelcomeColor(opts);
7562
+ const title = color ? white2("Mission Kit") : "Mission Kit";
7563
+ const product = color ? cyan3("agent-kit") : "agent-kit";
7564
+ const muted = (s) => color ? gray2(s) : s;
7565
+ const lines = [
7566
+ renderHelmetAscii(color),
7567
+ "",
7568
+ `${title} \xB7 ${product} v${version}`,
7569
+ muted("HITL framework for AI-assisted IDEs \xB7 @dadado/agent-kit-cli"),
7570
+ "",
7571
+ muted("Try:"),
7572
+ ...(() => {
7573
+ const width = Math.max(...WELCOME_UTILITY_HINTS.map(({ cmd }) => cmd.length));
7574
+ return WELCOME_UTILITY_HINTS.map(({ cmd, hint }) => {
7575
+ const pad = " ".repeat(width - cmd.length + 2);
7576
+ const left = color ? cyan3(` ${cmd}`) : ` ${cmd}`;
7577
+ return `${left}${pad}${muted(hint)}`;
7578
+ });
7579
+ })(),
7580
+ "",
7581
+ muted(
7582
+ "Chat HITL (start-project, git-staging/prod, run-plan-all) stays in Cursor slash commands."
7583
+ )
7584
+ ];
7585
+ return `${lines.join("\n")}
7586
+ `;
7587
+ }
7588
+ function printWelcomeScreen(opts = {}) {
7589
+ process.stdout.write(renderWelcomeScreen(opts));
7590
+ }
7591
+
7592
+ // src/welcome/help-groups.ts
7593
+ var CLI_HELP_GROUPS = [
7594
+ {
7595
+ id: "setup",
7596
+ title: "SETUP",
7597
+ commands: ["init", "install", "doctor", "status", "update", "add", "scan"]
7598
+ },
7599
+ {
7600
+ id: "mission",
7601
+ title: "MISSION",
7602
+ commands: ["handoff", "run-plan"]
7603
+ },
7604
+ {
7605
+ id: "dashboard",
7606
+ title: "DASHBOARD",
7607
+ commands: ["dashboard", "dashboard-broadcast", "monitors"]
7608
+ },
7609
+ {
7610
+ id: "integrity",
7611
+ title: "INTEGRITY",
7612
+ commands: ["validate", "guard", "hook", "cursor-awareness", "diff", "contribute"]
7613
+ }
7614
+ ];
7615
+ async function resolveCommandMeta(meta) {
7616
+ if (meta == null) return void 0;
7617
+ const value = typeof meta === "function" ? await meta() : meta;
7618
+ return await Promise.resolve(value);
7619
+ }
7620
+ async function resolveSubMeta(subCommands) {
7621
+ const out = /* @__PURE__ */ new Map();
7622
+ for (const [name, raw] of Object.entries(subCommands)) {
7623
+ const resolved = typeof raw === "function" ? await raw() : raw;
7624
+ const def = resolved;
7625
+ const meta = await resolveCommandMeta(def?.meta);
7626
+ out.set(name, meta?.description ?? "");
7627
+ }
7628
+ return out;
7629
+ }
7630
+ async function renderGroupedRootHelp(cmd) {
7631
+ const color = shouldUseWelcomeColor();
7632
+ const u = (s) => color ? underline(bold(s)) : s;
7633
+ const g = (s) => color ? gray3(s) : s;
7634
+ const meta = await resolveCommandMeta(cmd.meta);
7635
+ const name = meta?.name ?? "agent-kit";
7636
+ const version = meta?.version ?? KIT_VERSION;
7637
+ const description = meta?.description ?? "HITL framework for AI-assisted IDEs (Mission Kit family)";
7638
+ const subCommands = await (typeof cmd.subCommands === "function" ? cmd.subCommands() : cmd.subCommands);
7639
+ const descriptions = subCommands ? await resolveSubMeta(subCommands) : /* @__PURE__ */ new Map();
7640
+ const placed = new Set(CLI_HELP_GROUPS.flatMap((grp) => grp.commands));
7641
+ const leftover = [...descriptions.keys()].filter((c) => !placed.has(c)).sort();
7642
+ const groups = [...CLI_HELP_GROUPS];
7643
+ if (leftover.length > 0) {
7644
+ groups.push({ id: "other", title: "OTHER", commands: leftover });
7645
+ }
7646
+ const lines = [
7647
+ g(`${description} (${name} v${version})`),
7648
+ "",
7649
+ `${u("USAGE")} \`${name} <command> [OPTIONS]\``,
7650
+ ""
7651
+ ];
7652
+ for (const group of groups) {
7653
+ const rows = group.commands.filter((c) => descriptions.has(c));
7654
+ if (rows.length === 0) continue;
7655
+ lines.push(u(group.title), "");
7656
+ const width = Math.max(...rows.map((c) => c.length));
7657
+ for (const c of rows) {
7658
+ const pad = " ".repeat(width - c.length + 2);
7659
+ lines.push(` ${c}${pad}${descriptions.get(c) ?? ""}`);
7660
+ }
7661
+ lines.push("");
7662
+ }
7663
+ lines.push(
7664
+ g(`Use \`${name} <command> --help\` for more information about a command.`),
7665
+ g(
7666
+ "Chat-only HITL (start-project, backlog, git-staging/prod, run-plan-all) is not a CLI surface."
7667
+ ),
7668
+ ""
7669
+ );
7670
+ return lines.join("\n");
7671
+ }
7672
+
6685
7673
  // src/index.ts
6686
7674
  var main = defineCommand19({
6687
7675
  meta: {
6688
7676
  name: "agent-kit",
6689
- description: "HITL framework for AI-assisted IDEs",
7677
+ description: "HITL framework for AI-assisted IDEs (Mission Kit family)",
6690
7678
  version: KIT_VERSION
6691
7679
  },
6692
7680
  subCommands: {
@@ -6708,6 +7696,19 @@ var main = defineCommand19({
6708
7696
  guard: guardCommand,
6709
7697
  monitors: monitorsCommand,
6710
7698
  validate: validateCommand
7699
+ },
7700
+ async run({ rawArgs }) {
7701
+ if (hasCliSubcommand(rawArgs)) return;
7702
+ printWelcomeScreen();
7703
+ }
7704
+ });
7705
+ runMain(main, {
7706
+ showUsage: async (cmd, parent) => {
7707
+ if (!parent) {
7708
+ process.stdout.write(`${await renderGroupedRootHelp(cmd)}
7709
+ `);
7710
+ return;
7711
+ }
7712
+ await showUsage(cmd, parent);
6711
7713
  }
6712
7714
  });
6713
- runMain(main);