@dadado/agent-kit-cli 4.8.9 → 5.1.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/LICENSE +75 -21
- package/README.md +19 -0
- package/dashboard/dashboard-data.mjs +103 -0
- package/dashboard/dashboard.html +539 -172
- package/dashboard/lib/broadcast-share.mjs +251 -0
- package/dashboard/lib/guards.d.mts +10 -0
- package/dashboard/lib/guards.mjs +71 -4
- package/dashboard/lib/open-browser.d.mts +31 -0
- package/dashboard/lib/open-browser.mjs +298 -0
- package/dashboard/lib/semantic-model.mjs +215 -9
- package/dashboard/open.html +213 -0
- package/dashboard/serve.mjs +6 -2
- package/dashboard/start-broadcast.mjs +75 -31
- package/dashboard/start.mjs +23 -26
- package/dist/index.js +1837 -447
- package/package.json +4 -2
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,132 @@ 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
|
+
"09850afc201eccb3e700cb934400d49c9cbe7510d4a546ee84ee519771a8200e",
|
|
76
|
+
"0b5fe4729c3c68654434837630d775b847c082418f25b0b2c581700fe84b797a",
|
|
77
|
+
"1106b279558b6a20f87951102f172a680976f3e6530855d5dad083e1f89b9ba3",
|
|
78
|
+
"12bed56b332406477cf546d9e8360d3cab129bd82a638bea356726d00a6847a3",
|
|
79
|
+
"13f6a0711a2fcadcc7f1b751772448517ce4871cdef6a0269fc26dad51ebe26d",
|
|
73
80
|
"15566765ec95dad3bc3160f20ac910e01a13471df536e6d990f4b263700784a9",
|
|
74
81
|
"1681e6cf7b80a67cd999b94b8b6e16eb8caed0e11b32905848ba733da956e902",
|
|
82
|
+
"17818e47e06349ee35c1d8e1cc16e8bcbbbcc89c664014e9096e1f08c8567807",
|
|
75
83
|
"1974a5af0e4ba2bb0fc7e1872ef9ab70e323323ab216958c31731bcdd82c8241",
|
|
84
|
+
"1b384771cc6228215e31e6a9ab0943b13a5cdc463354a2641f1c0d85b4357e84",
|
|
85
|
+
"1b5f8c4197393dc9110d020f3b619b8502862adeb6522a6b9be4b49dd8794612",
|
|
86
|
+
"1c5c1af87951367ad04bb6fa5b1d6ebaaefbc0ee95989b624cabf406a1869907",
|
|
87
|
+
"1ded5c7b7eefc355fc1d17feb65ec472e35ed96076e47ce95f7bff5c1b84746c",
|
|
76
88
|
"1e58e0d4f2e8459b95a40d1b1e72c5c85a87230cbde945840a84073fe3a20422",
|
|
89
|
+
"2280e3ca96129ab2de8a85a301df7c2b15ffaa6204b734ed3b1f303e0055c41d",
|
|
77
90
|
"285668d08f1b15d96286d27e29cc4a67e126e097fb8a940bdde33ccf25bd2bcd",
|
|
91
|
+
"2acdb148b07f3a4872b896da419af796fd3fefaace7ac79cdee0aab447004a83",
|
|
92
|
+
"2cd7fc018a384b0188737259ecf593ce25b4b75cc38b3e410063ac91745894b9",
|
|
93
|
+
"2f6cedfa61cb62126efd6b845bbf4d8d7913e39223d5f799ac09ef64c0489824",
|
|
94
|
+
"3204a7449f3031916a8d57b37144e5d8ded6c94aa5659493fa5cc2c7321f4eab",
|
|
95
|
+
"33982aea21a074d21a638850475ce1fefa7d043ec035de3855889cc68d60cf35",
|
|
96
|
+
"38675aec695e9d4e7f16e8fbe3facc3280f47fb875bf9ac50bd6837b92b7a7fa",
|
|
97
|
+
"3ee94e2441218025c085856679d17b4a4fe7a6e3d0274423abe20a422a5ab05d",
|
|
78
98
|
"3f3354825ff06ff9f9ae1e29b71784fef4250020c23c1b490940cf06b2be0f28",
|
|
99
|
+
"46a1dd4a07a2190ab9fb09abc05a3962277209f8e0c28033bb41450301474c86",
|
|
100
|
+
"48a0d55e21132a305946b195db7f6b75da78d44a1d32166fb0c69bef4a5ffbe2",
|
|
101
|
+
"4b62a46c74e5fa6a80283f21e8e6711958fbcd577051dc73ff4050643a031475",
|
|
102
|
+
"4ba6e319ac79455a7d2713674f64fabbc9cd401b5d26b1c0edbd19c27a52297e",
|
|
79
103
|
"4bf23acbc9bd4e0f8468e61f4970e182600b72d9025d29e81ddbd83cb7b1710a",
|
|
80
104
|
"4c5167d0ebde685f266dce32c516a7c43876e0520647b93f07f2e2147e6a7b2f",
|
|
81
105
|
"4dc4efcd1b43c643d3889fb349df8d4964d0867c3a67eb67e2ec168fbb70fc75",
|
|
106
|
+
"4f133554d01cedc62280006f4cf6453e25a5cb41ef6dde4a43f9508cb36ef75d",
|
|
107
|
+
"4fe1e2bce8bd55a6decdd62c887a423cf85148b692f313aad4c777d0e50596b3",
|
|
82
108
|
"4ff844cf789cef4b1e809a55ca006316cd964bcdb2557d91c916a08e30ad15f6",
|
|
83
109
|
"533b41599c25698e5746166096a68fd4080efb5e16287b2a1ed5f73295cda446",
|
|
110
|
+
"54dda8addb74bc187ee23c31f8b7d17ad19e745a45169d29ffea094860eb18f2",
|
|
84
111
|
"570c3f91fd31a4cc1e56e997c85feab5c6756f45fc73dc741afe573912111231",
|
|
112
|
+
"5cb36f5dab846b2551afd05eab92c14122853c147d7f02136d1c04e01e3c9def",
|
|
85
113
|
"5d302d207521c53b9fa814ee0eba1376a5b2bfd2983c0373657c9fdf9884541b",
|
|
114
|
+
"5f5c125f7edf280029f214589ce3e088fc74bad717e30178405a3b537673c42f",
|
|
86
115
|
"621bc6cecf7c710a97e93463ca9b8872a1fcab1325c358cc8be65b418eebb339",
|
|
116
|
+
"693c732658e31d6403cf59e85985518f24ab92bf7fcdc3c225e9a79e54597a43",
|
|
87
117
|
"69715048da3963947aa8a39e7ac30ec01632fec862bfcb9559c46764b4833be0",
|
|
118
|
+
"6994595e3a62edb6c518006a6bece6848c7077977c041439cf7c822b89304a14",
|
|
88
119
|
"6a42e76fc038ca69ddad18541b8df161770c58980fefaf9e5e80d87407074bec",
|
|
89
120
|
"6a9af9e8a95dbb279347166c43dcee245ca673f9687841086b49911c54944c26",
|
|
121
|
+
"99fdd85310e658a1c1b0e8f92257493409c28c3d693f24deeaa665861788e1e4",
|
|
122
|
+
// public registry SoT n8n-workflows/SKILL.md (em dash description)
|
|
90
123
|
"6b66ed7747ea2d19cb457f5af656e3c07f9f563f7f32b3951fa4ae5f27f0ac46",
|
|
124
|
+
"6dede0588274f5b44d833bcc696ec4776eb978ed24a20efdcc9c38caf3df1576",
|
|
125
|
+
"71991a10972b8b956cae93c88517e31741c95e351814082df65e44f813590210",
|
|
126
|
+
"75ae3018a70ed838428c0e63e8b6901ac05bf1f1cf1845faa6f73c3940d22e4a",
|
|
127
|
+
"75e591bfde61ca1ca3ca1303cc9bc487197af2f713c68f346a46e94ca9432fd5",
|
|
91
128
|
"76ae85bbc336c6f435d9653e8f254ae49774b4ebaecf7335697115933eb8797c",
|
|
129
|
+
"76c4361e70a73276771e841e2c939253b4124d690a0fd25aff2a3ac636d8f4b0",
|
|
130
|
+
// mission-kit-comms/SKILL.md
|
|
131
|
+
"79878f0f6f59d1a0b65d94011c0cafe7b6f8e03e89f568aa638b8ba7a2c34076",
|
|
92
132
|
"8108b202c829627c3c2df33575016ebe942df42acf4f1cc393900be3b82b402c",
|
|
133
|
+
"819b7bd3ba9e8bf12dcd4f81a76c98e48261b34c397f339b36d36eb364d9c3f4",
|
|
93
134
|
"8619500b0c7024390d43e3aaefa2a2d731728d7c664aaf90e0acdd067f3397ce",
|
|
135
|
+
"875b0a8805280b2a2df43f2c42f9063fe0623a34fa19c3aa5d2ec48211f3ebae",
|
|
94
136
|
"8a11c80b0fbf6c4aabf60eec05b8cca7dccf5bca472787a8afa3028fd8fbedfa",
|
|
95
137
|
"8b25659727063013e8bf6d7925f815b2630c2919b21e6fdac354362bf122bfab",
|
|
96
138
|
"8c7edbacc74b1431fbecb04c9857c0e7a9cb1578fc6ca6367be888dbcec3c98f",
|
|
139
|
+
"8d8d60e929ebf717eed3c1a29ee0de7c71ae917d125eb1b220a77220597281ed",
|
|
140
|
+
"8ffc181bb152c68a3353309bfdc87c42bdb925b98675cdba959c0cdb90b2adea",
|
|
141
|
+
"91ce0c933e3c8ee81927b8224f944ec5ce5445098a2ad154773092e3541dbf69",
|
|
142
|
+
"9309af07a0a99a56143386b3ddd2a35fae29378fba3f6b4c9297a5d1c02a9724",
|
|
97
143
|
"9439da963acbe5dd88a3aa4d6ba4fb297db666ce8f2c9d3917dbc6d6e517f9aa",
|
|
144
|
+
"989e7e1bdfb29dedf850405f28648810d008885196efe7ae38c6e7554c7ce45d",
|
|
98
145
|
"98d96e25f67aa9c234cb0c422f0d701c22a04bfe184fb4eb68295003d429552b",
|
|
99
146
|
"99031145609af46e7e0f315006bf633f47002eccece8eb7af8b5efd762313ade",
|
|
100
147
|
"9a3cea681399b42ef930a0720802378fe1b06ee73370df9ede9d0309c3c60ab2",
|
|
148
|
+
"9ccf52fe53c6fa4d731650ab4928ce50e201fa34f3e174fee70e3d29867fef2a",
|
|
101
149
|
"9d905aca0123f3018c81c579217104f1749d8da802f3837b2407fbad8f2d803f",
|
|
150
|
+
"9e324c52c1a9d1ceee738bccf47da92910aca9f7a5a21cc41738d9573c414830",
|
|
151
|
+
"9eb457654167df562481637b25ecd91f0ff3c9282ba79409089d644d3b09b926",
|
|
152
|
+
"a013a6a476160183d6457ff6c1c51ec228bdca359b11eb15cd9b7fc177a705db",
|
|
102
153
|
"a08d04f8efa121ad498f96873778c22630c9172c07c216839e348cd0bb7c2292",
|
|
154
|
+
"a7cd7058bcb6fbd962c37317fa16893bc86a270d0164af5a0e6f3f026aa408ad",
|
|
155
|
+
"a833a7131cdfc003b45a48cb26c6ce33dc218c1814156047db5211a79192e0a0",
|
|
156
|
+
"ac2f1f7d9e68f9c98fc4fe5ca348b1a90cf484141bbd116db871d63bdcc3c7c3",
|
|
103
157
|
"b5c7a10df90d209be07159b7ff9bfa24e869538a0b6026bb5ce79e1cdd5b2528",
|
|
158
|
+
"bd9fd1efacbd132c5dafc12cd0cf2b651a2b1fab7b2d7a9ba4a3e44128097f48",
|
|
159
|
+
"be964ff33a40280e13ce6071fb3e30ce87c374527efbb74c25c9d4534084a634",
|
|
160
|
+
"c245035b539e41e822fab8c74fcde3b18c45f4b9f26f1c8583fc9e7254249698",
|
|
161
|
+
"c794accc6668d74a739c982c1b63985a8be4be08afe3766214ab1e08d25ea95c",
|
|
104
162
|
"c96d522be440fbb638dbe953cebf1bb31185c431ac2e14446369cfb7d155a951",
|
|
105
163
|
"cf2bd11891b934484171fa705f470de284e3874402e1e487750735dbc7692857",
|
|
164
|
+
"d0e3bf82efbe32c09761689802ffea7838046dbdd4803b88ead5ec3dfc7efb7e",
|
|
106
165
|
"d4659e4bb6fd6022559fc632b17db6a39007c04bc0ded59017a0dacc013632ed",
|
|
107
166
|
"d484243167e13c34f9df7ff378baf2967f7cbfcd88170d4d32e745efbcae7480",
|
|
167
|
+
"d67ac8fa1edec6e50c82aeca064335e799e408d0e4bbc95e1407cf7463a61de3",
|
|
168
|
+
"d7ad53f06a41bcb2f2b998fd4a4c99209a0cf287cf0d0d4acbcc36c73dd46e4a",
|
|
108
169
|
"d96aa15dde45147aa40db64684688a4b5c9ad660dcd16c55156b31933a42c37e",
|
|
109
170
|
"d9ecd90f80ebc25db73ebed27c32596b9cfce518b3026a3a4d5787321ba6324a",
|
|
110
171
|
"dd01d051dbab257e16609b02c983bad6a576ed8473781b4f1df9ef2eb5f85795",
|
|
172
|
+
"dd9ff2ff8aa7bcb2cdfc59092ad73227631c1c3619b495131f7faee65224cb17",
|
|
111
173
|
"de6efabde6a1d69357926b88b5a4ed3eec19e11b82597cb999f5365f952305b8",
|
|
174
|
+
"debc0758b3e39294df85ccb7eab3455e93a2b8c608602d309a7b0ebdb6618805",
|
|
112
175
|
"e03b5764b908f9ed5c592108fbc11d5962d9256fd129a63ab4a39c8efaa2ef3e",
|
|
113
176
|
"e102e77f9f520f938d26861978249e4d85a8c8755d48c8e79b21fef9423325ee",
|
|
177
|
+
"e2d89f780c6ae5393dc05818f9e3e9a8bf6629eac05702eea8f9a25b578e3ff1",
|
|
178
|
+
"e2f1e9108fd450817ab42570db656b1e43b20b5ba703b14027ce540e1b8fe502",
|
|
179
|
+
"e37c62695b5c70451ef1a0c90b31b73a9c442ebc55fb3fc3f79c294b993b99ea",
|
|
180
|
+
"e3c32aec17e8f728cbd13162a123a9debffb8bbcec527a00b1c2df30a1a20080",
|
|
114
181
|
"e511e04064ffb8c429b659d2ae2e0f223e8cc51039ae816753095a5f7a160595",
|
|
182
|
+
"e5682d4696e874d09f12a1d65891fee5971201a29c654c08f5d3b90fb9a6a0b7",
|
|
183
|
+
"e57437bf790fda1d43b895e2b5b0a7d2d57635c319ac0e2439ba2222a825bcde",
|
|
184
|
+
"e8bb214c3e551c44f379db13c5962ef20698b505b0227d283d3ae632e1bc3626",
|
|
185
|
+
"ea36e74c9d592980cba5c08d04ee70fc59a6cd01e87a50bbfae4f668557df4b4",
|
|
115
186
|
"ebd41f8df764a14b3460016fafcfdde98c4c3a59a3c17f773451ef061fc42f95",
|
|
187
|
+
"ef181a4e2bd9f82f45628637b8d66925ffa20801558808f1b03ea72b7cb82a72",
|
|
116
188
|
"f061432a2606049717c0cbf098ef07b1da4df2b136c4cb9cdaa7ea44c876bcd6",
|
|
117
189
|
"f061d36a1180293519fbb560bbf659c621bd90885e995fca6eaf2eda9e418a50",
|
|
118
|
-
"f28731a82f4b394a5765fdefe2d1b667810831f6dcea400ca62bc66e1d65371c"
|
|
190
|
+
"f28731a82f4b394a5765fdefe2d1b667810831f6dcea400ca62bc66e1d65371c",
|
|
191
|
+
"f43a5484e39aca4e6af86e7f8d2941773cdc1c960838ad88ae3540c49a1097df",
|
|
192
|
+
"f46c108457e4fe12953dca75b409e06fca9cb264baffabe0c0d04c89af471e56",
|
|
193
|
+
"f6bc17efa746680a44bbc49be67ac3f924d9c306c6d937f9959c57a1d3c5b9af",
|
|
194
|
+
"f981764422d468567b5aff31148dc659aeee22cb13dc0ecd873d737deaf07372",
|
|
195
|
+
"fa306a0cdb0f40c817e32164cd03b946564a02b4b7f7c3f4f0b9513096584d28",
|
|
196
|
+
"fa5cf460eb314437081f7cea30dc8041c0bd6fc3f560a74bc2d7be1bf07384b0",
|
|
197
|
+
"fc39ec6d8a22498697f968ffc0fe5f717bed97afd68f922c76a80ed1f10d6579"
|
|
119
198
|
]);
|
|
120
199
|
|
|
121
200
|
// src/lifecycle/paths.ts
|
|
@@ -475,11 +554,119 @@ function logApplyStats(stats) {
|
|
|
475
554
|
|
|
476
555
|
// src/registry/resolve.ts
|
|
477
556
|
import { execFile } from "child_process";
|
|
478
|
-
import { createHash as createHash2 } from "crypto";
|
|
557
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
558
|
+
import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
|
|
479
559
|
import { homedir } from "os";
|
|
480
560
|
import path5 from "path";
|
|
481
561
|
import { promisify } from "util";
|
|
482
562
|
var execFileAsync = promisify(execFile);
|
|
563
|
+
var LOCK_TIMEOUT_MS = 6e4;
|
|
564
|
+
var LOCK_RETRY_MS = 200;
|
|
565
|
+
var LOCK_STALE_MS = 5 * 6e4;
|
|
566
|
+
var LOCK_REFRESH_MS = 1e4;
|
|
567
|
+
function lockOwnerPath(lockDir) {
|
|
568
|
+
return path5.join(lockDir, "owner.json");
|
|
569
|
+
}
|
|
570
|
+
function isProcessAlive(pid) {
|
|
571
|
+
try {
|
|
572
|
+
process.kill(pid, 0);
|
|
573
|
+
return true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function readLockOwner(lockDir) {
|
|
579
|
+
try {
|
|
580
|
+
const raw = await readFile4(lockOwnerPath(lockDir), "utf8");
|
|
581
|
+
const parsed = JSON.parse(raw);
|
|
582
|
+
if (typeof parsed.pid === "number" && typeof parsed.uuid === "string") {
|
|
583
|
+
return parsed;
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
async function writeLockOwner(lockDir, owner) {
|
|
590
|
+
const finalPath = lockOwnerPath(lockDir);
|
|
591
|
+
const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.tmp`);
|
|
592
|
+
await writeFile3(tmpPath, JSON.stringify(owner), "utf8");
|
|
593
|
+
await rename(tmpPath, finalPath);
|
|
594
|
+
}
|
|
595
|
+
async function refreshLockOwner(lockDir, uuid) {
|
|
596
|
+
const owner = await readLockOwner(lockDir);
|
|
597
|
+
if (!owner || owner.uuid !== uuid) return;
|
|
598
|
+
await writeLockOwner(lockDir, { ...owner, updatedAt: Date.now() });
|
|
599
|
+
}
|
|
600
|
+
async function releaseCacheLock(lockDir, uuid) {
|
|
601
|
+
const owner = await readLockOwner(lockDir);
|
|
602
|
+
if (!owner || owner.uuid !== uuid) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function tryReclaimStaleLock(lockDir) {
|
|
611
|
+
let owner;
|
|
612
|
+
let lockStat;
|
|
613
|
+
try {
|
|
614
|
+
lockStat = await stat(lockDir);
|
|
615
|
+
owner = await readLockOwner(lockDir);
|
|
616
|
+
} catch {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
const now2 = Date.now();
|
|
620
|
+
if (owner) {
|
|
621
|
+
const ownerFresh = now2 - owner.updatedAt < LOCK_STALE_MS;
|
|
622
|
+
if (isProcessAlive(owner.pid) && ownerFresh) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (now2 - lockStat.mtimeMs > LOCK_STALE_MS) {
|
|
627
|
+
try {
|
|
628
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
629
|
+
return true;
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
async function acquireCacheLock(cacheDir) {
|
|
636
|
+
const lockDir = `${cacheDir}.lock`;
|
|
637
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
638
|
+
const uuid = randomUUID();
|
|
639
|
+
const owner = { pid: process.pid, uuid, updatedAt: Date.now() };
|
|
640
|
+
await mkdir4(path5.dirname(lockDir), { recursive: true });
|
|
641
|
+
while (Date.now() < deadline) {
|
|
642
|
+
try {
|
|
643
|
+
await mkdir4(lockDir, { recursive: false });
|
|
644
|
+
await writeLockOwner(lockDir, owner);
|
|
645
|
+
const refreshInterval = setInterval(() => {
|
|
646
|
+
refreshLockOwner(lockDir, uuid).catch(() => {
|
|
647
|
+
});
|
|
648
|
+
}, LOCK_REFRESH_MS);
|
|
649
|
+
return async () => {
|
|
650
|
+
clearInterval(refreshInterval);
|
|
651
|
+
await releaseCacheLock(lockDir, uuid);
|
|
652
|
+
};
|
|
653
|
+
} catch (err) {
|
|
654
|
+
const code = err.code;
|
|
655
|
+
if (code === "EEXIST") {
|
|
656
|
+
if (await tryReclaimStaleLock(lockDir)) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS + Math.random() * 100));
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (code === "ENOENT") {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
throw new Error(`Timed out waiting for cache lock on ${cacheDir}. Another install may be stuck.`);
|
|
669
|
+
}
|
|
483
670
|
var DEFAULT_REGISTRY_URL = "https://github.com/agent-kit-startup/agent-kit";
|
|
484
671
|
var DEFAULT_REGISTRY_REF = "main";
|
|
485
672
|
var SAFE_URL = /^https:\/\/[^\s]+$/;
|
|
@@ -504,15 +691,32 @@ async function hasRegistryIndex(root) {
|
|
|
504
691
|
return fileExists(path5.join(root, "registry", "registry.json"));
|
|
505
692
|
}
|
|
506
693
|
async function cloneRegistry(url, ref, dest) {
|
|
507
|
-
const { mkdir:
|
|
508
|
-
await
|
|
694
|
+
const { mkdir: mkdir7 } = await import("fs/promises");
|
|
695
|
+
await mkdir7(path5.dirname(dest), { recursive: true });
|
|
509
696
|
try {
|
|
510
697
|
await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
|
|
511
698
|
env: gitEnv()
|
|
512
699
|
});
|
|
513
|
-
} catch {
|
|
514
|
-
|
|
515
|
-
|
|
700
|
+
} catch (firstErr) {
|
|
701
|
+
try {
|
|
702
|
+
await execFileAsync("git", ["clone", "--depth", "1", "--", url, dest], { env: gitEnv() });
|
|
703
|
+
await execFileAsync("git", ["checkout", ref, "--"], { cwd: dest, env: gitEnv() });
|
|
704
|
+
} catch (secondErr) {
|
|
705
|
+
const msg = secondErr instanceof Error ? secondErr.message : String(secondErr);
|
|
706
|
+
if (/403|Authentication|could not read Username/i.test(msg)) {
|
|
707
|
+
throw new Error(
|
|
708
|
+
`Registry clone failed (auth/access): ${url}@${ref}. Check git credentials or use --registry <local-path> for a local checkout.`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
if (/Could not resolve host|Network is unreachable|Connection refused/i.test(msg)) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`Registry clone failed (network): ${url}@${ref}. Check network/proxy settings or use --registry <local-path>.`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
throw new Error(
|
|
717
|
+
`Registry clone failed: ${url}@${ref}. ${msg}. Use --registry <local-path> if you have a local checkout.`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
516
720
|
}
|
|
517
721
|
}
|
|
518
722
|
async function refreshCache(cacheDir) {
|
|
@@ -556,15 +760,24 @@ async function resolveRegistryRoot(options) {
|
|
|
556
760
|
const ref = options.registryRef ?? DEFAULT_REGISTRY_REF;
|
|
557
761
|
assertSafeRegistrySource(url, ref);
|
|
558
762
|
const dest = path5.join(homedir(), ".cache", "agent-kit", "registry", cacheKey(url, ref));
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
763
|
+
const unlock = await acquireCacheLock(dest);
|
|
764
|
+
let ownershipTransferred = false;
|
|
765
|
+
try {
|
|
766
|
+
if (await hasRegistryIndex(dest)) {
|
|
767
|
+
await refreshCache(dest);
|
|
768
|
+
} else {
|
|
769
|
+
await cloneRegistry(url, ref, dest);
|
|
770
|
+
if (!await hasRegistryIndex(dest)) {
|
|
771
|
+
throw new Error(`Cloned ${url}@${ref} but registry/registry.json is missing`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
ownershipTransferred = true;
|
|
775
|
+
return { root: dest, source: "remote-cache", url, ref, unlock };
|
|
776
|
+
} finally {
|
|
777
|
+
if (!ownershipTransferred) {
|
|
778
|
+
await unlock();
|
|
779
|
+
}
|
|
566
780
|
}
|
|
567
|
-
return { root: dest, source: "remote-cache", url, ref };
|
|
568
781
|
}
|
|
569
782
|
|
|
570
783
|
// src/lifecycle/resolve-cli.ts
|
|
@@ -826,7 +1039,7 @@ async function findPack(rootDir, packId) {
|
|
|
826
1039
|
}
|
|
827
1040
|
|
|
828
1041
|
// src/registry/install.ts
|
|
829
|
-
import { readFile as
|
|
1042
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
830
1043
|
import path8 from "path";
|
|
831
1044
|
function targetForMember(member) {
|
|
832
1045
|
switch (member.kind) {
|
|
@@ -945,7 +1158,7 @@ function packMemberTargets(member) {
|
|
|
945
1158
|
var addCommand = defineCommand({
|
|
946
1159
|
meta: {
|
|
947
1160
|
name: "add",
|
|
948
|
-
description: "Install a skill or L1 pack from the registry
|
|
1161
|
+
description: "Install a skill or L1 pack from the registry."
|
|
949
1162
|
},
|
|
950
1163
|
args: {
|
|
951
1164
|
id: {
|
|
@@ -982,64 +1195,68 @@ var addCommand = defineCommand({
|
|
|
982
1195
|
refresh: args.refresh,
|
|
983
1196
|
manifest: existing
|
|
984
1197
|
});
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1198
|
+
try {
|
|
1199
|
+
const protectedGlobs = resolveProtectedGlobs(existing);
|
|
1200
|
+
const index = await loadRegistry(registry.root);
|
|
1201
|
+
const asSkill = allSkills(index).find((s) => s.id === args.id);
|
|
1202
|
+
const asPack = allPacks(index).find((p) => p.id === args.id);
|
|
1203
|
+
const base = existing ? { ...existing } : buildManifest({
|
|
1204
|
+
version: KIT_VERSION,
|
|
1205
|
+
profile: "default",
|
|
1206
|
+
registryUrl: registry.url,
|
|
1207
|
+
registryRef: registry.ref
|
|
1208
|
+
});
|
|
1209
|
+
const finish = async (kind, id) => {
|
|
1210
|
+
if (kind === "skill") base.skills = upsertIdList(base.skills, id);
|
|
1211
|
+
else base.packs = upsertIdList(base.packs, id);
|
|
1212
|
+
if (registry.url || registry.ref) {
|
|
1213
|
+
base.registry = {
|
|
1214
|
+
url: registry.url ?? base.registry?.url,
|
|
1215
|
+
ref: registry.ref ?? base.registry?.ref
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
base.version = KIT_VERSION;
|
|
1219
|
+
await saveManifest(args.cwd, base);
|
|
1220
|
+
};
|
|
1221
|
+
if (args.skill) {
|
|
1222
|
+
const skill = await findSkill(registry.root, args.id);
|
|
1223
|
+
const stats = await installSkill(registry.root, args.cwd, skill, { protectedGlobs });
|
|
1224
|
+
logApplyStats(stats);
|
|
1225
|
+
await finish("skill", skill.id);
|
|
1226
|
+
logger.success(`Skill '${skill.id}' added.`);
|
|
1227
|
+
return;
|
|
1003
1228
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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;
|
|
1229
|
+
if (args.pack) {
|
|
1230
|
+
const pack = await findPack(registry.root, args.id);
|
|
1231
|
+
const stats = await installPack(registry.root, args.cwd, pack.id, { protectedGlobs });
|
|
1232
|
+
logApplyStats(stats);
|
|
1233
|
+
await finish("pack", pack.id);
|
|
1234
|
+
logger.success(`Pack '${pack.id}' added.`);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (asSkill && asPack) {
|
|
1238
|
+
throw new Error(
|
|
1239
|
+
`'${args.id}' is both a skill and a pack. Disambiguate: agent-kit add --skill ${args.id} | agent-kit add --pack ${args.id}`
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
if (asSkill) {
|
|
1243
|
+
const stats = await installSkill(registry.root, args.cwd, asSkill, { protectedGlobs });
|
|
1244
|
+
logApplyStats(stats);
|
|
1245
|
+
await finish("skill", asSkill.id);
|
|
1246
|
+
logger.success(`Skill '${asSkill.id}' added.`);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (asPack) {
|
|
1250
|
+
const stats = await installPack(registry.root, args.cwd, asPack.id, { protectedGlobs });
|
|
1251
|
+
logApplyStats(stats);
|
|
1252
|
+
await finish("pack", asPack.id);
|
|
1253
|
+
logger.success(`Pack '${asPack.id}' added.`);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
throw new Error(`'${args.id}' not found as skill or pack in registry.`);
|
|
1257
|
+
} finally {
|
|
1258
|
+
await registry.unlock?.();
|
|
1041
1259
|
}
|
|
1042
|
-
throw new Error(`'${args.id}' not found as skill or pack in registry.`);
|
|
1043
1260
|
}
|
|
1044
1261
|
});
|
|
1045
1262
|
|
|
@@ -1047,7 +1264,7 @@ var addCommand = defineCommand({
|
|
|
1047
1264
|
import { defineCommand as defineCommand2 } from "citty";
|
|
1048
1265
|
|
|
1049
1266
|
// src/lifecycle/contribute.ts
|
|
1050
|
-
import { mkdir as
|
|
1267
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
1051
1268
|
import path9 from "path";
|
|
1052
1269
|
|
|
1053
1270
|
// src/lifecycle/contribute-gate.ts
|
|
@@ -1240,6 +1457,9 @@ var L0_ARTIFACTS = [
|
|
|
1240
1457
|
source: ".cursor/commands/dogfood.md",
|
|
1241
1458
|
target: ".cursor/commands/dogfood.md"
|
|
1242
1459
|
},
|
|
1460
|
+
// Factory-only: `.cursor/commands/public-issue-triage.md` is intentionally
|
|
1461
|
+
// omitted from L0 (and excluded from public-sync). See ADR
|
|
1462
|
+
// 2026-08-05_factory-only-public-issue-triage-command.md.
|
|
1243
1463
|
{
|
|
1244
1464
|
source: ".cursor/commands/cursor-update-awareness.md",
|
|
1245
1465
|
target: ".cursor/commands/cursor-update-awareness.md"
|
|
@@ -1338,7 +1558,7 @@ var L0_ARTIFACTS = [
|
|
|
1338
1558
|
// src/lifecycle/contribute.ts
|
|
1339
1559
|
async function readLocal(projectRoot, rel) {
|
|
1340
1560
|
try {
|
|
1341
|
-
return await
|
|
1561
|
+
return await readFile6(resolveContained(projectRoot, rel), "utf8");
|
|
1342
1562
|
} catch {
|
|
1343
1563
|
return null;
|
|
1344
1564
|
}
|
|
@@ -1414,7 +1634,7 @@ async function planContribute(options) {
|
|
|
1414
1634
|
}
|
|
1415
1635
|
if (kind === "drift") {
|
|
1416
1636
|
try {
|
|
1417
|
-
const upstream = await
|
|
1637
|
+
const upstream = await readFile6(
|
|
1418
1638
|
resolveContained(options.registryRoot, registryPath),
|
|
1419
1639
|
"utf8"
|
|
1420
1640
|
);
|
|
@@ -1480,10 +1700,10 @@ async function writeContributeToRegistry(projectRoot, registryRoot, accepted) {
|
|
|
1480
1700
|
const written = [];
|
|
1481
1701
|
for (const c of accepted) {
|
|
1482
1702
|
if (!c.registryPath) continue;
|
|
1483
|
-
const content = await
|
|
1703
|
+
const content = await readFile6(resolveContained(projectRoot, c.projectPath), "utf8");
|
|
1484
1704
|
const dest = resolveContained(registryRoot, c.registryPath);
|
|
1485
|
-
await
|
|
1486
|
-
await
|
|
1705
|
+
await mkdir5(path9.dirname(dest), { recursive: true });
|
|
1706
|
+
await writeFile4(dest, content, "utf8");
|
|
1487
1707
|
written.push(c.registryPath);
|
|
1488
1708
|
}
|
|
1489
1709
|
return written;
|
|
@@ -1554,57 +1774,61 @@ var contributeCommand = defineCommand2({
|
|
|
1554
1774
|
refresh: args.refresh,
|
|
1555
1775
|
manifest
|
|
1556
1776
|
});
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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";
|
|
1777
|
+
try {
|
|
1778
|
+
if (registry.source !== "flag" && args.write) {
|
|
1779
|
+
logger.warn(
|
|
1780
|
+
"contribute --write needs a local kit checkout. Pass --registry /path/to/agent-kit."
|
|
1781
|
+
);
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
logger.info(`Registry: ${registry.root} (${registry.source})`);
|
|
1785
|
+
const plan = await planContribute({
|
|
1786
|
+
registryRoot: registry.root,
|
|
1787
|
+
projectRoot: args.cwd,
|
|
1788
|
+
manifest,
|
|
1789
|
+
extraPaths: parsePaths(args.path),
|
|
1790
|
+
includeDrift: args.drift
|
|
1791
|
+
});
|
|
1580
1792
|
console.log(
|
|
1581
|
-
|
|
1793
|
+
`Contribute plan: accepted=${plan.accepted.length} rejected=${plan.rejected.length} (drift scan=${args.drift})`
|
|
1582
1794
|
);
|
|
1583
|
-
|
|
1584
|
-
|
|
1795
|
+
if (plan.candidates.length === 0) {
|
|
1796
|
+
logger.success("Nothing to contribute (no drift / no --path).");
|
|
1797
|
+
return;
|
|
1585
1798
|
}
|
|
1799
|
+
for (const c of plan.candidates) {
|
|
1800
|
+
const mark = c.gateOk ? "ok" : "FAIL";
|
|
1801
|
+
console.log(
|
|
1802
|
+
`${mark.padEnd(4)} ${c.kind.padEnd(5)} ${c.projectPath} \u2192 ${c.registryPath || "(unmapped)"}`
|
|
1803
|
+
);
|
|
1804
|
+
for (const issue of c.issues) {
|
|
1805
|
+
console.log(` [${issue.code}] ${issue.message}`);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (plan.accepted.length === 0) {
|
|
1809
|
+
logger.warn("No files passed the contribute gate.");
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
console.log("\n--- Suggested PR body ---\n");
|
|
1813
|
+
console.log(formatPrBody(plan.accepted));
|
|
1814
|
+
if (!args.write) {
|
|
1815
|
+
logger.info(
|
|
1816
|
+
"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)."
|
|
1817
|
+
);
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
const written = await writeContributeToRegistry(args.cwd, registry.root, plan.accepted);
|
|
1821
|
+
logger.success(`Wrote ${written.length} file(s) into ${registry.root}`);
|
|
1822
|
+
for (const p of written) console.log(` + ${p}`);
|
|
1823
|
+
console.log("\nNext (HITL \u2014 review before push):");
|
|
1824
|
+
console.log(` cd ${registry.root}`);
|
|
1825
|
+
console.log(" git checkout -b contribute/<short-topic>");
|
|
1826
|
+
console.log(' git add <files> && git commit -m "feat: contribute <topic> from consumer"');
|
|
1827
|
+
console.log(" git push -u origin HEAD");
|
|
1828
|
+
console.log(' gh pr create --base main --title "feat: contribute <topic>" --body-file -');
|
|
1829
|
+
} finally {
|
|
1830
|
+
await registry.unlock?.();
|
|
1586
1831
|
}
|
|
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
1832
|
}
|
|
1609
1833
|
});
|
|
1610
1834
|
|
|
@@ -1612,13 +1836,30 @@ var contributeCommand = defineCommand2({
|
|
|
1612
1836
|
import { defineCommand as defineCommand3 } from "citty";
|
|
1613
1837
|
|
|
1614
1838
|
// src/lifecycle/cursor-update-awareness.ts
|
|
1615
|
-
import { readFile as
|
|
1839
|
+
import { access as access2, readFile as readFile7 } from "fs/promises";
|
|
1616
1840
|
import path10 from "path";
|
|
1617
1841
|
var DEFAULT_CURSOR_CHANGELOG_URL = "https://cursor.com/changelog";
|
|
1618
1842
|
var CHANGELOG_FETCH_TIMEOUT_MS = 15e3;
|
|
1619
1843
|
var CURSOR_VERSION_MAJOR_MAX = 20;
|
|
1620
1844
|
var INVENTORY_REL = path10.join("docs", "cursor-native-audit.md");
|
|
1621
1845
|
var FEATURES_REL = path10.join("docs", "cursor-3-features.md");
|
|
1846
|
+
async function resolveInventoryRoot(cwd) {
|
|
1847
|
+
let dir = path10.resolve(cwd);
|
|
1848
|
+
for (; ; ) {
|
|
1849
|
+
try {
|
|
1850
|
+
await access2(path10.join(dir, INVENTORY_REL));
|
|
1851
|
+
return dir;
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
const parent = path10.dirname(dir);
|
|
1855
|
+
if (parent === dir) break;
|
|
1856
|
+
dir = parent;
|
|
1857
|
+
}
|
|
1858
|
+
return null;
|
|
1859
|
+
}
|
|
1860
|
+
function missingInventoryMessage() {
|
|
1861
|
+
return `Missing inventory at ${INVENTORY_REL}. Run from the kit/repo root that contains that file, or pass --cwd <path>.`;
|
|
1862
|
+
}
|
|
1622
1863
|
var DEFAULT_PREFS = {
|
|
1623
1864
|
enabled: false,
|
|
1624
1865
|
intervalDays: 7,
|
|
@@ -1786,8 +2027,6 @@ function baseResult(partial) {
|
|
|
1786
2027
|
};
|
|
1787
2028
|
}
|
|
1788
2029
|
async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
1789
|
-
const inventoryPath = path10.join(cwd, INVENTORY_REL);
|
|
1790
|
-
const featuresPath = path10.join(cwd, FEATURES_REL);
|
|
1791
2030
|
const prefs = readCursorUpdateCheckPrefs(await loadContextConfig(cwd));
|
|
1792
2031
|
const changelogUrl = options.changelogUrl ?? prefs.changelogUrl;
|
|
1793
2032
|
if (options.respectPrefs) {
|
|
@@ -1820,9 +2059,26 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
1820
2059
|
});
|
|
1821
2060
|
}
|
|
1822
2061
|
}
|
|
2062
|
+
const inventoryRoot = await resolveInventoryRoot(cwd);
|
|
2063
|
+
if (!inventoryRoot) {
|
|
2064
|
+
return baseResult({
|
|
2065
|
+
status: "error",
|
|
2066
|
+
inventoryPath: INVENTORY_REL,
|
|
2067
|
+
featuresPath: FEATURES_REL,
|
|
2068
|
+
changelogUrl,
|
|
2069
|
+
latestCursorVersion: null,
|
|
2070
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
2071
|
+
inventoryRefreshed: null,
|
|
2072
|
+
openActionIds: [],
|
|
2073
|
+
gaps: [],
|
|
2074
|
+
message: missingInventoryMessage()
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
const inventoryPath = path10.join(inventoryRoot, INVENTORY_REL);
|
|
2078
|
+
const featuresPath = path10.join(inventoryRoot, FEATURES_REL);
|
|
1823
2079
|
let inventoryMd;
|
|
1824
2080
|
try {
|
|
1825
|
-
inventoryMd = await
|
|
2081
|
+
inventoryMd = await readFile7(inventoryPath, "utf8");
|
|
1826
2082
|
} catch {
|
|
1827
2083
|
return baseResult({
|
|
1828
2084
|
status: "error",
|
|
@@ -1834,7 +2090,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
1834
2090
|
inventoryRefreshed: null,
|
|
1835
2091
|
openActionIds: [],
|
|
1836
2092
|
gaps: [],
|
|
1837
|
-
message:
|
|
2093
|
+
message: missingInventoryMessage()
|
|
1838
2094
|
});
|
|
1839
2095
|
}
|
|
1840
2096
|
const inventoryRefreshed = parseInventoryRefreshed(inventoryMd);
|
|
@@ -1865,7 +2121,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
1865
2121
|
}
|
|
1866
2122
|
}
|
|
1867
2123
|
try {
|
|
1868
|
-
const featuresMd = await
|
|
2124
|
+
const featuresMd = await readFile7(featuresPath, "utf8");
|
|
1869
2125
|
if (parseFeatureMapNames(featuresMd).length === 0) {
|
|
1870
2126
|
gaps.push({
|
|
1871
2127
|
id: "features-map-empty",
|
|
@@ -1947,7 +2203,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
1947
2203
|
var cursorAwarenessCommand = defineCommand3({
|
|
1948
2204
|
meta: {
|
|
1949
2205
|
name: "cursor-awareness",
|
|
1950
|
-
description: "Opt-in advisory:
|
|
2206
|
+
description: "Opt-in advisory: Cursor changelog vs native-audit gaps (never apply)."
|
|
1951
2207
|
},
|
|
1952
2208
|
args: {
|
|
1953
2209
|
cwd: {
|
|
@@ -2009,17 +2265,132 @@ var cursorAwarenessCommand = defineCommand3({
|
|
|
2009
2265
|
});
|
|
2010
2266
|
|
|
2011
2267
|
// src/commands/dashboard-broadcast.ts
|
|
2012
|
-
import { spawn as
|
|
2013
|
-
import { access as
|
|
2268
|
+
import { spawn as spawn3 } from "child_process";
|
|
2269
|
+
import { access as access4 } from "fs/promises";
|
|
2014
2270
|
import path12 from "path";
|
|
2015
2271
|
import { defineCommand as defineCommand5 } from "citty";
|
|
2016
2272
|
|
|
2017
2273
|
// src/commands/dashboard.ts
|
|
2018
|
-
import { execFileSync, spawn } from "child_process";
|
|
2019
|
-
import {
|
|
2274
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
2275
|
+
import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
2276
|
+
import { access as access3 } from "fs/promises";
|
|
2020
2277
|
import path11 from "path";
|
|
2021
2278
|
import { fileURLToPath } from "url";
|
|
2022
2279
|
import { defineCommand as defineCommand4 } from "citty";
|
|
2280
|
+
|
|
2281
|
+
// ../../dashboard/lib/guards.mjs
|
|
2282
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
2283
|
+
import { networkInterfaces } from "os";
|
|
2284
|
+
import { resolve } from "path";
|
|
2285
|
+
var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
|
|
2286
|
+
var CONTEXT_CONFIG_REL = ".cursor/context/config.json";
|
|
2287
|
+
var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
|
|
2288
|
+
var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
|
|
2289
|
+
var CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
|
|
2290
|
+
var CONFIG_REVIEW_MODES = Object.freeze(["paste", "autonomous"]);
|
|
2291
|
+
var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
|
|
2292
|
+
function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
2293
|
+
const exists2 = fsHooks.existsSync;
|
|
2294
|
+
const realpath = fsHooks.realpathSync;
|
|
2295
|
+
const mkdir7 = fsHooks.mkdirSync;
|
|
2296
|
+
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2297
|
+
return { ok: false, error: "invalid repo root" };
|
|
2298
|
+
}
|
|
2299
|
+
const abs = resolve(repoRoot, CONTEXT_CONFIG_REL);
|
|
2300
|
+
const contextDir = resolve(repoRoot, ".cursor", "context");
|
|
2301
|
+
const absNorm = abs.replace(/\\/g, "/");
|
|
2302
|
+
if (!absNorm.endsWith("/.cursor/context/config.json")) {
|
|
2303
|
+
return { ok: false, error: "path escape" };
|
|
2304
|
+
}
|
|
2305
|
+
try {
|
|
2306
|
+
if (typeof mkdir7 === "function" && typeof exists2 === "function" && !exists2(contextDir)) {
|
|
2307
|
+
mkdir7(contextDir, { recursive: true });
|
|
2308
|
+
}
|
|
2309
|
+
if (typeof realpath === "function" && typeof exists2 === "function" && exists2(abs)) {
|
|
2310
|
+
const fileReal = String(realpath(abs)).replace(/\\/g, "/");
|
|
2311
|
+
const rootReal = String(realpath(repoRoot)).replace(/\\/g, "/");
|
|
2312
|
+
if (!fileReal.startsWith(`${rootReal}/`)) {
|
|
2313
|
+
return { ok: false, error: "path escape" };
|
|
2314
|
+
}
|
|
2315
|
+
if (!fileReal.endsWith("/.cursor/context/config.json")) {
|
|
2316
|
+
return { ok: false, error: "path escape" };
|
|
2317
|
+
}
|
|
2318
|
+
return { ok: true, path: realpath(abs) };
|
|
2319
|
+
}
|
|
2320
|
+
} catch {
|
|
2321
|
+
return { ok: false, error: "path escape" };
|
|
2322
|
+
}
|
|
2323
|
+
return { ok: true, path: abs };
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// ../../dashboard/lib/open-browser.mjs
|
|
2327
|
+
import { spawn, spawnSync } from "child_process";
|
|
2328
|
+
import { readFileSync } from "fs";
|
|
2329
|
+
import { platform as osPlatform } from "os";
|
|
2330
|
+
var OS_DEFAULT_TOKENS = /* @__PURE__ */ new Set(["", "default", "os", "ask"]);
|
|
2331
|
+
function isSafePreferredBrowser(value) {
|
|
2332
|
+
if (typeof value !== "string") return false;
|
|
2333
|
+
const s = value.trim();
|
|
2334
|
+
if (!s) return false;
|
|
2335
|
+
if (/[/\\]/.test(s)) return false;
|
|
2336
|
+
if (/[\0-\x1f\x7f]/.test(s)) return false;
|
|
2337
|
+
if (/[$`;&|<>(){}[\]!*?#~"'%^=,+]/.test(s)) return false;
|
|
2338
|
+
if (s.includes(":")) return false;
|
|
2339
|
+
if (/^-/.test(s)) return false;
|
|
2340
|
+
return true;
|
|
2341
|
+
}
|
|
2342
|
+
function normalizePreferredBrowser(value) {
|
|
2343
|
+
if (value == null) return null;
|
|
2344
|
+
const s = String(value).trim();
|
|
2345
|
+
if (!s || OS_DEFAULT_TOKENS.has(s.toLowerCase())) return null;
|
|
2346
|
+
if (!isSafePreferredBrowser(s)) return null;
|
|
2347
|
+
return s;
|
|
2348
|
+
}
|
|
2349
|
+
function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
2350
|
+
const read = fsHooks.readFileSync ?? readFileSync;
|
|
2351
|
+
try {
|
|
2352
|
+
const raw = read(configPath, "utf8");
|
|
2353
|
+
const data = JSON.parse(raw);
|
|
2354
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
2355
|
+
const mc = data.missionControl;
|
|
2356
|
+
if (!mc || typeof mc !== "object" || Array.isArray(mc)) return null;
|
|
2357
|
+
return mc.preferredBrowser ?? null;
|
|
2358
|
+
} catch {
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/commands/dashboard.ts
|
|
2364
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile22 = readFileSync2) {
|
|
2365
|
+
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2366
|
+
existsSync,
|
|
2367
|
+
realpathSync
|
|
2368
|
+
});
|
|
2369
|
+
if (!resolved.ok) return null;
|
|
2370
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile22 });
|
|
2371
|
+
return normalizePreferredBrowser(value);
|
|
2372
|
+
}
|
|
2373
|
+
function applyDashboardOpenEnv(env, opts) {
|
|
2374
|
+
const next = { ...env };
|
|
2375
|
+
if (opts.noOpen) next.MISSION_CONTROL_NO_OPEN = "1";
|
|
2376
|
+
const flag = opts.browser?.trim();
|
|
2377
|
+
if (flag) {
|
|
2378
|
+
const safe = normalizePreferredBrowser(flag);
|
|
2379
|
+
if (safe) next.MISSION_CONTROL_PREFERRED_BROWSER = safe;
|
|
2380
|
+
} else if (!next.MISSION_CONTROL_PREFERRED_BROWSER && opts.cwd) {
|
|
2381
|
+
const fromConfig = readPreferredBrowserFromWorkspace(opts.cwd);
|
|
2382
|
+
if (fromConfig) next.MISSION_CONTROL_PREFERRED_BROWSER = fromConfig;
|
|
2383
|
+
}
|
|
2384
|
+
return next;
|
|
2385
|
+
}
|
|
2386
|
+
function dashboardSpawnEnv(env, snapshotRoot, opts) {
|
|
2387
|
+
const root = path11.resolve(snapshotRoot);
|
|
2388
|
+
return applyDashboardOpenEnv({ ...env, MISSION_CONTROL_REPO_ROOT: root }, { ...opts, cwd: root });
|
|
2389
|
+
}
|
|
2390
|
+
function dashboardProcessTitle(snapshotRoot) {
|
|
2391
|
+
const base = path11.basename(path11.resolve(snapshotRoot)) || "workspace";
|
|
2392
|
+
return `Mission Control \xB7 ${base}`;
|
|
2393
|
+
}
|
|
2023
2394
|
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
2024
2395
|
const here = path11.dirname(fileURLToPath(moduleUrl));
|
|
2025
2396
|
return [
|
|
@@ -2034,7 +2405,7 @@ function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
|
2034
2405
|
async function firstExisting(candidates) {
|
|
2035
2406
|
for (const candidate2 of candidates) {
|
|
2036
2407
|
try {
|
|
2037
|
-
await
|
|
2408
|
+
await access3(candidate2);
|
|
2038
2409
|
return path11.resolve(candidate2);
|
|
2039
2410
|
} catch {
|
|
2040
2411
|
}
|
|
@@ -2046,7 +2417,7 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
2046
2417
|
for (; ; ) {
|
|
2047
2418
|
const candidate2 = path11.join(dir, "dashboard", "start.mjs");
|
|
2048
2419
|
try {
|
|
2049
|
-
await
|
|
2420
|
+
await access3(candidate2);
|
|
2050
2421
|
return candidate2;
|
|
2051
2422
|
} catch {
|
|
2052
2423
|
}
|
|
@@ -2059,14 +2430,14 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
2059
2430
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2060
2431
|
const candidate2 = path11.join(path11.resolve(base.trim()), "dashboard", "start.mjs");
|
|
2061
2432
|
try {
|
|
2062
|
-
await
|
|
2433
|
+
await access3(candidate2);
|
|
2063
2434
|
return candidate2;
|
|
2064
2435
|
} catch {
|
|
2065
2436
|
}
|
|
2066
2437
|
}
|
|
2067
2438
|
const sibling = path11.join(path11.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
2068
2439
|
try {
|
|
2069
|
-
await
|
|
2440
|
+
await access3(sibling);
|
|
2070
2441
|
return path11.resolve(sibling);
|
|
2071
2442
|
} catch {
|
|
2072
2443
|
}
|
|
@@ -2076,6 +2447,15 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
2076
2447
|
}
|
|
2077
2448
|
function resolveDashboardSnapshotRoot(cwd) {
|
|
2078
2449
|
const abs = path11.resolve(cwd);
|
|
2450
|
+
let dir = abs;
|
|
2451
|
+
for (; ; ) {
|
|
2452
|
+
if (existsSync(path11.join(dir, ".cursor", "agent-kit.json"))) {
|
|
2453
|
+
return dir;
|
|
2454
|
+
}
|
|
2455
|
+
const parent = path11.dirname(dir);
|
|
2456
|
+
if (parent === dir) break;
|
|
2457
|
+
dir = parent;
|
|
2458
|
+
}
|
|
2079
2459
|
try {
|
|
2080
2460
|
const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
|
|
2081
2461
|
encoding: "utf8",
|
|
@@ -2087,46 +2467,69 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
2087
2467
|
return abs;
|
|
2088
2468
|
}
|
|
2089
2469
|
function runStartScript(startPath, env) {
|
|
2090
|
-
return new Promise((
|
|
2091
|
-
const child =
|
|
2470
|
+
return new Promise((resolve2, reject) => {
|
|
2471
|
+
const child = spawn2(process.execPath, [startPath], {
|
|
2092
2472
|
cwd: path11.dirname(path11.dirname(startPath)),
|
|
2093
2473
|
env,
|
|
2094
2474
|
stdio: "inherit"
|
|
2095
2475
|
});
|
|
2096
2476
|
child.on("error", reject);
|
|
2097
|
-
child.on("close", (code) =>
|
|
2477
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
2098
2478
|
});
|
|
2099
2479
|
}
|
|
2100
2480
|
var dashboardCommand = defineCommand4({
|
|
2101
2481
|
meta: {
|
|
2102
2482
|
name: "dashboard",
|
|
2103
|
-
description: "Start Mission Control for this workspace (
|
|
2483
|
+
description: "Start Mission Control for this workspace (loopback; opens panel URL)."
|
|
2104
2484
|
},
|
|
2105
2485
|
args: {
|
|
2106
2486
|
cwd: {
|
|
2107
2487
|
type: "string",
|
|
2108
2488
|
default: process.cwd(),
|
|
2109
|
-
description: "Workspace to snapshot (git root
|
|
2489
|
+
description: "Workspace to snapshot (nearest .cursor/agent-kit.json, else git root); also searched upward for dashboard/start.mjs"
|
|
2110
2490
|
},
|
|
2111
2491
|
"no-open": {
|
|
2112
2492
|
type: "boolean",
|
|
2113
2493
|
default: false,
|
|
2114
2494
|
description: "Do not open a browser; only ensure the server is up and print the URL"
|
|
2495
|
+
},
|
|
2496
|
+
browser: {
|
|
2497
|
+
type: "string",
|
|
2498
|
+
description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
|
|
2115
2499
|
}
|
|
2116
2500
|
},
|
|
2117
2501
|
async run({ args }) {
|
|
2118
2502
|
const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
|
|
2119
2503
|
const startPath = await findDashboardStart(args.cwd);
|
|
2120
2504
|
if (!startPath) {
|
|
2121
|
-
logger.error(
|
|
2122
|
-
|
|
2505
|
+
logger.error("No dashboard/start.mjs found.");
|
|
2506
|
+
console.error(
|
|
2507
|
+
[
|
|
2508
|
+
"",
|
|
2509
|
+
"The dashboard runtime is not available in this workspace.",
|
|
2510
|
+
"L0 install provides the /dashboard command text but not the panel itself.",
|
|
2511
|
+
"",
|
|
2512
|
+
"Recovery (pick one):",
|
|
2513
|
+
" 1. Upgrade the CLI: npx @dadado/agent-kit-cli@latest dashboard",
|
|
2514
|
+
" (Path C ships dashboard/ from 4.8.2 onward)",
|
|
2515
|
+
" 2. Set an env var pointing to an agent-kit checkout:",
|
|
2516
|
+
" export MISSION_CONTROL_KIT_ROOT=/path/to/agent-kit",
|
|
2517
|
+
" agent-kit dashboard",
|
|
2518
|
+
" 3. Place an agent-kit sibling: ../agent-kit/dashboard/start.mjs",
|
|
2519
|
+
" 4. Run directly from a kit tree: node dashboard/start.mjs",
|
|
2520
|
+
"",
|
|
2521
|
+
"Works in Cursor, VS Code, and any Node.js terminal.",
|
|
2522
|
+
""
|
|
2523
|
+
].join("\n")
|
|
2123
2524
|
);
|
|
2124
2525
|
process.exitCode = 1;
|
|
2125
2526
|
return;
|
|
2126
2527
|
}
|
|
2127
|
-
const env = { ...process.env }
|
|
2128
|
-
|
|
2129
|
-
|
|
2528
|
+
const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
|
|
2529
|
+
noOpen: Boolean(args["no-open"]),
|
|
2530
|
+
browser: args.browser
|
|
2531
|
+
});
|
|
2532
|
+
process.title = dashboardProcessTitle(snapshotRoot);
|
|
2130
2533
|
const code = await runStartScript(startPath, env);
|
|
2131
2534
|
if (code !== 0) process.exitCode = code;
|
|
2132
2535
|
}
|
|
@@ -2138,7 +2541,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2138
2541
|
for (; ; ) {
|
|
2139
2542
|
const candidate2 = path12.join(dir, "dashboard", "start-broadcast.mjs");
|
|
2140
2543
|
try {
|
|
2141
|
-
await
|
|
2544
|
+
await access4(candidate2);
|
|
2142
2545
|
return candidate2;
|
|
2143
2546
|
} catch {
|
|
2144
2547
|
}
|
|
@@ -2151,7 +2554,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2151
2554
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2152
2555
|
const candidate2 = path12.join(path12.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
|
|
2153
2556
|
try {
|
|
2154
|
-
await
|
|
2557
|
+
await access4(candidate2);
|
|
2155
2558
|
return candidate2;
|
|
2156
2559
|
} catch {
|
|
2157
2560
|
}
|
|
@@ -2164,7 +2567,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2164
2567
|
"start-broadcast.mjs"
|
|
2165
2568
|
);
|
|
2166
2569
|
try {
|
|
2167
|
-
await
|
|
2570
|
+
await access4(sibling);
|
|
2168
2571
|
return path12.resolve(sibling);
|
|
2169
2572
|
} catch {
|
|
2170
2573
|
}
|
|
@@ -2173,7 +2576,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2173
2576
|
options.moduleUrl ?? import.meta.url
|
|
2174
2577
|
)) {
|
|
2175
2578
|
try {
|
|
2176
|
-
await
|
|
2579
|
+
await access4(candidate2);
|
|
2177
2580
|
return path12.resolve(candidate2);
|
|
2178
2581
|
} catch {
|
|
2179
2582
|
}
|
|
@@ -2181,14 +2584,14 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2181
2584
|
return null;
|
|
2182
2585
|
}
|
|
2183
2586
|
function runStartScript2(startPath, env) {
|
|
2184
|
-
return new Promise((
|
|
2185
|
-
const child =
|
|
2587
|
+
return new Promise((resolve2, reject) => {
|
|
2588
|
+
const child = spawn3(process.execPath, [startPath], {
|
|
2186
2589
|
cwd: path12.dirname(path12.dirname(startPath)),
|
|
2187
2590
|
env,
|
|
2188
2591
|
stdio: "inherit"
|
|
2189
2592
|
});
|
|
2190
2593
|
child.on("error", reject);
|
|
2191
|
-
child.on("close", (code) =>
|
|
2594
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
2192
2595
|
});
|
|
2193
2596
|
}
|
|
2194
2597
|
var dashboardBroadcastCommand = defineCommand5({
|
|
@@ -2205,7 +2608,11 @@ var dashboardBroadcastCommand = defineCommand5({
|
|
|
2205
2608
|
"no-open": {
|
|
2206
2609
|
type: "boolean",
|
|
2207
2610
|
default: false,
|
|
2208
|
-
description: "Do not open a browser; only ensure the server is up and print
|
|
2611
|
+
description: "Do not open a browser; only ensure the server is up and print Share URL (when masking is on) plus token / LAN lines"
|
|
2612
|
+
},
|
|
2613
|
+
browser: {
|
|
2614
|
+
type: "string",
|
|
2615
|
+
description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
|
|
2209
2616
|
}
|
|
2210
2617
|
},
|
|
2211
2618
|
async run({ args }) {
|
|
@@ -2218,8 +2625,12 @@ var dashboardBroadcastCommand = defineCommand5({
|
|
|
2218
2625
|
process.exitCode = 1;
|
|
2219
2626
|
return;
|
|
2220
2627
|
}
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2628
|
+
const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
|
|
2629
|
+
const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
|
|
2630
|
+
noOpen: Boolean(args["no-open"]),
|
|
2631
|
+
browser: args.browser
|
|
2632
|
+
});
|
|
2633
|
+
process.title = dashboardProcessTitle(snapshotRoot);
|
|
2223
2634
|
const code = await runStartScript2(startPath, env);
|
|
2224
2635
|
if (code !== 0) process.exitCode = code;
|
|
2225
2636
|
}
|
|
@@ -2229,11 +2640,11 @@ var dashboardBroadcastCommand = defineCommand5({
|
|
|
2229
2640
|
import { defineCommand as defineCommand6 } from "citty";
|
|
2230
2641
|
|
|
2231
2642
|
// src/lifecycle/diff.ts
|
|
2232
|
-
import { readFile as
|
|
2643
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2233
2644
|
import path13 from "path";
|
|
2234
2645
|
async function readIfExists(abs) {
|
|
2235
2646
|
try {
|
|
2236
|
-
return await
|
|
2647
|
+
return await readFile8(abs, "utf8");
|
|
2237
2648
|
} catch {
|
|
2238
2649
|
return null;
|
|
2239
2650
|
}
|
|
@@ -2347,19 +2758,23 @@ var diffCommand = defineCommand6({
|
|
|
2347
2758
|
refresh: args.refresh,
|
|
2348
2759
|
manifest
|
|
2349
2760
|
});
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2761
|
+
try {
|
|
2762
|
+
logger.info(`Registry: ${registry.root} (${registry.source})`);
|
|
2763
|
+
const entries = await diffAgainstRegistry(registry.root, args.cwd, manifest);
|
|
2764
|
+
const summary = summarizeDiff(entries);
|
|
2765
|
+
console.log(
|
|
2766
|
+
`Summary: match=${summary.match} drift=${summary.drift} missing-local=${summary["missing-local"]} missing-registry=${summary["missing-registry"]} protected=${summary.protected}`
|
|
2767
|
+
);
|
|
2768
|
+
const rows = args.all ? entries : entries.filter((e) => e.status !== "match");
|
|
2769
|
+
if (rows.length === 0) {
|
|
2770
|
+
logger.success("No drift (matches only).");
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
for (const e of rows) {
|
|
2774
|
+
console.log(`${e.status.padEnd(18)} ${e.path}`);
|
|
2775
|
+
}
|
|
2776
|
+
} finally {
|
|
2777
|
+
await registry.unlock?.();
|
|
2363
2778
|
}
|
|
2364
2779
|
}
|
|
2365
2780
|
});
|
|
@@ -2370,7 +2785,7 @@ import { defineCommand as defineCommand7 } from "citty";
|
|
|
2370
2785
|
|
|
2371
2786
|
// src/invariants/hooks-health.ts
|
|
2372
2787
|
import { execFile as execFile2 } from "child_process";
|
|
2373
|
-
import { constants as constants2, access as
|
|
2788
|
+
import { constants as constants2, access as access5, readFile as readFile9, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
2374
2789
|
import path14 from "path";
|
|
2375
2790
|
import { promisify as promisify2 } from "util";
|
|
2376
2791
|
var execFileAsync2 = promisify2(execFile2);
|
|
@@ -2384,7 +2799,7 @@ var EXPECTED_EVENTS = [
|
|
|
2384
2799
|
];
|
|
2385
2800
|
async function exists(p) {
|
|
2386
2801
|
try {
|
|
2387
|
-
await
|
|
2802
|
+
await access5(p);
|
|
2388
2803
|
return true;
|
|
2389
2804
|
} catch {
|
|
2390
2805
|
return false;
|
|
@@ -2392,7 +2807,7 @@ async function exists(p) {
|
|
|
2392
2807
|
}
|
|
2393
2808
|
async function isExecutable(p) {
|
|
2394
2809
|
try {
|
|
2395
|
-
await
|
|
2810
|
+
await access5(p, constants2.X_OK);
|
|
2396
2811
|
return true;
|
|
2397
2812
|
} catch {
|
|
2398
2813
|
return false;
|
|
@@ -2444,7 +2859,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
2444
2859
|
const gitDir = path14.join(root, ".git");
|
|
2445
2860
|
if (!await exists(gitDir)) return [];
|
|
2446
2861
|
try {
|
|
2447
|
-
const st = await
|
|
2862
|
+
const st = await stat2(gitDir);
|
|
2448
2863
|
if (!st.isDirectory()) return [];
|
|
2449
2864
|
} catch {
|
|
2450
2865
|
return [];
|
|
@@ -2472,7 +2887,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
2472
2887
|
continue;
|
|
2473
2888
|
}
|
|
2474
2889
|
try {
|
|
2475
|
-
const [a, b] = await Promise.all([
|
|
2890
|
+
const [a, b] = await Promise.all([readFile9(canonical, "utf8"), readFile9(installed, "utf8")]);
|
|
2476
2891
|
if (a !== b) {
|
|
2477
2892
|
advisories.push(
|
|
2478
2893
|
`git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
|
|
@@ -2505,7 +2920,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
2505
2920
|
}
|
|
2506
2921
|
let parsed;
|
|
2507
2922
|
try {
|
|
2508
|
-
parsed = JSON.parse(await
|
|
2923
|
+
parsed = JSON.parse(await readFile9(hooksJsonAbs, "utf8"));
|
|
2509
2924
|
} catch {
|
|
2510
2925
|
return {
|
|
2511
2926
|
status: "degraded",
|
|
@@ -2548,7 +2963,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
2548
2963
|
continue;
|
|
2549
2964
|
}
|
|
2550
2965
|
try {
|
|
2551
|
-
const st = await
|
|
2966
|
+
const st = await stat2(abs);
|
|
2552
2967
|
if (!st.isFile()) {
|
|
2553
2968
|
reasons.push(`adapter path is not a file: \`${rel}\``);
|
|
2554
2969
|
continue;
|
|
@@ -2828,11 +3243,11 @@ function createReadinessReport(scan, options) {
|
|
|
2828
3243
|
}
|
|
2829
3244
|
|
|
2830
3245
|
// src/scanner/safe-fixes.ts
|
|
2831
|
-
import { readFile as
|
|
3246
|
+
import { readFile as readFile12, writeFile as writeFile5 } from "fs/promises";
|
|
2832
3247
|
import path22 from "path";
|
|
2833
3248
|
|
|
2834
3249
|
// src/scanner/detect-repository.ts
|
|
2835
|
-
import { readFile as
|
|
3250
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2836
3251
|
import path15 from "path";
|
|
2837
3252
|
var CONTEXT_PATHS = [
|
|
2838
3253
|
["README.md", "README"],
|
|
@@ -2938,7 +3353,7 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
2938
3353
|
async function detectSafety(rootDir, trackedFiles) {
|
|
2939
3354
|
const gitignorePath = path15.join(rootDir, ".gitignore");
|
|
2940
3355
|
const hasGitignore = await fileExists(gitignorePath);
|
|
2941
|
-
const gitignore = hasGitignore ? await
|
|
3356
|
+
const gitignore = hasGitignore ? await readFile10(gitignorePath, "utf8") : "";
|
|
2942
3357
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
2943
3358
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
2944
3359
|
(pattern) => lines.includes(pattern)
|
|
@@ -2956,7 +3371,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2956
3371
|
];
|
|
2957
3372
|
const guardContents = await Promise.all(
|
|
2958
3373
|
guardCandidates.map(
|
|
2959
|
-
async (item) => await fileExists(path15.join(rootDir, item)) ?
|
|
3374
|
+
async (item) => await fileExists(path15.join(rootDir, item)) ? readFile10(path15.join(rootDir, item), "utf8") : ""
|
|
2960
3375
|
)
|
|
2961
3376
|
);
|
|
2962
3377
|
return {
|
|
@@ -3163,6 +3578,33 @@ async function detectIde(rootDir) {
|
|
|
3163
3578
|
import path18 from "path";
|
|
3164
3579
|
|
|
3165
3580
|
// src/types.ts
|
|
3581
|
+
var GIT_PLATFORM_META = {
|
|
3582
|
+
github: {
|
|
3583
|
+
cli: "gh",
|
|
3584
|
+
prTerm: "Pull Request",
|
|
3585
|
+
prCommand: "gh pr create",
|
|
3586
|
+
ciDefault: "github-actions"
|
|
3587
|
+
},
|
|
3588
|
+
gitlab: {
|
|
3589
|
+
cli: "glab",
|
|
3590
|
+
prTerm: "Merge Request",
|
|
3591
|
+
prCommand: "glab mr create",
|
|
3592
|
+
ciDefault: "gitlab-ci"
|
|
3593
|
+
},
|
|
3594
|
+
bitbucket: {
|
|
3595
|
+
cli: "bb",
|
|
3596
|
+
prTerm: "Pull Request",
|
|
3597
|
+
prCommand: "bb pr create",
|
|
3598
|
+
ciDefault: "bitbucket-pipelines"
|
|
3599
|
+
},
|
|
3600
|
+
"azure-devops": {
|
|
3601
|
+
cli: "az repos",
|
|
3602
|
+
prTerm: "Pull Request",
|
|
3603
|
+
prCommand: "az repos pr create",
|
|
3604
|
+
ciDefault: "azure-pipelines"
|
|
3605
|
+
},
|
|
3606
|
+
gitea: { cli: "tea", prTerm: "Pull Request", prCommand: "tea pr create", ciDefault: "none" }
|
|
3607
|
+
};
|
|
3166
3608
|
var CI_PLATFORM_FILES = {
|
|
3167
3609
|
"github-actions": ".github/workflows",
|
|
3168
3610
|
"gitlab-ci": ".gitlab-ci.yml",
|
|
@@ -3231,7 +3673,7 @@ async function detectInfra(rootDir) {
|
|
|
3231
3673
|
}
|
|
3232
3674
|
|
|
3233
3675
|
// src/scanner/detect-services.ts
|
|
3234
|
-
import { readFile as
|
|
3676
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3235
3677
|
import path19 from "path";
|
|
3236
3678
|
async function detectProjectManagement(rootDir) {
|
|
3237
3679
|
const tools = [];
|
|
@@ -3242,7 +3684,7 @@ async function detectProjectManagement(rootDir) {
|
|
|
3242
3684
|
for (const configPath of mcpConfigPaths) {
|
|
3243
3685
|
if (!await fileExists(configPath)) continue;
|
|
3244
3686
|
try {
|
|
3245
|
-
const raw = await
|
|
3687
|
+
const raw = await readFile11(configPath, "utf8");
|
|
3246
3688
|
const lower = raw.toLowerCase();
|
|
3247
3689
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
3248
3690
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -3625,10 +4067,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
3625
4067
|
}
|
|
3626
4068
|
const gitignoreRelativePath = ".gitignore";
|
|
3627
4069
|
const gitignorePath = path22.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
3628
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
4070
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile12(gitignorePath, "utf8") : "";
|
|
3629
4071
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
3630
4072
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
3631
|
-
if (gitignoreChanged && !dryRun) await
|
|
4073
|
+
if (gitignoreChanged && !dryRun) await writeFile5(gitignorePath, mergedGitignore, "utf8");
|
|
3632
4074
|
recordChange(
|
|
3633
4075
|
changes,
|
|
3634
4076
|
"merge-secret-ignores",
|
|
@@ -3699,6 +4141,169 @@ async function writeReadinessSnapshot(rootDir, report) {
|
|
|
3699
4141
|
return snapshotPath;
|
|
3700
4142
|
}
|
|
3701
4143
|
|
|
4144
|
+
// src/welcome/visual-kit.ts
|
|
4145
|
+
import { cyan as cyan2, gray, options as koloristOptions } from "kolorist";
|
|
4146
|
+
var HELMET_OUTLINE = "#e2e8f0";
|
|
4147
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4148
|
+
var SPACE_MARKS = {
|
|
4149
|
+
star: "\u2726",
|
|
4150
|
+
tick: "\xB7",
|
|
4151
|
+
diamond: "\u2727",
|
|
4152
|
+
visor: "( )"
|
|
4153
|
+
};
|
|
4154
|
+
var KIT_TIPS = [
|
|
4155
|
+
"Try agent-kit doctor for repository readiness.",
|
|
4156
|
+
"HITL gates stay in Cursor slash commands.",
|
|
4157
|
+
"/run-plan never promotes to production.",
|
|
4158
|
+
"Mission Control is the dashboard, not the CLI name.",
|
|
4159
|
+
"NO_COLOR and CI keep this output static.",
|
|
4160
|
+
"agent-kit --help groups SETUP, MISSION, DASHBOARD, INTEGRITY."
|
|
4161
|
+
];
|
|
4162
|
+
function shouldUseWelcomeColor(opts = {}) {
|
|
4163
|
+
if (opts.color === false) return false;
|
|
4164
|
+
if (process.env.NO_COLOR) return false;
|
|
4165
|
+
if (process.env.NODE_DISABLE_COLORS) return false;
|
|
4166
|
+
if (process.env.FORCE_COLOR === "0") return false;
|
|
4167
|
+
if (process.env.CI != null && process.env.CI !== "") return false;
|
|
4168
|
+
if (opts.color === true) return true;
|
|
4169
|
+
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
4170
|
+
return tty;
|
|
4171
|
+
}
|
|
4172
|
+
function shouldUseVisualMotion(opts = {}) {
|
|
4173
|
+
if (process.env.AGENT_KIT_REDUCED_MOTION === "1") return false;
|
|
4174
|
+
return shouldUseWelcomeColor(opts);
|
|
4175
|
+
}
|
|
4176
|
+
function spinnerFrame(index) {
|
|
4177
|
+
const n = SPINNER_FRAMES.length;
|
|
4178
|
+
return SPINNER_FRAMES[(index % n + n) % n] ?? SPINNER_FRAMES[0];
|
|
4179
|
+
}
|
|
4180
|
+
function tipAt(index) {
|
|
4181
|
+
const n = KIT_TIPS.length;
|
|
4182
|
+
return KIT_TIPS[(index % n + n) % n] ?? KIT_TIPS[0];
|
|
4183
|
+
}
|
|
4184
|
+
function wrapNarrow(text, columns) {
|
|
4185
|
+
const width = Math.max(8, Number.isFinite(columns) ? Math.floor(columns) : 80);
|
|
4186
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
4187
|
+
if (words.length === 0) return "";
|
|
4188
|
+
const lines = [];
|
|
4189
|
+
let current = "";
|
|
4190
|
+
for (const word of words) {
|
|
4191
|
+
if (word.length > width) {
|
|
4192
|
+
if (current) {
|
|
4193
|
+
lines.push(current);
|
|
4194
|
+
current = "";
|
|
4195
|
+
}
|
|
4196
|
+
for (let i = 0; i < word.length; i += width) {
|
|
4197
|
+
const chunk = word.slice(i, i + width);
|
|
4198
|
+
if (i + width >= word.length) current = chunk;
|
|
4199
|
+
else lines.push(chunk);
|
|
4200
|
+
}
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
const next = current ? `${current} ${word}` : word;
|
|
4204
|
+
if (next.length > width) {
|
|
4205
|
+
lines.push(current);
|
|
4206
|
+
current = word;
|
|
4207
|
+
} else {
|
|
4208
|
+
current = next;
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
if (current) lines.push(current);
|
|
4212
|
+
return lines.join("\n");
|
|
4213
|
+
}
|
|
4214
|
+
function renderProgressLine(opts) {
|
|
4215
|
+
const motion = opts.motion ?? shouldUseVisualMotion();
|
|
4216
|
+
const color = opts.color ?? shouldUseWelcomeColor();
|
|
4217
|
+
const mark = motion ? spinnerFrame(opts.frameIndex) : SPACE_MARKS.tick;
|
|
4218
|
+
const tip = tipAt(opts.tipIndex);
|
|
4219
|
+
const label = opts.label?.trim() ?? "";
|
|
4220
|
+
const raw = label ? `${mark} ${label} ${SPACE_MARKS.star} ${tip}` : `${mark} ${tip}`;
|
|
4221
|
+
const cols = opts.columns ?? process.stdout.columns ?? 80;
|
|
4222
|
+
const wrapped = wrapNarrow(raw, cols);
|
|
4223
|
+
if (!color) return wrapped;
|
|
4224
|
+
const prevEnabled = koloristOptions.enabled;
|
|
4225
|
+
koloristOptions.enabled = true;
|
|
4226
|
+
try {
|
|
4227
|
+
return wrapped.split("\n").map((line, i) => i === 0 ? cyan2(line) : gray(line)).join("\n");
|
|
4228
|
+
} finally {
|
|
4229
|
+
koloristOptions.enabled = prevEnabled;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
var TtySpinner = class {
|
|
4233
|
+
timer = null;
|
|
4234
|
+
frame = 0;
|
|
4235
|
+
startedAt = 0;
|
|
4236
|
+
label = "";
|
|
4237
|
+
write;
|
|
4238
|
+
columns;
|
|
4239
|
+
motion;
|
|
4240
|
+
color;
|
|
4241
|
+
intervalMs;
|
|
4242
|
+
now;
|
|
4243
|
+
constructor(opts = {}) {
|
|
4244
|
+
this.write = opts.write ?? ((chunk) => process.stdout.write(chunk));
|
|
4245
|
+
this.columns = opts.columns ?? (() => process.stdout.columns ?? 80);
|
|
4246
|
+
this.motion = opts.motion ?? shouldUseVisualMotion();
|
|
4247
|
+
this.color = opts.color ?? shouldUseWelcomeColor();
|
|
4248
|
+
this.intervalMs = opts.intervalMs ?? 80;
|
|
4249
|
+
this.now = opts.now ?? (() => Date.now());
|
|
4250
|
+
}
|
|
4251
|
+
start(label = "") {
|
|
4252
|
+
this.stop();
|
|
4253
|
+
this.label = label;
|
|
4254
|
+
this.frame = 0;
|
|
4255
|
+
this.startedAt = this.now();
|
|
4256
|
+
this.paint(false);
|
|
4257
|
+
if (!this.motion) return;
|
|
4258
|
+
this.timer = setInterval(() => {
|
|
4259
|
+
this.frame += 1;
|
|
4260
|
+
this.paint(true);
|
|
4261
|
+
}, this.intervalMs);
|
|
4262
|
+
this.timer.unref?.();
|
|
4263
|
+
}
|
|
4264
|
+
stop(finalLine) {
|
|
4265
|
+
if (this.timer) {
|
|
4266
|
+
clearInterval(this.timer);
|
|
4267
|
+
this.timer = null;
|
|
4268
|
+
}
|
|
4269
|
+
if (this.motion) this.write("\r\x1B[K");
|
|
4270
|
+
if (finalLine) this.write(`${finalLine}
|
|
4271
|
+
`);
|
|
4272
|
+
}
|
|
4273
|
+
paint(overwrite) {
|
|
4274
|
+
const elapsed = this.now() - this.startedAt;
|
|
4275
|
+
const tipIndex = Math.floor(elapsed / 4e3);
|
|
4276
|
+
const line = renderProgressLine({
|
|
4277
|
+
frameIndex: this.frame,
|
|
4278
|
+
tipIndex,
|
|
4279
|
+
columns: this.columns(),
|
|
4280
|
+
label: this.label,
|
|
4281
|
+
motion: this.motion,
|
|
4282
|
+
color: this.color
|
|
4283
|
+
}).split("\n")[0] ?? "";
|
|
4284
|
+
if (overwrite && this.motion) this.write(`\r\x1B[K${line}`);
|
|
4285
|
+
else if (this.motion) this.write(line);
|
|
4286
|
+
else this.write(`${line}
|
|
4287
|
+
`);
|
|
4288
|
+
}
|
|
4289
|
+
};
|
|
4290
|
+
async function withCliProgress(label, fn, opts) {
|
|
4291
|
+
const motion = opts?.motion ?? shouldUseVisualMotion();
|
|
4292
|
+
if (!motion) {
|
|
4293
|
+
return fn();
|
|
4294
|
+
}
|
|
4295
|
+
const spinner = new TtySpinner({ ...opts, motion: true });
|
|
4296
|
+
spinner.start(label);
|
|
4297
|
+
try {
|
|
4298
|
+
const result = await fn();
|
|
4299
|
+
spinner.stop();
|
|
4300
|
+
return result;
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
spinner.stop();
|
|
4303
|
+
throw err;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
|
|
3702
4307
|
// src/commands/doctor.ts
|
|
3703
4308
|
async function runDoctor(cwd, options = {}) {
|
|
3704
4309
|
const rootDir = path24.resolve(cwd);
|
|
@@ -3753,7 +4358,7 @@ function printDoctorSummary(result) {
|
|
|
3753
4358
|
var doctorCommand = defineCommand7({
|
|
3754
4359
|
meta: {
|
|
3755
4360
|
name: "doctor",
|
|
3756
|
-
description: "Diagnose repository readiness
|
|
4361
|
+
description: "Diagnose repository readiness; optional --fix-safe local repairs."
|
|
3757
4362
|
},
|
|
3758
4363
|
args: {
|
|
3759
4364
|
cwd: {
|
|
@@ -3772,7 +4377,8 @@ var doctorCommand = defineCommand7({
|
|
|
3772
4377
|
}
|
|
3773
4378
|
},
|
|
3774
4379
|
async run({ args }) {
|
|
3775
|
-
const
|
|
4380
|
+
const run = () => runDoctor(args.cwd, { fixSafe: args["fix-safe"] });
|
|
4381
|
+
const result = args.json ? await run() : await withCliProgress("doctor", run);
|
|
3776
4382
|
if (args.json) {
|
|
3777
4383
|
console.log(JSON.stringify(result, null, 2));
|
|
3778
4384
|
return;
|
|
@@ -4044,7 +4650,7 @@ async function detectCurrentBranch() {
|
|
|
4044
4650
|
var guardCommand = defineCommand8({
|
|
4045
4651
|
meta: {
|
|
4046
4652
|
name: "guard",
|
|
4047
|
-
description: "
|
|
4653
|
+
description: "Deny/annotate guards (shell, prompt). Hooks are thin adapters."
|
|
4048
4654
|
},
|
|
4049
4655
|
subCommands: {
|
|
4050
4656
|
shell: defineCommand8({
|
|
@@ -4107,8 +4713,8 @@ var guardCommand = defineCommand8({
|
|
|
4107
4713
|
});
|
|
4108
4714
|
|
|
4109
4715
|
// src/commands/handoff.ts
|
|
4110
|
-
import { spawn as
|
|
4111
|
-
import { readFile as
|
|
4716
|
+
import { spawn as spawn4 } from "child_process";
|
|
4717
|
+
import { readFile as readFile13, readdir as readdir4, writeFile as writeFile6 } from "fs/promises";
|
|
4112
4718
|
import path25 from "path";
|
|
4113
4719
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4114
4720
|
function parsePlanFrontmatter(raw) {
|
|
@@ -4129,13 +4735,13 @@ async function findActivePlan(plansDir) {
|
|
|
4129
4735
|
if (!await fileExists(plansDir)) return null;
|
|
4130
4736
|
const files = (await readdir4(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
4131
4737
|
for (const file of files) {
|
|
4132
|
-
const raw = await
|
|
4738
|
+
const raw = await readFile13(path25.join(plansDir, file), "utf8");
|
|
4133
4739
|
const fm = parsePlanFrontmatter(raw);
|
|
4134
4740
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
4135
4741
|
return { file, raw };
|
|
4136
4742
|
}
|
|
4137
4743
|
}
|
|
4138
|
-
return files[0] ? { file: files[0], raw: await
|
|
4744
|
+
return files[0] ? { file: files[0], raw: await readFile13(path25.join(plansDir, files[0]), "utf8") } : null;
|
|
4139
4745
|
}
|
|
4140
4746
|
function now() {
|
|
4141
4747
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
@@ -4218,19 +4824,19 @@ function printV3Guidance() {
|
|
|
4218
4824
|
logger.info("Resume in IDE: /resume, summaries and transcripts.");
|
|
4219
4825
|
}
|
|
4220
4826
|
function runCursorHandoff(scriptPath, cwd) {
|
|
4221
|
-
return new Promise((
|
|
4222
|
-
const child =
|
|
4827
|
+
return new Promise((resolve2, reject) => {
|
|
4828
|
+
const child = spawn4("sh", [scriptPath, "handoff"], {
|
|
4223
4829
|
cwd,
|
|
4224
4830
|
stdio: "inherit"
|
|
4225
4831
|
});
|
|
4226
4832
|
child.on("error", (err) => reject(err));
|
|
4227
|
-
child.on("close", (code) =>
|
|
4833
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
4228
4834
|
});
|
|
4229
4835
|
}
|
|
4230
4836
|
var handoffCommand = defineCommand9({
|
|
4231
4837
|
meta: {
|
|
4232
4838
|
name: "handoff",
|
|
4233
|
-
description: "Write .cursor/HANDOFF.md from the active
|
|
4839
|
+
description: "Write .cursor/HANDOFF.md from the active plan (or cursor-handoff fallback)."
|
|
4234
4840
|
},
|
|
4235
4841
|
args: {
|
|
4236
4842
|
cwd: {
|
|
@@ -4249,7 +4855,7 @@ var handoffCommand = defineCommand9({
|
|
|
4249
4855
|
if (fm) {
|
|
4250
4856
|
await ensureDir(path25.join(args.cwd, ".cursor"));
|
|
4251
4857
|
const content = buildHandoff(plan.file, fm, profile);
|
|
4252
|
-
await
|
|
4858
|
+
await writeFile6(handoffPath, content, "utf8");
|
|
4253
4859
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
4254
4860
|
logger.info(
|
|
4255
4861
|
`Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
|
|
@@ -4305,8 +4911,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
4305
4911
|
}
|
|
4306
4912
|
|
|
4307
4913
|
// src/hooks/session-start.ts
|
|
4308
|
-
import { spawn as
|
|
4309
|
-
import { access as
|
|
4914
|
+
import { spawn as spawn5 } from "child_process";
|
|
4915
|
+
import { access as access6, readFile as readFile14 } from "fs/promises";
|
|
4310
4916
|
import path26 from "path";
|
|
4311
4917
|
|
|
4312
4918
|
// src/invariants/handoff-schema.ts
|
|
@@ -4381,7 +4987,7 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
4381
4987
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
4382
4988
|
async function readTextLimited(filePath, limit = 60) {
|
|
4383
4989
|
try {
|
|
4384
|
-
const lines = (await
|
|
4990
|
+
const lines = (await readFile14(filePath, "utf8")).split(/\r?\n/);
|
|
4385
4991
|
return lines.slice(0, limit).join("\n").trim();
|
|
4386
4992
|
} catch {
|
|
4387
4993
|
return "";
|
|
@@ -4389,14 +4995,14 @@ async function readTextLimited(filePath, limit = 60) {
|
|
|
4389
4995
|
}
|
|
4390
4996
|
async function readFull(filePath) {
|
|
4391
4997
|
try {
|
|
4392
|
-
return await
|
|
4998
|
+
return await readFile14(filePath, "utf8");
|
|
4393
4999
|
} catch {
|
|
4394
5000
|
return "";
|
|
4395
5001
|
}
|
|
4396
5002
|
}
|
|
4397
5003
|
async function fileExists2(p) {
|
|
4398
5004
|
try {
|
|
4399
|
-
await
|
|
5005
|
+
await access6(p);
|
|
4400
5006
|
return true;
|
|
4401
5007
|
} catch {
|
|
4402
5008
|
return false;
|
|
@@ -4405,22 +5011,55 @@ async function fileExists2(p) {
|
|
|
4405
5011
|
function parseUnprocessedDogfoodItems(readmeText) {
|
|
4406
5012
|
const items = [];
|
|
4407
5013
|
let inSection = false;
|
|
5014
|
+
let sectionLevel = 0;
|
|
4408
5015
|
for (const line of readmeText.split(/\r?\n/)) {
|
|
4409
|
-
|
|
5016
|
+
const unprocessedMatch = /^(#{2,3})\s+Unprocessed Files\b/.exec(line);
|
|
5017
|
+
if (unprocessedMatch) {
|
|
5018
|
+
const hashes = unprocessedMatch[1];
|
|
5019
|
+
if (!hashes) continue;
|
|
4410
5020
|
inSection = true;
|
|
5021
|
+
sectionLevel = hashes.length;
|
|
4411
5022
|
continue;
|
|
4412
5023
|
}
|
|
4413
5024
|
if (!inSection) continue;
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
5025
|
+
const headingMatch = /^(#{1,6})\s+/.exec(line);
|
|
5026
|
+
if (headingMatch) {
|
|
5027
|
+
if (/\bProcessed Files\b/.test(line)) break;
|
|
5028
|
+
const hashes = headingMatch[1];
|
|
5029
|
+
if (hashes && hashes.length <= sectionLevel) break;
|
|
5030
|
+
continue;
|
|
5031
|
+
}
|
|
5032
|
+
const body = extractUnprocessedDogfoodLine(line);
|
|
5033
|
+
if (!body) continue;
|
|
4420
5034
|
items.push(body);
|
|
4421
5035
|
}
|
|
4422
5036
|
return items;
|
|
4423
5037
|
}
|
|
5038
|
+
function extractUnprocessedDogfoodLine(line) {
|
|
5039
|
+
const stripped = line.trim();
|
|
5040
|
+
if (!stripped) return null;
|
|
5041
|
+
let raw = null;
|
|
5042
|
+
if (stripped.startsWith("- ")) {
|
|
5043
|
+
raw = stripped.slice(2).trim();
|
|
5044
|
+
} else {
|
|
5045
|
+
const numbered = /^(\d+)[.)]\s+(.+)$/.exec(stripped);
|
|
5046
|
+
if (numbered?.[2]) {
|
|
5047
|
+
raw = numbered[2].trim();
|
|
5048
|
+
} else if (stripped.startsWith("|")) {
|
|
5049
|
+
const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
5050
|
+
if (parts.length === 0) return null;
|
|
5051
|
+
if (parts.every((cell) => /^:?-+:?$/.test(cell))) return null;
|
|
5052
|
+
const first = parts[0] ?? "";
|
|
5053
|
+
const headerish = first.toLowerCase().replace(/[*_`]/g, "").trim();
|
|
5054
|
+
if (/^(note|file|entrada|title|name|item|path)$/.test(headerish)) return null;
|
|
5055
|
+
raw = first.trim();
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
if (!raw) return null;
|
|
5059
|
+
const normalized = raw.toLowerCase().replace(/[*_]/g, "").trim();
|
|
5060
|
+
if (!normalized || NONE_PLACEHOLDERS.has(normalized)) return null;
|
|
5061
|
+
return raw;
|
|
5062
|
+
}
|
|
4424
5063
|
async function l0Present(root) {
|
|
4425
5064
|
const cursor = path26.join(root, ".cursor");
|
|
4426
5065
|
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 +5104,7 @@ async function readinessSection(root) {
|
|
|
4465
5104
|
const snapshotPath = path26.join(root, ".cursor", "context", "readiness.json");
|
|
4466
5105
|
let data;
|
|
4467
5106
|
try {
|
|
4468
|
-
data = JSON.parse(await
|
|
5107
|
+
data = JSON.parse(await readFile14(snapshotPath, "utf8"));
|
|
4469
5108
|
} catch {
|
|
4470
5109
|
return null;
|
|
4471
5110
|
}
|
|
@@ -4502,7 +5141,7 @@ async function dogfoodInboxSection(root) {
|
|
|
4502
5141
|
for (const readme of candidateReadmes) {
|
|
4503
5142
|
if (!await fileExists2(readme)) continue;
|
|
4504
5143
|
try {
|
|
4505
|
-
const text = await
|
|
5144
|
+
const text = await readFile14(readme, "utf8");
|
|
4506
5145
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
4507
5146
|
} catch {
|
|
4508
5147
|
}
|
|
@@ -4512,7 +5151,7 @@ async function dogfoodInboxSection(root) {
|
|
|
4512
5151
|
async function loadUpdateCheckPrefs(root) {
|
|
4513
5152
|
try {
|
|
4514
5153
|
const data = JSON.parse(
|
|
4515
|
-
await
|
|
5154
|
+
await readFile14(path26.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
4516
5155
|
);
|
|
4517
5156
|
const uc = data.updateCheck;
|
|
4518
5157
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -4524,8 +5163,8 @@ async function loadUpdateCheckPrefs(root) {
|
|
|
4524
5163
|
}
|
|
4525
5164
|
}
|
|
4526
5165
|
function runUpdateCheckJson(root) {
|
|
4527
|
-
return new Promise((
|
|
4528
|
-
const child =
|
|
5166
|
+
return new Promise((resolve2) => {
|
|
5167
|
+
const child = spawn5(
|
|
4529
5168
|
process.execPath,
|
|
4530
5169
|
[
|
|
4531
5170
|
process.argv[1] ?? "",
|
|
@@ -4543,21 +5182,21 @@ function runUpdateCheckJson(root) {
|
|
|
4543
5182
|
child.stdout?.on("data", (chunk) => {
|
|
4544
5183
|
out += chunk.toString("utf8");
|
|
4545
5184
|
});
|
|
4546
|
-
child.on("error", () =>
|
|
5185
|
+
child.on("error", () => resolve2(null));
|
|
4547
5186
|
child.on("close", () => {
|
|
4548
5187
|
try {
|
|
4549
5188
|
const parsed = JSON.parse(out.trim());
|
|
4550
|
-
|
|
5189
|
+
resolve2(parsed && typeof parsed === "object" ? parsed : null);
|
|
4551
5190
|
} catch {
|
|
4552
|
-
|
|
5191
|
+
resolve2(null);
|
|
4553
5192
|
}
|
|
4554
5193
|
});
|
|
4555
5194
|
});
|
|
4556
5195
|
}
|
|
4557
5196
|
async function updateCheckSection(root) {
|
|
4558
5197
|
if (await loadUpdateCheckPrefs(root) === null) return null;
|
|
4559
|
-
const result = await new Promise((
|
|
4560
|
-
const child =
|
|
5198
|
+
const result = await new Promise((resolve2) => {
|
|
5199
|
+
const child = spawn5(
|
|
4561
5200
|
"agent-kit",
|
|
4562
5201
|
["update", "--check", "--json", "--respect-prefs", "--stamp", "--cwd", root],
|
|
4563
5202
|
{ stdio: ["ignore", "pipe", "ignore"], timeout: 12e3, shell: false }
|
|
@@ -4567,17 +5206,17 @@ async function updateCheckSection(root) {
|
|
|
4567
5206
|
out += chunk.toString("utf8");
|
|
4568
5207
|
});
|
|
4569
5208
|
child.on("error", () => {
|
|
4570
|
-
void runUpdateCheckJson(root).then(
|
|
5209
|
+
void runUpdateCheckJson(root).then(resolve2);
|
|
4571
5210
|
});
|
|
4572
5211
|
child.on("close", (code) => {
|
|
4573
5212
|
if (code !== 0 && !out.trim()) {
|
|
4574
|
-
void runUpdateCheckJson(root).then(
|
|
5213
|
+
void runUpdateCheckJson(root).then(resolve2);
|
|
4575
5214
|
return;
|
|
4576
5215
|
}
|
|
4577
5216
|
try {
|
|
4578
|
-
|
|
5217
|
+
resolve2(JSON.parse(out.trim()));
|
|
4579
5218
|
} catch {
|
|
4580
|
-
void runUpdateCheckJson(root).then(
|
|
5219
|
+
void runUpdateCheckJson(root).then(resolve2);
|
|
4581
5220
|
}
|
|
4582
5221
|
});
|
|
4583
5222
|
});
|
|
@@ -4590,7 +5229,7 @@ async function updateCheckSection(root) {
|
|
|
4590
5229
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
4591
5230
|
try {
|
|
4592
5231
|
const data = JSON.parse(
|
|
4593
|
-
await
|
|
5232
|
+
await readFile14(path26.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
4594
5233
|
);
|
|
4595
5234
|
const uc = data.cursorUpdateCheck;
|
|
4596
5235
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -4602,8 +5241,8 @@ async function loadCursorUpdateCheckPrefs(root) {
|
|
|
4602
5241
|
}
|
|
4603
5242
|
}
|
|
4604
5243
|
function runCursorAwarenessJson(root) {
|
|
4605
|
-
return new Promise((
|
|
4606
|
-
const child =
|
|
5244
|
+
return new Promise((resolve2) => {
|
|
5245
|
+
const child = spawn5(
|
|
4607
5246
|
process.execPath,
|
|
4608
5247
|
[
|
|
4609
5248
|
process.argv[1] ?? "",
|
|
@@ -4621,13 +5260,13 @@ function runCursorAwarenessJson(root) {
|
|
|
4621
5260
|
child.stdout?.on("data", (chunk) => {
|
|
4622
5261
|
out += chunk.toString("utf8");
|
|
4623
5262
|
});
|
|
4624
|
-
child.on("error", () =>
|
|
5263
|
+
child.on("error", () => resolve2(null));
|
|
4625
5264
|
child.on("close", () => {
|
|
4626
5265
|
try {
|
|
4627
5266
|
const parsed = JSON.parse(out.trim());
|
|
4628
|
-
|
|
5267
|
+
resolve2(parsed && typeof parsed === "object" ? parsed : null);
|
|
4629
5268
|
} catch {
|
|
4630
|
-
|
|
5269
|
+
resolve2(null);
|
|
4631
5270
|
}
|
|
4632
5271
|
});
|
|
4633
5272
|
});
|
|
@@ -4642,15 +5281,15 @@ function shouldEmitCursorAwarenessNudge(result) {
|
|
|
4642
5281
|
}
|
|
4643
5282
|
async function cursorAwarenessSection(root, deps = {}) {
|
|
4644
5283
|
if (await loadCursorUpdateCheckPrefs(root) === null) return null;
|
|
4645
|
-
const spawnFn = deps.spawnFn ??
|
|
5284
|
+
const spawnFn = deps.spawnFn ?? spawn5;
|
|
4646
5285
|
const runFallback = deps.runFallback ?? runCursorAwarenessJson;
|
|
4647
|
-
const result = await new Promise((
|
|
5286
|
+
const result = await new Promise((resolve2) => {
|
|
4648
5287
|
let settled = false;
|
|
4649
5288
|
let fallbackStarted = false;
|
|
4650
5289
|
const finish = (value) => {
|
|
4651
5290
|
if (settled) return;
|
|
4652
5291
|
settled = true;
|
|
4653
|
-
|
|
5292
|
+
resolve2(value);
|
|
4654
5293
|
};
|
|
4655
5294
|
const startFallback = () => {
|
|
4656
5295
|
if (settled || fallbackStarted) return;
|
|
@@ -4739,7 +5378,7 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
|
4739
5378
|
var hookCommand = defineCommand10({
|
|
4740
5379
|
meta: {
|
|
4741
5380
|
name: "hook",
|
|
4742
|
-
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT
|
|
5381
|
+
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT."
|
|
4743
5382
|
},
|
|
4744
5383
|
subCommands: {
|
|
4745
5384
|
"session-start": defineCommand10({
|
|
@@ -4778,24 +5417,294 @@ var hookCommand = defineCommand10({
|
|
|
4778
5417
|
import { intro, outro } from "@clack/prompts";
|
|
4779
5418
|
import { defineCommand as defineCommand12 } from "citty";
|
|
4780
5419
|
|
|
4781
|
-
// src/
|
|
4782
|
-
import
|
|
4783
|
-
import { defineCommand as defineCommand11 } from "citty";
|
|
4784
|
-
|
|
4785
|
-
// src/generator/personalization.ts
|
|
4786
|
-
import { readFile as readFile14, writeFile as writeFile6 } from "fs/promises";
|
|
5420
|
+
// src/utils/terminal.ts
|
|
5421
|
+
import { homedir as homedir2 } from "os";
|
|
4787
5422
|
import path28 from "path";
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
5423
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
5424
|
+
function isNonInteractive() {
|
|
5425
|
+
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
5426
|
+
if (process.env.AGENT_KIT_YES === "1") return true;
|
|
5427
|
+
return !process.stdin.isTTY;
|
|
5428
|
+
}
|
|
5429
|
+
async function validateProjectRoot(resolved) {
|
|
5430
|
+
const home = path28.resolve(homedir2());
|
|
5431
|
+
if (resolved === "/" || resolved === home) {
|
|
5432
|
+
return { ok: false, reason: `Refused to use ${resolved} as a project root.` };
|
|
5433
|
+
}
|
|
5434
|
+
const hasGit = await fileExists(path28.join(resolved, ".git"));
|
|
5435
|
+
const hasManifest = await fileExists(path28.join(resolved, ".cursor", "agent-kit.json"));
|
|
5436
|
+
if (!hasGit && !hasManifest) {
|
|
5437
|
+
return {
|
|
5438
|
+
ok: false,
|
|
5439
|
+
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json. Run from a project directory or use --force-root.`
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
return { ok: true };
|
|
5443
|
+
}
|
|
5444
|
+
async function confirmProjectRoot(cwd, opts) {
|
|
5445
|
+
const resolved = path28.resolve(cwd);
|
|
5446
|
+
if (opts.forceRoot) {
|
|
5447
|
+
return resolved;
|
|
5448
|
+
}
|
|
5449
|
+
const validation = await validateProjectRoot(resolved);
|
|
5450
|
+
if (!validation.ok) {
|
|
5451
|
+
if (opts.nonInteractive) {
|
|
5452
|
+
throw new RootRefusedError(resolved, validation.reason);
|
|
5453
|
+
}
|
|
5454
|
+
const ok2 = await confirm({
|
|
5455
|
+
message: `${validation.reason} Proceed anyway?`,
|
|
5456
|
+
initialValue: false
|
|
5457
|
+
});
|
|
5458
|
+
if (isCancel(ok2) || !ok2) {
|
|
5459
|
+
throw new RootRefusedError(resolved, validation.reason);
|
|
5460
|
+
}
|
|
5461
|
+
return resolved;
|
|
5462
|
+
}
|
|
5463
|
+
if (opts.nonInteractive) {
|
|
5464
|
+
return resolved;
|
|
5465
|
+
}
|
|
5466
|
+
const ok = await confirm({
|
|
5467
|
+
message: `${opts.command === "install" ? "Install" : "Update"} Agent Kit in: ${resolved}`,
|
|
5468
|
+
initialValue: true
|
|
5469
|
+
});
|
|
5470
|
+
if (isCancel(ok) || !ok) {
|
|
5471
|
+
throw new RootRefusedError(resolved);
|
|
5472
|
+
}
|
|
5473
|
+
return resolved;
|
|
5474
|
+
}
|
|
5475
|
+
var RootRefusedError = class extends Error {
|
|
5476
|
+
constructor(root, reason) {
|
|
5477
|
+
super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
|
|
5478
|
+
this.root = root;
|
|
5479
|
+
this.name = "RootRefusedError";
|
|
5480
|
+
}
|
|
5481
|
+
root;
|
|
5482
|
+
};
|
|
5483
|
+
function classifyInstallError(err) {
|
|
5484
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5485
|
+
const code = err?.code;
|
|
5486
|
+
if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
|
|
5487
|
+
return {
|
|
5488
|
+
kind: "eperm",
|
|
5489
|
+
message: `Permission error: ${msg}`,
|
|
5490
|
+
recovery: [
|
|
5491
|
+
"The npm cache may have ownership drift (root-written files in a user dir).",
|
|
5492
|
+
"Recovery options:",
|
|
5493
|
+
" 1. npx --cache .npm-cache @dadado/agent-kit-cli install",
|
|
5494
|
+
" 2. npm cache clean --force && npx @dadado/agent-kit-cli install",
|
|
5495
|
+
" 3. Use Port B fallback: drag install.md into the Cursor chat"
|
|
5496
|
+
].join("\n")
|
|
5497
|
+
};
|
|
5498
|
+
}
|
|
5499
|
+
if (/403|Forbidden|unauthorized|E401|ENEEDAUTH/.test(msg)) {
|
|
5500
|
+
return {
|
|
5501
|
+
kind: "registry-auth",
|
|
5502
|
+
message: `Registry access denied: ${msg}`,
|
|
5503
|
+
recovery: [
|
|
5504
|
+
"The npm registry returned 403/401 for the scoped package.",
|
|
5505
|
+
"Recovery options:",
|
|
5506
|
+
" 1. Check npm auth: npm whoami (login if needed: npm login)",
|
|
5507
|
+
" 2. If using a private registry, verify .npmrc scope config",
|
|
5508
|
+
" 3. Use Port B fallback: drag install.md into the Cursor chat"
|
|
5509
|
+
].join("\n")
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5512
|
+
if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED|ECONNRESET|EAI_AGAIN|fetch failed/.test(msg) || code === "ENOTFOUND" || code === "ETIMEDOUT") {
|
|
5513
|
+
return {
|
|
5514
|
+
kind: "network",
|
|
5515
|
+
message: `Network error: ${msg}`,
|
|
5516
|
+
recovery: [
|
|
5517
|
+
"Could not reach the registry or git remote.",
|
|
5518
|
+
"Recovery options:",
|
|
5519
|
+
" 1. Check network/proxy/VPN settings",
|
|
5520
|
+
" 2. Retry: npx @dadado/agent-kit-cli install",
|
|
5521
|
+
" 3. Use --registry <local-path> if you have a local checkout"
|
|
5522
|
+
].join("\n")
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
return {
|
|
5526
|
+
kind: "unknown",
|
|
5527
|
+
message: msg,
|
|
5528
|
+
recovery: "Unexpected error. Check the message above and retry, or use Port B fallback."
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
|
|
5532
|
+
// src/commands/install.ts
|
|
5533
|
+
import path33 from "path";
|
|
5534
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
5535
|
+
|
|
5536
|
+
// src/generator/personalization.ts
|
|
5537
|
+
import { readFile as readFile15, writeFile as writeFile9 } from "fs/promises";
|
|
5538
|
+
import path31 from "path";
|
|
5539
|
+
|
|
5540
|
+
// src/generator/claude-kit-load.ts
|
|
5541
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
5542
|
+
import path29 from "path";
|
|
5543
|
+
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
5544
|
+
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
5545
|
+
function renderClaudeMd() {
|
|
5546
|
+
return `# Agent Kit (Claude Code)
|
|
5547
|
+
|
|
5548
|
+
This repository uses Agent Kit / Mission Control. Before rediscovering the tree, read the shared sources of truth (paths below are pointers; do not treat this file as a second rulebook).
|
|
5549
|
+
|
|
5550
|
+
## Read first
|
|
5551
|
+
|
|
5552
|
+
1. \`AGENTS.md\` - cross-IDE contract
|
|
5553
|
+
2. \`.cursor/project-context.md\` - verified repository facts (derived; prefer code, tests, SHAs when docs conflict)
|
|
5554
|
+
3. \`.cursor/HANDOFF.md\` - if present: active plan, next to-do, queue fields
|
|
5555
|
+
4. \`.cursor/commands/\` - slash catalog (Cursor). Follow the same HITL contracts here.
|
|
5556
|
+
|
|
5557
|
+
Mid-session refresh: \`/agent-kit\`.
|
|
5558
|
+
|
|
5559
|
+
## HITL
|
|
5560
|
+
|
|
5561
|
+
Cursor Ask questions is not available in this CLI. When a command requires a choice, list the same labels as a numbered list and wait. Skip or cancel means stop. Never \`/git-prod\` without an explicit operator yes.
|
|
5562
|
+
|
|
5563
|
+
## Non-goals
|
|
5564
|
+
|
|
5565
|
+
- Not Action A7 (Windsurf / VS Code generator parity)
|
|
5566
|
+
- Not Claude external plan-review audits (\`/plan-external-review\`)
|
|
5567
|
+
- Not \`--backend claude\` plan-loop ticks
|
|
5568
|
+
- Not a copy of Cursor \`sessionStart\` / other IDE hooks
|
|
5569
|
+
`;
|
|
5570
|
+
}
|
|
5571
|
+
function renderAgentKitCommand() {
|
|
5572
|
+
return `---
|
|
5573
|
+
description: Load Agent Kit session context (HANDOFF, project-context, commands). Manual refresh only.
|
|
5574
|
+
disable-model-invocation: true
|
|
5575
|
+
---
|
|
5576
|
+
|
|
5577
|
+
Read these files if they exist, then summarize the active plan, next to-do, and any Gaps. Do not scan the whole repository first.
|
|
5578
|
+
|
|
5579
|
+
1. \`AGENTS.md\`
|
|
5580
|
+
2. \`.cursor/project-context.md\`
|
|
5581
|
+
3. \`.cursor/HANDOFF.md\`
|
|
5582
|
+
4. The plan file named in HANDOFF \`- **Plan:**\` under \`.cursor/plans/\`
|
|
5583
|
+
|
|
5584
|
+
If HANDOFF is missing, say so and point at \`/agent-kit-onboard\` or \`/start-project\` rather than inventing a plan.
|
|
5585
|
+
|
|
5586
|
+
HITL: numbered-list fallback for Ask questions labels. Never \`/git-prod\` from this skill.
|
|
5587
|
+
|
|
5588
|
+
Non-goals: not audits / \`/plan-external-review\`, not \`--backend claude\` ticks, not A7, not Cursor hook clones.
|
|
5589
|
+
`;
|
|
5590
|
+
}
|
|
5591
|
+
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
5592
|
+
const target = path29.join(rootDir, relativePath);
|
|
5593
|
+
if (await fileExists(target)) {
|
|
5594
|
+
return { relativePath, status: "skipped-customized" };
|
|
5595
|
+
}
|
|
5596
|
+
await ensureDir(path29.dirname(target));
|
|
5597
|
+
await writeFile7(target, content, "utf8");
|
|
5598
|
+
return { relativePath, status: "applied" };
|
|
5599
|
+
}
|
|
5600
|
+
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
5601
|
+
return Promise.all([
|
|
5602
|
+
writeUnlessExists(rootDir, CLAUDE_MD_REL, renderClaudeMd()),
|
|
5603
|
+
writeUnlessExists(rootDir, AGENT_KIT_COMMAND_REL, renderAgentKitCommand())
|
|
5604
|
+
]);
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/generator/vscode.ts
|
|
5608
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
5609
|
+
import path30 from "path";
|
|
5610
|
+
|
|
5611
|
+
// src/generator/platform.ts
|
|
5612
|
+
function gitProviderLabel(profile) {
|
|
5613
|
+
const p = profile.git.provider;
|
|
5614
|
+
if (!p || p === "other") return "Git";
|
|
5615
|
+
const labels = {
|
|
5616
|
+
github: "GitHub",
|
|
5617
|
+
gitlab: "GitLab",
|
|
5618
|
+
bitbucket: "Bitbucket",
|
|
5619
|
+
"azure-devops": "Azure DevOps",
|
|
5620
|
+
gitea: "Gitea"
|
|
5621
|
+
};
|
|
5622
|
+
return labels[p] ?? "Git";
|
|
5623
|
+
}
|
|
5624
|
+
function prTerminology(profile) {
|
|
5625
|
+
const p = profile.git.provider;
|
|
5626
|
+
if (!p || p === "other") return "PR/MR";
|
|
5627
|
+
return GIT_PLATFORM_META[p]?.prTerm ?? "PR/MR";
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5630
|
+
// src/generator/vscode.ts
|
|
5631
|
+
async function generateVSCodeArtifacts(profile) {
|
|
5632
|
+
const results = [];
|
|
5633
|
+
const vscodeDir = path30.join(profile.rootDir, ".vscode");
|
|
5634
|
+
const githubDir = path30.join(profile.rootDir, ".github");
|
|
5635
|
+
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
5636
|
+
const settingsPath = path30.join(vscodeDir, "settings.json");
|
|
5637
|
+
if (await fileExists(settingsPath)) {
|
|
5638
|
+
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
5639
|
+
} else {
|
|
5640
|
+
await writeFile8(
|
|
5641
|
+
settingsPath,
|
|
5642
|
+
`${JSON.stringify(
|
|
5643
|
+
{
|
|
5644
|
+
"editor.formatOnSave": true,
|
|
5645
|
+
"editor.codeActionsOnSave": {
|
|
5646
|
+
"source.fixAll": "explicit"
|
|
5647
|
+
},
|
|
5648
|
+
"files.autoSave": "afterDelay"
|
|
5649
|
+
},
|
|
5650
|
+
null,
|
|
5651
|
+
2
|
|
5652
|
+
)}
|
|
5653
|
+
`,
|
|
5654
|
+
"utf8"
|
|
5655
|
+
);
|
|
5656
|
+
results.push({ relativePath: ".vscode/settings.json", status: "applied" });
|
|
5657
|
+
}
|
|
5658
|
+
const provider = gitProviderLabel(profile);
|
|
5659
|
+
const prTerm = prTerminology(profile);
|
|
5660
|
+
const copilotPath = path30.join(githubDir, "copilot-instructions.md");
|
|
5661
|
+
if (await fileExists(copilotPath)) {
|
|
5662
|
+
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
5663
|
+
} else {
|
|
5664
|
+
await writeFile8(
|
|
5665
|
+
copilotPath,
|
|
5666
|
+
`# Copilot Instructions
|
|
5667
|
+
|
|
5668
|
+
- Keep code changes small and testable.
|
|
5669
|
+
- Use Conventional Commits (feat:, fix:, docs:, etc.).
|
|
5670
|
+
- Prefer security-safe defaults.
|
|
5671
|
+
- Git platform: ${provider}. Always create a ${prTerm} for review.
|
|
5672
|
+
`,
|
|
5673
|
+
"utf8"
|
|
5674
|
+
);
|
|
5675
|
+
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
5676
|
+
}
|
|
5677
|
+
if (profile.ide.plan === "vscode-pro") {
|
|
5678
|
+
const securityPath = path30.join(vscodeDir, "security-review.agent.md");
|
|
5679
|
+
if (await fileExists(securityPath)) {
|
|
5680
|
+
results.push({
|
|
5681
|
+
relativePath: ".vscode/security-review.agent.md",
|
|
5682
|
+
status: "skipped-customized"
|
|
5683
|
+
});
|
|
5684
|
+
} else {
|
|
5685
|
+
await writeFile8(
|
|
5686
|
+
securityPath,
|
|
5687
|
+
"# Security Review Agent\n\nSpecialized mode for security review.\n",
|
|
5688
|
+
"utf8"
|
|
5689
|
+
);
|
|
5690
|
+
results.push({ relativePath: ".vscode/security-review.agent.md", status: "applied" });
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
return results;
|
|
5694
|
+
}
|
|
5695
|
+
|
|
5696
|
+
// src/generator/personalization.ts
|
|
5697
|
+
var PERSONALIZATION_CONTRACT_VERSION = 1;
|
|
5698
|
+
var CONTEXT_PATH = ".cursor/project-context.md";
|
|
5699
|
+
var AGENTS_PATH = "AGENTS.md";
|
|
5700
|
+
var RESULT_PATH = ".cursor/context/personalization.json";
|
|
5701
|
+
function uniqueEvidence(evidence) {
|
|
5702
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5703
|
+
return evidence.filter((item) => {
|
|
5704
|
+
const key = `${item.source}:${item.value}`;
|
|
5705
|
+
if (seen.has(key)) return false;
|
|
5706
|
+
seen.add(key);
|
|
5707
|
+
return true;
|
|
4799
5708
|
});
|
|
4800
5709
|
}
|
|
4801
5710
|
function purposeEvidence(profile) {
|
|
@@ -4928,6 +5837,16 @@ function renderProjectContext(profile) {
|
|
|
4928
5837
|
if (profile.infra.ci !== "none" && profile.infra.ciFiles.length > 0) {
|
|
4929
5838
|
sections.push(`- CI: ${profile.infra.ci}.`);
|
|
4930
5839
|
}
|
|
5840
|
+
sections.push(
|
|
5841
|
+
"",
|
|
5842
|
+
"## Relevant skills",
|
|
5843
|
+
"",
|
|
5844
|
+
"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/`.",
|
|
5845
|
+
"",
|
|
5846
|
+
"| Skill / path | Role | Evidence |",
|
|
5847
|
+
"|--------------|------|----------|",
|
|
5848
|
+
"| (none yet) | Add rows when `/agent-kit-onboard` scaffolds domain skills or personalization installs packs | \u2014 |"
|
|
5849
|
+
);
|
|
4931
5850
|
if (profile.context.sources.length > 0) {
|
|
4932
5851
|
sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
|
|
4933
5852
|
}
|
|
@@ -4935,7 +5854,7 @@ function renderProjectContext(profile) {
|
|
|
4935
5854
|
`;
|
|
4936
5855
|
}
|
|
4937
5856
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
4938
|
-
const target =
|
|
5857
|
+
const target = path31.join(rootDir, relativePath);
|
|
4939
5858
|
if (await fileExists(target)) {
|
|
4940
5859
|
return {
|
|
4941
5860
|
kind: "file",
|
|
@@ -4945,8 +5864,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
4945
5864
|
evidence
|
|
4946
5865
|
};
|
|
4947
5866
|
}
|
|
4948
|
-
await ensureDir(
|
|
4949
|
-
await
|
|
5867
|
+
await ensureDir(path31.dirname(target));
|
|
5868
|
+
await writeFile9(target, content, "utf8");
|
|
4950
5869
|
return {
|
|
4951
5870
|
kind: "file",
|
|
4952
5871
|
id: relativePath,
|
|
@@ -4962,7 +5881,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
4962
5881
|
async function existingTargets(projectRoot, targets) {
|
|
4963
5882
|
const checks = await Promise.all(
|
|
4964
5883
|
targets.map(
|
|
4965
|
-
async (target) => await fileExists(
|
|
5884
|
+
async (target) => await fileExists(path31.join(projectRoot, target)) ? target : null
|
|
4966
5885
|
)
|
|
4967
5886
|
);
|
|
4968
5887
|
return checks.filter((target) => target !== null);
|
|
@@ -4984,14 +5903,14 @@ async function applyPersonalization(input) {
|
|
|
4984
5903
|
componentResults.push({ ...item, status: "unavailable" });
|
|
4985
5904
|
continue;
|
|
4986
5905
|
}
|
|
4987
|
-
const target =
|
|
5906
|
+
const target = path31.posix.join(
|
|
4988
5907
|
".cursor",
|
|
4989
5908
|
"skills",
|
|
4990
5909
|
skill.path.includes("/core/") ? "core" : "community",
|
|
4991
5910
|
skill.id,
|
|
4992
5911
|
"SKILL.md"
|
|
4993
5912
|
);
|
|
4994
|
-
if (await fileExists(
|
|
5913
|
+
if (await fileExists(path31.join(input.rootDir, target))) {
|
|
4995
5914
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
4996
5915
|
protectedPaths.add(target);
|
|
4997
5916
|
continue;
|
|
@@ -5039,14 +5958,68 @@ async function applyPersonalization(input) {
|
|
|
5039
5958
|
]);
|
|
5040
5959
|
protectedPaths.add(CONTEXT_PATH);
|
|
5041
5960
|
protectedPaths.add(AGENTS_PATH);
|
|
5961
|
+
const claudeResults = await generateClaudeKitLoadArtifacts(input.rootDir);
|
|
5962
|
+
const claudeItems = claudeResults.map((artifact) => {
|
|
5963
|
+
protectedPaths.add(artifact.relativePath);
|
|
5964
|
+
return {
|
|
5965
|
+
kind: "file",
|
|
5966
|
+
id: artifact.relativePath,
|
|
5967
|
+
path: artifact.relativePath,
|
|
5968
|
+
status: artifact.status,
|
|
5969
|
+
evidence: profileEvidence
|
|
5970
|
+
};
|
|
5971
|
+
});
|
|
5972
|
+
const ideDetection = await detectIde(input.rootDir);
|
|
5973
|
+
if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
|
|
5974
|
+
const git = {
|
|
5975
|
+
providerKind: input.profile.git.providerKind ?? "unknown",
|
|
5976
|
+
providerConfidence: input.profile.git.providerConfidence ?? "low",
|
|
5977
|
+
providerEvidence: input.profile.git.providerEvidence ?? [],
|
|
5978
|
+
remotes: input.profile.git.remotes ?? [],
|
|
5979
|
+
mode: input.profile.git.mode ?? "none",
|
|
5980
|
+
workflow: input.profile.git.workflow ?? "unknown",
|
|
5981
|
+
isDirty: input.profile.git.isDirty ?? false,
|
|
5982
|
+
hasLocalStaging: input.profile.git.hasLocalStaging ?? false,
|
|
5983
|
+
hasRemoteStaging: input.profile.git.hasRemoteStaging ?? false,
|
|
5984
|
+
provider: input.profile.git.provider,
|
|
5985
|
+
remoteUrl: input.profile.git.remoteUrl,
|
|
5986
|
+
remoteName: input.profile.git.remoteName,
|
|
5987
|
+
currentBranch: input.profile.git.currentBranch,
|
|
5988
|
+
defaultBranch: input.profile.git.defaultBranch
|
|
5989
|
+
};
|
|
5990
|
+
const projectProfile = {
|
|
5991
|
+
rootDir: input.rootDir,
|
|
5992
|
+
stack: input.profile.stack,
|
|
5993
|
+
git,
|
|
5994
|
+
ide: ideDetection,
|
|
5995
|
+
infra: input.profile.infra,
|
|
5996
|
+
services: input.profile.services,
|
|
5997
|
+
installHooks: false,
|
|
5998
|
+
selectedCoreComponents: []
|
|
5999
|
+
};
|
|
6000
|
+
const vscodeResults = await generateVSCodeArtifacts(projectProfile);
|
|
6001
|
+
const ideEvidence = [
|
|
6002
|
+
{ source: "derived", value: `ide:${ideDetection.ide}` }
|
|
6003
|
+
];
|
|
6004
|
+
for (const artifact of vscodeResults) {
|
|
6005
|
+
protectedPaths.add(artifact.relativePath);
|
|
6006
|
+
componentResults.push({
|
|
6007
|
+
kind: "file",
|
|
6008
|
+
id: artifact.relativePath,
|
|
6009
|
+
path: artifact.relativePath,
|
|
6010
|
+
status: artifact.status,
|
|
6011
|
+
evidence: ideEvidence
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
5042
6015
|
const result = {
|
|
5043
6016
|
contractVersion: PERSONALIZATION_CONTRACT_VERSION,
|
|
5044
6017
|
generatorVersion: input.generatorVersion,
|
|
5045
6018
|
repositoryFingerprint: input.report.repositoryFingerprint,
|
|
5046
|
-
items: [...fileResults, ...componentResults],
|
|
6019
|
+
items: [...fileResults, ...claudeItems, ...componentResults],
|
|
5047
6020
|
protectedPaths: [...protectedPaths].sort()
|
|
5048
6021
|
};
|
|
5049
|
-
await writeJson(
|
|
6022
|
+
await writeJson(path31.join(input.rootDir, RESULT_PATH), result);
|
|
5050
6023
|
return {
|
|
5051
6024
|
result,
|
|
5052
6025
|
manifest: {
|
|
@@ -5064,26 +6037,26 @@ async function applyPersonalization(input) {
|
|
|
5064
6037
|
};
|
|
5065
6038
|
}
|
|
5066
6039
|
async function readRepositoryProfile(rootDir) {
|
|
5067
|
-
const target =
|
|
6040
|
+
const target = path31.join(rootDir, ".cursor/agent-kit.config.json");
|
|
5068
6041
|
if (!await fileExists(target)) return null;
|
|
5069
|
-
return JSON.parse(await
|
|
6042
|
+
return JSON.parse(await readFile15(target, "utf8"));
|
|
5070
6043
|
}
|
|
5071
6044
|
|
|
5072
6045
|
// src/lifecycle/onboard-migration.ts
|
|
5073
6046
|
import { createHash as createHash4 } from "crypto";
|
|
5074
|
-
import { readFile as
|
|
5075
|
-
import
|
|
6047
|
+
import { readFile as readFile16, unlink } from "fs/promises";
|
|
6048
|
+
import path32 from "path";
|
|
5076
6049
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
5077
6050
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
5078
6051
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
5079
6052
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
5080
6053
|
]);
|
|
5081
6054
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
5082
|
-
const legacyPath =
|
|
6055
|
+
const legacyPath = path32.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
5083
6056
|
if (!await fileExists(legacyPath)) return "absent";
|
|
5084
|
-
const namespacedPath =
|
|
6057
|
+
const namespacedPath = path32.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
5085
6058
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
5086
|
-
const content = await
|
|
6059
|
+
const content = await readFile16(legacyPath);
|
|
5087
6060
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
5088
6061
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
5089
6062
|
await unlink(legacyPath);
|
|
@@ -5156,7 +6129,7 @@ function printReadinessNarrative(result) {
|
|
|
5156
6129
|
);
|
|
5157
6130
|
}
|
|
5158
6131
|
async function performInstall(options) {
|
|
5159
|
-
const projectRoot =
|
|
6132
|
+
const projectRoot = path33.resolve(options.cwd);
|
|
5160
6133
|
const packs = parsePackList(options.pack);
|
|
5161
6134
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
5162
6135
|
const registry = await resolveRegistryFromCli({
|
|
@@ -5167,53 +6140,57 @@ async function performInstall(options) {
|
|
|
5167
6140
|
refresh: options.refresh,
|
|
5168
6141
|
manifest: existing
|
|
5169
6142
|
});
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
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
|
|
6143
|
+
try {
|
|
6144
|
+
const draft = buildManifest({
|
|
6145
|
+
version: KIT_VERSION,
|
|
6146
|
+
profile: options.profile ?? existing?.profile ?? "default",
|
|
6147
|
+
packs: packs.length > 0 ? packs : existing?.packs,
|
|
6148
|
+
skills: existing?.skills,
|
|
6149
|
+
protected: existing?.protected,
|
|
6150
|
+
personalization: existing?.personalization,
|
|
6151
|
+
registryUrl: registry.url ?? existing?.registry?.url,
|
|
6152
|
+
registryRef: registry.ref ?? existing?.registry?.ref
|
|
5197
6153
|
});
|
|
5198
|
-
await
|
|
5199
|
-
|
|
6154
|
+
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));
|
|
6155
|
+
const manifestPath = await saveManifest(projectRoot, draft);
|
|
6156
|
+
const readinessExecution = await executeSafeReadinessFixes(projectRoot, {
|
|
5200
6157
|
generatorVersion: KIT_VERSION
|
|
5201
6158
|
});
|
|
5202
|
-
readiness
|
|
6159
|
+
let readiness = readinessExecution.after;
|
|
6160
|
+
const profile = await readRepositoryProfile(projectRoot);
|
|
6161
|
+
if (profile) {
|
|
6162
|
+
const registryIndex = await loadRegistry(registry.root);
|
|
6163
|
+
const personalization = await applyPersonalization({
|
|
6164
|
+
rootDir: projectRoot,
|
|
6165
|
+
registryRoot: registry.root,
|
|
6166
|
+
profile,
|
|
6167
|
+
report: readinessExecution.after,
|
|
6168
|
+
registry: registryIndex,
|
|
6169
|
+
manifest: draft,
|
|
6170
|
+
generatorVersion: KIT_VERSION
|
|
6171
|
+
});
|
|
6172
|
+
await saveManifest(projectRoot, personalization.manifest);
|
|
6173
|
+
readiness = createReadinessReport(await runScanner(projectRoot), {
|
|
6174
|
+
generatorVersion: KIT_VERSION
|
|
6175
|
+
});
|
|
6176
|
+
readiness.appliedSafeFixes = readinessExecution.after.appliedSafeFixes;
|
|
6177
|
+
}
|
|
6178
|
+
await writeReadinessSnapshot(projectRoot, readiness);
|
|
6179
|
+
return {
|
|
6180
|
+
projectRoot,
|
|
6181
|
+
manifestPath,
|
|
6182
|
+
stats,
|
|
6183
|
+
readiness,
|
|
6184
|
+
safeChanges: readinessExecution.changes
|
|
6185
|
+
};
|
|
6186
|
+
} finally {
|
|
6187
|
+
await registry.unlock?.();
|
|
5203
6188
|
}
|
|
5204
|
-
await writeReadinessSnapshot(projectRoot, readiness);
|
|
5205
|
-
return {
|
|
5206
|
-
projectRoot,
|
|
5207
|
-
manifestPath,
|
|
5208
|
-
stats,
|
|
5209
|
-
readiness,
|
|
5210
|
-
safeChanges: readinessExecution.changes
|
|
5211
|
-
};
|
|
5212
6189
|
}
|
|
5213
6190
|
var installCommand = defineCommand11({
|
|
5214
6191
|
meta: {
|
|
5215
6192
|
name: "install",
|
|
5216
|
-
description: "Bootstrap L0 (+ optional packs)
|
|
6193
|
+
description: "Bootstrap L0 (+ optional packs) and write .cursor/agent-kit.json."
|
|
5217
6194
|
},
|
|
5218
6195
|
args: {
|
|
5219
6196
|
profile: {
|
|
@@ -5225,6 +6202,17 @@ var installCommand = defineCommand11({
|
|
|
5225
6202
|
type: "string",
|
|
5226
6203
|
description: `Comma-separated L1 pack ids (e.g. cybersec,devops). Known: ${DOMAIN_PACK_IDS.join(", ")}`
|
|
5227
6204
|
},
|
|
6205
|
+
yes: {
|
|
6206
|
+
type: "boolean",
|
|
6207
|
+
alias: "y",
|
|
6208
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
6209
|
+
default: false
|
|
6210
|
+
},
|
|
6211
|
+
"force-root": {
|
|
6212
|
+
type: "boolean",
|
|
6213
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
6214
|
+
default: false
|
|
6215
|
+
},
|
|
5228
6216
|
cwd: {
|
|
5229
6217
|
type: "string",
|
|
5230
6218
|
default: process.cwd()
|
|
@@ -5232,7 +6220,25 @@ var installCommand = defineCommand11({
|
|
|
5232
6220
|
...REGISTRY_CLI_ARGS
|
|
5233
6221
|
},
|
|
5234
6222
|
async run({ args }) {
|
|
5235
|
-
const
|
|
6223
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
6224
|
+
if (nonInteractive) {
|
|
6225
|
+
logger.info("Non-interactive mode: skipping prompts, using defaults.");
|
|
6226
|
+
}
|
|
6227
|
+
let projectRoot;
|
|
6228
|
+
try {
|
|
6229
|
+
projectRoot = await confirmProjectRoot(args.cwd, {
|
|
6230
|
+
nonInteractive,
|
|
6231
|
+
command: "install",
|
|
6232
|
+
forceRoot: args["force-root"]
|
|
6233
|
+
});
|
|
6234
|
+
} catch (err) {
|
|
6235
|
+
if (err instanceof RootRefusedError) {
|
|
6236
|
+
logger.error(err.message);
|
|
6237
|
+
process.exitCode = 1;
|
|
6238
|
+
return;
|
|
6239
|
+
}
|
|
6240
|
+
throw err;
|
|
6241
|
+
}
|
|
5236
6242
|
logger.info(`Installing into: ${projectRoot}`);
|
|
5237
6243
|
const packs = parsePackList(args.pack);
|
|
5238
6244
|
for (const id of packs) {
|
|
@@ -5240,19 +6246,31 @@ var installCommand = defineCommand11({
|
|
|
5240
6246
|
logger.warn(`Pack '${id}' is not in the known L1 list; will still try registry.`);
|
|
5241
6247
|
}
|
|
5242
6248
|
}
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
6249
|
+
try {
|
|
6250
|
+
const result = await withCliProgress(
|
|
6251
|
+
"install",
|
|
6252
|
+
() => performInstall({
|
|
6253
|
+
cwd: projectRoot,
|
|
6254
|
+
profile: args.profile,
|
|
6255
|
+
pack: args.pack,
|
|
6256
|
+
registry: args.registry,
|
|
6257
|
+
url: args.url,
|
|
6258
|
+
ref: args.ref,
|
|
6259
|
+
refresh: args.refresh
|
|
6260
|
+
})
|
|
6261
|
+
);
|
|
6262
|
+
logApplyStats(result.stats);
|
|
6263
|
+
logger.success(`Manifest written: ${result.manifestPath}`);
|
|
6264
|
+
logger.success("Readiness snapshot written: .cursor/context/readiness.json");
|
|
6265
|
+
printReadinessNarrative(result);
|
|
6266
|
+
} catch (err) {
|
|
6267
|
+
const hint = classifyInstallError(err);
|
|
6268
|
+
logger.error(hint.message);
|
|
6269
|
+
console.error(`
|
|
6270
|
+
${hint.recovery}
|
|
6271
|
+
`);
|
|
6272
|
+
process.exit(1);
|
|
6273
|
+
}
|
|
5256
6274
|
}
|
|
5257
6275
|
});
|
|
5258
6276
|
|
|
@@ -5263,7 +6281,7 @@ async function runInitCompatibility(cwd, installer = performInstall) {
|
|
|
5263
6281
|
var initCommand = defineCommand12({
|
|
5264
6282
|
meta: {
|
|
5265
6283
|
name: "init",
|
|
5266
|
-
description: "Guided
|
|
6284
|
+
description: "Guided setup entry (compat path to install + readiness)."
|
|
5267
6285
|
},
|
|
5268
6286
|
args: {
|
|
5269
6287
|
cwd: {
|
|
@@ -5273,25 +6291,42 @@ var initCommand = defineCommand12({
|
|
|
5273
6291
|
}
|
|
5274
6292
|
},
|
|
5275
6293
|
async run({ args }) {
|
|
5276
|
-
|
|
6294
|
+
const nonInteractive = isNonInteractive();
|
|
6295
|
+
if (!nonInteractive) {
|
|
6296
|
+
intro(`agent-kit v${KIT_VERSION}`);
|
|
6297
|
+
} else {
|
|
6298
|
+
logger.info(`agent-kit v${KIT_VERSION} (non-interactive mode)`);
|
|
6299
|
+
}
|
|
5277
6300
|
logger.info("init now uses the canonical install and readiness workflow.");
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
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
|
-
|
|
6301
|
+
try {
|
|
6302
|
+
const result = await withCliProgress("init", () => runInitCompatibility(args.cwd));
|
|
6303
|
+
const pending = result.readiness.pendingActions.length;
|
|
6304
|
+
logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
|
|
6305
|
+
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.";
|
|
6306
|
+
if (!nonInteractive) {
|
|
6307
|
+
outro(nextStep);
|
|
6308
|
+
} else {
|
|
6309
|
+
logger.info(nextStep);
|
|
6310
|
+
}
|
|
6311
|
+
} catch (err) {
|
|
6312
|
+
const hint = classifyInstallError(err);
|
|
6313
|
+
logger.error(hint.message);
|
|
6314
|
+
console.error(`
|
|
6315
|
+
${hint.recovery}
|
|
6316
|
+
`);
|
|
6317
|
+
process.exit(1);
|
|
6318
|
+
}
|
|
5284
6319
|
}
|
|
5285
6320
|
});
|
|
5286
6321
|
|
|
5287
6322
|
// src/commands/monitors.ts
|
|
5288
|
-
import
|
|
6323
|
+
import path35 from "path";
|
|
5289
6324
|
import { defineCommand as defineCommand13 } from "citty";
|
|
5290
6325
|
|
|
5291
6326
|
// src/invariants/monitors-untriaged.ts
|
|
5292
6327
|
import { execFile as execFile5 } from "child_process";
|
|
5293
|
-
import { readFile as
|
|
5294
|
-
import
|
|
6328
|
+
import { readFile as readFile17, readdir as readdir5, stat as stat3 } from "fs/promises";
|
|
6329
|
+
import path34 from "path";
|
|
5295
6330
|
import { promisify as promisify5 } from "util";
|
|
5296
6331
|
|
|
5297
6332
|
// src/invariants/triage-heading.ts
|
|
@@ -5332,7 +6367,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
5332
6367
|
for (const line of stdout.split("\n")) {
|
|
5333
6368
|
if (!line.trim()) continue;
|
|
5334
6369
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
5335
|
-
const base =
|
|
6370
|
+
const base = path34.basename(file);
|
|
5336
6371
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
5337
6372
|
names.add(base);
|
|
5338
6373
|
}
|
|
@@ -5355,15 +6390,15 @@ function monitorSlugFromName(fileName) {
|
|
|
5355
6390
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
5356
6391
|
}
|
|
5357
6392
|
async function selectUntriagedMonitors(rootDir) {
|
|
5358
|
-
const root =
|
|
5359
|
-
const memoryDir =
|
|
6393
|
+
const root = path34.resolve(rootDir);
|
|
6394
|
+
const memoryDir = path34.join(root, ".cursor", "memory");
|
|
5360
6395
|
const allNames = await listMonitorFiles(memoryDir);
|
|
5361
6396
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
5362
6397
|
const byName = /* @__PURE__ */ new Map();
|
|
5363
6398
|
for (const name of allNames) {
|
|
5364
|
-
const abs =
|
|
6399
|
+
const abs = path34.join(memoryDir, name);
|
|
5365
6400
|
try {
|
|
5366
|
-
const [content, st] = await Promise.all([
|
|
6401
|
+
const [content, st] = await Promise.all([readFile17(abs, "utf8"), stat3(abs)]);
|
|
5367
6402
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
5368
6403
|
} catch {
|
|
5369
6404
|
}
|
|
@@ -5376,7 +6411,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
5376
6411
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
5377
6412
|
let handoff = "";
|
|
5378
6413
|
try {
|
|
5379
|
-
handoff = await
|
|
6414
|
+
handoff = await readFile17(path34.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
5380
6415
|
} catch {
|
|
5381
6416
|
handoff = "";
|
|
5382
6417
|
}
|
|
@@ -5396,8 +6431,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
5396
6431
|
const row = byName.get(name);
|
|
5397
6432
|
if (!row) continue;
|
|
5398
6433
|
entries.push({
|
|
5399
|
-
path:
|
|
5400
|
-
relativePath:
|
|
6434
|
+
path: path34.join(memoryDir, name),
|
|
6435
|
+
relativePath: path34.relative(root, path34.join(memoryDir, name)).split(path34.sep).join("/"),
|
|
5401
6436
|
mtimeMs: row.mtimeMs,
|
|
5402
6437
|
hasTriageHeading: false,
|
|
5403
6438
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -5419,7 +6454,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
5419
6454
|
var monitorsCommand = defineCommand13({
|
|
5420
6455
|
meta: {
|
|
5421
6456
|
name: "monitors",
|
|
5422
|
-
description: "
|
|
6457
|
+
description: "Select untriaged plan monitors (--untriaged; SoT for chat triage)."
|
|
5423
6458
|
},
|
|
5424
6459
|
args: {
|
|
5425
6460
|
cwd: {
|
|
@@ -5443,7 +6478,7 @@ var monitorsCommand = defineCommand13({
|
|
|
5443
6478
|
process.exitCode = 2;
|
|
5444
6479
|
return;
|
|
5445
6480
|
}
|
|
5446
|
-
const result = await selectUntriagedMonitors(
|
|
6481
|
+
const result = await selectUntriagedMonitors(path35.resolve(args.cwd));
|
|
5447
6482
|
if (args.json) {
|
|
5448
6483
|
console.log(JSON.stringify(result, null, 2));
|
|
5449
6484
|
return;
|
|
@@ -5459,11 +6494,11 @@ var monitorsCommand = defineCommand13({
|
|
|
5459
6494
|
});
|
|
5460
6495
|
|
|
5461
6496
|
// src/commands/run-plan.ts
|
|
5462
|
-
import
|
|
6497
|
+
import path40 from "path";
|
|
5463
6498
|
import { defineCommand as defineCommand14 } from "citty";
|
|
5464
6499
|
|
|
5465
6500
|
// src/plan-loop/backends.ts
|
|
5466
|
-
import { execFileSync as execFileSync2, spawn as
|
|
6501
|
+
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
5467
6502
|
import { createWriteStream } from "fs";
|
|
5468
6503
|
async function which(bin) {
|
|
5469
6504
|
try {
|
|
@@ -5474,9 +6509,9 @@ async function which(bin) {
|
|
|
5474
6509
|
}
|
|
5475
6510
|
}
|
|
5476
6511
|
function spawnLogged(command, args, logPath) {
|
|
5477
|
-
return new Promise((
|
|
6512
|
+
return new Promise((resolve2, reject) => {
|
|
5478
6513
|
const out = createWriteStream(logPath, { flags: "w" });
|
|
5479
|
-
const child =
|
|
6514
|
+
const child = spawn6(command, args, {
|
|
5480
6515
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5481
6516
|
});
|
|
5482
6517
|
const onData = (chunk) => {
|
|
@@ -5490,7 +6525,7 @@ function spawnLogged(command, args, logPath) {
|
|
|
5490
6525
|
reject(err);
|
|
5491
6526
|
});
|
|
5492
6527
|
child.on("close", (code) => {
|
|
5493
|
-
out.end(() =>
|
|
6528
|
+
out.end(() => resolve2({ exitCode: code ?? 1 }));
|
|
5494
6529
|
});
|
|
5495
6530
|
});
|
|
5496
6531
|
}
|
|
@@ -5544,14 +6579,14 @@ function listBackendIds() {
|
|
|
5544
6579
|
}
|
|
5545
6580
|
|
|
5546
6581
|
// src/plan-loop/run-loop.ts
|
|
5547
|
-
import { mkdir as
|
|
5548
|
-
import
|
|
6582
|
+
import { mkdir as mkdir6, readFile as readFile20, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
6583
|
+
import path39 from "path";
|
|
5549
6584
|
|
|
5550
6585
|
// src/plan-loop/external-review.ts
|
|
5551
|
-
import { spawn as
|
|
5552
|
-
import
|
|
5553
|
-
var CANONICAL_REL =
|
|
5554
|
-
var FALLBACK_REL =
|
|
6586
|
+
import { spawn as spawn7 } from "child_process";
|
|
6587
|
+
import path36 from "path";
|
|
6588
|
+
var CANONICAL_REL = path36.join(".cursor", "scripts", "plan-external-review.sh");
|
|
6589
|
+
var FALLBACK_REL = path36.join("scripts", "plan-external-review.sh");
|
|
5555
6590
|
function isPlanExhaustedReason(reason) {
|
|
5556
6591
|
const r = reason.trim().toLowerCase();
|
|
5557
6592
|
if (!r) return false;
|
|
@@ -5569,12 +6604,12 @@ function shouldArmExternalPlanReview(input) {
|
|
|
5569
6604
|
return false;
|
|
5570
6605
|
}
|
|
5571
6606
|
async function armExternalPlanReview(root, options = {}) {
|
|
5572
|
-
const spawnFn = options.spawnFn ??
|
|
6607
|
+
const spawnFn = options.spawnFn ?? spawn7;
|
|
5573
6608
|
const existsFn = options.existsFn ?? fileExists;
|
|
5574
6609
|
const log = options.log ?? ((line) => console.log(line));
|
|
5575
6610
|
const force = options.force === true;
|
|
5576
|
-
const canonicalPath =
|
|
5577
|
-
const fallbackPath =
|
|
6611
|
+
const canonicalPath = path36.join(root, CANONICAL_REL);
|
|
6612
|
+
const fallbackPath = path36.join(root, FALLBACK_REL);
|
|
5578
6613
|
let scriptPath = null;
|
|
5579
6614
|
let scriptRel = CANONICAL_REL;
|
|
5580
6615
|
if (await existsFn(canonicalPath)) {
|
|
@@ -5592,7 +6627,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
5592
6627
|
}
|
|
5593
6628
|
log("Plan exhausted: arming optional external plan review (opt-in handled by script)...");
|
|
5594
6629
|
const args = force ? [scriptPath, "--force", "--print"] : [scriptPath, "--print"];
|
|
5595
|
-
return new Promise((
|
|
6630
|
+
return new Promise((resolve2) => {
|
|
5596
6631
|
const child = spawnFn("bash", args, {
|
|
5597
6632
|
cwd: root,
|
|
5598
6633
|
env: { ...process.env, AGENT_KIT_HEADLESS: "1" }
|
|
@@ -5613,7 +6648,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
5613
6648
|
log(
|
|
5614
6649
|
`tip: external plan review launcher failed (${err.message}). Manual: ${scriptRel} or /plan-external-review`
|
|
5615
6650
|
);
|
|
5616
|
-
|
|
6651
|
+
resolve2({ invoked: true, exitCode: null, output });
|
|
5617
6652
|
});
|
|
5618
6653
|
child.on("close", (code) => {
|
|
5619
6654
|
if (code !== 0 && code !== null) {
|
|
@@ -5621,17 +6656,17 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
5621
6656
|
`tip: external plan review exited ${code} (ignored; does not fail the loop). Manual: ${scriptRel} or /plan-external-review`
|
|
5622
6657
|
);
|
|
5623
6658
|
}
|
|
5624
|
-
|
|
6659
|
+
resolve2({ invoked: true, exitCode: code, output });
|
|
5625
6660
|
});
|
|
5626
6661
|
});
|
|
5627
6662
|
}
|
|
5628
6663
|
|
|
5629
6664
|
// src/plan-loop/persona-banners.ts
|
|
5630
|
-
import
|
|
6665
|
+
import path37 from "path";
|
|
5631
6666
|
import {
|
|
5632
6667
|
blue,
|
|
5633
|
-
cyan as
|
|
5634
|
-
gray,
|
|
6668
|
+
cyan as cyan3,
|
|
6669
|
+
gray as gray2,
|
|
5635
6670
|
green as green2,
|
|
5636
6671
|
lightGray,
|
|
5637
6672
|
lightGreen,
|
|
@@ -5644,9 +6679,9 @@ var CLI_RUN_PLAN_MODE = "cli-run-plan";
|
|
|
5644
6679
|
var DEFAULT_CLI_PERSONA_ID = "ghost-runner";
|
|
5645
6680
|
var COLORS = {
|
|
5646
6681
|
white,
|
|
5647
|
-
gray,
|
|
6682
|
+
gray: gray2,
|
|
5648
6683
|
green: green2,
|
|
5649
|
-
cyan:
|
|
6684
|
+
cyan: cyan3,
|
|
5650
6685
|
magenta,
|
|
5651
6686
|
yellow: yellow2,
|
|
5652
6687
|
red: red2,
|
|
@@ -5663,7 +6698,7 @@ function resolveColor(name, fallback) {
|
|
|
5663
6698
|
async function resolveCliPersonaId(root) {
|
|
5664
6699
|
try {
|
|
5665
6700
|
const cfg = await readJson(
|
|
5666
|
-
|
|
6701
|
+
path37.join(root, ".cursor", "context", "config.json")
|
|
5667
6702
|
);
|
|
5668
6703
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
5669
6704
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -5674,7 +6709,7 @@ async function resolveCliPersonaId(root) {
|
|
|
5674
6709
|
}
|
|
5675
6710
|
async function loadPersonaPack(root, personaId) {
|
|
5676
6711
|
try {
|
|
5677
|
-
const personaPath =
|
|
6712
|
+
const personaPath = path37.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
5678
6713
|
const pack = await readJson(personaPath);
|
|
5679
6714
|
if (!pack || typeof pack.id !== "string") return null;
|
|
5680
6715
|
return pack;
|
|
@@ -5691,7 +6726,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
5691
6726
|
const banners = persona.cliBanners;
|
|
5692
6727
|
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
5693
6728
|
const primary = resolveColor(persona.ansiPalette?.primary, white);
|
|
5694
|
-
const secondary = resolveColor(persona.ansiPalette?.secondary,
|
|
6729
|
+
const secondary = resolveColor(persona.ansiPalette?.secondary, gray2);
|
|
5695
6730
|
const accent = resolveColor(persona.ansiPalette?.accent, green2);
|
|
5696
6731
|
return {
|
|
5697
6732
|
tickStart(detail) {
|
|
@@ -5723,8 +6758,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
5723
6758
|
}
|
|
5724
6759
|
|
|
5725
6760
|
// src/plan-loop/plan-state.ts
|
|
5726
|
-
import { readFile as
|
|
5727
|
-
import
|
|
6761
|
+
import { readFile as readFile18, readdir as readdir6 } from "fs/promises";
|
|
6762
|
+
import path38 from "path";
|
|
5728
6763
|
function countPendingTodos(raw) {
|
|
5729
6764
|
const lines = raw.split(/\r?\n/);
|
|
5730
6765
|
let inFront = 0;
|
|
@@ -5752,14 +6787,14 @@ function countPendingTodos(raw) {
|
|
|
5752
6787
|
async function findActivePlanFile(plansDir) {
|
|
5753
6788
|
if (!await fileExists(plansDir)) return null;
|
|
5754
6789
|
const files = (await readdir6(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
5755
|
-
return files[0] ?
|
|
6790
|
+
return files[0] ? path38.join(plansDir, files[0]) : null;
|
|
5756
6791
|
}
|
|
5757
6792
|
async function readPlan(planPath) {
|
|
5758
|
-
return
|
|
6793
|
+
return readFile18(planPath, "utf8");
|
|
5759
6794
|
}
|
|
5760
6795
|
|
|
5761
6796
|
// src/plan-loop/sentinel.ts
|
|
5762
|
-
import { readFile as
|
|
6797
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
5763
6798
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
5764
6799
|
function takeFromText(text) {
|
|
5765
6800
|
if (!text) return null;
|
|
@@ -5806,7 +6841,7 @@ function parseSentinelFromLog(content) {
|
|
|
5806
6841
|
}
|
|
5807
6842
|
async function parseSentinelFromLogFile(logPath) {
|
|
5808
6843
|
try {
|
|
5809
|
-
const content = await
|
|
6844
|
+
const content = await readFile19(logPath, "utf8");
|
|
5810
6845
|
return parseSentinelFromLog(content);
|
|
5811
6846
|
} catch {
|
|
5812
6847
|
return { kind: "missing" };
|
|
@@ -5829,16 +6864,16 @@ function sleep(ms) {
|
|
|
5829
6864
|
return new Promise((r) => setTimeout(r, ms));
|
|
5830
6865
|
}
|
|
5831
6866
|
async function runPlanLoop(opts) {
|
|
5832
|
-
const plansDir =
|
|
5833
|
-
const stopFile =
|
|
5834
|
-
const logDir =
|
|
6867
|
+
const plansDir = path39.join(opts.root, ".cursor", "plans");
|
|
6868
|
+
const stopFile = path39.join(opts.root, ".cursor", "loop.stop");
|
|
6869
|
+
const logDir = path39.join(opts.root, ".cursor", "loop-logs");
|
|
5835
6870
|
const planPath = await findActivePlanFile(plansDir);
|
|
5836
6871
|
if (!planPath) {
|
|
5837
6872
|
logger.error("No active plan in .cursor/plans/");
|
|
5838
6873
|
return 1;
|
|
5839
6874
|
}
|
|
5840
6875
|
const pending = async () => countPendingTodos(await readPlan(planPath));
|
|
5841
|
-
await
|
|
6876
|
+
await mkdir6(logDir, { recursive: true });
|
|
5842
6877
|
try {
|
|
5843
6878
|
await unlink2(stopFile);
|
|
5844
6879
|
} catch {
|
|
@@ -5852,7 +6887,7 @@ async function runPlanLoop(opts) {
|
|
|
5852
6887
|
try {
|
|
5853
6888
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
5854
6889
|
const banners = createPersonaBannerPrinter(persona);
|
|
5855
|
-
console.log(`Active plan: ${
|
|
6890
|
+
console.log(`Active plan: ${path39.basename(planPath)}`);
|
|
5856
6891
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
5857
6892
|
console.log(`Backend: ${opts.backend.id}`);
|
|
5858
6893
|
if (persona) {
|
|
@@ -5884,7 +6919,7 @@ async function runPlanLoop(opts) {
|
|
|
5884
6919
|
if (banners) banners.stop(msg);
|
|
5885
6920
|
else console.log(msg);
|
|
5886
6921
|
try {
|
|
5887
|
-
await
|
|
6922
|
+
await rm2(stopFile, { force: true });
|
|
5888
6923
|
} catch {
|
|
5889
6924
|
}
|
|
5890
6925
|
break;
|
|
@@ -5895,27 +6930,30 @@ async function runPlanLoop(opts) {
|
|
|
5895
6930
|
planExhausted = true;
|
|
5896
6931
|
break;
|
|
5897
6932
|
}
|
|
5898
|
-
const logPath =
|
|
5899
|
-
const relLog =
|
|
6933
|
+
const logPath = path39.join(logDir, `tick-${stamp()}.log`);
|
|
6934
|
+
const relLog = path39.relative(opts.root, logPath);
|
|
5900
6935
|
console.log("");
|
|
5901
6936
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
5902
6937
|
if (banners) banners.tickStart(tickLine);
|
|
5903
6938
|
else console.log(tickLine);
|
|
5904
6939
|
let agentExit = 0;
|
|
5905
6940
|
try {
|
|
5906
|
-
const result = await
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
6941
|
+
const result = await withCliProgress(
|
|
6942
|
+
`tick ${tick}`,
|
|
6943
|
+
() => opts.backend.run({
|
|
6944
|
+
workspace: opts.root,
|
|
6945
|
+
prompt: TICK_PROMPT,
|
|
6946
|
+
model: opts.model,
|
|
6947
|
+
logPath
|
|
6948
|
+
})
|
|
6949
|
+
);
|
|
5912
6950
|
agentExit = result.exitCode;
|
|
5913
6951
|
} catch (err) {
|
|
5914
6952
|
logger.error(String(err));
|
|
5915
6953
|
return 1;
|
|
5916
6954
|
}
|
|
5917
6955
|
try {
|
|
5918
|
-
const logText = await
|
|
6956
|
+
const logText = await readFile20(logPath, "utf8");
|
|
5919
6957
|
if (logText.includes("Too many MCP tools")) {
|
|
5920
6958
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
5921
6959
|
if (banners) banners.stop(msg);
|
|
@@ -5964,7 +7002,7 @@ async function runPlanLoop(opts) {
|
|
|
5964
7002
|
}
|
|
5965
7003
|
if (opts.sleepSeconds > 0) {
|
|
5966
7004
|
if (banners) banners.sleep(opts.sleepSeconds);
|
|
5967
|
-
await sleep(opts.sleepSeconds * 1e3);
|
|
7005
|
+
await withCliProgress(`sleep ${opts.sleepSeconds}s`, () => sleep(opts.sleepSeconds * 1e3));
|
|
5968
7006
|
}
|
|
5969
7007
|
}
|
|
5970
7008
|
const pendingNow = await pending();
|
|
@@ -5972,7 +7010,7 @@ async function runPlanLoop(opts) {
|
|
|
5972
7010
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
5973
7011
|
if (banners) banners.phaseComplete(finishDetail);
|
|
5974
7012
|
console.log(
|
|
5975
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
7013
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path39.relative(opts.root, logDir)}/`
|
|
5976
7014
|
);
|
|
5977
7015
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
5978
7016
|
await armExternalPlanReview(opts.root);
|
|
@@ -5987,7 +7025,7 @@ async function runPlanLoop(opts) {
|
|
|
5987
7025
|
var runPlanCommand = defineCommand14({
|
|
5988
7026
|
meta: {
|
|
5989
7027
|
name: "run-plan",
|
|
5990
|
-
description: "Headless continuous plan runner
|
|
7028
|
+
description: "Headless continuous plan runner (one fresh agent per tick). Never git-prod."
|
|
5991
7029
|
},
|
|
5992
7030
|
args: {
|
|
5993
7031
|
cwd: {
|
|
@@ -6043,7 +7081,7 @@ var runPlanCommand = defineCommand14({
|
|
|
6043
7081
|
return;
|
|
6044
7082
|
}
|
|
6045
7083
|
const code = await runPlanLoop({
|
|
6046
|
-
root:
|
|
7084
|
+
root: path40.resolve(args.cwd),
|
|
6047
7085
|
maxTicks,
|
|
6048
7086
|
sleepSeconds,
|
|
6049
7087
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -6059,7 +7097,7 @@ import { defineCommand as defineCommand15 } from "citty";
|
|
|
6059
7097
|
var scanCommand = defineCommand15({
|
|
6060
7098
|
meta: {
|
|
6061
7099
|
name: "scan",
|
|
6062
|
-
description: "Scan the
|
|
7100
|
+
description: "Scan the repository and print the detected profile."
|
|
6063
7101
|
},
|
|
6064
7102
|
args: {
|
|
6065
7103
|
cwd: {
|
|
@@ -6077,7 +7115,7 @@ var scanCommand = defineCommand15({
|
|
|
6077
7115
|
});
|
|
6078
7116
|
|
|
6079
7117
|
// src/commands/status.ts
|
|
6080
|
-
import
|
|
7118
|
+
import path41 from "path";
|
|
6081
7119
|
import { defineCommand as defineCommand16 } from "citty";
|
|
6082
7120
|
function profileStatus(profile) {
|
|
6083
7121
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
@@ -6094,7 +7132,7 @@ function profileStatus(profile) {
|
|
|
6094
7132
|
var statusCommand = defineCommand16({
|
|
6095
7133
|
meta: {
|
|
6096
7134
|
name: "status",
|
|
6097
|
-
description: "Show
|
|
7135
|
+
description: "Show installed kit version, manifest, and optional profile."
|
|
6098
7136
|
},
|
|
6099
7137
|
args: {
|
|
6100
7138
|
cwd: {
|
|
@@ -6108,11 +7146,11 @@ var statusCommand = defineCommand16({
|
|
|
6108
7146
|
}
|
|
6109
7147
|
},
|
|
6110
7148
|
async run({ args }) {
|
|
6111
|
-
const rootDir =
|
|
7149
|
+
const rootDir = path41.resolve(args.cwd);
|
|
6112
7150
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
6113
7151
|
loadAgentKitManifest(rootDir),
|
|
6114
7152
|
readJson(
|
|
6115
|
-
|
|
7153
|
+
path41.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
6116
7154
|
),
|
|
6117
7155
|
runScanner(rootDir)
|
|
6118
7156
|
]);
|
|
@@ -6171,7 +7209,7 @@ import { defineCommand as defineCommand17 } from "citty";
|
|
|
6171
7209
|
|
|
6172
7210
|
// src/lifecycle/check-updates.ts
|
|
6173
7211
|
import { execFile as execFile6 } from "child_process";
|
|
6174
|
-
import
|
|
7212
|
+
import path42 from "path";
|
|
6175
7213
|
import { promisify as promisify6 } from "util";
|
|
6176
7214
|
var execFileAsync5 = promisify6(execFile6);
|
|
6177
7215
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -6262,11 +7300,11 @@ function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
|
6262
7300
|
return Date.now() - last >= ms;
|
|
6263
7301
|
}
|
|
6264
7302
|
async function loadContextConfig2(cwd) {
|
|
6265
|
-
const configPath =
|
|
7303
|
+
const configPath = path42.join(cwd, ".cursor", "context", "config.json");
|
|
6266
7304
|
return readJson(configPath);
|
|
6267
7305
|
}
|
|
6268
7306
|
async function stampLastCheckedAt(cwd) {
|
|
6269
|
-
const configPath =
|
|
7307
|
+
const configPath = path42.join(cwd, ".cursor", "context", "config.json");
|
|
6270
7308
|
const existing = await loadContextConfig2(cwd) ?? {};
|
|
6271
7309
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
6272
7310
|
existing.updateCheck = {
|
|
@@ -6276,6 +7314,117 @@ async function stampLastCheckedAt(cwd) {
|
|
|
6276
7314
|
};
|
|
6277
7315
|
await writeJson(configPath, existing);
|
|
6278
7316
|
}
|
|
7317
|
+
async function readLocalKitVersion(registryRoot) {
|
|
7318
|
+
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
7319
|
+
const data = await readJson(path42.join(registryRoot, rel));
|
|
7320
|
+
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
7321
|
+
return data.version;
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
7324
|
+
return null;
|
|
7325
|
+
}
|
|
7326
|
+
async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
7327
|
+
const registryPath = options.registryPath;
|
|
7328
|
+
if (!registryPath) {
|
|
7329
|
+
throw new Error("checkAgainstLocalRegistry requires registryPath");
|
|
7330
|
+
}
|
|
7331
|
+
let resolved;
|
|
7332
|
+
try {
|
|
7333
|
+
resolved = await resolveRegistryRoot({ cwd, registryPath });
|
|
7334
|
+
} catch (err) {
|
|
7335
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7336
|
+
return {
|
|
7337
|
+
status: "error",
|
|
7338
|
+
installedVersion: manifest.version,
|
|
7339
|
+
latestVersion: null,
|
|
7340
|
+
registryUrl: path42.resolve(registryPath),
|
|
7341
|
+
registryRef: "local",
|
|
7342
|
+
applyRecommended: false,
|
|
7343
|
+
message: `Failed to resolve --registry: ${msg}`
|
|
7344
|
+
};
|
|
7345
|
+
}
|
|
7346
|
+
const registryUrl = resolved.root;
|
|
7347
|
+
const registryRef = "local";
|
|
7348
|
+
try {
|
|
7349
|
+
let latest;
|
|
7350
|
+
if (options.latestVersion !== void 0) {
|
|
7351
|
+
latest = options.latestVersion;
|
|
7352
|
+
} else {
|
|
7353
|
+
const raw = await readLocalKitVersion(resolved.root);
|
|
7354
|
+
latest = raw ? normalizeSemver(raw) : null;
|
|
7355
|
+
}
|
|
7356
|
+
const summary = summarizeDiff(await diffAgainstRegistry(resolved.root, cwd, manifest));
|
|
7357
|
+
const hasFileDrift = summary.drift > 0 || summary["missing-local"] > 0;
|
|
7358
|
+
const installed = normalizeSemver(manifest.version);
|
|
7359
|
+
if (!installed) {
|
|
7360
|
+
return {
|
|
7361
|
+
status: "error",
|
|
7362
|
+
installedVersion: manifest.version,
|
|
7363
|
+
latestVersion: latest,
|
|
7364
|
+
registryUrl,
|
|
7365
|
+
registryRef,
|
|
7366
|
+
applyRecommended: false,
|
|
7367
|
+
message: `Installed version "${manifest.version}" is not valid semver.`
|
|
7368
|
+
};
|
|
7369
|
+
}
|
|
7370
|
+
if (!latest) {
|
|
7371
|
+
return {
|
|
7372
|
+
status: "error",
|
|
7373
|
+
installedVersion: installed,
|
|
7374
|
+
latestVersion: null,
|
|
7375
|
+
registryUrl,
|
|
7376
|
+
registryRef,
|
|
7377
|
+
applyRecommended: false,
|
|
7378
|
+
message: "No semver found in the local --registry checkout (packages/cli/package.json or package.json)."
|
|
7379
|
+
};
|
|
7380
|
+
}
|
|
7381
|
+
const cmp = compareSemver(installed, latest);
|
|
7382
|
+
if (hasFileDrift) {
|
|
7383
|
+
return {
|
|
7384
|
+
status: "update-available",
|
|
7385
|
+
installedVersion: installed,
|
|
7386
|
+
latestVersion: latest,
|
|
7387
|
+
registryUrl,
|
|
7388
|
+
registryRef,
|
|
7389
|
+
applyRecommended: false,
|
|
7390
|
+
message: `Local registry has drift vs this install (drift=${summary.drift}, missing-local=${summary["missing-local"]}; source v${latest}). Run /update --registry <path> (Ask confirm) to apply; never silent.`
|
|
7391
|
+
};
|
|
7392
|
+
}
|
|
7393
|
+
if (cmp === 0) {
|
|
7394
|
+
return {
|
|
7395
|
+
status: "up-to-date",
|
|
7396
|
+
installedVersion: installed,
|
|
7397
|
+
latestVersion: latest,
|
|
7398
|
+
registryUrl,
|
|
7399
|
+
registryRef,
|
|
7400
|
+
applyRecommended: false,
|
|
7401
|
+
message: `Installed v${installed} matches local registry v${latest}.`
|
|
7402
|
+
};
|
|
7403
|
+
}
|
|
7404
|
+
if (cmp < 0) {
|
|
7405
|
+
return {
|
|
7406
|
+
status: "update-available",
|
|
7407
|
+
installedVersion: installed,
|
|
7408
|
+
latestVersion: latest,
|
|
7409
|
+
registryUrl,
|
|
7410
|
+
registryRef,
|
|
7411
|
+
applyRecommended: false,
|
|
7412
|
+
message: `Update available from local registry: v${installed} \u2192 v${latest}. Run /update --registry <path> (Ask confirm) to apply; never silent.`
|
|
7413
|
+
};
|
|
7414
|
+
}
|
|
7415
|
+
return {
|
|
7416
|
+
status: "ahead-of-public",
|
|
7417
|
+
installedVersion: installed,
|
|
7418
|
+
latestVersion: latest,
|
|
7419
|
+
registryUrl,
|
|
7420
|
+
registryRef,
|
|
7421
|
+
applyRecommended: false,
|
|
7422
|
+
message: `Installed v${installed} is ahead of local registry v${latest}.`
|
|
7423
|
+
};
|
|
7424
|
+
} finally {
|
|
7425
|
+
await resolved.unlock?.();
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
6279
7428
|
async function checkForUpdates(cwd, options = {}) {
|
|
6280
7429
|
const manifest = await loadAgentKitManifest(cwd);
|
|
6281
7430
|
if (!manifest) {
|
|
@@ -6289,7 +7438,10 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
6289
7438
|
message: "No .cursor/agent-kit.json \u2014 run agent-kit install first."
|
|
6290
7439
|
};
|
|
6291
7440
|
}
|
|
6292
|
-
|
|
7441
|
+
if (options.registryPath) {
|
|
7442
|
+
return checkAgainstLocalRegistry(cwd, manifest, options);
|
|
7443
|
+
}
|
|
7444
|
+
const registryUrl = options.publicRegistryUrl ?? manifest.registry?.url ?? null;
|
|
6293
7445
|
const registryRef = manifest.registry?.ref ?? null;
|
|
6294
7446
|
if (isFactoryOrDevRegistry(registryUrl, registryRef)) {
|
|
6295
7447
|
return {
|
|
@@ -6410,7 +7562,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
6410
7562
|
var updateCommand = defineCommand17({
|
|
6411
7563
|
meta: {
|
|
6412
7564
|
name: "update",
|
|
6413
|
-
description: "Re-apply L0/packs/skills from the registry
|
|
7565
|
+
description: "Re-apply L0/packs/skills from the registry (never overwrites L3). --check = notify-only."
|
|
6414
7566
|
},
|
|
6415
7567
|
args: {
|
|
6416
7568
|
cwd: {
|
|
@@ -6419,7 +7571,7 @@ var updateCommand = defineCommand17({
|
|
|
6419
7571
|
},
|
|
6420
7572
|
check: {
|
|
6421
7573
|
type: "boolean",
|
|
6422
|
-
description: "Check-only: compare installed version to
|
|
7574
|
+
description: "Check-only: compare installed version to the resolved registry (public tags, or --registry local checkout); never apply L0 writes",
|
|
6423
7575
|
default: false
|
|
6424
7576
|
},
|
|
6425
7577
|
json: {
|
|
@@ -6442,6 +7594,17 @@ var updateCommand = defineCommand17({
|
|
|
6442
7594
|
description: "Seed the managed-hash ledger from current local overlay files before applying (factory/dogfood only; consumers should not use this)",
|
|
6443
7595
|
default: false
|
|
6444
7596
|
},
|
|
7597
|
+
yes: {
|
|
7598
|
+
type: "boolean",
|
|
7599
|
+
alias: "y",
|
|
7600
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
7601
|
+
default: false
|
|
7602
|
+
},
|
|
7603
|
+
"force-root": {
|
|
7604
|
+
type: "boolean",
|
|
7605
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
7606
|
+
default: false
|
|
7607
|
+
},
|
|
6445
7608
|
...REGISTRY_CLI_ARGS
|
|
6446
7609
|
},
|
|
6447
7610
|
async run({ args }) {
|
|
@@ -6449,7 +7612,8 @@ var updateCommand = defineCommand17({
|
|
|
6449
7612
|
const result = await checkForUpdates(args.cwd, {
|
|
6450
7613
|
respectPrefs: Boolean(args["respect-prefs"]),
|
|
6451
7614
|
stamp: Boolean(args.stamp),
|
|
6452
|
-
publicRegistryUrl: typeof args.url === "string" && args.url ? args.url : void 0
|
|
7615
|
+
publicRegistryUrl: typeof args.url === "string" && args.url ? args.url : void 0,
|
|
7616
|
+
registryPath: typeof args.registry === "string" && args.registry ? args.registry : void 0
|
|
6453
7617
|
});
|
|
6454
7618
|
if (args.json) {
|
|
6455
7619
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -6463,48 +7627,71 @@ var updateCommand = defineCommand17({
|
|
|
6463
7627
|
if (result.status === "error") process.exitCode = 2;
|
|
6464
7628
|
return;
|
|
6465
7629
|
}
|
|
6466
|
-
const
|
|
7630
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
7631
|
+
let projectRoot;
|
|
7632
|
+
try {
|
|
7633
|
+
projectRoot = await confirmProjectRoot(args.cwd, {
|
|
7634
|
+
nonInteractive,
|
|
7635
|
+
command: "update",
|
|
7636
|
+
forceRoot: args["force-root"]
|
|
7637
|
+
});
|
|
7638
|
+
} catch (err) {
|
|
7639
|
+
if (err instanceof RootRefusedError) {
|
|
7640
|
+
logger.error(err.message);
|
|
7641
|
+
process.exitCode = 1;
|
|
7642
|
+
return;
|
|
7643
|
+
}
|
|
7644
|
+
throw err;
|
|
7645
|
+
}
|
|
7646
|
+
const existing = await loadAgentKitManifest(projectRoot);
|
|
6467
7647
|
if (!existing) {
|
|
6468
7648
|
logger.warn("No .cursor/agent-kit.json \u2014 run agent-kit install first.");
|
|
6469
7649
|
return;
|
|
6470
7650
|
}
|
|
6471
7651
|
const registry = await resolveRegistryFromCli({
|
|
6472
|
-
cwd:
|
|
7652
|
+
cwd: projectRoot,
|
|
6473
7653
|
registry: args.registry,
|
|
6474
7654
|
url: args.url,
|
|
6475
7655
|
ref: args.ref,
|
|
6476
7656
|
refresh: args.refresh,
|
|
6477
7657
|
manifest: existing
|
|
6478
7658
|
});
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
next.
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
7659
|
+
try {
|
|
7660
|
+
logger.info(`Registry: ${registry.root} (${registry.source})`);
|
|
7661
|
+
const next = buildManifest({
|
|
7662
|
+
version: KIT_VERSION,
|
|
7663
|
+
profile: existing.profile,
|
|
7664
|
+
packs: existing.packs,
|
|
7665
|
+
skills: existing.skills,
|
|
7666
|
+
protected: existing.protected,
|
|
7667
|
+
personalization: existing.personalization,
|
|
7668
|
+
registryUrl: registry.url ?? existing.registry?.url,
|
|
7669
|
+
registryRef: registry.ref ?? existing.registry?.ref
|
|
7670
|
+
});
|
|
7671
|
+
if (existing.overrides?.length) next.overrides = existing.overrides;
|
|
7672
|
+
if (next.version === existing.version && existing.installedAt) {
|
|
7673
|
+
next.installedAt = existing.installedAt;
|
|
7674
|
+
}
|
|
7675
|
+
if (args["seed-overlay"]) {
|
|
7676
|
+
await seedManagedHashLedger(projectRoot);
|
|
7677
|
+
logger.info("Seeded managed-hash ledger from current local overlay files.");
|
|
7678
|
+
}
|
|
7679
|
+
const stats = await withCliProgress(
|
|
7680
|
+
"update",
|
|
7681
|
+
() => syncFromManifest(registry.root, projectRoot, next)
|
|
7682
|
+
);
|
|
7683
|
+
await saveManifest(projectRoot, next);
|
|
7684
|
+
logApplyStats(stats);
|
|
7685
|
+
logger.success("Update complete (L3 protected paths left untouched).");
|
|
7686
|
+
} finally {
|
|
7687
|
+
await registry.unlock?.();
|
|
6497
7688
|
}
|
|
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
7689
|
}
|
|
6503
7690
|
});
|
|
6504
7691
|
|
|
6505
7692
|
// src/commands/validate.ts
|
|
6506
|
-
import { readFile as
|
|
6507
|
-
import
|
|
7693
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
7694
|
+
import path43 from "path";
|
|
6508
7695
|
import { defineCommand as defineCommand18 } from "citty";
|
|
6509
7696
|
|
|
6510
7697
|
// src/invariants/plan-schema.ts
|
|
@@ -6551,9 +7738,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
6551
7738
|
// src/commands/validate.ts
|
|
6552
7739
|
async function resolveEditedPath(cwd, explicit) {
|
|
6553
7740
|
if (explicit) {
|
|
6554
|
-
const filePath2 =
|
|
7741
|
+
const filePath2 = path43.resolve(cwd, explicit);
|
|
6555
7742
|
try {
|
|
6556
|
-
return { filePath: filePath2, content: await
|
|
7743
|
+
return { filePath: filePath2, content: await readFile21(filePath2, "utf8") };
|
|
6557
7744
|
} catch {
|
|
6558
7745
|
return null;
|
|
6559
7746
|
}
|
|
@@ -6561,9 +7748,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
6561
7748
|
const payload = await readStdinJson();
|
|
6562
7749
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
6563
7750
|
if (!rel) return null;
|
|
6564
|
-
const filePath =
|
|
7751
|
+
const filePath = path43.isAbsolute(rel) ? rel : path43.resolve(cwd, rel);
|
|
6565
7752
|
try {
|
|
6566
|
-
return { filePath, content: await
|
|
7753
|
+
return { filePath, content: await readFile21(filePath, "utf8") };
|
|
6567
7754
|
} catch {
|
|
6568
7755
|
return null;
|
|
6569
7756
|
}
|
|
@@ -6578,7 +7765,7 @@ function isPlanPath(filePath) {
|
|
|
6578
7765
|
var validateCommand = defineCommand18({
|
|
6579
7766
|
meta: {
|
|
6580
7767
|
name: "validate",
|
|
6581
|
-
description: "Advisory validators for HANDOFF / plan frontmatter (
|
|
7768
|
+
description: "Advisory validators for HANDOFF / plan frontmatter (hook adapter)."
|
|
6582
7769
|
},
|
|
6583
7770
|
subCommands: {
|
|
6584
7771
|
handoff: defineCommand18({
|
|
@@ -6591,10 +7778,10 @@ var validateCommand = defineCommand18({
|
|
|
6591
7778
|
async run({ args }) {
|
|
6592
7779
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
6593
7780
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
6594
|
-
const filePath = fileArg ?
|
|
7781
|
+
const filePath = fileArg ? path43.resolve(cwd, fileArg) : path43.join(path43.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
6595
7782
|
let content = "";
|
|
6596
7783
|
try {
|
|
6597
|
-
content = await
|
|
7784
|
+
content = await readFile21(filePath, "utf8");
|
|
6598
7785
|
} catch {
|
|
6599
7786
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
6600
7787
|
return;
|
|
@@ -6618,10 +7805,10 @@ var validateCommand = defineCommand18({
|
|
|
6618
7805
|
process.exitCode = 2;
|
|
6619
7806
|
return;
|
|
6620
7807
|
}
|
|
6621
|
-
const filePath =
|
|
7808
|
+
const filePath = path43.resolve(cwd, fileArg);
|
|
6622
7809
|
let content = "";
|
|
6623
7810
|
try {
|
|
6624
|
-
content = await
|
|
7811
|
+
content = await readFile21(filePath, "utf8");
|
|
6625
7812
|
} catch {
|
|
6626
7813
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
6627
7814
|
return;
|
|
@@ -6640,7 +7827,7 @@ var validateCommand = defineCommand18({
|
|
|
6640
7827
|
},
|
|
6641
7828
|
async run({ args }) {
|
|
6642
7829
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
6643
|
-
const resolved = await resolveEditedPath(
|
|
7830
|
+
const resolved = await resolveEditedPath(path43.resolve(cwd));
|
|
6644
7831
|
if (!resolved) {
|
|
6645
7832
|
console.log(JSON.stringify({}));
|
|
6646
7833
|
return;
|
|
@@ -6682,11 +7869,201 @@ var validateCommand = defineCommand18({
|
|
|
6682
7869
|
}
|
|
6683
7870
|
});
|
|
6684
7871
|
|
|
7872
|
+
// src/welcome/help-groups.ts
|
|
7873
|
+
import { bold, gray as gray4, underline } from "kolorist";
|
|
7874
|
+
|
|
7875
|
+
// src/welcome/screen.ts
|
|
7876
|
+
import {
|
|
7877
|
+
blue as blue2,
|
|
7878
|
+
cyan as cyan4,
|
|
7879
|
+
gray as gray3,
|
|
7880
|
+
options as koloristOptions2,
|
|
7881
|
+
lightCyan,
|
|
7882
|
+
trueColor,
|
|
7883
|
+
white as white2
|
|
7884
|
+
} from "kolorist";
|
|
7885
|
+
var KOLORIST_TRUECOLOR = 3;
|
|
7886
|
+
var HELMET_ASCII = [
|
|
7887
|
+
" ____",
|
|
7888
|
+
" .-' '-.",
|
|
7889
|
+
" / .--. \\",
|
|
7890
|
+
" | / \\ |",
|
|
7891
|
+
" | | () | |",
|
|
7892
|
+
" | \\ / |",
|
|
7893
|
+
" \\ '--' /",
|
|
7894
|
+
" '-.____.-'",
|
|
7895
|
+
" /_/ \\_\\"
|
|
7896
|
+
];
|
|
7897
|
+
function hexToRgb(hex) {
|
|
7898
|
+
const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
|
|
7899
|
+
const hexBody = m?.[1];
|
|
7900
|
+
if (!hexBody) throw new Error(`invalid hex color: ${hex}`);
|
|
7901
|
+
const n = Number.parseInt(hexBody, 16);
|
|
7902
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
7903
|
+
}
|
|
7904
|
+
function outlineAnsi(line) {
|
|
7905
|
+
const [r, g, b] = hexToRgb(HELMET_OUTLINE);
|
|
7906
|
+
return trueColor(r, g, b)(line);
|
|
7907
|
+
}
|
|
7908
|
+
function withKoloristColor(fn) {
|
|
7909
|
+
const prevEnabled = koloristOptions2.enabled;
|
|
7910
|
+
const prevLevel = koloristOptions2.supportLevel;
|
|
7911
|
+
koloristOptions2.enabled = true;
|
|
7912
|
+
koloristOptions2.supportLevel = KOLORIST_TRUECOLOR;
|
|
7913
|
+
try {
|
|
7914
|
+
return fn();
|
|
7915
|
+
} finally {
|
|
7916
|
+
koloristOptions2.enabled = prevEnabled;
|
|
7917
|
+
koloristOptions2.supportLevel = prevLevel;
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
function hasCliSubcommand(rawArgs) {
|
|
7921
|
+
return Boolean(rawArgs?.some((arg) => !arg.startsWith("-")));
|
|
7922
|
+
}
|
|
7923
|
+
function renderHelmetAscii(color) {
|
|
7924
|
+
if (!color) return HELMET_ASCII.join("\n");
|
|
7925
|
+
return withKoloristColor(
|
|
7926
|
+
() => HELMET_ASCII.map((line, i) => {
|
|
7927
|
+
if (i <= 1 || i >= HELMET_ASCII.length - 1) return outlineAnsi(line);
|
|
7928
|
+
if (i === 4) return lightCyan(line);
|
|
7929
|
+
return blue2(line);
|
|
7930
|
+
}).join("\n")
|
|
7931
|
+
);
|
|
7932
|
+
}
|
|
7933
|
+
var WELCOME_UTILITY_HINTS = [
|
|
7934
|
+
{ cmd: "agent-kit --help", hint: "grouped commands" },
|
|
7935
|
+
{ cmd: "agent-kit doctor", hint: "repository readiness" },
|
|
7936
|
+
{ cmd: "agent-kit status", hint: "installed kit version" },
|
|
7937
|
+
{ cmd: "agent-kit dashboard", hint: "Mission Control panel" },
|
|
7938
|
+
{ cmd: "agent-kit init", hint: "guided install entry" }
|
|
7939
|
+
];
|
|
7940
|
+
function renderWelcomeScreen(opts = {}) {
|
|
7941
|
+
const version = opts.version ?? KIT_VERSION;
|
|
7942
|
+
const color = shouldUseWelcomeColor(opts);
|
|
7943
|
+
const title = color ? white2("Mission Kit") : "Mission Kit";
|
|
7944
|
+
const product = color ? cyan4("agent-kit") : "agent-kit";
|
|
7945
|
+
const muted = (s) => color ? gray3(s) : s;
|
|
7946
|
+
const lines = [
|
|
7947
|
+
renderHelmetAscii(color),
|
|
7948
|
+
"",
|
|
7949
|
+
`${title} \xB7 ${product} v${version}`,
|
|
7950
|
+
muted("HITL framework for AI-assisted IDEs \xB7 @dadado/agent-kit-cli"),
|
|
7951
|
+
"",
|
|
7952
|
+
muted("Try:"),
|
|
7953
|
+
...(() => {
|
|
7954
|
+
const width = Math.max(...WELCOME_UTILITY_HINTS.map(({ cmd }) => cmd.length));
|
|
7955
|
+
return WELCOME_UTILITY_HINTS.map(({ cmd, hint }) => {
|
|
7956
|
+
const pad = " ".repeat(width - cmd.length + 2);
|
|
7957
|
+
const left = color ? cyan4(` ${cmd}`) : ` ${cmd}`;
|
|
7958
|
+
return `${left}${pad}${muted(hint)}`;
|
|
7959
|
+
});
|
|
7960
|
+
})(),
|
|
7961
|
+
"",
|
|
7962
|
+
muted(
|
|
7963
|
+
"Chat HITL (start-project, git-staging/prod, run-plan-all) stays in Cursor slash commands."
|
|
7964
|
+
),
|
|
7965
|
+
muted(
|
|
7966
|
+
`${shouldUseVisualMotion(opts) ? SPACE_MARKS.star : SPACE_MARKS.tick} ${tipAt(
|
|
7967
|
+
[...version].reduce((n, c) => n + c.charCodeAt(0), 0)
|
|
7968
|
+
)}`
|
|
7969
|
+
)
|
|
7970
|
+
];
|
|
7971
|
+
return `${lines.join("\n")}
|
|
7972
|
+
`;
|
|
7973
|
+
}
|
|
7974
|
+
function printWelcomeScreen(opts = {}) {
|
|
7975
|
+
process.stdout.write(renderWelcomeScreen(opts));
|
|
7976
|
+
}
|
|
7977
|
+
|
|
7978
|
+
// src/welcome/help-groups.ts
|
|
7979
|
+
var CLI_HELP_GROUPS = [
|
|
7980
|
+
{
|
|
7981
|
+
id: "setup",
|
|
7982
|
+
title: "SETUP",
|
|
7983
|
+
commands: ["init", "install", "doctor", "status", "update", "add", "scan"]
|
|
7984
|
+
},
|
|
7985
|
+
{
|
|
7986
|
+
id: "mission",
|
|
7987
|
+
title: "MISSION",
|
|
7988
|
+
commands: ["handoff", "run-plan"]
|
|
7989
|
+
},
|
|
7990
|
+
{
|
|
7991
|
+
id: "dashboard",
|
|
7992
|
+
title: "DASHBOARD",
|
|
7993
|
+
commands: ["dashboard", "dashboard-broadcast", "monitors"]
|
|
7994
|
+
},
|
|
7995
|
+
{
|
|
7996
|
+
id: "integrity",
|
|
7997
|
+
title: "INTEGRITY",
|
|
7998
|
+
commands: ["validate", "guard", "hook", "cursor-awareness", "diff", "contribute"]
|
|
7999
|
+
}
|
|
8000
|
+
];
|
|
8001
|
+
async function resolveCommandMeta(meta) {
|
|
8002
|
+
if (meta == null) return void 0;
|
|
8003
|
+
const value = typeof meta === "function" ? await meta() : meta;
|
|
8004
|
+
return await Promise.resolve(value);
|
|
8005
|
+
}
|
|
8006
|
+
async function resolveSubMeta(subCommands) {
|
|
8007
|
+
const out = /* @__PURE__ */ new Map();
|
|
8008
|
+
for (const [name, raw] of Object.entries(subCommands)) {
|
|
8009
|
+
const resolved = typeof raw === "function" ? await raw() : raw;
|
|
8010
|
+
const def = resolved;
|
|
8011
|
+
const meta = await resolveCommandMeta(def?.meta);
|
|
8012
|
+
out.set(name, meta?.description ?? "");
|
|
8013
|
+
}
|
|
8014
|
+
return out;
|
|
8015
|
+
}
|
|
8016
|
+
async function renderGroupedRootHelp(cmd) {
|
|
8017
|
+
const color = shouldUseWelcomeColor();
|
|
8018
|
+
const u = (s) => color ? underline(bold(s)) : s;
|
|
8019
|
+
const g = (s) => color ? gray4(s) : s;
|
|
8020
|
+
const meta = await resolveCommandMeta(cmd.meta);
|
|
8021
|
+
const name = meta?.name ?? "agent-kit";
|
|
8022
|
+
const version = meta?.version ?? KIT_VERSION;
|
|
8023
|
+
const description = meta?.description ?? "HITL framework for AI-assisted IDEs (Mission Kit family)";
|
|
8024
|
+
const subCommands = await (typeof cmd.subCommands === "function" ? cmd.subCommands() : cmd.subCommands);
|
|
8025
|
+
const descriptions = subCommands ? await resolveSubMeta(subCommands) : /* @__PURE__ */ new Map();
|
|
8026
|
+
const placed = new Set(CLI_HELP_GROUPS.flatMap((grp) => grp.commands));
|
|
8027
|
+
const leftover = [...descriptions.keys()].filter((c) => !placed.has(c)).sort();
|
|
8028
|
+
const groups = [...CLI_HELP_GROUPS];
|
|
8029
|
+
if (leftover.length > 0) {
|
|
8030
|
+
groups.push({ id: "other", title: "OTHER", commands: leftover });
|
|
8031
|
+
}
|
|
8032
|
+
const lines = [
|
|
8033
|
+
g(`${description} (${name} v${version})`),
|
|
8034
|
+
"",
|
|
8035
|
+
`${u("USAGE")} \`${name} <command> [OPTIONS]\``,
|
|
8036
|
+
""
|
|
8037
|
+
];
|
|
8038
|
+
for (const group of groups) {
|
|
8039
|
+
const rows = group.commands.filter((c) => descriptions.has(c));
|
|
8040
|
+
if (rows.length === 0) continue;
|
|
8041
|
+
lines.push(u(group.title), "");
|
|
8042
|
+
const width = Math.max(...rows.map((c) => c.length));
|
|
8043
|
+
for (const c of rows) {
|
|
8044
|
+
const pad = " ".repeat(width - c.length + 2);
|
|
8045
|
+
lines.push(` ${c}${pad}${descriptions.get(c) ?? ""}`);
|
|
8046
|
+
}
|
|
8047
|
+
lines.push("");
|
|
8048
|
+
}
|
|
8049
|
+
const tipMark = shouldUseVisualMotion() ? SPACE_MARKS.star : SPACE_MARKS.tick;
|
|
8050
|
+
const tip = tipAt([...version].reduce((n, c) => n + c.charCodeAt(0), 0));
|
|
8051
|
+
lines.push(
|
|
8052
|
+
g(`Use \`${name} <command> --help\` for more information about a command.`),
|
|
8053
|
+
g(
|
|
8054
|
+
"Chat-only HITL (start-project, backlog, git-staging/prod, run-plan-all) is not a CLI surface."
|
|
8055
|
+
),
|
|
8056
|
+
g(`${tipMark} ${tip}`),
|
|
8057
|
+
""
|
|
8058
|
+
);
|
|
8059
|
+
return lines.join("\n");
|
|
8060
|
+
}
|
|
8061
|
+
|
|
6685
8062
|
// src/index.ts
|
|
6686
8063
|
var main = defineCommand19({
|
|
6687
8064
|
meta: {
|
|
6688
8065
|
name: "agent-kit",
|
|
6689
|
-
description: "HITL framework for AI-assisted IDEs",
|
|
8066
|
+
description: "HITL framework for AI-assisted IDEs (Mission Kit family)",
|
|
6690
8067
|
version: KIT_VERSION
|
|
6691
8068
|
},
|
|
6692
8069
|
subCommands: {
|
|
@@ -6708,6 +8085,19 @@ var main = defineCommand19({
|
|
|
6708
8085
|
guard: guardCommand,
|
|
6709
8086
|
monitors: monitorsCommand,
|
|
6710
8087
|
validate: validateCommand
|
|
8088
|
+
},
|
|
8089
|
+
async run({ rawArgs }) {
|
|
8090
|
+
if (hasCliSubcommand(rawArgs)) return;
|
|
8091
|
+
printWelcomeScreen();
|
|
8092
|
+
}
|
|
8093
|
+
});
|
|
8094
|
+
runMain(main, {
|
|
8095
|
+
showUsage: async (cmd, parent) => {
|
|
8096
|
+
if (!parent) {
|
|
8097
|
+
process.stdout.write(`${await renderGroupedRootHelp(cmd)}
|
|
8098
|
+
`);
|
|
8099
|
+
return;
|
|
8100
|
+
}
|
|
8101
|
+
await showUsage(cmd, parent);
|
|
6711
8102
|
}
|
|
6712
8103
|
});
|
|
6713
|
-
runMain(main);
|