@dadado/agent-kit-cli 5.4.0 → 5.6.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/README.md +9 -1
- package/dashboard/dashboard-data.mjs +72 -1
- package/dashboard/dashboard.html +19 -1
- package/dashboard/social-preview.png +0 -0
- package/dashboard/social-preview.svg +18 -0
- package/dist/index.js +1227 -251
- package/package.json +1 -1
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
|
|
4
|
+
import { defineCommand as defineCommand20, runMain, showUsage } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -216,7 +216,11 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
216
216
|
"86afbea8f64de68a79ad5e374f3132bdbe2582b94321fb3d438314838e36c776",
|
|
217
217
|
"65cc1c0293b145e48ed73ad0ca9ab33cbba5ed834a5bfb31f195ed30c2f143df",
|
|
218
218
|
"f04fcfe31354d1b09aeb256a17e4aab91c98ea48a5cff25e4a0281af3cfb289f",
|
|
219
|
-
"34e559ad9036d93d9cbc96d394bc2bbeb10ce50ead00d58a914158ae19daa76c"
|
|
219
|
+
"34e559ad9036d93d9cbc96d394bc2bbeb10ce50ead00d58a914158ae19daa76c",
|
|
220
|
+
"359a142aeadd769a9b89ac909ae5129940e7e6b0892b70256098c584d97631ce",
|
|
221
|
+
"8eab94f7a78149db1bbbc0fd45f21dc2d874209d534bcff39077fd6e1d2c42fb",
|
|
222
|
+
"9be406f92f7dca71f3af814b14fb26b011f9f12c37c789f7ddc3d647b1a7ab60",
|
|
223
|
+
"bb67ddcdd9ab58ed89287421f5e4b6ebfb3941e6a789287ddec9b9c25b56b1ed"
|
|
220
224
|
]);
|
|
221
225
|
|
|
222
226
|
// src/lifecycle/paths.ts
|
|
@@ -239,7 +243,8 @@ var MANAGED_HASHES_REL = ".cursor/agent-kit.managed-hashes.json";
|
|
|
239
243
|
var CONSUMER_OVERLAY_PREFIXES = [
|
|
240
244
|
".cursor/agents/",
|
|
241
245
|
".cursor/skills/",
|
|
242
|
-
".cursor/commands/"
|
|
246
|
+
".cursor/commands/",
|
|
247
|
+
".claude/commands/"
|
|
243
248
|
];
|
|
244
249
|
function isConsumerOverlayPath(relPath) {
|
|
245
250
|
const norm = relPath.split(path3.sep).join("/");
|
|
@@ -742,8 +747,8 @@ async function hasRegistryIndex(root) {
|
|
|
742
747
|
return fileExists(path5.join(root, "registry", "registry.json"));
|
|
743
748
|
}
|
|
744
749
|
async function cloneRegistry(url, ref, dest) {
|
|
745
|
-
const { mkdir:
|
|
746
|
-
await
|
|
750
|
+
const { mkdir: mkdir8 } = await import("fs/promises");
|
|
751
|
+
await mkdir8(path5.dirname(dest), { recursive: true });
|
|
747
752
|
try {
|
|
748
753
|
await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
|
|
749
754
|
env: gitEnv()
|
|
@@ -1513,6 +1518,10 @@ var L0_ARTIFACTS = [
|
|
|
1513
1518
|
source: ".cursor/commands/dashboard.md",
|
|
1514
1519
|
target: ".cursor/commands/dashboard.md"
|
|
1515
1520
|
},
|
|
1521
|
+
{
|
|
1522
|
+
source: ".cursor/commands/dashboard-broadcast.md",
|
|
1523
|
+
target: ".cursor/commands/dashboard-broadcast.md"
|
|
1524
|
+
},
|
|
1516
1525
|
{
|
|
1517
1526
|
source: ".cursor/commands/git-staging.md",
|
|
1518
1527
|
target: ".cursor/commands/git-staging.md"
|
|
@@ -1541,6 +1550,10 @@ var L0_ARTIFACTS = [
|
|
|
1541
1550
|
source: ".cursor/commands/cursor-update-awareness.md",
|
|
1542
1551
|
target: ".cursor/commands/cursor-update-awareness.md"
|
|
1543
1552
|
},
|
|
1553
|
+
{
|
|
1554
|
+
source: ".cursor/commands/update.md",
|
|
1555
|
+
target: ".cursor/commands/update.md"
|
|
1556
|
+
},
|
|
1544
1557
|
// Context (templates + example config; private config.json is not L0)
|
|
1545
1558
|
{
|
|
1546
1559
|
source: ".cursor/context/templates/plan-external-review-prompt.md",
|
|
@@ -2383,7 +2396,7 @@ var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
|
|
|
2383
2396
|
function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
2384
2397
|
const exists2 = fsHooks.existsSync;
|
|
2385
2398
|
const realpath = fsHooks.realpathSync;
|
|
2386
|
-
const
|
|
2399
|
+
const mkdir8 = fsHooks.mkdirSync;
|
|
2387
2400
|
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2388
2401
|
return { ok: false, error: "invalid repo root" };
|
|
2389
2402
|
}
|
|
@@ -2394,8 +2407,8 @@ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
|
2394
2407
|
return { ok: false, error: "path escape" };
|
|
2395
2408
|
}
|
|
2396
2409
|
try {
|
|
2397
|
-
if (typeof
|
|
2398
|
-
|
|
2410
|
+
if (typeof mkdir8 === "function" && typeof exists2 === "function" && !exists2(contextDir)) {
|
|
2411
|
+
mkdir8(contextDir, { recursive: true });
|
|
2399
2412
|
}
|
|
2400
2413
|
if (typeof realpath === "function" && typeof exists2 === "function" && exists2(abs)) {
|
|
2401
2414
|
const fileReal = String(realpath(abs)).replace(/\\/g, "/");
|
|
@@ -2452,13 +2465,13 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2452
2465
|
}
|
|
2453
2466
|
|
|
2454
2467
|
// src/commands/dashboard.ts
|
|
2455
|
-
function readPreferredBrowserFromWorkspace(cwd,
|
|
2468
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile26 = readFileSync2) {
|
|
2456
2469
|
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2457
2470
|
existsSync,
|
|
2458
2471
|
realpathSync
|
|
2459
2472
|
});
|
|
2460
2473
|
if (!resolved.ok) return null;
|
|
2461
|
-
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync:
|
|
2474
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile26 });
|
|
2462
2475
|
return normalizePreferredBrowser(value);
|
|
2463
2476
|
}
|
|
2464
2477
|
function applyDashboardOpenEnv(env, opts) {
|
|
@@ -2876,7 +2889,7 @@ var diffCommand = defineCommand6({
|
|
|
2876
2889
|
});
|
|
2877
2890
|
|
|
2878
2891
|
// src/commands/doctor.ts
|
|
2879
|
-
import
|
|
2892
|
+
import path25 from "path";
|
|
2880
2893
|
import { defineCommand as defineCommand7 } from "citty";
|
|
2881
2894
|
|
|
2882
2895
|
// src/invariants/hooks-health.ts
|
|
@@ -3092,6 +3105,142 @@ async function assessHooksHealth(rootDir) {
|
|
|
3092
3105
|
};
|
|
3093
3106
|
}
|
|
3094
3107
|
|
|
3108
|
+
// src/readiness/env-checks.ts
|
|
3109
|
+
import { constants as constants3, access as access6, readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
3110
|
+
import { homedir as homedir2 } from "os";
|
|
3111
|
+
import path15 from "path";
|
|
3112
|
+
var MIN_NODE_MAJOR = 20;
|
|
3113
|
+
async function checkBinOnPath(binName, env, platform) {
|
|
3114
|
+
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3115
|
+
if (!pathVar) return false;
|
|
3116
|
+
const dirs = pathVar.split(path15.delimiter).filter(Boolean);
|
|
3117
|
+
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3118
|
+
for (const dir of dirs) {
|
|
3119
|
+
for (const candidate2 of candidates) {
|
|
3120
|
+
try {
|
|
3121
|
+
await access6(path15.join(dir, candidate2), platform === "win32" ? void 0 : constants3.X_OK);
|
|
3122
|
+
return true;
|
|
3123
|
+
} catch {
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return false;
|
|
3128
|
+
}
|
|
3129
|
+
function isNodeVersionOk(nodeVersion, minMajor = MIN_NODE_MAJOR) {
|
|
3130
|
+
const match = /^v?(\d+)/.exec(nodeVersion);
|
|
3131
|
+
if (!match) return false;
|
|
3132
|
+
const major = Number(match[1]);
|
|
3133
|
+
return Number.isFinite(major) && major >= minMajor;
|
|
3134
|
+
}
|
|
3135
|
+
function detectShellName(env, platform) {
|
|
3136
|
+
if (platform === "win32") {
|
|
3137
|
+
if (env.PSModulePath) return "powershell";
|
|
3138
|
+
if (env.ComSpec) return "cmd";
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
const shellPath = env.SHELL;
|
|
3142
|
+
if (!shellPath) return null;
|
|
3143
|
+
const base = path15.basename(shellPath).trim();
|
|
3144
|
+
return base || null;
|
|
3145
|
+
}
|
|
3146
|
+
function detectShellProfile(env, platform, homeDir) {
|
|
3147
|
+
const shellName = detectShellName(env, platform);
|
|
3148
|
+
if (shellName === "zsh") return path15.join(homeDir, ".zshrc");
|
|
3149
|
+
if (shellName === "bash") return path15.join(homeDir, ".bashrc");
|
|
3150
|
+
return null;
|
|
3151
|
+
}
|
|
3152
|
+
function parseNpmrcPrefix(content, homeDir) {
|
|
3153
|
+
const match = /^\s*prefix\s*=\s*(.+?)\s*$/m.exec(content);
|
|
3154
|
+
const captured = match?.[1];
|
|
3155
|
+
if (!captured) return null;
|
|
3156
|
+
let value = captured.trim().replace(/^["']|["']$/g, "");
|
|
3157
|
+
if (value.startsWith("~")) {
|
|
3158
|
+
value = path15.join(homeDir, value.slice(1));
|
|
3159
|
+
}
|
|
3160
|
+
return value || null;
|
|
3161
|
+
}
|
|
3162
|
+
function heuristicPrefixFromExecPath(execPath, platform) {
|
|
3163
|
+
const p = platform === "win32" ? path15.win32 : path15.posix;
|
|
3164
|
+
return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
|
|
3165
|
+
}
|
|
3166
|
+
async function detectNpmPrefix(options = {}) {
|
|
3167
|
+
const env = options.env ?? process.env;
|
|
3168
|
+
const platform = options.platform ?? process.platform;
|
|
3169
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3170
|
+
const execPath = options.execPath ?? process.execPath;
|
|
3171
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile10(filePath, "utf8"));
|
|
3172
|
+
const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
3173
|
+
if (envPrefix?.trim()) {
|
|
3174
|
+
return { prefix: envPrefix.trim(), source: "env" };
|
|
3175
|
+
}
|
|
3176
|
+
const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path15.join(homeDir, ".npmrc");
|
|
3177
|
+
try {
|
|
3178
|
+
const content = await readFileImpl(userconfigPath);
|
|
3179
|
+
const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
|
|
3180
|
+
if (npmrcPrefix) {
|
|
3181
|
+
return { prefix: npmrcPrefix, source: "npmrc" };
|
|
3182
|
+
}
|
|
3183
|
+
} catch {
|
|
3184
|
+
}
|
|
3185
|
+
return { prefix: heuristicPrefixFromExecPath(execPath, platform), source: "heuristic" };
|
|
3186
|
+
}
|
|
3187
|
+
async function describeUnwritablePrefix(prefix) {
|
|
3188
|
+
try {
|
|
3189
|
+
const info = await stat3(prefix);
|
|
3190
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
3191
|
+
if (process.platform !== "win32" && currentUid !== void 0 && info.uid === 0 && currentUid !== 0) {
|
|
3192
|
+
return `root-owned prefix (${prefix}); the classic fresh-install PATH/EACCES blocker`;
|
|
3193
|
+
}
|
|
3194
|
+
} catch {
|
|
3195
|
+
}
|
|
3196
|
+
return `npm prefix is not writable: ${prefix}`;
|
|
3197
|
+
}
|
|
3198
|
+
async function checkNpmPrefixWritable(options = {}) {
|
|
3199
|
+
let detected;
|
|
3200
|
+
try {
|
|
3201
|
+
detected = await detectNpmPrefix(options);
|
|
3202
|
+
} catch {
|
|
3203
|
+
return { prefix: null, writable: false, reason: "npm prefix could not be determined" };
|
|
3204
|
+
}
|
|
3205
|
+
try {
|
|
3206
|
+
await access6(detected.prefix, constants3.W_OK);
|
|
3207
|
+
return { prefix: detected.prefix, writable: true, source: detected.source };
|
|
3208
|
+
} catch {
|
|
3209
|
+
return {
|
|
3210
|
+
prefix: detected.prefix,
|
|
3211
|
+
writable: false,
|
|
3212
|
+
source: detected.source,
|
|
3213
|
+
reason: await describeUnwritablePrefix(detected.prefix)
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
async function assessEnvironment(options = {}) {
|
|
3218
|
+
const env = options.env ?? process.env;
|
|
3219
|
+
const platform = options.platform ?? process.platform;
|
|
3220
|
+
const nodeVersion = options.nodeVersion ?? process.version;
|
|
3221
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3222
|
+
const binName = options.binName ?? "agent-kit";
|
|
3223
|
+
const [binOnPath, npmPrefix] = await Promise.all([
|
|
3224
|
+
checkBinOnPath(binName, env, platform).catch(() => false),
|
|
3225
|
+
checkNpmPrefixWritable(options).catch(
|
|
3226
|
+
() => ({
|
|
3227
|
+
prefix: null,
|
|
3228
|
+
writable: false,
|
|
3229
|
+
reason: "npm prefix check failed unexpectedly"
|
|
3230
|
+
})
|
|
3231
|
+
)
|
|
3232
|
+
]);
|
|
3233
|
+
return {
|
|
3234
|
+
binOnPath,
|
|
3235
|
+
npmPrefixWritable: npmPrefix.writable,
|
|
3236
|
+
npmPrefix,
|
|
3237
|
+
nodeVersionOk: isNodeVersionOk(nodeVersion),
|
|
3238
|
+
nodeVersion,
|
|
3239
|
+
shell: detectShellName(env, platform),
|
|
3240
|
+
shellProfile: detectShellProfile(env, platform, homeDir)
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3095
3244
|
// src/scanner/readiness.ts
|
|
3096
3245
|
import { createHash as createHash3 } from "crypto";
|
|
3097
3246
|
function action(id, status, recommendation, owner) {
|
|
@@ -3339,12 +3488,12 @@ function createReadinessReport(scan, options) {
|
|
|
3339
3488
|
}
|
|
3340
3489
|
|
|
3341
3490
|
// src/scanner/safe-fixes.ts
|
|
3342
|
-
import { readFile as
|
|
3343
|
-
import
|
|
3491
|
+
import { readFile as readFile13, writeFile as writeFile5 } from "fs/promises";
|
|
3492
|
+
import path23 from "path";
|
|
3344
3493
|
|
|
3345
3494
|
// src/scanner/detect-repository.ts
|
|
3346
|
-
import { readFile as
|
|
3347
|
-
import
|
|
3495
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3496
|
+
import path16 from "path";
|
|
3348
3497
|
var CONTEXT_PATHS = [
|
|
3349
3498
|
["README.md", "README"],
|
|
3350
3499
|
["README", "README"],
|
|
@@ -3363,7 +3512,7 @@ var CONTEXT_PATHS = [
|
|
|
3363
3512
|
async function existingEvidence(rootDir, candidates) {
|
|
3364
3513
|
const evidence = await Promise.all(
|
|
3365
3514
|
candidates.map(
|
|
3366
|
-
async ([relativePath, label]) => await fileExists(
|
|
3515
|
+
async ([relativePath, label]) => await fileExists(path16.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
3367
3516
|
)
|
|
3368
3517
|
);
|
|
3369
3518
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -3384,7 +3533,7 @@ async function detectContext(rootDir) {
|
|
|
3384
3533
|
async function detectPurpose(rootDir, stack) {
|
|
3385
3534
|
const entries = await listDirectory(rootDir);
|
|
3386
3535
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
3387
|
-
const packageJson = await readJson(
|
|
3536
|
+
const packageJson = await readJson(path16.join(rootDir, "package.json"));
|
|
3388
3537
|
const categories = [];
|
|
3389
3538
|
const evidence = [];
|
|
3390
3539
|
const add = (category, value2) => {
|
|
@@ -3424,16 +3573,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
3424
3573
|
}
|
|
3425
3574
|
async function detectAgentKit(rootDir) {
|
|
3426
3575
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
3427
|
-
const manifestPath =
|
|
3576
|
+
const manifestPath = path16.join(rootDir, manifestRelativePath);
|
|
3428
3577
|
const installed = await fileExists(manifestPath);
|
|
3429
3578
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
3430
3579
|
return {
|
|
3431
3580
|
installed,
|
|
3432
3581
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
3433
3582
|
version: manifest?.version,
|
|
3434
|
-
hasPlans: await fileExists(
|
|
3435
|
-
hasHandoff: await fileExists(
|
|
3436
|
-
hasMemory: await fileExists(
|
|
3583
|
+
hasPlans: await fileExists(path16.join(rootDir, ".cursor/plans")),
|
|
3584
|
+
hasHandoff: await fileExists(path16.join(rootDir, ".cursor/HANDOFF.md")),
|
|
3585
|
+
hasMemory: await fileExists(path16.join(rootDir, ".cursor/memory"))
|
|
3437
3586
|
};
|
|
3438
3587
|
}
|
|
3439
3588
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -3447,9 +3596,9 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
3447
3596
|
"*service-account*.json"
|
|
3448
3597
|
];
|
|
3449
3598
|
async function detectSafety(rootDir, trackedFiles) {
|
|
3450
|
-
const gitignorePath =
|
|
3599
|
+
const gitignorePath = path16.join(rootDir, ".gitignore");
|
|
3451
3600
|
const hasGitignore = await fileExists(gitignorePath);
|
|
3452
|
-
const gitignore = hasGitignore ? await
|
|
3601
|
+
const gitignore = hasGitignore ? await readFile11(gitignorePath, "utf8") : "";
|
|
3453
3602
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
3454
3603
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
3455
3604
|
(pattern) => lines.includes(pattern)
|
|
@@ -3458,7 +3607,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3458
3607
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
3459
3608
|
);
|
|
3460
3609
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
3461
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
3610
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path16.join(rootDir, item))))).some(Boolean);
|
|
3462
3611
|
const guardCandidates = [
|
|
3463
3612
|
".husky/pre-commit",
|
|
3464
3613
|
".husky/pre-push",
|
|
@@ -3467,7 +3616,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3467
3616
|
];
|
|
3468
3617
|
const guardContents = await Promise.all(
|
|
3469
3618
|
guardCandidates.map(
|
|
3470
|
-
async (item) => await fileExists(
|
|
3619
|
+
async (item) => await fileExists(path16.join(rootDir, item)) ? readFile11(path16.join(rootDir, item), "utf8") : ""
|
|
3471
3620
|
)
|
|
3472
3621
|
);
|
|
3473
3622
|
return {
|
|
@@ -3485,11 +3634,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3485
3634
|
}
|
|
3486
3635
|
|
|
3487
3636
|
// src/scanner/scan.ts
|
|
3488
|
-
import
|
|
3637
|
+
import path22 from "path";
|
|
3489
3638
|
|
|
3490
3639
|
// src/scanner/detect-git.ts
|
|
3491
3640
|
import { execFile as execFile3 } from "child_process";
|
|
3492
|
-
import
|
|
3641
|
+
import path17 from "path";
|
|
3493
3642
|
import { promisify as promisify3 } from "util";
|
|
3494
3643
|
var exec = promisify3(execFile3);
|
|
3495
3644
|
function remoteHostname(remoteUrl) {
|
|
@@ -3513,7 +3662,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
3513
3662
|
}
|
|
3514
3663
|
async function detectProvider(rootDir, remoteUrl) {
|
|
3515
3664
|
const configuration = await readJson(
|
|
3516
|
-
|
|
3665
|
+
path17.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
3517
3666
|
);
|
|
3518
3667
|
const configuredProvider = configuration?.git?.provider;
|
|
3519
3668
|
if (configuredProvider) {
|
|
@@ -3570,7 +3719,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
3570
3719
|
evidence: remoteEvidence
|
|
3571
3720
|
};
|
|
3572
3721
|
}
|
|
3573
|
-
if (await fileExists(
|
|
3722
|
+
if (await fileExists(path17.join(rootDir, ".gitlab-ci.yml"))) {
|
|
3574
3723
|
return {
|
|
3575
3724
|
provider: "gitlab",
|
|
3576
3725
|
providerKind: "gitlab-self-hosted",
|
|
@@ -3659,11 +3808,11 @@ async function detectGit(rootDir) {
|
|
|
3659
3808
|
}
|
|
3660
3809
|
|
|
3661
3810
|
// src/scanner/detect-ide.ts
|
|
3662
|
-
import
|
|
3811
|
+
import path18 from "path";
|
|
3663
3812
|
async function detectIde(rootDir) {
|
|
3664
|
-
const hasCursor = await fileExists(
|
|
3665
|
-
const hasVSCode = await fileExists(
|
|
3666
|
-
const hasWindsurf = await fileExists(
|
|
3813
|
+
const hasCursor = await fileExists(path18.join(rootDir, ".cursor"));
|
|
3814
|
+
const hasVSCode = await fileExists(path18.join(rootDir, ".vscode"));
|
|
3815
|
+
const hasWindsurf = await fileExists(path18.join(rootDir, ".windsurfrules"));
|
|
3667
3816
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
3668
3817
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
3669
3818
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -3671,7 +3820,7 @@ async function detectIde(rootDir) {
|
|
|
3671
3820
|
}
|
|
3672
3821
|
|
|
3673
3822
|
// src/scanner/detect-infra.ts
|
|
3674
|
-
import
|
|
3823
|
+
import path19 from "path";
|
|
3675
3824
|
|
|
3676
3825
|
// src/types.ts
|
|
3677
3826
|
var GIT_PLATFORM_META = {
|
|
@@ -3727,12 +3876,12 @@ var PM_TOOL_LABELS = {
|
|
|
3727
3876
|
|
|
3728
3877
|
// src/scanner/detect-infra.ts
|
|
3729
3878
|
async function detectInfra(rootDir) {
|
|
3730
|
-
const docker = await fileExists(
|
|
3731
|
-
const kubernetes = await fileExists(
|
|
3879
|
+
const docker = await fileExists(path19.join(rootDir, "Dockerfile")) || await fileExists(path19.join(rootDir, "docker-compose.yml")) || await fileExists(path19.join(rootDir, "docker-compose.yaml"));
|
|
3880
|
+
const kubernetes = await fileExists(path19.join(rootDir, "k8s")) || await fileExists(path19.join(rootDir, "kubernetes"));
|
|
3732
3881
|
let ci = "none";
|
|
3733
3882
|
const ciFiles = [];
|
|
3734
3883
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
3735
|
-
if (await fileExists(
|
|
3884
|
+
if (await fileExists(path19.join(rootDir, filePath))) {
|
|
3736
3885
|
if (ci === "none") ci = platform;
|
|
3737
3886
|
ciFiles.push(filePath);
|
|
3738
3887
|
}
|
|
@@ -3757,30 +3906,30 @@ async function detectInfra(rootDir) {
|
|
|
3757
3906
|
];
|
|
3758
3907
|
const infrastructureFiles = (await Promise.all(
|
|
3759
3908
|
infrastructureCandidates.map(
|
|
3760
|
-
async (file) => await fileExists(
|
|
3909
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3761
3910
|
)
|
|
3762
3911
|
)).filter((file) => file !== void 0);
|
|
3763
3912
|
const deploymentFiles = (await Promise.all(
|
|
3764
3913
|
deploymentCandidates.map(
|
|
3765
|
-
async (file) => await fileExists(
|
|
3914
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3766
3915
|
)
|
|
3767
3916
|
)).filter((file) => file !== void 0);
|
|
3768
3917
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
3769
3918
|
}
|
|
3770
3919
|
|
|
3771
3920
|
// src/scanner/detect-services.ts
|
|
3772
|
-
import { readFile as
|
|
3773
|
-
import
|
|
3921
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
3922
|
+
import path20 from "path";
|
|
3774
3923
|
async function detectProjectManagement(rootDir) {
|
|
3775
3924
|
const tools = [];
|
|
3776
3925
|
const mcpConfigPaths = [
|
|
3777
|
-
|
|
3778
|
-
|
|
3926
|
+
path20.join(rootDir, ".cursor", "mcp.json"),
|
|
3927
|
+
path20.join(rootDir, "mcp.json")
|
|
3779
3928
|
];
|
|
3780
3929
|
for (const configPath of mcpConfigPaths) {
|
|
3781
3930
|
if (!await fileExists(configPath)) continue;
|
|
3782
3931
|
try {
|
|
3783
|
-
const raw = await
|
|
3932
|
+
const raw = await readFile12(configPath, "utf8");
|
|
3784
3933
|
const lower = raw.toLowerCase();
|
|
3785
3934
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
3786
3935
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -3791,20 +3940,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
3791
3940
|
} catch {
|
|
3792
3941
|
}
|
|
3793
3942
|
}
|
|
3794
|
-
if (await fileExists(
|
|
3943
|
+
if (await fileExists(path20.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
3795
3944
|
tools.push("github-issues");
|
|
3796
3945
|
}
|
|
3797
|
-
if (await fileExists(
|
|
3946
|
+
if (await fileExists(path20.join(rootDir, ".github", "projects"))) {
|
|
3798
3947
|
tools.push("github-projects");
|
|
3799
3948
|
}
|
|
3800
3949
|
return [...new Set(tools)];
|
|
3801
3950
|
}
|
|
3802
3951
|
async function detectServices(rootDir) {
|
|
3803
|
-
const hasPrisma = await fileExists(
|
|
3804
|
-
const hasSequelize = await fileExists(
|
|
3805
|
-
const hasDrizzle = await fileExists(
|
|
3806
|
-
const hasKnex = await fileExists(
|
|
3807
|
-
const hasTypeorm = await fileExists(
|
|
3952
|
+
const hasPrisma = await fileExists(path20.join(rootDir, "prisma/schema.prisma"));
|
|
3953
|
+
const hasSequelize = await fileExists(path20.join(rootDir, "sequelize"));
|
|
3954
|
+
const hasDrizzle = await fileExists(path20.join(rootDir, "drizzle.config.ts"));
|
|
3955
|
+
const hasKnex = await fileExists(path20.join(rootDir, "knexfile.ts"));
|
|
3956
|
+
const hasTypeorm = await fileExists(path20.join(rootDir, "ormconfig.json"));
|
|
3808
3957
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
3809
3958
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
3810
3959
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -3816,7 +3965,7 @@ async function detectServices(rootDir) {
|
|
|
3816
3965
|
}
|
|
3817
3966
|
|
|
3818
3967
|
// src/scanner/detect-stack.ts
|
|
3819
|
-
import
|
|
3968
|
+
import path21 from "path";
|
|
3820
3969
|
var PROJECT_MARKERS = [
|
|
3821
3970
|
"package.json",
|
|
3822
3971
|
"requirements.txt",
|
|
@@ -3845,7 +3994,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
3845
3994
|
};
|
|
3846
3995
|
}
|
|
3847
3996
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
3848
|
-
if (await fileExists(
|
|
3997
|
+
if (await fileExists(path21.join(rootDir, lockfile))) {
|
|
3849
3998
|
return {
|
|
3850
3999
|
packageManager,
|
|
3851
4000
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -3862,27 +4011,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
3862
4011
|
return { testCommands, validationCommands };
|
|
3863
4012
|
}
|
|
3864
4013
|
async function detectStack(rootDir) {
|
|
3865
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
3866
|
-
const hasPackageJson = await fileExists(
|
|
4014
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path21.join(rootDir, item))))).some(Boolean);
|
|
4015
|
+
const hasPackageJson = await fileExists(path21.join(rootDir, "package.json"));
|
|
3867
4016
|
if (hasPackageJson) {
|
|
3868
|
-
const packageJson = await readJson(
|
|
4017
|
+
const packageJson = await readJson(path21.join(rootDir, "package.json")) ?? {};
|
|
3869
4018
|
const scripts = packageJson.scripts ?? {};
|
|
3870
4019
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
3871
4020
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
3872
|
-
const hasNextConfig = await fileExists(
|
|
3873
|
-
const hasNestConfig = await fileExists(
|
|
4021
|
+
const hasNextConfig = await fileExists(path21.join(rootDir, "next.config.js")) || await fileExists(path21.join(rootDir, "next.config.mjs")) || await fileExists(path21.join(rootDir, "next.config.ts"));
|
|
4022
|
+
const hasNestConfig = await fileExists(path21.join(rootDir, "nest-cli.json"));
|
|
3874
4023
|
return {
|
|
3875
4024
|
language: "node",
|
|
3876
4025
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
3877
4026
|
packageManager: packageManager.packageManager,
|
|
3878
4027
|
packageManagerEvidence: packageManager.evidence,
|
|
3879
4028
|
scripts,
|
|
3880
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
4029
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path21.join(rootDir, "pnpm-workspace.yaml")),
|
|
3881
4030
|
...commands,
|
|
3882
4031
|
hasProjectFiles: hasAnyProjectMarker
|
|
3883
4032
|
};
|
|
3884
4033
|
}
|
|
3885
|
-
if (await fileExists(
|
|
4034
|
+
if (await fileExists(path21.join(rootDir, "pyproject.toml"))) {
|
|
3886
4035
|
return {
|
|
3887
4036
|
language: "python",
|
|
3888
4037
|
framework: "python",
|
|
@@ -3892,7 +4041,7 @@ async function detectStack(rootDir) {
|
|
|
3892
4041
|
hasProjectFiles: hasAnyProjectMarker
|
|
3893
4042
|
};
|
|
3894
4043
|
}
|
|
3895
|
-
if (await fileExists(
|
|
4044
|
+
if (await fileExists(path21.join(rootDir, "go.mod"))) {
|
|
3896
4045
|
return {
|
|
3897
4046
|
language: "go",
|
|
3898
4047
|
framework: "go",
|
|
@@ -3902,7 +4051,7 @@ async function detectStack(rootDir) {
|
|
|
3902
4051
|
hasProjectFiles: hasAnyProjectMarker
|
|
3903
4052
|
};
|
|
3904
4053
|
}
|
|
3905
|
-
if (await fileExists(
|
|
4054
|
+
if (await fileExists(path21.join(rootDir, "Cargo.toml"))) {
|
|
3906
4055
|
return {
|
|
3907
4056
|
language: "rust",
|
|
3908
4057
|
framework: "rust",
|
|
@@ -3912,7 +4061,7 @@ async function detectStack(rootDir) {
|
|
|
3912
4061
|
hasProjectFiles: hasAnyProjectMarker
|
|
3913
4062
|
};
|
|
3914
4063
|
}
|
|
3915
|
-
if (await fileExists(
|
|
4064
|
+
if (await fileExists(path21.join(rootDir, "composer.json"))) {
|
|
3916
4065
|
return {
|
|
3917
4066
|
language: "php",
|
|
3918
4067
|
framework: "php",
|
|
@@ -3945,7 +4094,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
3945
4094
|
return meaningful.length === 0;
|
|
3946
4095
|
}
|
|
3947
4096
|
async function runScanner(rootDir) {
|
|
3948
|
-
const normalizedRoot =
|
|
4097
|
+
const normalizedRoot = path22.resolve(rootDir);
|
|
3949
4098
|
const entries = await listDirectory(normalizedRoot);
|
|
3950
4099
|
const stack = await detectStack(normalizedRoot);
|
|
3951
4100
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -4149,7 +4298,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4149
4298
|
});
|
|
4150
4299
|
const changes = [];
|
|
4151
4300
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
4152
|
-
const absolutePath =
|
|
4301
|
+
const absolutePath = path23.join(beforeScan.rootDir, relativePath);
|
|
4153
4302
|
const exists2 = await fileExists(absolutePath);
|
|
4154
4303
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
4155
4304
|
recordChange(
|
|
@@ -4162,8 +4311,8 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4162
4311
|
);
|
|
4163
4312
|
}
|
|
4164
4313
|
const gitignoreRelativePath = ".gitignore";
|
|
4165
|
-
const gitignorePath =
|
|
4166
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
4314
|
+
const gitignorePath = path23.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
4315
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile13(gitignorePath, "utf8") : "";
|
|
4167
4316
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
4168
4317
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
4169
4318
|
if (gitignoreChanged && !dryRun) await writeFile5(gitignorePath, mergedGitignore, "utf8");
|
|
@@ -4178,7 +4327,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4178
4327
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
4179
4328
|
)
|
|
4180
4329
|
);
|
|
4181
|
-
const profilePath =
|
|
4330
|
+
const profilePath = path23.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
4182
4331
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
4183
4332
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
4184
4333
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -4200,7 +4349,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4200
4349
|
generatorVersion: options.generatorVersion,
|
|
4201
4350
|
generatedAt
|
|
4202
4351
|
});
|
|
4203
|
-
const contextConfigPath =
|
|
4352
|
+
const contextConfigPath = path23.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
4204
4353
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
4205
4354
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
4206
4355
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -4229,10 +4378,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4229
4378
|
}
|
|
4230
4379
|
|
|
4231
4380
|
// src/scanner/snapshot.ts
|
|
4232
|
-
import
|
|
4381
|
+
import path24 from "path";
|
|
4233
4382
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
4234
4383
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
4235
|
-
const snapshotPath =
|
|
4384
|
+
const snapshotPath = path24.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
4236
4385
|
await writeJson(snapshotPath, report);
|
|
4237
4386
|
return snapshotPath;
|
|
4238
4387
|
}
|
|
@@ -4402,15 +4551,16 @@ async function withCliProgress(label, fn, opts) {
|
|
|
4402
4551
|
|
|
4403
4552
|
// src/commands/doctor.ts
|
|
4404
4553
|
async function runDoctor(cwd, options = {}) {
|
|
4405
|
-
const rootDir =
|
|
4554
|
+
const rootDir = path25.resolve(cwd);
|
|
4406
4555
|
const hooks = await assessHooksHealth(rootDir);
|
|
4556
|
+
const env = await assessEnvironment();
|
|
4407
4557
|
if (options.fixSafe) {
|
|
4408
4558
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
4409
4559
|
generatorVersion: KIT_VERSION,
|
|
4410
4560
|
generatedAt: options.generatedAt
|
|
4411
4561
|
});
|
|
4412
4562
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
4413
|
-
return { report: execution.after, safeChanges: execution.changes, hooks };
|
|
4563
|
+
return { report: execution.after, safeChanges: execution.changes, hooks, env };
|
|
4414
4564
|
}
|
|
4415
4565
|
const scan = await runScanner(rootDir);
|
|
4416
4566
|
const report = createReadinessReport(scan, {
|
|
@@ -4418,7 +4568,7 @@ async function runDoctor(cwd, options = {}) {
|
|
|
4418
4568
|
generatedAt: options.generatedAt
|
|
4419
4569
|
});
|
|
4420
4570
|
await writeReadinessSnapshot(rootDir, report);
|
|
4421
|
-
return { report, safeChanges: [], hooks };
|
|
4571
|
+
return { report, safeChanges: [], hooks, env };
|
|
4422
4572
|
}
|
|
4423
4573
|
function printDoctorSummary(result) {
|
|
4424
4574
|
const { summary, pendingActions } = result.report;
|
|
@@ -4442,6 +4592,20 @@ function printDoctorSummary(result) {
|
|
|
4442
4592
|
console.log(` - ${tip}`);
|
|
4443
4593
|
}
|
|
4444
4594
|
}
|
|
4595
|
+
console.log("environment:");
|
|
4596
|
+
console.log(` - bin on PATH (agent-kit): ${result.env.binOnPath ? "ok" : "MISSING"}`);
|
|
4597
|
+
console.log(
|
|
4598
|
+
` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
|
|
4599
|
+
);
|
|
4600
|
+
if (!result.env.npmPrefixWritable && result.env.npmPrefix.reason) {
|
|
4601
|
+
console.log(` - ${result.env.npmPrefix.reason}`);
|
|
4602
|
+
}
|
|
4603
|
+
console.log(
|
|
4604
|
+
` - node version >= 20: ${result.env.nodeVersionOk ? "ok" : "TOO OLD"} (${result.env.nodeVersion})`
|
|
4605
|
+
);
|
|
4606
|
+
console.log(
|
|
4607
|
+
` - shell profile: ${result.env.shellProfile ?? "not detected (zsh/bash only)"}${result.env.shell ? ` (shell: ${result.env.shell})` : ""}`
|
|
4608
|
+
);
|
|
4445
4609
|
if (process.env.ALLOW_MAIN_PUSH === "1") {
|
|
4446
4610
|
console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
|
|
4447
4611
|
console.log(" This disables main-push protection for agent Shell commands.");
|
|
@@ -4522,13 +4686,21 @@ var SECRET_PATTERNS2 = [
|
|
|
4522
4686
|
id: "github-pat",
|
|
4523
4687
|
re: /\bghp_[A-Za-z0-9_]{36,}\b/
|
|
4524
4688
|
},
|
|
4689
|
+
// Hyphenated vendor keys (`sk-ant-api03-…`, `sk-proj-…`) cannot be matched by
|
|
4690
|
+
// `openai-sk`: its body class excludes `-`, so it stops at the first separator.
|
|
4691
|
+
// Listed before `openai-sk`; the two cannot both hit the same span.
|
|
4692
|
+
{
|
|
4693
|
+
id: "sk-hyphenated-vendor",
|
|
4694
|
+
re: /\bsk-[A-Za-z0-9]{2,12}-[A-Za-z0-9_-]{16,}\b/
|
|
4695
|
+
},
|
|
4525
4696
|
{
|
|
4697
|
+
// Single-segment `sk-` bodies only (no `-` in the class) — see `sk-hyphenated-vendor`.
|
|
4526
4698
|
id: "openai-sk",
|
|
4527
4699
|
re: /\bsk-[A-Za-z0-9]{20,}\b/
|
|
4528
4700
|
}
|
|
4529
4701
|
];
|
|
4530
4702
|
function maskSecretExcerpt(raw) {
|
|
4531
|
-
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_]{4,})/g, (_m, p1, p2) => {
|
|
4703
|
+
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_-]{4,})/g, (_m, p1, p2) => {
|
|
4532
4704
|
return `${p1}${"*".repeat(Math.min(8, p2.length))}`;
|
|
4533
4705
|
}).replace(
|
|
4534
4706
|
/(=\s*['"]?)([^\s'"]{4,})/g,
|
|
@@ -4752,7 +4924,7 @@ var guardCommand = defineCommand8({
|
|
|
4752
4924
|
shell: defineCommand8({
|
|
4753
4925
|
meta: {
|
|
4754
4926
|
name: "shell",
|
|
4755
|
-
description: "Evaluate a shell command against the
|
|
4927
|
+
description: "Evaluate a shell command against the git-workflow / protected-branch deny-list (git checkout|restore|reset --hard|clean -fd + pushes to main/master/prod). Not a general destructive-command guard: rm -rf, chmod, dd are allowed."
|
|
4756
4928
|
},
|
|
4757
4929
|
args: {
|
|
4758
4930
|
json: {
|
|
@@ -4810,8 +4982,8 @@ var guardCommand = defineCommand8({
|
|
|
4810
4982
|
|
|
4811
4983
|
// src/commands/handoff.ts
|
|
4812
4984
|
import { spawn as spawn4 } from "child_process";
|
|
4813
|
-
import { readFile as
|
|
4814
|
-
import
|
|
4985
|
+
import { readFile as readFile14, readdir as readdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4986
|
+
import path26 from "path";
|
|
4815
4987
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4816
4988
|
function parsePlanFrontmatter(raw) {
|
|
4817
4989
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -4831,19 +5003,19 @@ async function findActivePlan(plansDir) {
|
|
|
4831
5003
|
if (!await fileExists(plansDir)) return null;
|
|
4832
5004
|
const files = (await readdir5(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
4833
5005
|
for (const file of files) {
|
|
4834
|
-
const raw = await
|
|
5006
|
+
const raw = await readFile14(path26.join(plansDir, file), "utf8");
|
|
4835
5007
|
const fm = parsePlanFrontmatter(raw);
|
|
4836
5008
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
4837
5009
|
return { file, raw };
|
|
4838
5010
|
}
|
|
4839
5011
|
}
|
|
4840
|
-
return files[0] ? { file: files[0], raw: await
|
|
5012
|
+
return files[0] ? { file: files[0], raw: await readFile14(path26.join(plansDir, files[0]), "utf8") } : null;
|
|
4841
5013
|
}
|
|
4842
5014
|
function now() {
|
|
4843
5015
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
4844
5016
|
}
|
|
4845
5017
|
async function loadProfile(rootDir) {
|
|
4846
|
-
const configPath =
|
|
5018
|
+
const configPath = path26.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
4847
5019
|
try {
|
|
4848
5020
|
return await readJson(configPath);
|
|
4849
5021
|
} catch {
|
|
@@ -4943,13 +5115,13 @@ var handoffCommand = defineCommand9({
|
|
|
4943
5115
|
},
|
|
4944
5116
|
async run({ args }) {
|
|
4945
5117
|
const profile = await loadProfile(args.cwd);
|
|
4946
|
-
const plansDir =
|
|
4947
|
-
const handoffPath =
|
|
5118
|
+
const plansDir = path26.join(args.cwd, ".cursor", "plans");
|
|
5119
|
+
const handoffPath = path26.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
4948
5120
|
const plan = await findActivePlan(plansDir);
|
|
4949
5121
|
if (plan) {
|
|
4950
5122
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
4951
5123
|
if (fm) {
|
|
4952
|
-
await ensureDir(
|
|
5124
|
+
await ensureDir(path26.join(args.cwd, ".cursor"));
|
|
4953
5125
|
const content = buildHandoff(plan.file, fm, profile);
|
|
4954
5126
|
await writeFile6(handoffPath, content, "utf8");
|
|
4955
5127
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
@@ -4969,7 +5141,7 @@ var handoffCommand = defineCommand9({
|
|
|
4969
5141
|
}
|
|
4970
5142
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
4971
5143
|
}
|
|
4972
|
-
const scriptPath =
|
|
5144
|
+
const scriptPath = path26.join(args.cwd, "cursor-handoff");
|
|
4973
5145
|
if (!await fileExists(scriptPath)) {
|
|
4974
5146
|
printV3Guidance();
|
|
4975
5147
|
return;
|
|
@@ -4994,9 +5166,19 @@ var handoffCommand = defineCommand9({
|
|
|
4994
5166
|
});
|
|
4995
5167
|
|
|
4996
5168
|
// src/commands/hook.ts
|
|
4997
|
-
import
|
|
5169
|
+
import path28 from "path";
|
|
4998
5170
|
import { defineCommand as defineCommand10 } from "citty";
|
|
4999
5171
|
|
|
5172
|
+
// src/hooks/format-session-start.ts
|
|
5173
|
+
function resolveSessionStartFormat(value) {
|
|
5174
|
+
return value === "claude" ? "claude" : "cursor";
|
|
5175
|
+
}
|
|
5176
|
+
function formatSessionStartOutput(additionalContext, format) {
|
|
5177
|
+
if (format === "claude") return additionalContext;
|
|
5178
|
+
return JSON.stringify({ additional_context: additionalContext });
|
|
5179
|
+
}
|
|
5180
|
+
var SESSION_START_DEGRADED_MESSAGE = "Agent Kit session-start context is unavailable this session (internal error, fail-open mode). Nothing else is affected; retry next session.";
|
|
5181
|
+
|
|
5000
5182
|
// src/hooks/pre-compact.ts
|
|
5001
5183
|
function buildPreCompactUserMessage(payload = {}) {
|
|
5002
5184
|
const pct = payload.context_usage_percent;
|
|
@@ -5008,8 +5190,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
5008
5190
|
|
|
5009
5191
|
// src/hooks/session-start.ts
|
|
5010
5192
|
import { spawn as spawn5 } from "child_process";
|
|
5011
|
-
import { access as
|
|
5012
|
-
import
|
|
5193
|
+
import { access as access7, readFile as readFile15, stat as stat4 } from "fs/promises";
|
|
5194
|
+
import path27 from "path";
|
|
5013
5195
|
|
|
5014
5196
|
// src/invariants/handoff-schema.ts
|
|
5015
5197
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -5083,7 +5265,7 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
5083
5265
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
5084
5266
|
async function readTextLimited(filePath, limit = 60) {
|
|
5085
5267
|
try {
|
|
5086
|
-
const lines = (await
|
|
5268
|
+
const lines = (await readFile15(filePath, "utf8")).split(/\r?\n/);
|
|
5087
5269
|
return lines.slice(0, limit).join("\n").trim();
|
|
5088
5270
|
} catch {
|
|
5089
5271
|
return "";
|
|
@@ -5091,14 +5273,14 @@ async function readTextLimited(filePath, limit = 60) {
|
|
|
5091
5273
|
}
|
|
5092
5274
|
async function readFull(filePath) {
|
|
5093
5275
|
try {
|
|
5094
|
-
return await
|
|
5276
|
+
return await readFile15(filePath, "utf8");
|
|
5095
5277
|
} catch {
|
|
5096
5278
|
return "";
|
|
5097
5279
|
}
|
|
5098
5280
|
}
|
|
5099
5281
|
async function fileExists2(p) {
|
|
5100
5282
|
try {
|
|
5101
|
-
await
|
|
5283
|
+
await access7(p);
|
|
5102
5284
|
return true;
|
|
5103
5285
|
} catch {
|
|
5104
5286
|
return false;
|
|
@@ -5165,8 +5347,8 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
5165
5347
|
return raw;
|
|
5166
5348
|
}
|
|
5167
5349
|
async function l0Present(root) {
|
|
5168
|
-
const cursor =
|
|
5169
|
-
return await fileExists2(
|
|
5350
|
+
const cursor = path27.join(root, ".cursor");
|
|
5351
|
+
return await fileExists2(path27.join(cursor, "agent-kit.json")) || await fileExists2(path27.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path27.join(cursor, "commands", "start-project.md"));
|
|
5170
5352
|
}
|
|
5171
5353
|
function checkLabelAndRecommendation(check2) {
|
|
5172
5354
|
const checkId = check2.id;
|
|
@@ -5205,10 +5387,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
5205
5387
|
return { essential, nonessential };
|
|
5206
5388
|
}
|
|
5207
5389
|
async function readinessSection(root) {
|
|
5208
|
-
const snapshotPath =
|
|
5390
|
+
const snapshotPath = path27.join(root, ".cursor", "context", "readiness.json");
|
|
5209
5391
|
let data;
|
|
5210
5392
|
try {
|
|
5211
|
-
data = JSON.parse(await
|
|
5393
|
+
data = JSON.parse(await readFile15(snapshotPath, "utf8"));
|
|
5212
5394
|
} catch {
|
|
5213
5395
|
return null;
|
|
5214
5396
|
}
|
|
@@ -5239,13 +5421,13 @@ Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not
|
|
|
5239
5421
|
}
|
|
5240
5422
|
async function dogfoodInboxSection(root) {
|
|
5241
5423
|
const candidateReadmes = [
|
|
5242
|
-
|
|
5243
|
-
|
|
5424
|
+
path27.join(root, "dogfood", "README.md"),
|
|
5425
|
+
path27.join(root, ".cursor", "dogfood", "README.md")
|
|
5244
5426
|
];
|
|
5245
5427
|
for (const readme of candidateReadmes) {
|
|
5246
5428
|
if (!await fileExists2(readme)) continue;
|
|
5247
5429
|
try {
|
|
5248
|
-
const text = await
|
|
5430
|
+
const text = await readFile15(readme, "utf8");
|
|
5249
5431
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
5250
5432
|
} catch {
|
|
5251
5433
|
}
|
|
@@ -5255,7 +5437,7 @@ async function dogfoodInboxSection(root) {
|
|
|
5255
5437
|
async function loadUpdateCheckPrefs(root) {
|
|
5256
5438
|
try {
|
|
5257
5439
|
const data = JSON.parse(
|
|
5258
|
-
await
|
|
5440
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5259
5441
|
);
|
|
5260
5442
|
const uc = data.updateCheck;
|
|
5261
5443
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5333,7 +5515,7 @@ async function updateCheckSection(root) {
|
|
|
5333
5515
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
5334
5516
|
try {
|
|
5335
5517
|
const data = JSON.parse(
|
|
5336
|
-
await
|
|
5518
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5337
5519
|
);
|
|
5338
5520
|
const uc = data.cursorUpdateCheck;
|
|
5339
5521
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5432,9 +5614,101 @@ async function cursorAwarenessSection(root, deps = {}) {
|
|
|
5432
5614
|
if (!shouldEmitCursorAwarenessNudge(result)) return null;
|
|
5433
5615
|
return CURSOR_AWARENESS_NUDGE;
|
|
5434
5616
|
}
|
|
5617
|
+
var AUDIT_SESSION_NS_PREFIX = "agent-kit-audit-";
|
|
5618
|
+
var AUDIT_SESSION_LIST_TIMEOUT_MS = 2e3;
|
|
5619
|
+
function runAuditSessionCommand(cmd, args) {
|
|
5620
|
+
return new Promise((resolve2) => {
|
|
5621
|
+
const child = spawn5(cmd, args, {
|
|
5622
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5623
|
+
timeout: AUDIT_SESSION_LIST_TIMEOUT_MS,
|
|
5624
|
+
shell: false
|
|
5625
|
+
});
|
|
5626
|
+
let out = "";
|
|
5627
|
+
child.stdout?.on("data", (chunk) => {
|
|
5628
|
+
out += chunk.toString("utf8");
|
|
5629
|
+
});
|
|
5630
|
+
child.on("error", () => resolve2(null));
|
|
5631
|
+
child.on("close", () => resolve2(out));
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
function parseTmuxDetachedAuditSessions(out, nowEpochSeconds) {
|
|
5635
|
+
const sessions = [];
|
|
5636
|
+
for (const line of out.split(/\r?\n/)) {
|
|
5637
|
+
const m = /^(\S+)\s+(\d+)\s+(\d+)$/.exec(line.trim());
|
|
5638
|
+
if (!m) continue;
|
|
5639
|
+
const [, name, attached, created] = m;
|
|
5640
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5641
|
+
if (Number(attached) > 0) continue;
|
|
5642
|
+
const createdEpoch = Number(created);
|
|
5643
|
+
const ageSeconds = Number.isFinite(createdEpoch) && nowEpochSeconds >= createdEpoch ? nowEpochSeconds - createdEpoch : -1;
|
|
5644
|
+
sessions.push({ channel: "tmux", name, ageSeconds });
|
|
5645
|
+
}
|
|
5646
|
+
return sessions;
|
|
5647
|
+
}
|
|
5648
|
+
function parseScreenDetachedAuditSessions(listing) {
|
|
5649
|
+
const lines = listing.split(/\r?\n/);
|
|
5650
|
+
let sockdir = null;
|
|
5651
|
+
for (const line of lines) {
|
|
5652
|
+
const dirMatch = /^\d+\s+Sockets?\s+in\s+(.+)\.$/.exec(line);
|
|
5653
|
+
if (dirMatch?.[1]) sockdir = dirMatch[1];
|
|
5654
|
+
}
|
|
5655
|
+
const entries = [];
|
|
5656
|
+
for (const line of lines) {
|
|
5657
|
+
const m = /^\s+(\d+)\.(\S+)\s+\((.*)\)/.exec(line);
|
|
5658
|
+
if (!m) continue;
|
|
5659
|
+
const [, pid, name, marker] = m;
|
|
5660
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5661
|
+
if (marker && /[Aa]ttached/.test(marker)) continue;
|
|
5662
|
+
entries.push({ name, socketPath: sockdir ? path27.join(sockdir, `${pid}.${name}`) : null });
|
|
5663
|
+
}
|
|
5664
|
+
return entries;
|
|
5665
|
+
}
|
|
5666
|
+
function formatSessionAge(seconds) {
|
|
5667
|
+
if (seconds >= 86400) return `${Math.floor(seconds / 86400)}d`;
|
|
5668
|
+
if (seconds >= 3600) return `${Math.floor(seconds / 3600)}h`;
|
|
5669
|
+
if (seconds >= 60) return `${Math.floor(seconds / 60)}m`;
|
|
5670
|
+
return `${seconds}s`;
|
|
5671
|
+
}
|
|
5672
|
+
async function detachedAuditSessionsSection(deps = {}) {
|
|
5673
|
+
try {
|
|
5674
|
+
const run = deps.runCommand ?? runAuditSessionCommand;
|
|
5675
|
+
const nowMs = (deps.now ?? Date.now)();
|
|
5676
|
+
const [tmuxOut, screenOut] = await Promise.all([
|
|
5677
|
+
run("tmux", [
|
|
5678
|
+
"list-sessions",
|
|
5679
|
+
"-F",
|
|
5680
|
+
"#{session_name} #{session_attached} #{session_created}"
|
|
5681
|
+
]),
|
|
5682
|
+
run("screen", ["-ls"])
|
|
5683
|
+
]);
|
|
5684
|
+
const sessions = tmuxOut ? parseTmuxDetachedAuditSessions(tmuxOut, Math.floor(nowMs / 1e3)) : [];
|
|
5685
|
+
if (screenOut) {
|
|
5686
|
+
for (const entry of parseScreenDetachedAuditSessions(screenOut)) {
|
|
5687
|
+
let ageSeconds = -1;
|
|
5688
|
+
if (entry.socketPath) {
|
|
5689
|
+
try {
|
|
5690
|
+
const { mtimeMs } = await stat4(entry.socketPath);
|
|
5691
|
+
if (nowMs >= mtimeMs) ageSeconds = Math.floor((nowMs - mtimeMs) / 1e3);
|
|
5692
|
+
} catch {
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
sessions.push({ channel: "screen", name: entry.name, ageSeconds });
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
if (sessions.length === 0) return null;
|
|
5699
|
+
const knownAges = sessions.map((s) => s.ageSeconds).filter((a) => a >= 0);
|
|
5700
|
+
const oldest = knownAges.length ? `oldest ~${formatSessionAge(Math.max(...knownAges))}` : "oldest age unknown";
|
|
5701
|
+
const noun = sessions.length === 1 ? "session" : "sessions";
|
|
5702
|
+
return `## Detached audit sessions (host)
|
|
5703
|
+
|
|
5704
|
+
${sessions.length} detached \`agent-kit-audit-*\` PTY ${noun} on this host (${oldest}). These are external plan-review terminals: inspect with \`tmux attach -t <name>\` / \`screen -r <name>\`, or let the audit launcher's session GC dispose of them on the next spawn.`;
|
|
5705
|
+
} catch {
|
|
5706
|
+
return null;
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5435
5709
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
5436
|
-
const root =
|
|
5437
|
-
const handoffPath =
|
|
5710
|
+
const root = path27.resolve(rootDir);
|
|
5711
|
+
const handoffPath = path27.join(root, ".cursor", "HANDOFF.md");
|
|
5438
5712
|
const handoffFull = await readFull(handoffPath);
|
|
5439
5713
|
const handoff = await readTextLimited(handoffPath);
|
|
5440
5714
|
const parts = [HARD_RULES];
|
|
@@ -5448,6 +5722,8 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
5448
5722
|
if (updateNudge) parts.push(updateNudge);
|
|
5449
5723
|
const cursorNudge = await cursorAwarenessSection(root);
|
|
5450
5724
|
if (cursorNudge) parts.push(cursorNudge);
|
|
5725
|
+
const auditSessions = await detachedAuditSessionsSection().catch(() => null);
|
|
5726
|
+
if (auditSessions) parts.push(auditSessions);
|
|
5451
5727
|
const formatWarnings = validateHandoffText(handoffFull);
|
|
5452
5728
|
if (formatWarnings.length) {
|
|
5453
5729
|
const bullet = formatWarnings.map((w) => `- ${w.message}`).join("\n");
|
|
@@ -5479,29 +5755,44 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
|
5479
5755
|
}
|
|
5480
5756
|
|
|
5481
5757
|
// src/commands/hook.ts
|
|
5758
|
+
async function runSessionStartHook(cwd, formatArg, deps = {}) {
|
|
5759
|
+
const format = resolveSessionStartFormat(formatArg);
|
|
5760
|
+
try {
|
|
5761
|
+
const readStdin = deps.readStdin ?? readStdinJson;
|
|
5762
|
+
const buildContext = deps.buildContext ?? buildSessionStartAdditionalContext;
|
|
5763
|
+
const payload = await readStdin();
|
|
5764
|
+
const root = resolveSessionRoot(payload, path28.resolve(cwd));
|
|
5765
|
+
const out = await buildContext(root, payload);
|
|
5766
|
+
return formatSessionStartOutput(out.additional_context, format);
|
|
5767
|
+
} catch {
|
|
5768
|
+
return formatSessionStartOutput(SESSION_START_DEGRADED_MESSAGE, format);
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5482
5771
|
var hookCommand = defineCommand10({
|
|
5483
5772
|
meta: {
|
|
5484
5773
|
name: "hook",
|
|
5485
|
-
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5774
|
+
description: "Cursor + Claude Code hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5486
5775
|
},
|
|
5487
5776
|
subCommands: {
|
|
5488
5777
|
"session-start": defineCommand10({
|
|
5489
5778
|
meta: {
|
|
5490
5779
|
name: "session-start",
|
|
5491
|
-
description: "Emit sessionStart
|
|
5780
|
+
description: "Emit sessionStart context (stdin: host payload). --format cursor (default, JSON additional_context) | claude (plain stdout)"
|
|
5492
5781
|
},
|
|
5493
5782
|
args: {
|
|
5494
5783
|
cwd: {
|
|
5495
5784
|
type: "string",
|
|
5496
5785
|
default: process.cwd()
|
|
5786
|
+
},
|
|
5787
|
+
format: {
|
|
5788
|
+
type: "string",
|
|
5789
|
+
default: "cursor",
|
|
5790
|
+
description: "cursor (default) | claude"
|
|
5497
5791
|
}
|
|
5498
5792
|
},
|
|
5499
5793
|
async run({ args }) {
|
|
5500
|
-
const payload = await readStdinJson();
|
|
5501
5794
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5502
|
-
|
|
5503
|
-
const out = await buildSessionStartAdditionalContext(root, payload);
|
|
5504
|
-
console.log(JSON.stringify(out));
|
|
5795
|
+
console.log(await runSessionStartHook(cwd, args.format));
|
|
5505
5796
|
}
|
|
5506
5797
|
}),
|
|
5507
5798
|
"pre-compact": defineCommand10({
|
|
@@ -5522,45 +5813,96 @@ import { intro, outro } from "@clack/prompts";
|
|
|
5522
5813
|
import { defineCommand as defineCommand12 } from "citty";
|
|
5523
5814
|
|
|
5524
5815
|
// src/utils/terminal.ts
|
|
5525
|
-
import {
|
|
5526
|
-
import
|
|
5816
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
5817
|
+
import { homedir as homedir3 } from "os";
|
|
5818
|
+
import path29 from "path";
|
|
5527
5819
|
import { confirm, isCancel } from "@clack/prompts";
|
|
5528
5820
|
function isNonInteractive() {
|
|
5529
5821
|
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
5530
5822
|
if (process.env.AGENT_KIT_YES === "1") return true;
|
|
5531
5823
|
return !process.stdin.isTTY;
|
|
5532
5824
|
}
|
|
5825
|
+
var NESTED_REPO_AMBIGUITY_THRESHOLD = 2;
|
|
5826
|
+
var NESTED_REPO_SCAN_LIMIT = 200;
|
|
5827
|
+
async function findNestedRepoChildren(resolved) {
|
|
5828
|
+
let entries;
|
|
5829
|
+
try {
|
|
5830
|
+
entries = await readdir6(resolved, { withFileTypes: true });
|
|
5831
|
+
} catch {
|
|
5832
|
+
return [];
|
|
5833
|
+
}
|
|
5834
|
+
const nested = [];
|
|
5835
|
+
let scanned = 0;
|
|
5836
|
+
for (const entry of entries) {
|
|
5837
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) break;
|
|
5838
|
+
if (scanned >= NESTED_REPO_SCAN_LIMIT) break;
|
|
5839
|
+
if (!entry.isDirectory()) continue;
|
|
5840
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
5841
|
+
scanned += 1;
|
|
5842
|
+
if (await fileExists(path29.join(resolved, entry.name, ".git"))) {
|
|
5843
|
+
nested.push(entry.name);
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
return nested;
|
|
5847
|
+
}
|
|
5533
5848
|
async function validateProjectRoot(resolved) {
|
|
5534
|
-
const home =
|
|
5849
|
+
const home = path29.resolve(homedir3());
|
|
5535
5850
|
if (resolved === "/" || resolved === home) {
|
|
5536
|
-
return {
|
|
5851
|
+
return {
|
|
5852
|
+
ok: false,
|
|
5853
|
+
reason: `Refused to use ${resolved} as a project root.`,
|
|
5854
|
+
recovery: "Change into a project directory and re-run. Agent Kit installs per project."
|
|
5855
|
+
};
|
|
5537
5856
|
}
|
|
5538
|
-
const hasGit = await fileExists(
|
|
5539
|
-
const hasManifest = await fileExists(
|
|
5857
|
+
const hasGit = await fileExists(path29.join(resolved, ".git"));
|
|
5858
|
+
const hasManifest = await fileExists(path29.join(resolved, ".cursor", "agent-kit.json"));
|
|
5540
5859
|
if (!hasGit && !hasManifest) {
|
|
5541
5860
|
return {
|
|
5542
5861
|
ok: false,
|
|
5543
|
-
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json
|
|
5862
|
+
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json.`,
|
|
5863
|
+
recovery: [
|
|
5864
|
+
"Starting from an empty folder? Pick one of these:",
|
|
5865
|
+
" 1. git init - then re-run. Recommended: readiness and the",
|
|
5866
|
+
" staging -> prod flow both want Git.",
|
|
5867
|
+
" 2. --force-root - install without Git. /agent-kit-onboard will",
|
|
5868
|
+
" still offer to initialize it later.",
|
|
5869
|
+
" 3. Answer yes to the 'Proceed anyway?' prompt in an interactive terminal.",
|
|
5870
|
+
"Or re-run from the project directory you actually meant."
|
|
5871
|
+
].join("\n")
|
|
5544
5872
|
};
|
|
5545
5873
|
}
|
|
5874
|
+
if (hasGit && !hasManifest) {
|
|
5875
|
+
const nested = await findNestedRepoChildren(resolved);
|
|
5876
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) {
|
|
5877
|
+
return {
|
|
5878
|
+
ok: false,
|
|
5879
|
+
reason: `Refused ${resolved}: it has .git but also contains child repositories (${nested.join(", ")}). This looks like a parent-of-repos folder, not a project root.`,
|
|
5880
|
+
recovery: [
|
|
5881
|
+
"L0 belongs in one project, not in the folder that holds several.",
|
|
5882
|
+
" 1. cd into the project you meant, then re-run.",
|
|
5883
|
+
" 2. --force-root - only if this parent folder really is the project root."
|
|
5884
|
+
].join("\n")
|
|
5885
|
+
};
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5546
5888
|
return { ok: true };
|
|
5547
5889
|
}
|
|
5548
5890
|
async function confirmProjectRoot(cwd, opts) {
|
|
5549
|
-
const resolved =
|
|
5891
|
+
const resolved = path29.resolve(cwd);
|
|
5550
5892
|
if (opts.forceRoot) {
|
|
5551
5893
|
return resolved;
|
|
5552
5894
|
}
|
|
5553
5895
|
const validation = await validateProjectRoot(resolved);
|
|
5554
5896
|
if (!validation.ok) {
|
|
5555
5897
|
if (opts.nonInteractive) {
|
|
5556
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5898
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5557
5899
|
}
|
|
5558
5900
|
const ok2 = await confirm({
|
|
5559
5901
|
message: `${validation.reason} Proceed anyway?`,
|
|
5560
5902
|
initialValue: false
|
|
5561
5903
|
});
|
|
5562
5904
|
if (isCancel(ok2) || !ok2) {
|
|
5563
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5905
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5564
5906
|
}
|
|
5565
5907
|
return resolved;
|
|
5566
5908
|
}
|
|
@@ -5577,16 +5919,38 @@ async function confirmProjectRoot(cwd, opts) {
|
|
|
5577
5919
|
return resolved;
|
|
5578
5920
|
}
|
|
5579
5921
|
var RootRefusedError = class extends Error {
|
|
5580
|
-
constructor(root, reason) {
|
|
5922
|
+
constructor(root, reason, recovery) {
|
|
5581
5923
|
super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
|
|
5582
5924
|
this.root = root;
|
|
5925
|
+
this.recovery = recovery;
|
|
5583
5926
|
this.name = "RootRefusedError";
|
|
5584
5927
|
}
|
|
5585
5928
|
root;
|
|
5929
|
+
recovery;
|
|
5586
5930
|
};
|
|
5931
|
+
var NPM_GLOBAL_PREFIX_PATH_RE = /\/lib\/node_modules|Program Files\\nodejs\\node_modules/;
|
|
5932
|
+
var NPM_GLOBAL_NODE_MODULES_RE = /node_modules/;
|
|
5933
|
+
function isNpmGlobalPrefixError(msg, code) {
|
|
5934
|
+
const isPermissionError = code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg);
|
|
5935
|
+
if (!isPermissionError) return false;
|
|
5936
|
+
return NPM_GLOBAL_PREFIX_PATH_RE.test(msg) || NPM_GLOBAL_NODE_MODULES_RE.test(msg);
|
|
5937
|
+
}
|
|
5587
5938
|
function classifyInstallError(err) {
|
|
5588
5939
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5589
5940
|
const code = err?.code;
|
|
5941
|
+
if (isNpmGlobalPrefixError(msg, code)) {
|
|
5942
|
+
return {
|
|
5943
|
+
kind: "npm-global-eacces",
|
|
5944
|
+
message: `Permission error (root-owned npm prefix): ${msg}`,
|
|
5945
|
+
recovery: [
|
|
5946
|
+
"npm's global install prefix (e.g. /usr/local/lib/node_modules) is owned by root, so global installs fail.",
|
|
5947
|
+
"Recovery options:",
|
|
5948
|
+
" 1. Run: npx @dadado/agent-kit-cli setup-global (relocates npm's prefix to a folder you own, fixes PATH, reinstalls)",
|
|
5949
|
+
' 2. Manual fix: mkdir -p ~/.npm-global && npm config set prefix "~/.npm-global" && export PATH="~/.npm-global/bin:$PATH" (add to your shell profile) && npm i -g @dadado/agent-kit-cli',
|
|
5950
|
+
" 3. Use Port B fallback: drag install.md into the Cursor chat"
|
|
5951
|
+
].join("\n")
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5590
5954
|
if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
|
|
5591
5955
|
return {
|
|
5592
5956
|
kind: "eperm",
|
|
@@ -5634,16 +5998,108 @@ function classifyInstallError(err) {
|
|
|
5634
5998
|
}
|
|
5635
5999
|
|
|
5636
6000
|
// src/commands/install.ts
|
|
5637
|
-
import
|
|
6001
|
+
import path36 from "path";
|
|
5638
6002
|
import { defineCommand as defineCommand11 } from "citty";
|
|
6003
|
+
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
5639
6004
|
|
|
5640
6005
|
// src/generator/personalization.ts
|
|
5641
|
-
import { readFile as
|
|
5642
|
-
import
|
|
6006
|
+
import { readFile as readFile18, writeFile as writeFile11 } from "fs/promises";
|
|
6007
|
+
import path34 from "path";
|
|
6008
|
+
|
|
6009
|
+
// src/generator/claude-command-adapters.ts
|
|
6010
|
+
import { readFile as readFile16, readdir as readdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6011
|
+
import path30 from "path";
|
|
6012
|
+
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
6013
|
+
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
6014
|
+
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
6015
|
+
function parseCommandFrontmatter(name, raw) {
|
|
6016
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
6017
|
+
if (!match) return null;
|
|
6018
|
+
const body = match[1] ?? "";
|
|
6019
|
+
const descMatch = /^description:\s*(.+)$/m.exec(body);
|
|
6020
|
+
if (!descMatch) return null;
|
|
6021
|
+
const description = (descMatch[1] ?? "").trim();
|
|
6022
|
+
if (!description) return null;
|
|
6023
|
+
return { name, description };
|
|
6024
|
+
}
|
|
6025
|
+
function renderClaudeCommandAdapter(command) {
|
|
6026
|
+
return `---
|
|
6027
|
+
description: ${command.description}
|
|
6028
|
+
---
|
|
6029
|
+
|
|
6030
|
+
Read \`.cursor/commands/${command.name}.md\` now and follow that contract exactly \u2014 it is the source of truth for /${command.name}; this file is only a thin adapter for Claude Code.
|
|
6031
|
+
|
|
6032
|
+
Adapter rules (Claude Code CLI):
|
|
6033
|
+
- Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else present the same labels as one numbered list per message and WAIT for the answer.
|
|
6034
|
+
- Skip or cancel means stop.
|
|
6035
|
+
- Never \`/git-prod\` without an explicit operator yes.
|
|
6036
|
+
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
6037
|
+
`;
|
|
6038
|
+
}
|
|
6039
|
+
async function discoverInstalledCommands(rootDir) {
|
|
6040
|
+
const dir = path30.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
6041
|
+
let entries;
|
|
6042
|
+
try {
|
|
6043
|
+
entries = (await readdir7(dir)).filter((f) => f.endsWith(".md"));
|
|
6044
|
+
} catch {
|
|
6045
|
+
return [];
|
|
6046
|
+
}
|
|
6047
|
+
const commands = [];
|
|
6048
|
+
for (const file of entries.sort()) {
|
|
6049
|
+
const name = file.slice(0, -3);
|
|
6050
|
+
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
6051
|
+
try {
|
|
6052
|
+
const raw = await readFile16(path30.join(dir, file), "utf8");
|
|
6053
|
+
const parsed = parseCommandFrontmatter(name, raw);
|
|
6054
|
+
if (parsed) commands.push(parsed);
|
|
6055
|
+
} catch {
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
return commands;
|
|
6059
|
+
}
|
|
6060
|
+
async function generateClaudeCommandAdapters(rootDir) {
|
|
6061
|
+
const commands = await discoverInstalledCommands(rootDir);
|
|
6062
|
+
if (commands.length === 0) return [];
|
|
6063
|
+
const ledger = await loadManagedHashLedger(rootDir);
|
|
6064
|
+
const results = [];
|
|
6065
|
+
let ledgerDirty = false;
|
|
6066
|
+
for (const command of commands) {
|
|
6067
|
+
const relPath = path30.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
6068
|
+
const rendered = renderClaudeCommandAdapter(command);
|
|
6069
|
+
const abs = path30.join(rootDir, relPath);
|
|
6070
|
+
if (!await fileExists(abs)) {
|
|
6071
|
+
await ensureDir(path30.dirname(abs));
|
|
6072
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6073
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6074
|
+
ledgerDirty = true;
|
|
6075
|
+
results.push({ relativePath: relPath, status: "applied" });
|
|
6076
|
+
continue;
|
|
6077
|
+
}
|
|
6078
|
+
const localContent = await readFile16(abs, "utf8");
|
|
6079
|
+
if (localContent === rendered) {
|
|
6080
|
+
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
6081
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6082
|
+
ledgerDirty = true;
|
|
6083
|
+
}
|
|
6084
|
+
results.push({ relativePath: relPath, status: "unchanged" });
|
|
6085
|
+
continue;
|
|
6086
|
+
}
|
|
6087
|
+
if (shouldPreserveCustomizedOverlay(localContent, ledger.hashes[relPath])) {
|
|
6088
|
+
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
6089
|
+
continue;
|
|
6090
|
+
}
|
|
6091
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6092
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6093
|
+
ledgerDirty = true;
|
|
6094
|
+
results.push({ relativePath: relPath, status: "refreshed" });
|
|
6095
|
+
}
|
|
6096
|
+
if (ledgerDirty) await saveManagedHashLedger(rootDir, ledger);
|
|
6097
|
+
return results;
|
|
6098
|
+
}
|
|
5643
6099
|
|
|
5644
6100
|
// src/generator/claude-kit-load.ts
|
|
5645
|
-
import { writeFile as
|
|
5646
|
-
import
|
|
6101
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
6102
|
+
import path31 from "path";
|
|
5647
6103
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
5648
6104
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
5649
6105
|
function renderClaudeMd() {
|
|
@@ -5669,7 +6125,7 @@ Cursor Ask questions is not available in this CLI. When a command requires a cho
|
|
|
5669
6125
|
- Not Action A7 (Windsurf / VS Code generator parity)
|
|
5670
6126
|
- Not Claude external plan-review audits (\`/plan-external-review\`)
|
|
5671
6127
|
- Not \`--backend claude\` plan-loop ticks
|
|
5672
|
-
- Not a copy of Cursor \`
|
|
6128
|
+
- Not a copy of Cursor hooks beyond the opt-in SessionStart context adapter (\`agent-kit hook session-start --format claude\`); no \`.claude/rules/\` mirrors, no \`.claude/agents/\` generated from the registry
|
|
5673
6129
|
`;
|
|
5674
6130
|
}
|
|
5675
6131
|
function renderAgentKitCommand() {
|
|
@@ -5693,12 +6149,12 @@ Non-goals: not audits / \`/plan-external-review\`, not \`--backend claude\` tick
|
|
|
5693
6149
|
`;
|
|
5694
6150
|
}
|
|
5695
6151
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
5696
|
-
const target =
|
|
6152
|
+
const target = path31.join(rootDir, relativePath);
|
|
5697
6153
|
if (await fileExists(target)) {
|
|
5698
6154
|
return { relativePath, status: "skipped-customized" };
|
|
5699
6155
|
}
|
|
5700
|
-
await ensureDir(
|
|
5701
|
-
await
|
|
6156
|
+
await ensureDir(path31.dirname(target));
|
|
6157
|
+
await writeFile8(target, content, "utf8");
|
|
5702
6158
|
return { relativePath, status: "applied" };
|
|
5703
6159
|
}
|
|
5704
6160
|
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
@@ -5708,9 +6164,109 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
5708
6164
|
]);
|
|
5709
6165
|
}
|
|
5710
6166
|
|
|
6167
|
+
// src/generator/claude-session-start-hook.ts
|
|
6168
|
+
import { readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
|
|
6169
|
+
import path32 from "path";
|
|
6170
|
+
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
6171
|
+
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
6172
|
+
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
6173
|
+
var SESSION_START_DEGRADED_TEXT = "Agent Kit hooks are running in degraded fail-open mode: the agent-kit CLI could not be resolved (checked AGENT_KIT_HOOK_BIN, PATH, node_modules/.bin/agent-kit). Slash command adapters still work; session-context injection is inactive. Fix: install the CLI (npm i -D @dadado/agent-kit-cli) or set AGENT_KIT_HOOK_BIN.";
|
|
6174
|
+
function buildSessionStartHookCommand() {
|
|
6175
|
+
return `. "\${CLAUDE_PROJECT_DIR}/${RESOLVE_AGENT_KIT_REL}" 2>/dev/null && resolve_agent_kit && exec $AGENT_KIT_RESOLVED hook session-start --format claude; printf '%s' ${shellSingleQuote(SESSION_START_DEGRADED_TEXT)}`;
|
|
6176
|
+
}
|
|
6177
|
+
function shellSingleQuote(text) {
|
|
6178
|
+
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
6179
|
+
}
|
|
6180
|
+
function buildSessionStartHookEntry() {
|
|
6181
|
+
return {
|
|
6182
|
+
type: "command",
|
|
6183
|
+
command: buildSessionStartHookCommand(),
|
|
6184
|
+
timeout: 15,
|
|
6185
|
+
statusMessage: "Loading Agent Kit session context"
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6188
|
+
function isPlainObject2(value) {
|
|
6189
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6190
|
+
}
|
|
6191
|
+
function groupHasMarker(group) {
|
|
6192
|
+
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
6193
|
+
return group.hooks.some(
|
|
6194
|
+
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
6195
|
+
);
|
|
6196
|
+
}
|
|
6197
|
+
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
6198
|
+
const entry = buildSessionStartHookEntry();
|
|
6199
|
+
const newGroup = { hooks: [entry] };
|
|
6200
|
+
let root = {};
|
|
6201
|
+
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
6202
|
+
try {
|
|
6203
|
+
const parsed = JSON.parse(existingRaw);
|
|
6204
|
+
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
6205
|
+
root = parsed;
|
|
6206
|
+
} catch {
|
|
6207
|
+
return {
|
|
6208
|
+
content: null,
|
|
6209
|
+
status: "unavailable",
|
|
6210
|
+
instructions: instructionsBlock(entry)
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
6215
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
6216
|
+
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
6217
|
+
let status;
|
|
6218
|
+
if (existingIndex === -1) {
|
|
6219
|
+
sessionStart.push(newGroup);
|
|
6220
|
+
status = "applied";
|
|
6221
|
+
} else {
|
|
6222
|
+
const current = sessionStart[existingIndex];
|
|
6223
|
+
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
6224
|
+
status = "unchanged";
|
|
6225
|
+
} else {
|
|
6226
|
+
sessionStart[existingIndex] = newGroup;
|
|
6227
|
+
status = "refreshed";
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
hooks.SessionStart = sessionStart;
|
|
6231
|
+
root.hooks = hooks;
|
|
6232
|
+
return { content: `${JSON.stringify(root, null, 2)}
|
|
6233
|
+
`, status };
|
|
6234
|
+
}
|
|
6235
|
+
function instructionsBlock(entry) {
|
|
6236
|
+
return [
|
|
6237
|
+
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
6238
|
+
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
6239
|
+
"",
|
|
6240
|
+
JSON.stringify(entry, null, 2)
|
|
6241
|
+
].join("\n");
|
|
6242
|
+
}
|
|
6243
|
+
async function writeClaudeSessionStartHook(rootDir) {
|
|
6244
|
+
const abs = path32.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
6245
|
+
let existing = null;
|
|
6246
|
+
try {
|
|
6247
|
+
existing = await readFile17(abs, "utf8");
|
|
6248
|
+
} catch {
|
|
6249
|
+
existing = null;
|
|
6250
|
+
}
|
|
6251
|
+
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
6252
|
+
if (merged.status === "unavailable" || merged.content === null) {
|
|
6253
|
+
return {
|
|
6254
|
+
relativePath: CLAUDE_SETTINGS_REL,
|
|
6255
|
+
status: "unavailable",
|
|
6256
|
+
instructions: merged.instructions
|
|
6257
|
+
};
|
|
6258
|
+
}
|
|
6259
|
+
if (merged.status === "unchanged") {
|
|
6260
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
6261
|
+
}
|
|
6262
|
+
await ensureDir(path32.dirname(abs));
|
|
6263
|
+
await writeFile9(abs, merged.content, "utf8");
|
|
6264
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
6265
|
+
}
|
|
6266
|
+
|
|
5711
6267
|
// src/generator/vscode.ts
|
|
5712
|
-
import { writeFile as
|
|
5713
|
-
import
|
|
6268
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
6269
|
+
import path33 from "path";
|
|
5714
6270
|
|
|
5715
6271
|
// src/generator/platform.ts
|
|
5716
6272
|
function gitProviderLabel(profile) {
|
|
@@ -5734,14 +6290,14 @@ function prTerminology(profile) {
|
|
|
5734
6290
|
// src/generator/vscode.ts
|
|
5735
6291
|
async function generateVSCodeArtifacts(profile) {
|
|
5736
6292
|
const results = [];
|
|
5737
|
-
const vscodeDir =
|
|
5738
|
-
const githubDir =
|
|
6293
|
+
const vscodeDir = path33.join(profile.rootDir, ".vscode");
|
|
6294
|
+
const githubDir = path33.join(profile.rootDir, ".github");
|
|
5739
6295
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
5740
|
-
const settingsPath =
|
|
6296
|
+
const settingsPath = path33.join(vscodeDir, "settings.json");
|
|
5741
6297
|
if (await fileExists(settingsPath)) {
|
|
5742
6298
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
5743
6299
|
} else {
|
|
5744
|
-
await
|
|
6300
|
+
await writeFile10(
|
|
5745
6301
|
settingsPath,
|
|
5746
6302
|
`${JSON.stringify(
|
|
5747
6303
|
{
|
|
@@ -5761,11 +6317,11 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5761
6317
|
}
|
|
5762
6318
|
const provider = gitProviderLabel(profile);
|
|
5763
6319
|
const prTerm = prTerminology(profile);
|
|
5764
|
-
const copilotPath =
|
|
6320
|
+
const copilotPath = path33.join(githubDir, "copilot-instructions.md");
|
|
5765
6321
|
if (await fileExists(copilotPath)) {
|
|
5766
6322
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
5767
6323
|
} else {
|
|
5768
|
-
await
|
|
6324
|
+
await writeFile10(
|
|
5769
6325
|
copilotPath,
|
|
5770
6326
|
`# Copilot Instructions
|
|
5771
6327
|
|
|
@@ -5779,14 +6335,14 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5779
6335
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
5780
6336
|
}
|
|
5781
6337
|
if (profile.ide.plan === "vscode-pro") {
|
|
5782
|
-
const securityPath =
|
|
6338
|
+
const securityPath = path33.join(vscodeDir, "security-review.agent.md");
|
|
5783
6339
|
if (await fileExists(securityPath)) {
|
|
5784
6340
|
results.push({
|
|
5785
6341
|
relativePath: ".vscode/security-review.agent.md",
|
|
5786
6342
|
status: "skipped-customized"
|
|
5787
6343
|
});
|
|
5788
6344
|
} else {
|
|
5789
|
-
await
|
|
6345
|
+
await writeFile10(
|
|
5790
6346
|
securityPath,
|
|
5791
6347
|
"# Security Review Agent\n\nSpecialized mode for security review.\n",
|
|
5792
6348
|
"utf8"
|
|
@@ -5991,7 +6547,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
5991
6547
|
`;
|
|
5992
6548
|
}
|
|
5993
6549
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
5994
|
-
const target =
|
|
6550
|
+
const target = path34.join(rootDir, relativePath);
|
|
5995
6551
|
if (await fileExists(target)) {
|
|
5996
6552
|
return {
|
|
5997
6553
|
kind: "file",
|
|
@@ -6001,8 +6557,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
6001
6557
|
evidence
|
|
6002
6558
|
};
|
|
6003
6559
|
}
|
|
6004
|
-
await ensureDir(
|
|
6005
|
-
await
|
|
6560
|
+
await ensureDir(path34.dirname(target));
|
|
6561
|
+
await writeFile11(target, content, "utf8");
|
|
6006
6562
|
return {
|
|
6007
6563
|
kind: "file",
|
|
6008
6564
|
id: relativePath,
|
|
@@ -6018,7 +6574,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
6018
6574
|
async function existingTargets(projectRoot, targets) {
|
|
6019
6575
|
const checks = await Promise.all(
|
|
6020
6576
|
targets.map(
|
|
6021
|
-
async (target) => await fileExists(
|
|
6577
|
+
async (target) => await fileExists(path34.join(projectRoot, target)) ? target : null
|
|
6022
6578
|
)
|
|
6023
6579
|
);
|
|
6024
6580
|
return checks.filter((target) => target !== null);
|
|
@@ -6040,14 +6596,14 @@ async function applyPersonalization(input) {
|
|
|
6040
6596
|
componentResults.push({ ...item, status: "unavailable" });
|
|
6041
6597
|
continue;
|
|
6042
6598
|
}
|
|
6043
|
-
const target =
|
|
6599
|
+
const target = path34.posix.join(
|
|
6044
6600
|
".cursor",
|
|
6045
6601
|
"skills",
|
|
6046
6602
|
skill.path.includes("/core/") ? "core" : "community",
|
|
6047
6603
|
skill.id,
|
|
6048
6604
|
"SKILL.md"
|
|
6049
6605
|
);
|
|
6050
|
-
if (await fileExists(
|
|
6606
|
+
if (await fileExists(path34.join(input.rootDir, target))) {
|
|
6051
6607
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
6052
6608
|
protectedPaths.add(target);
|
|
6053
6609
|
continue;
|
|
@@ -6106,6 +6662,41 @@ async function applyPersonalization(input) {
|
|
|
6106
6662
|
evidence: profileEvidence
|
|
6107
6663
|
};
|
|
6108
6664
|
});
|
|
6665
|
+
const claudeCommandItems = [];
|
|
6666
|
+
let claudeSessionStartInstructions;
|
|
6667
|
+
if (input.claudeAdapters) {
|
|
6668
|
+
const adapterResults = await generateClaudeCommandAdapters(input.rootDir);
|
|
6669
|
+
for (const artifact of adapterResults) {
|
|
6670
|
+
protectedPaths.add(artifact.relativePath);
|
|
6671
|
+
claudeCommandItems.push({
|
|
6672
|
+
kind: "file",
|
|
6673
|
+
id: artifact.relativePath,
|
|
6674
|
+
path: artifact.relativePath,
|
|
6675
|
+
// Overlay statuses (applied/unchanged/refreshed/preserved-customized)
|
|
6676
|
+
// fold onto the shared PersonalizationStatus union: anything written
|
|
6677
|
+
// or already current reads as "applied"; a hand-edited adapter left
|
|
6678
|
+
// alone reads as "skipped-customized" (same meaning as elsewhere in
|
|
6679
|
+
// this file — never silently clobbered).
|
|
6680
|
+
status: artifact.status === "preserved-customized" ? "skipped-customized" : "applied",
|
|
6681
|
+
evidence: profileEvidence
|
|
6682
|
+
});
|
|
6683
|
+
}
|
|
6684
|
+
const hookResult = await writeClaudeSessionStartHook(input.rootDir);
|
|
6685
|
+
protectedPaths.add(hookResult.relativePath);
|
|
6686
|
+
claudeCommandItems.push({
|
|
6687
|
+
kind: "file",
|
|
6688
|
+
id: hookResult.relativePath,
|
|
6689
|
+
path: hookResult.relativePath,
|
|
6690
|
+
// "unavailable" (existing .claude/settings.json unparseable) is a real
|
|
6691
|
+
// PersonalizationStatus value already; every other hook status folds
|
|
6692
|
+
// onto "applied" the same way the command-adapter statuses do above.
|
|
6693
|
+
status: hookResult.status === "unavailable" ? "unavailable" : "applied",
|
|
6694
|
+
evidence: profileEvidence
|
|
6695
|
+
});
|
|
6696
|
+
if (hookResult.status === "unavailable" && hookResult.instructions) {
|
|
6697
|
+
claudeSessionStartInstructions = hookResult.instructions;
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
6109
6700
|
const ideDetection = await detectIde(input.rootDir);
|
|
6110
6701
|
if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
|
|
6111
6702
|
const git = {
|
|
@@ -6153,10 +6744,11 @@ async function applyPersonalization(input) {
|
|
|
6153
6744
|
contractVersion: PERSONALIZATION_CONTRACT_VERSION,
|
|
6154
6745
|
generatorVersion: input.generatorVersion,
|
|
6155
6746
|
repositoryFingerprint: input.report.repositoryFingerprint,
|
|
6156
|
-
items: [...fileResults, ...claudeItems, ...componentResults],
|
|
6157
|
-
protectedPaths: [...protectedPaths].sort()
|
|
6747
|
+
items: [...fileResults, ...claudeItems, ...claudeCommandItems, ...componentResults],
|
|
6748
|
+
protectedPaths: [...protectedPaths].sort(),
|
|
6749
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6158
6750
|
};
|
|
6159
|
-
await writeJson(
|
|
6751
|
+
await writeJson(path34.join(input.rootDir, RESULT_PATH), result);
|
|
6160
6752
|
return {
|
|
6161
6753
|
result,
|
|
6162
6754
|
manifest: {
|
|
@@ -6174,26 +6766,26 @@ async function applyPersonalization(input) {
|
|
|
6174
6766
|
};
|
|
6175
6767
|
}
|
|
6176
6768
|
async function readRepositoryProfile(rootDir) {
|
|
6177
|
-
const target =
|
|
6769
|
+
const target = path34.join(rootDir, ".cursor/agent-kit.config.json");
|
|
6178
6770
|
if (!await fileExists(target)) return null;
|
|
6179
|
-
return JSON.parse(await
|
|
6771
|
+
return JSON.parse(await readFile18(target, "utf8"));
|
|
6180
6772
|
}
|
|
6181
6773
|
|
|
6182
6774
|
// src/lifecycle/onboard-migration.ts
|
|
6183
6775
|
import { createHash as createHash4 } from "crypto";
|
|
6184
|
-
import { readFile as
|
|
6185
|
-
import
|
|
6776
|
+
import { readFile as readFile19, unlink } from "fs/promises";
|
|
6777
|
+
import path35 from "path";
|
|
6186
6778
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
6187
6779
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
6188
6780
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
6189
6781
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
6190
6782
|
]);
|
|
6191
6783
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
6192
|
-
const legacyPath =
|
|
6784
|
+
const legacyPath = path35.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
6193
6785
|
if (!await fileExists(legacyPath)) return "absent";
|
|
6194
|
-
const namespacedPath =
|
|
6786
|
+
const namespacedPath = path35.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
6195
6787
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
6196
|
-
const content = await
|
|
6788
|
+
const content = await readFile19(legacyPath);
|
|
6197
6789
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
6198
6790
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
6199
6791
|
await unlink(legacyPath);
|
|
@@ -6252,6 +6844,9 @@ function parsePackList(raw) {
|
|
|
6252
6844
|
)
|
|
6253
6845
|
];
|
|
6254
6846
|
}
|
|
6847
|
+
function nextStepAfterInstall(pendingActions) {
|
|
6848
|
+
return pendingActions > 0 ? "Next: run /agent-kit-onboard in Cursor to resolve the first pending action." : "Next: run /start-project in Cursor when you have a deliverable.";
|
|
6849
|
+
}
|
|
6255
6850
|
function printReadinessNarrative(result) {
|
|
6256
6851
|
const { summary, pendingActions } = result.readiness;
|
|
6257
6852
|
const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
|
|
@@ -6261,12 +6856,61 @@ function printReadinessNarrative(result) {
|
|
|
6261
6856
|
);
|
|
6262
6857
|
console.log(` safe fixes applied: ${fixed}`);
|
|
6263
6858
|
console.log(` pending actions: ${pendingActions.length}`);
|
|
6264
|
-
console.log(
|
|
6265
|
-
|
|
6266
|
-
|
|
6859
|
+
console.log(nextStepAfterInstall(pendingActions.length));
|
|
6860
|
+
}
|
|
6861
|
+
function paint(fn, text) {
|
|
6862
|
+
const prevEnabled = koloristOptions2.enabled;
|
|
6863
|
+
const prevSupportLevel = koloristOptions2.supportLevel;
|
|
6864
|
+
koloristOptions2.enabled = true;
|
|
6865
|
+
koloristOptions2.supportLevel = 3;
|
|
6866
|
+
try {
|
|
6867
|
+
return fn(text);
|
|
6868
|
+
} finally {
|
|
6869
|
+
koloristOptions2.enabled = prevEnabled;
|
|
6870
|
+
koloristOptions2.supportLevel = prevSupportLevel;
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
function printInstallEpilogue(env, options = {}) {
|
|
6874
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
6875
|
+
const color = options.color ?? shouldUseWelcomeColor();
|
|
6876
|
+
if (env.binOnPath) {
|
|
6877
|
+
const line = "`agent-kit` is on PATH \u2014 run it directly, e.g. `agent-kit doctor`.";
|
|
6878
|
+
print(color ? paint(green2, line) : line);
|
|
6879
|
+
return;
|
|
6880
|
+
}
|
|
6881
|
+
const divider = "\u2500".repeat(60);
|
|
6882
|
+
const body = [
|
|
6883
|
+
"You ran this through npx, so a bare `agent-kit` isn't on PATH yet.",
|
|
6884
|
+
'If you try `agent-kit <subcommand>` next, you will see "command not',
|
|
6885
|
+
'found". Pick one:',
|
|
6886
|
+
"",
|
|
6887
|
+
" 1. Keep using npx \u2014 works right now, no action needed",
|
|
6888
|
+
" npx @dadado/agent-kit-cli <subcommand>",
|
|
6889
|
+
"",
|
|
6890
|
+
" 2. Put a bare `agent-kit` on PATH",
|
|
6891
|
+
" npx @dadado/agent-kit-cli setup-global",
|
|
6892
|
+
" (fixes a root-owned npm prefix if that's the blocker, or just installs)",
|
|
6893
|
+
"",
|
|
6894
|
+
" 3. Manual steps",
|
|
6895
|
+
" See docs/getting-started.md (Troubleshooting npm failures), or:",
|
|
6896
|
+
" mkdir -p ~/.npm-global",
|
|
6897
|
+
' npm config set prefix "~/.npm-global"',
|
|
6898
|
+
' export PATH="~/.npm-global/bin:$PATH"',
|
|
6899
|
+
" npm i -g @dadado/agent-kit-cli"
|
|
6900
|
+
];
|
|
6901
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6902
|
+
const heading = "Heads up: a bare `agent-kit` command won't work yet";
|
|
6903
|
+
print(color ? paint(bold, paint(cyan3, heading)) : heading);
|
|
6904
|
+
for (const line of body) print(line);
|
|
6905
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6906
|
+
}
|
|
6907
|
+
async function printPostInstallSummary(result) {
|
|
6908
|
+
printReadinessNarrative(result);
|
|
6909
|
+
const env = await assessEnvironment();
|
|
6910
|
+
printInstallEpilogue(env);
|
|
6267
6911
|
}
|
|
6268
6912
|
async function performInstall(options) {
|
|
6269
|
-
const projectRoot =
|
|
6913
|
+
const projectRoot = path36.resolve(options.cwd);
|
|
6270
6914
|
const packs = parsePackList(options.pack);
|
|
6271
6915
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
6272
6916
|
const registry = await resolveRegistryFromCli({
|
|
@@ -6294,6 +6938,7 @@ async function performInstall(options) {
|
|
|
6294
6938
|
generatorVersion: KIT_VERSION
|
|
6295
6939
|
});
|
|
6296
6940
|
let readiness = readinessExecution.after;
|
|
6941
|
+
let claudeSessionStartInstructions;
|
|
6297
6942
|
const profile = await readRepositoryProfile(projectRoot);
|
|
6298
6943
|
if (profile) {
|
|
6299
6944
|
const registryIndex = await loadRegistry(registry.root);
|
|
@@ -6304,9 +6949,11 @@ async function performInstall(options) {
|
|
|
6304
6949
|
report: readinessExecution.after,
|
|
6305
6950
|
registry: registryIndex,
|
|
6306
6951
|
manifest: draft,
|
|
6307
|
-
generatorVersion: KIT_VERSION
|
|
6952
|
+
generatorVersion: KIT_VERSION,
|
|
6953
|
+
claudeAdapters: options.claudeAdapters
|
|
6308
6954
|
});
|
|
6309
6955
|
await saveManifest(projectRoot, personalization.manifest);
|
|
6956
|
+
claudeSessionStartInstructions = personalization.result.claudeSessionStartInstructions;
|
|
6310
6957
|
readiness = createReadinessReport(await runScanner(projectRoot), {
|
|
6311
6958
|
generatorVersion: KIT_VERSION
|
|
6312
6959
|
});
|
|
@@ -6318,7 +6965,8 @@ async function performInstall(options) {
|
|
|
6318
6965
|
manifestPath,
|
|
6319
6966
|
stats,
|
|
6320
6967
|
readiness,
|
|
6321
|
-
safeChanges: readinessExecution.changes
|
|
6968
|
+
safeChanges: readinessExecution.changes,
|
|
6969
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6322
6970
|
};
|
|
6323
6971
|
} finally {
|
|
6324
6972
|
await registry.unlock?.();
|
|
@@ -6350,6 +6998,11 @@ var installCommand = defineCommand11({
|
|
|
6350
6998
|
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
6351
6999
|
default: false
|
|
6352
7000
|
},
|
|
7001
|
+
claude: {
|
|
7002
|
+
type: "boolean",
|
|
7003
|
+
description: "Opt-in: generate .claude/commands/*.md thin pointer adapters for the installed .cursor/commands set, and merge a SessionStart context hook into .claude/settings.json (default install behavior is unchanged without this flag)",
|
|
7004
|
+
default: false
|
|
7005
|
+
},
|
|
6353
7006
|
cwd: {
|
|
6354
7007
|
type: "string",
|
|
6355
7008
|
default: process.cwd()
|
|
@@ -6371,6 +7024,9 @@ var installCommand = defineCommand11({
|
|
|
6371
7024
|
} catch (err) {
|
|
6372
7025
|
if (err instanceof RootRefusedError) {
|
|
6373
7026
|
logger.error(err.message);
|
|
7027
|
+
if (err.recovery) console.error(`
|
|
7028
|
+
${err.recovery}
|
|
7029
|
+
`);
|
|
6374
7030
|
process.exitCode = 1;
|
|
6375
7031
|
return;
|
|
6376
7032
|
}
|
|
@@ -6393,13 +7049,20 @@ var installCommand = defineCommand11({
|
|
|
6393
7049
|
registry: args.registry,
|
|
6394
7050
|
url: args.url,
|
|
6395
7051
|
ref: args.ref,
|
|
6396
|
-
refresh: args.refresh
|
|
7052
|
+
refresh: args.refresh,
|
|
7053
|
+
claudeAdapters: args.claude
|
|
6397
7054
|
})
|
|
6398
7055
|
);
|
|
6399
7056
|
logApplyStats(result.stats);
|
|
6400
7057
|
logger.success(`Manifest written: ${result.manifestPath}`);
|
|
6401
7058
|
logger.success("Readiness snapshot written: .cursor/context/readiness.json");
|
|
6402
|
-
|
|
7059
|
+
await printPostInstallSummary(result);
|
|
7060
|
+
if (result.claudeSessionStartInstructions) {
|
|
7061
|
+
logger.warn("Could not merge the Claude Code SessionStart hook automatically:");
|
|
7062
|
+
console.log(`
|
|
7063
|
+
${result.claudeSessionStartInstructions}
|
|
7064
|
+
`);
|
|
7065
|
+
}
|
|
6403
7066
|
} catch (err) {
|
|
6404
7067
|
const hint = classifyInstallError(err);
|
|
6405
7068
|
logger.error(hint.message);
|
|
@@ -6426,25 +7089,57 @@ var initCommand = defineCommand12({
|
|
|
6426
7089
|
type: "string",
|
|
6427
7090
|
description: "Project root directory",
|
|
6428
7091
|
default: process.cwd()
|
|
7092
|
+
},
|
|
7093
|
+
yes: {
|
|
7094
|
+
type: "boolean",
|
|
7095
|
+
alias: "y",
|
|
7096
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
7097
|
+
default: false
|
|
7098
|
+
},
|
|
7099
|
+
"force-root": {
|
|
7100
|
+
type: "boolean",
|
|
7101
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
7102
|
+
default: false
|
|
6429
7103
|
}
|
|
6430
7104
|
},
|
|
6431
7105
|
async run({ args }) {
|
|
6432
|
-
const nonInteractive = isNonInteractive();
|
|
7106
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
6433
7107
|
if (!nonInteractive) {
|
|
6434
7108
|
intro(`agent-kit v${KIT_VERSION}`);
|
|
6435
7109
|
} else {
|
|
6436
7110
|
logger.info(`agent-kit v${KIT_VERSION} (non-interactive mode)`);
|
|
6437
7111
|
}
|
|
6438
7112
|
logger.info("init now uses the canonical install and readiness workflow.");
|
|
7113
|
+
let projectRoot;
|
|
6439
7114
|
try {
|
|
6440
|
-
|
|
7115
|
+
projectRoot = await confirmProjectRoot(args.cwd, {
|
|
7116
|
+
nonInteractive,
|
|
7117
|
+
command: "install",
|
|
7118
|
+
forceRoot: args["force-root"]
|
|
7119
|
+
});
|
|
7120
|
+
} catch (err) {
|
|
7121
|
+
if (err instanceof RootRefusedError) {
|
|
7122
|
+
logger.error(err.message);
|
|
7123
|
+
if (err.recovery) console.error(`
|
|
7124
|
+
${err.recovery}
|
|
7125
|
+
`);
|
|
7126
|
+
process.exitCode = 1;
|
|
7127
|
+
return;
|
|
7128
|
+
}
|
|
7129
|
+
throw err;
|
|
7130
|
+
}
|
|
7131
|
+
try {
|
|
7132
|
+
const result = await withCliProgress("init", () => runInitCompatibility(projectRoot));
|
|
6441
7133
|
const pending = result.readiness.pendingActions.length;
|
|
6442
7134
|
logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
|
|
6443
|
-
const nextStep = pending
|
|
7135
|
+
const nextStep = nextStepAfterInstall(pending);
|
|
7136
|
+
const env = await assessEnvironment();
|
|
6444
7137
|
if (!nonInteractive) {
|
|
7138
|
+
printInstallEpilogue(env);
|
|
6445
7139
|
outro(nextStep);
|
|
6446
7140
|
} else {
|
|
6447
7141
|
logger.info(nextStep);
|
|
7142
|
+
printInstallEpilogue(env);
|
|
6448
7143
|
}
|
|
6449
7144
|
} catch (err) {
|
|
6450
7145
|
const hint = classifyInstallError(err);
|
|
@@ -6458,13 +7153,13 @@ ${hint.recovery}
|
|
|
6458
7153
|
});
|
|
6459
7154
|
|
|
6460
7155
|
// src/commands/monitors.ts
|
|
6461
|
-
import
|
|
7156
|
+
import path38 from "path";
|
|
6462
7157
|
import { defineCommand as defineCommand13 } from "citty";
|
|
6463
7158
|
|
|
6464
7159
|
// src/invariants/monitors-untriaged.ts
|
|
6465
7160
|
import { execFile as execFile5 } from "child_process";
|
|
6466
|
-
import { readFile as
|
|
6467
|
-
import
|
|
7161
|
+
import { readFile as readFile20, readdir as readdir8, stat as stat5 } from "fs/promises";
|
|
7162
|
+
import path37 from "path";
|
|
6468
7163
|
import { promisify as promisify5 } from "util";
|
|
6469
7164
|
|
|
6470
7165
|
// src/invariants/triage-heading.ts
|
|
@@ -6488,7 +7183,7 @@ function hasOpenGaps(content) {
|
|
|
6488
7183
|
}
|
|
6489
7184
|
async function listMonitorFiles(memoryDir) {
|
|
6490
7185
|
try {
|
|
6491
|
-
const names = await
|
|
7186
|
+
const names = await readdir8(memoryDir);
|
|
6492
7187
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
6493
7188
|
} catch {
|
|
6494
7189
|
return [];
|
|
@@ -6505,7 +7200,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
6505
7200
|
for (const line of stdout.split("\n")) {
|
|
6506
7201
|
if (!line.trim()) continue;
|
|
6507
7202
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
6508
|
-
const base =
|
|
7203
|
+
const base = path37.basename(file);
|
|
6509
7204
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
6510
7205
|
names.add(base);
|
|
6511
7206
|
}
|
|
@@ -6528,15 +7223,15 @@ function monitorSlugFromName(fileName) {
|
|
|
6528
7223
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
6529
7224
|
}
|
|
6530
7225
|
async function selectUntriagedMonitors(rootDir) {
|
|
6531
|
-
const root =
|
|
6532
|
-
const memoryDir =
|
|
7226
|
+
const root = path37.resolve(rootDir);
|
|
7227
|
+
const memoryDir = path37.join(root, ".cursor", "memory");
|
|
6533
7228
|
const allNames = await listMonitorFiles(memoryDir);
|
|
6534
7229
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
6535
7230
|
const byName = /* @__PURE__ */ new Map();
|
|
6536
7231
|
for (const name of allNames) {
|
|
6537
|
-
const abs =
|
|
7232
|
+
const abs = path37.join(memoryDir, name);
|
|
6538
7233
|
try {
|
|
6539
|
-
const [content, st] = await Promise.all([
|
|
7234
|
+
const [content, st] = await Promise.all([readFile20(abs, "utf8"), stat5(abs)]);
|
|
6540
7235
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
6541
7236
|
} catch {
|
|
6542
7237
|
}
|
|
@@ -6549,7 +7244,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6549
7244
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
6550
7245
|
let handoff = "";
|
|
6551
7246
|
try {
|
|
6552
|
-
handoff = await
|
|
7247
|
+
handoff = await readFile20(path37.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
6553
7248
|
} catch {
|
|
6554
7249
|
handoff = "";
|
|
6555
7250
|
}
|
|
@@ -6569,8 +7264,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6569
7264
|
const row = byName.get(name);
|
|
6570
7265
|
if (!row) continue;
|
|
6571
7266
|
entries.push({
|
|
6572
|
-
path:
|
|
6573
|
-
relativePath:
|
|
7267
|
+
path: path37.join(memoryDir, name),
|
|
7268
|
+
relativePath: path37.relative(root, path37.join(memoryDir, name)).split(path37.sep).join("/"),
|
|
6574
7269
|
mtimeMs: row.mtimeMs,
|
|
6575
7270
|
hasTriageHeading: false,
|
|
6576
7271
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -6616,7 +7311,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6616
7311
|
process.exitCode = 2;
|
|
6617
7312
|
return;
|
|
6618
7313
|
}
|
|
6619
|
-
const result = await selectUntriagedMonitors(
|
|
7314
|
+
const result = await selectUntriagedMonitors(path38.resolve(args.cwd));
|
|
6620
7315
|
if (args.json) {
|
|
6621
7316
|
console.log(JSON.stringify(result, null, 2));
|
|
6622
7317
|
return;
|
|
@@ -6632,7 +7327,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6632
7327
|
});
|
|
6633
7328
|
|
|
6634
7329
|
// src/commands/run-plan.ts
|
|
6635
|
-
import
|
|
7330
|
+
import path43 from "path";
|
|
6636
7331
|
import { defineCommand as defineCommand14 } from "citty";
|
|
6637
7332
|
|
|
6638
7333
|
// src/plan-loop/backends.ts
|
|
@@ -6717,14 +7412,14 @@ function listBackendIds() {
|
|
|
6717
7412
|
}
|
|
6718
7413
|
|
|
6719
7414
|
// src/plan-loop/run-loop.ts
|
|
6720
|
-
import { mkdir as mkdir6, readFile as
|
|
6721
|
-
import
|
|
7415
|
+
import { mkdir as mkdir6, readFile as readFile23, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
7416
|
+
import path42 from "path";
|
|
6722
7417
|
|
|
6723
7418
|
// src/plan-loop/external-review.ts
|
|
6724
7419
|
import { spawn as spawn7 } from "child_process";
|
|
6725
|
-
import
|
|
6726
|
-
var CANONICAL_REL =
|
|
6727
|
-
var FALLBACK_REL =
|
|
7420
|
+
import path39 from "path";
|
|
7421
|
+
var CANONICAL_REL = path39.join(".cursor", "scripts", "plan-external-review.sh");
|
|
7422
|
+
var FALLBACK_REL = path39.join("scripts", "plan-external-review.sh");
|
|
6728
7423
|
function isPlanExhaustedReason(reason) {
|
|
6729
7424
|
const r = reason.trim().toLowerCase();
|
|
6730
7425
|
if (!r) return false;
|
|
@@ -6746,8 +7441,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6746
7441
|
const existsFn = options.existsFn ?? fileExists;
|
|
6747
7442
|
const log = options.log ?? ((line) => console.log(line));
|
|
6748
7443
|
const force = options.force === true;
|
|
6749
|
-
const canonicalPath =
|
|
6750
|
-
const fallbackPath =
|
|
7444
|
+
const canonicalPath = path39.join(root, CANONICAL_REL);
|
|
7445
|
+
const fallbackPath = path39.join(root, FALLBACK_REL);
|
|
6751
7446
|
let scriptPath = null;
|
|
6752
7447
|
let scriptRel = CANONICAL_REL;
|
|
6753
7448
|
if (await existsFn(canonicalPath)) {
|
|
@@ -6800,12 +7495,12 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6800
7495
|
}
|
|
6801
7496
|
|
|
6802
7497
|
// src/plan-loop/persona-banners.ts
|
|
6803
|
-
import
|
|
7498
|
+
import path40 from "path";
|
|
6804
7499
|
import {
|
|
6805
7500
|
blue,
|
|
6806
|
-
cyan as
|
|
7501
|
+
cyan as cyan4,
|
|
6807
7502
|
gray as gray2,
|
|
6808
|
-
green as
|
|
7503
|
+
green as green3,
|
|
6809
7504
|
lightGray,
|
|
6810
7505
|
lightGreen,
|
|
6811
7506
|
magenta,
|
|
@@ -6818,8 +7513,8 @@ var DEFAULT_CLI_PERSONA_ID = "ghost-runner";
|
|
|
6818
7513
|
var COLORS = {
|
|
6819
7514
|
white,
|
|
6820
7515
|
gray: gray2,
|
|
6821
|
-
green:
|
|
6822
|
-
cyan:
|
|
7516
|
+
green: green3,
|
|
7517
|
+
cyan: cyan4,
|
|
6823
7518
|
magenta,
|
|
6824
7519
|
yellow: yellow2,
|
|
6825
7520
|
red: red2,
|
|
@@ -6836,7 +7531,7 @@ function resolveColor(name, fallback) {
|
|
|
6836
7531
|
async function resolveCliPersonaId(root) {
|
|
6837
7532
|
try {
|
|
6838
7533
|
const cfg = await readJson(
|
|
6839
|
-
|
|
7534
|
+
path40.join(root, ".cursor", "context", "config.json")
|
|
6840
7535
|
);
|
|
6841
7536
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
6842
7537
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -6847,7 +7542,7 @@ async function resolveCliPersonaId(root) {
|
|
|
6847
7542
|
}
|
|
6848
7543
|
async function loadPersonaPack(root, personaId) {
|
|
6849
7544
|
try {
|
|
6850
|
-
const personaPath =
|
|
7545
|
+
const personaPath = path40.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
6851
7546
|
const pack = await readJson(personaPath);
|
|
6852
7547
|
if (!pack || typeof pack.id !== "string") return null;
|
|
6853
7548
|
return pack;
|
|
@@ -6865,7 +7560,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6865
7560
|
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
6866
7561
|
const primary = resolveColor(persona.ansiPalette?.primary, white);
|
|
6867
7562
|
const secondary = resolveColor(persona.ansiPalette?.secondary, gray2);
|
|
6868
|
-
const accent = resolveColor(persona.ansiPalette?.accent,
|
|
7563
|
+
const accent = resolveColor(persona.ansiPalette?.accent, green3);
|
|
6869
7564
|
return {
|
|
6870
7565
|
tickStart(detail) {
|
|
6871
7566
|
if (banners.tickStart) {
|
|
@@ -6896,8 +7591,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6896
7591
|
}
|
|
6897
7592
|
|
|
6898
7593
|
// src/plan-loop/plan-state.ts
|
|
6899
|
-
import { readFile as
|
|
6900
|
-
import
|
|
7594
|
+
import { readFile as readFile21, readdir as readdir9 } from "fs/promises";
|
|
7595
|
+
import path41 from "path";
|
|
6901
7596
|
function countPendingTodos(raw) {
|
|
6902
7597
|
const lines = raw.split(/\r?\n/);
|
|
6903
7598
|
let inFront = 0;
|
|
@@ -6924,15 +7619,15 @@ function countPendingTodos(raw) {
|
|
|
6924
7619
|
}
|
|
6925
7620
|
async function findActivePlanFile(plansDir) {
|
|
6926
7621
|
if (!await fileExists(plansDir)) return null;
|
|
6927
|
-
const files = (await
|
|
6928
|
-
return files[0] ?
|
|
7622
|
+
const files = (await readdir9(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
7623
|
+
return files[0] ? path41.join(plansDir, files[0]) : null;
|
|
6929
7624
|
}
|
|
6930
7625
|
async function readPlan(planPath) {
|
|
6931
|
-
return
|
|
7626
|
+
return readFile21(planPath, "utf8");
|
|
6932
7627
|
}
|
|
6933
7628
|
|
|
6934
7629
|
// src/plan-loop/sentinel.ts
|
|
6935
|
-
import { readFile as
|
|
7630
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
6936
7631
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
6937
7632
|
function takeFromText(text) {
|
|
6938
7633
|
if (!text) return null;
|
|
@@ -6979,7 +7674,7 @@ function parseSentinelFromLog(content) {
|
|
|
6979
7674
|
}
|
|
6980
7675
|
async function parseSentinelFromLogFile(logPath) {
|
|
6981
7676
|
try {
|
|
6982
|
-
const content = await
|
|
7677
|
+
const content = await readFile22(logPath, "utf8");
|
|
6983
7678
|
return parseSentinelFromLog(content);
|
|
6984
7679
|
} catch {
|
|
6985
7680
|
return { kind: "missing" };
|
|
@@ -7002,9 +7697,9 @@ function sleep(ms) {
|
|
|
7002
7697
|
return new Promise((r) => setTimeout(r, ms));
|
|
7003
7698
|
}
|
|
7004
7699
|
async function runPlanLoop(opts) {
|
|
7005
|
-
const plansDir =
|
|
7006
|
-
const stopFile =
|
|
7007
|
-
const logDir =
|
|
7700
|
+
const plansDir = path42.join(opts.root, ".cursor", "plans");
|
|
7701
|
+
const stopFile = path42.join(opts.root, ".cursor", "loop.stop");
|
|
7702
|
+
const logDir = path42.join(opts.root, ".cursor", "loop-logs");
|
|
7008
7703
|
const planPath = await findActivePlanFile(plansDir);
|
|
7009
7704
|
if (!planPath) {
|
|
7010
7705
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -7025,7 +7720,7 @@ async function runPlanLoop(opts) {
|
|
|
7025
7720
|
try {
|
|
7026
7721
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
7027
7722
|
const banners = createPersonaBannerPrinter(persona);
|
|
7028
|
-
console.log(`Active plan: ${
|
|
7723
|
+
console.log(`Active plan: ${path42.basename(planPath)}`);
|
|
7029
7724
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
7030
7725
|
console.log(`Backend: ${opts.backend.id}`);
|
|
7031
7726
|
if (persona) {
|
|
@@ -7068,8 +7763,8 @@ async function runPlanLoop(opts) {
|
|
|
7068
7763
|
planExhausted = true;
|
|
7069
7764
|
break;
|
|
7070
7765
|
}
|
|
7071
|
-
const logPath =
|
|
7072
|
-
const relLog =
|
|
7766
|
+
const logPath = path42.join(logDir, `tick-${stamp()}.log`);
|
|
7767
|
+
const relLog = path42.relative(opts.root, logPath);
|
|
7073
7768
|
console.log("");
|
|
7074
7769
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
7075
7770
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -7091,7 +7786,7 @@ async function runPlanLoop(opts) {
|
|
|
7091
7786
|
return 1;
|
|
7092
7787
|
}
|
|
7093
7788
|
try {
|
|
7094
|
-
const logText = await
|
|
7789
|
+
const logText = await readFile23(logPath, "utf8");
|
|
7095
7790
|
if (logText.includes("Too many MCP tools")) {
|
|
7096
7791
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
7097
7792
|
if (banners) banners.stop(msg);
|
|
@@ -7148,7 +7843,7 @@ async function runPlanLoop(opts) {
|
|
|
7148
7843
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
7149
7844
|
if (banners) banners.phaseComplete(finishDetail);
|
|
7150
7845
|
console.log(
|
|
7151
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
7846
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path42.relative(opts.root, logDir)}/`
|
|
7152
7847
|
);
|
|
7153
7848
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
7154
7849
|
await armExternalPlanReview(opts.root);
|
|
@@ -7219,7 +7914,7 @@ var runPlanCommand = defineCommand14({
|
|
|
7219
7914
|
return;
|
|
7220
7915
|
}
|
|
7221
7916
|
const code = await runPlanLoop({
|
|
7222
|
-
root:
|
|
7917
|
+
root: path43.resolve(args.cwd),
|
|
7223
7918
|
maxTicks,
|
|
7224
7919
|
sleepSeconds,
|
|
7225
7920
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -7252,9 +7947,286 @@ var scanCommand = defineCommand15({
|
|
|
7252
7947
|
}
|
|
7253
7948
|
});
|
|
7254
7949
|
|
|
7255
|
-
// src/commands/
|
|
7256
|
-
import
|
|
7950
|
+
// src/commands/setup-global.ts
|
|
7951
|
+
import { spawn as spawn8 } from "child_process";
|
|
7952
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile24, writeFile as writeFile12 } from "fs/promises";
|
|
7953
|
+
import { homedir as homedir4 } from "os";
|
|
7954
|
+
import path44 from "path";
|
|
7955
|
+
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
7257
7956
|
import { defineCommand as defineCommand16 } from "citty";
|
|
7957
|
+
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
7958
|
+
var NPM_GLOBAL_DIR_NAME = ".npm-global";
|
|
7959
|
+
var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
7960
|
+
var DEFAULT_PACKAGE_SPEC = "@dadado/agent-kit-cli";
|
|
7961
|
+
function planSetupGlobalSteps(env, options = {}) {
|
|
7962
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
7963
|
+
const packageSpec = options.packageSpec ?? DEFAULT_PACKAGE_SPEC;
|
|
7964
|
+
const npmGlobalDir = path44.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
7965
|
+
const npmGlobalBin = path44.join(npmGlobalDir, "bin");
|
|
7966
|
+
const npmrcPath = path44.join(homeDir, ".npmrc");
|
|
7967
|
+
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
7968
|
+
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
7969
|
+
const shellProfile = env.shellProfile;
|
|
7970
|
+
const shellSupported = shellProfile != null;
|
|
7971
|
+
const steps = [
|
|
7972
|
+
{
|
|
7973
|
+
id: "set-prefix",
|
|
7974
|
+
title: "Set npm's global install prefix to a folder you own",
|
|
7975
|
+
detail: [
|
|
7976
|
+
`mkdir -p ${npmGlobalDir}`,
|
|
7977
|
+
`npm config set prefix "${npmrcPrefixValue}" (writes "prefix = ${npmrcPrefixValue}" to ${npmrcPath})`
|
|
7978
|
+
]
|
|
7979
|
+
},
|
|
7980
|
+
{
|
|
7981
|
+
id: "append-path",
|
|
7982
|
+
title: shellSupported ? `Put ${npmGlobalBin} on PATH via ${shellProfile}` : "Put npm's global bin on PATH (manual \u2014 shell not auto-detected)",
|
|
7983
|
+
detail: shellSupported ? [`Append to ${shellProfile}:`, ` ${SETUP_GLOBAL_MARKER}`, ` ${pathExportLine}`] : [
|
|
7984
|
+
`Shell could not be auto-detected as zsh or bash (detected: ${env.shell ?? "unknown"}).`,
|
|
7985
|
+
"You'll need to add this line to your shell's startup file yourself:",
|
|
7986
|
+
` ${pathExportLine}`
|
|
7987
|
+
]
|
|
7988
|
+
},
|
|
7989
|
+
{
|
|
7990
|
+
id: "npm-install",
|
|
7991
|
+
title: `Reinstall ${packageSpec} globally, now into the new prefix`,
|
|
7992
|
+
detail: [`npm i -g ${packageSpec}`]
|
|
7993
|
+
},
|
|
7994
|
+
{
|
|
7995
|
+
id: "verify",
|
|
7996
|
+
title: "Verify `agent-kit` resolves on PATH",
|
|
7997
|
+
detail: [
|
|
7998
|
+
"Re-check whether a bare `agent-kit` resolves on PATH.",
|
|
7999
|
+
"Needs a new shell session (or `source` the profile) to take effect \u2014 this process's own PATH can't reflect it."
|
|
8000
|
+
]
|
|
8001
|
+
}
|
|
8002
|
+
];
|
|
8003
|
+
return {
|
|
8004
|
+
packageSpec,
|
|
8005
|
+
homeDir,
|
|
8006
|
+
npmGlobalDir,
|
|
8007
|
+
npmGlobalBin,
|
|
8008
|
+
npmrcPath,
|
|
8009
|
+
npmrcPrefixValue,
|
|
8010
|
+
pathExportLine,
|
|
8011
|
+
markerComment: SETUP_GLOBAL_MARKER,
|
|
8012
|
+
shellProfile,
|
|
8013
|
+
shellSupported,
|
|
8014
|
+
alreadyWritable: env.npmPrefixWritable,
|
|
8015
|
+
currentPrefix: env.npmPrefix.prefix,
|
|
8016
|
+
steps
|
|
8017
|
+
};
|
|
8018
|
+
}
|
|
8019
|
+
function upsertNpmrcPrefix(content, prefixValue) {
|
|
8020
|
+
const line = `prefix = ${prefixValue}`;
|
|
8021
|
+
const prefixLineRe = /^\s*prefix\s*=.*$/m;
|
|
8022
|
+
if (prefixLineRe.test(content)) {
|
|
8023
|
+
return content.replace(prefixLineRe, line);
|
|
8024
|
+
}
|
|
8025
|
+
const withTrailingNewline = content.length > 0 && !content.endsWith("\n") ? `${content}
|
|
8026
|
+
` : content;
|
|
8027
|
+
return `${withTrailingNewline}${line}
|
|
8028
|
+
`;
|
|
8029
|
+
}
|
|
8030
|
+
var defaultFsImpl = {
|
|
8031
|
+
mkdir: async (dir) => {
|
|
8032
|
+
await mkdir7(dir, { recursive: true });
|
|
8033
|
+
},
|
|
8034
|
+
readFile: (filePath) => readFile24(filePath, "utf8"),
|
|
8035
|
+
writeFile: (filePath, content) => writeFile12(filePath, content, "utf8"),
|
|
8036
|
+
appendFile: (filePath, content) => appendFile(filePath, content, "utf8")
|
|
8037
|
+
};
|
|
8038
|
+
async function safeReadFile(fs, filePath) {
|
|
8039
|
+
try {
|
|
8040
|
+
return await fs.readFile(filePath);
|
|
8041
|
+
} catch {
|
|
8042
|
+
return "";
|
|
8043
|
+
}
|
|
8044
|
+
}
|
|
8045
|
+
var defaultNpmInstallImpl = (packageSpec) => new Promise((resolve2) => {
|
|
8046
|
+
const child = spawn8("npm", ["i", "-g", packageSpec], { stdio: "inherit" });
|
|
8047
|
+
child.on("error", (error) => resolve2({ ok: false, error }));
|
|
8048
|
+
child.on("close", (code) => {
|
|
8049
|
+
if (code === 0) resolve2({ ok: true });
|
|
8050
|
+
else resolve2({ ok: false, error: new Error(`npm exited with code ${code ?? "unknown"}`) });
|
|
8051
|
+
});
|
|
8052
|
+
});
|
|
8053
|
+
var defaultConfirmImpl = async (message) => {
|
|
8054
|
+
const answer = await confirm2({ message, initialValue: true });
|
|
8055
|
+
if (isCancel2(answer)) return false;
|
|
8056
|
+
return Boolean(answer);
|
|
8057
|
+
};
|
|
8058
|
+
function printHeader(env, print) {
|
|
8059
|
+
print(cyan5("agent-kit setup-global"));
|
|
8060
|
+
print(
|
|
8061
|
+
` npm prefix: ${env.npmPrefix.prefix ?? "unknown"} (${env.npmPrefixWritable ? "writable" : "NOT writable"})`
|
|
8062
|
+
);
|
|
8063
|
+
if (!env.npmPrefixWritable && env.npmPrefix.reason) {
|
|
8064
|
+
print(` ${env.npmPrefix.reason}`);
|
|
8065
|
+
}
|
|
8066
|
+
print(
|
|
8067
|
+
` shell: ${env.shell ?? "unknown"}${env.shellProfile ? ` (profile: ${env.shellProfile})` : " (profile not auto-detected: zsh/bash only)"}`
|
|
8068
|
+
);
|
|
8069
|
+
}
|
|
8070
|
+
function printPlanSteps(plan, print) {
|
|
8071
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
8072
|
+
print(`${index + 1}. ${step.title}`);
|
|
8073
|
+
for (const line of step.detail) print(` ${line}`);
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
function printManualInstructions(plan, print) {
|
|
8077
|
+
print("No changes made. Same fix, as commands you can run yourself:");
|
|
8078
|
+
print(` mkdir -p ${plan.npmGlobalDir}`);
|
|
8079
|
+
print(` npm config set prefix "${plan.npmrcPrefixValue}"`);
|
|
8080
|
+
if (plan.shellSupported) {
|
|
8081
|
+
print(` echo '${plan.markerComment}' >> ${plan.shellProfile}`);
|
|
8082
|
+
print(` echo '${plan.pathExportLine}' >> ${plan.shellProfile}`);
|
|
8083
|
+
print(` source ${plan.shellProfile}`);
|
|
8084
|
+
} else {
|
|
8085
|
+
print(` # add this line to your shell's startup file:`);
|
|
8086
|
+
print(` ${plan.pathExportLine}`);
|
|
8087
|
+
}
|
|
8088
|
+
print(` npm i -g ${plan.packageSpec}`);
|
|
8089
|
+
print(" agent-kit --version # verify, in a new shell session");
|
|
8090
|
+
}
|
|
8091
|
+
async function runSetupGlobal(options = {}) {
|
|
8092
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
8093
|
+
const assessEnvironmentImpl = options.assessEnvironmentImpl ?? assessEnvironment;
|
|
8094
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
8095
|
+
const env = await assessEnvironmentImpl(options);
|
|
8096
|
+
const plan = planSetupGlobalSteps(env, { homeDir, packageSpec: options.packageSpec });
|
|
8097
|
+
if (plan.alreadyWritable) {
|
|
8098
|
+
printHeader(env, print);
|
|
8099
|
+
print(green4("npm's global prefix is already writable \u2014 nothing to fix."));
|
|
8100
|
+
return { exitCode: 0, mutated: false, outcome: "already-ok", env, plan };
|
|
8101
|
+
}
|
|
8102
|
+
if (options.dryRun) {
|
|
8103
|
+
printHeader(env, print);
|
|
8104
|
+
print("Dry run \u2014 no changes will be made. Steps that would run:");
|
|
8105
|
+
printPlanSteps(plan, print);
|
|
8106
|
+
return { exitCode: 0, mutated: false, outcome: "dry-run", env, plan };
|
|
8107
|
+
}
|
|
8108
|
+
const nonInteractive = options.nonInteractive ?? isNonInteractive();
|
|
8109
|
+
if (nonInteractive) {
|
|
8110
|
+
printHeader(env, print);
|
|
8111
|
+
printManualInstructions(plan, print);
|
|
8112
|
+
return { exitCode: 0, mutated: false, outcome: "manual-instructions", env, plan };
|
|
8113
|
+
}
|
|
8114
|
+
printHeader(env, print);
|
|
8115
|
+
print("You just hit the classic 'command not found' / EACCES fresh-install blocker.");
|
|
8116
|
+
print("The following steps need your confirmation, one at a time:");
|
|
8117
|
+
printPlanSteps(plan, print);
|
|
8118
|
+
const fs = options.fsImpl ?? defaultFsImpl;
|
|
8119
|
+
const confirmStep = options.confirmImpl ?? defaultConfirmImpl;
|
|
8120
|
+
const npmInstall = options.npmInstallImpl ?? defaultNpmInstallImpl;
|
|
8121
|
+
let mutated = false;
|
|
8122
|
+
const setPrefixStep = plan.steps[0];
|
|
8123
|
+
print(`
|
|
8124
|
+
${setPrefixStep.title}`);
|
|
8125
|
+
for (const line of setPrefixStep.detail) print(` ${line}`);
|
|
8126
|
+
const proceedPrefix = await confirmStep(`Set npm's global prefix to ${plan.npmGlobalDir}?`);
|
|
8127
|
+
if (!proceedPrefix) {
|
|
8128
|
+
print(yellow3("Cancelled \u2014 no changes made."));
|
|
8129
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8130
|
+
}
|
|
8131
|
+
await fs.mkdir(plan.npmGlobalDir);
|
|
8132
|
+
const npmrcContent = await safeReadFile(fs, plan.npmrcPath);
|
|
8133
|
+
await fs.writeFile(plan.npmrcPath, upsertNpmrcPrefix(npmrcContent, plan.npmrcPrefixValue));
|
|
8134
|
+
mutated = true;
|
|
8135
|
+
print(green4(` done: prefix set (${plan.npmrcPath}).`));
|
|
8136
|
+
const appendPathStep = plan.steps[1];
|
|
8137
|
+
print(`
|
|
8138
|
+
${appendPathStep.title}`);
|
|
8139
|
+
for (const line of appendPathStep.detail) print(` ${line}`);
|
|
8140
|
+
if (!plan.shellSupported) {
|
|
8141
|
+
print(yellow3(" shell not auto-detected as zsh/bash \u2014 add the line above yourself."));
|
|
8142
|
+
} else {
|
|
8143
|
+
const profilePath = plan.shellProfile;
|
|
8144
|
+
const profileContent = await safeReadFile(fs, profilePath);
|
|
8145
|
+
if (profileContent.includes(plan.markerComment)) {
|
|
8146
|
+
print(` already present in ${profilePath} (marker found) \u2014 skipping.`);
|
|
8147
|
+
} else {
|
|
8148
|
+
const proceedPath = await confirmStep(`Append the PATH export to ${profilePath}?`);
|
|
8149
|
+
if (!proceedPath) {
|
|
8150
|
+
print(yellow3("Cancelled \u2014 prefix was set, PATH export was not appended."));
|
|
8151
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8152
|
+
}
|
|
8153
|
+
await fs.appendFile(profilePath, `
|
|
8154
|
+
${plan.markerComment}
|
|
8155
|
+
${plan.pathExportLine}
|
|
8156
|
+
`);
|
|
8157
|
+
mutated = true;
|
|
8158
|
+
print(green4(` done: PATH export appended to ${profilePath}.`));
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
const installStep = plan.steps[2];
|
|
8162
|
+
print(`
|
|
8163
|
+
${installStep.title}`);
|
|
8164
|
+
for (const line of installStep.detail) print(` ${line}`);
|
|
8165
|
+
const proceedInstall = await confirmStep(`Run: npm i -g ${plan.packageSpec}?`);
|
|
8166
|
+
if (!proceedInstall) {
|
|
8167
|
+
print(yellow3("Cancelled \u2014 prefix/PATH changes above are still in place."));
|
|
8168
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8169
|
+
}
|
|
8170
|
+
const installResult = await npmInstall(plan.packageSpec);
|
|
8171
|
+
if (!installResult.ok) {
|
|
8172
|
+
const hint = classifyInstallError(installResult.error);
|
|
8173
|
+
print(` npm install failed: ${hint.message}`);
|
|
8174
|
+
print(hint.recovery);
|
|
8175
|
+
return { exitCode: 1, mutated, outcome: "error", env, plan };
|
|
8176
|
+
}
|
|
8177
|
+
mutated = true;
|
|
8178
|
+
print(green4(` done: ${plan.packageSpec} installed globally.`));
|
|
8179
|
+
const verifyStep = plan.steps[3];
|
|
8180
|
+
print(`
|
|
8181
|
+
${verifyStep.title}`);
|
|
8182
|
+
for (const line of verifyStep.detail) print(` ${line}`);
|
|
8183
|
+
const proceedVerify = await confirmStep("Verify now (re-check PATH in this process)?");
|
|
8184
|
+
if (proceedVerify) {
|
|
8185
|
+
const verifyEnv = await assessEnvironmentImpl(options);
|
|
8186
|
+
if (verifyEnv.binOnPath) {
|
|
8187
|
+
print(green4(" verify: `agent-kit` resolves on PATH."));
|
|
8188
|
+
} else {
|
|
8189
|
+
print(" verify: `agent-kit` isn't resolvable in THIS process's PATH yet \u2014 that's expected.");
|
|
8190
|
+
print(
|
|
8191
|
+
` Open a new terminal (or run: source ${plan.shellProfile ?? "<your shell profile>"}) and re-check with: agent-kit --version`
|
|
8192
|
+
);
|
|
8193
|
+
}
|
|
8194
|
+
} else {
|
|
8195
|
+
print(" skipped verification. Open a new terminal and run: agent-kit --version");
|
|
8196
|
+
}
|
|
8197
|
+
return { exitCode: 0, mutated, outcome: "completed", env, plan };
|
|
8198
|
+
}
|
|
8199
|
+
var setupGlobalCommand = defineCommand16({
|
|
8200
|
+
meta: {
|
|
8201
|
+
name: "setup-global",
|
|
8202
|
+
description: "Self-heal a root-owned npm prefix: relocate to ~/.npm-global, fix PATH, reinstall globally."
|
|
8203
|
+
},
|
|
8204
|
+
args: {
|
|
8205
|
+
"dry-run": {
|
|
8206
|
+
type: "boolean",
|
|
8207
|
+
description: "Print the resolved plan; mutate nothing.",
|
|
8208
|
+
default: false
|
|
8209
|
+
},
|
|
8210
|
+
yes: {
|
|
8211
|
+
type: "boolean",
|
|
8212
|
+
alias: "y",
|
|
8213
|
+
description: "Treat as non-interactive: print the manual steps instead of prompting (never mutates).",
|
|
8214
|
+
default: false
|
|
8215
|
+
}
|
|
8216
|
+
},
|
|
8217
|
+
async run({ args }) {
|
|
8218
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
8219
|
+
const result = await runSetupGlobal({
|
|
8220
|
+
dryRun: Boolean(args["dry-run"]),
|
|
8221
|
+
nonInteractive
|
|
8222
|
+
});
|
|
8223
|
+
process.exitCode = result.exitCode;
|
|
8224
|
+
}
|
|
8225
|
+
});
|
|
8226
|
+
|
|
8227
|
+
// src/commands/status.ts
|
|
8228
|
+
import path45 from "path";
|
|
8229
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
7258
8230
|
function profileStatus(profile) {
|
|
7259
8231
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
7260
8232
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -7267,7 +8239,7 @@ function profileStatus(profile) {
|
|
|
7267
8239
|
}
|
|
7268
8240
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
7269
8241
|
}
|
|
7270
|
-
var statusCommand =
|
|
8242
|
+
var statusCommand = defineCommand17({
|
|
7271
8243
|
meta: {
|
|
7272
8244
|
name: "status",
|
|
7273
8245
|
description: "Show installed kit version, manifest, and optional profile."
|
|
@@ -7284,11 +8256,11 @@ var statusCommand = defineCommand16({
|
|
|
7284
8256
|
}
|
|
7285
8257
|
},
|
|
7286
8258
|
async run({ args }) {
|
|
7287
|
-
const rootDir =
|
|
8259
|
+
const rootDir = path45.resolve(args.cwd);
|
|
7288
8260
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
7289
8261
|
loadAgentKitManifest(rootDir),
|
|
7290
8262
|
readJson(
|
|
7291
|
-
|
|
8263
|
+
path45.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
7292
8264
|
),
|
|
7293
8265
|
runScanner(rootDir)
|
|
7294
8266
|
]);
|
|
@@ -7343,11 +8315,11 @@ var statusCommand = defineCommand16({
|
|
|
7343
8315
|
});
|
|
7344
8316
|
|
|
7345
8317
|
// src/commands/update.ts
|
|
7346
|
-
import { defineCommand as
|
|
8318
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
7347
8319
|
|
|
7348
8320
|
// src/lifecycle/check-updates.ts
|
|
7349
8321
|
import { execFile as execFile6 } from "child_process";
|
|
7350
|
-
import
|
|
8322
|
+
import path46 from "path";
|
|
7351
8323
|
import { promisify as promisify6 } from "util";
|
|
7352
8324
|
var execFileAsync5 = promisify6(execFile6);
|
|
7353
8325
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -7438,11 +8410,11 @@ function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
|
7438
8410
|
return Date.now() - last >= ms;
|
|
7439
8411
|
}
|
|
7440
8412
|
async function loadContextConfig2(cwd) {
|
|
7441
|
-
const configPath =
|
|
8413
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7442
8414
|
return readJson(configPath);
|
|
7443
8415
|
}
|
|
7444
8416
|
async function stampLastCheckedAt(cwd) {
|
|
7445
|
-
const configPath =
|
|
8417
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7446
8418
|
const existing = await loadContextConfig2(cwd) ?? {};
|
|
7447
8419
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
7448
8420
|
existing.updateCheck = {
|
|
@@ -7454,7 +8426,7 @@ async function stampLastCheckedAt(cwd) {
|
|
|
7454
8426
|
}
|
|
7455
8427
|
async function readLocalKitVersion(registryRoot) {
|
|
7456
8428
|
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
7457
|
-
const data = await readJson(
|
|
8429
|
+
const data = await readJson(path46.join(registryRoot, rel));
|
|
7458
8430
|
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
7459
8431
|
return data.version;
|
|
7460
8432
|
}
|
|
@@ -7475,7 +8447,7 @@ async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
|
7475
8447
|
status: "error",
|
|
7476
8448
|
installedVersion: manifest.version,
|
|
7477
8449
|
latestVersion: null,
|
|
7478
|
-
registryUrl:
|
|
8450
|
+
registryUrl: path46.resolve(registryPath),
|
|
7479
8451
|
registryRef: "local",
|
|
7480
8452
|
applyRecommended: false,
|
|
7481
8453
|
message: `Failed to resolve --registry: ${msg}`
|
|
@@ -7697,7 +8669,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
7697
8669
|
}
|
|
7698
8670
|
|
|
7699
8671
|
// src/commands/update.ts
|
|
7700
|
-
var updateCommand =
|
|
8672
|
+
var updateCommand = defineCommand18({
|
|
7701
8673
|
meta: {
|
|
7702
8674
|
name: "update",
|
|
7703
8675
|
description: "Re-apply L0/packs/skills from the registry (never overwrites L3). --check = notify-only."
|
|
@@ -7776,6 +8748,9 @@ var updateCommand = defineCommand17({
|
|
|
7776
8748
|
} catch (err) {
|
|
7777
8749
|
if (err instanceof RootRefusedError) {
|
|
7778
8750
|
logger.error(err.message);
|
|
8751
|
+
if (err.recovery) console.error(`
|
|
8752
|
+
${err.recovery}
|
|
8753
|
+
`);
|
|
7779
8754
|
process.exitCode = 1;
|
|
7780
8755
|
return;
|
|
7781
8756
|
}
|
|
@@ -7828,9 +8803,9 @@ var updateCommand = defineCommand17({
|
|
|
7828
8803
|
});
|
|
7829
8804
|
|
|
7830
8805
|
// src/commands/validate.ts
|
|
7831
|
-
import { readFile as
|
|
7832
|
-
import
|
|
7833
|
-
import { defineCommand as
|
|
8806
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
8807
|
+
import path47 from "path";
|
|
8808
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
7834
8809
|
|
|
7835
8810
|
// src/invariants/plan-schema.ts
|
|
7836
8811
|
var CITE5 = "agent-kit validate plan (.cursor/context/templates/plan.md)";
|
|
@@ -7876,9 +8851,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
7876
8851
|
// src/commands/validate.ts
|
|
7877
8852
|
async function resolveEditedPath(cwd, explicit) {
|
|
7878
8853
|
if (explicit) {
|
|
7879
|
-
const filePath2 =
|
|
8854
|
+
const filePath2 = path47.resolve(cwd, explicit);
|
|
7880
8855
|
try {
|
|
7881
|
-
return { filePath: filePath2, content: await
|
|
8856
|
+
return { filePath: filePath2, content: await readFile25(filePath2, "utf8") };
|
|
7882
8857
|
} catch {
|
|
7883
8858
|
return null;
|
|
7884
8859
|
}
|
|
@@ -7886,9 +8861,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
7886
8861
|
const payload = await readStdinJson();
|
|
7887
8862
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
7888
8863
|
if (!rel) return null;
|
|
7889
|
-
const filePath =
|
|
8864
|
+
const filePath = path47.isAbsolute(rel) ? rel : path47.resolve(cwd, rel);
|
|
7890
8865
|
try {
|
|
7891
|
-
return { filePath, content: await
|
|
8866
|
+
return { filePath, content: await readFile25(filePath, "utf8") };
|
|
7892
8867
|
} catch {
|
|
7893
8868
|
return null;
|
|
7894
8869
|
}
|
|
@@ -7900,13 +8875,13 @@ function isPlanPath(filePath) {
|
|
|
7900
8875
|
const norm = filePath.replace(/\\/g, "/");
|
|
7901
8876
|
return norm.includes("/.cursor/plans/") && norm.endsWith(".plan.md");
|
|
7902
8877
|
}
|
|
7903
|
-
var validateCommand =
|
|
8878
|
+
var validateCommand = defineCommand19({
|
|
7904
8879
|
meta: {
|
|
7905
8880
|
name: "validate",
|
|
7906
8881
|
description: "Advisory validators for HANDOFF / plan frontmatter (hook adapter)."
|
|
7907
8882
|
},
|
|
7908
8883
|
subCommands: {
|
|
7909
|
-
handoff:
|
|
8884
|
+
handoff: defineCommand19({
|
|
7910
8885
|
meta: { name: "handoff", description: "Validate HANDOFF machine fields" },
|
|
7911
8886
|
args: {
|
|
7912
8887
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7916,10 +8891,10 @@ var validateCommand = defineCommand18({
|
|
|
7916
8891
|
async run({ args }) {
|
|
7917
8892
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7918
8893
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
7919
|
-
const filePath = fileArg ?
|
|
8894
|
+
const filePath = fileArg ? path47.resolve(cwd, fileArg) : path47.join(path47.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
7920
8895
|
let content = "";
|
|
7921
8896
|
try {
|
|
7922
|
-
content = await
|
|
8897
|
+
content = await readFile25(filePath, "utf8");
|
|
7923
8898
|
} catch {
|
|
7924
8899
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7925
8900
|
return;
|
|
@@ -7928,7 +8903,7 @@ var validateCommand = defineCommand18({
|
|
|
7928
8903
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7929
8904
|
}
|
|
7930
8905
|
}),
|
|
7931
|
-
plan:
|
|
8906
|
+
plan: defineCommand19({
|
|
7932
8907
|
meta: { name: "plan", description: "Validate plan frontmatter" },
|
|
7933
8908
|
args: {
|
|
7934
8909
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7943,10 +8918,10 @@ var validateCommand = defineCommand18({
|
|
|
7943
8918
|
process.exitCode = 2;
|
|
7944
8919
|
return;
|
|
7945
8920
|
}
|
|
7946
|
-
const filePath =
|
|
8921
|
+
const filePath = path47.resolve(cwd, fileArg);
|
|
7947
8922
|
let content = "";
|
|
7948
8923
|
try {
|
|
7949
|
-
content = await
|
|
8924
|
+
content = await readFile25(filePath, "utf8");
|
|
7950
8925
|
} catch {
|
|
7951
8926
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7952
8927
|
return;
|
|
@@ -7955,7 +8930,7 @@ var validateCommand = defineCommand18({
|
|
|
7955
8930
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7956
8931
|
}
|
|
7957
8932
|
}),
|
|
7958
|
-
"after-edit":
|
|
8933
|
+
"after-edit": defineCommand19({
|
|
7959
8934
|
meta: {
|
|
7960
8935
|
name: "after-edit",
|
|
7961
8936
|
description: "Advisory afterFileEdit: annotate HANDOFF/plan issues (never block)"
|
|
@@ -7965,7 +8940,7 @@ var validateCommand = defineCommand18({
|
|
|
7965
8940
|
},
|
|
7966
8941
|
async run({ args }) {
|
|
7967
8942
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7968
|
-
const resolved = await resolveEditedPath(
|
|
8943
|
+
const resolved = await resolveEditedPath(path47.resolve(cwd));
|
|
7969
8944
|
if (!resolved) {
|
|
7970
8945
|
console.log(JSON.stringify({}));
|
|
7971
8946
|
return;
|
|
@@ -8008,14 +8983,14 @@ var validateCommand = defineCommand18({
|
|
|
8008
8983
|
});
|
|
8009
8984
|
|
|
8010
8985
|
// src/welcome/help-groups.ts
|
|
8011
|
-
import { bold, gray as gray4, underline } from "kolorist";
|
|
8986
|
+
import { bold as bold2, gray as gray4, underline } from "kolorist";
|
|
8012
8987
|
|
|
8013
8988
|
// src/welcome/screen.ts
|
|
8014
8989
|
import {
|
|
8015
8990
|
blue as blue2,
|
|
8016
|
-
cyan as
|
|
8991
|
+
cyan as cyan6,
|
|
8017
8992
|
gray as gray3,
|
|
8018
|
-
options as
|
|
8993
|
+
options as koloristOptions3,
|
|
8019
8994
|
lightCyan,
|
|
8020
8995
|
trueColor,
|
|
8021
8996
|
white as white2
|
|
@@ -8044,15 +9019,15 @@ function outlineAnsi(line) {
|
|
|
8044
9019
|
return trueColor(r, g, b)(line);
|
|
8045
9020
|
}
|
|
8046
9021
|
function withKoloristColor(fn) {
|
|
8047
|
-
const prevEnabled =
|
|
8048
|
-
const prevLevel =
|
|
8049
|
-
|
|
8050
|
-
|
|
9022
|
+
const prevEnabled = koloristOptions3.enabled;
|
|
9023
|
+
const prevLevel = koloristOptions3.supportLevel;
|
|
9024
|
+
koloristOptions3.enabled = true;
|
|
9025
|
+
koloristOptions3.supportLevel = KOLORIST_TRUECOLOR;
|
|
8051
9026
|
try {
|
|
8052
9027
|
return fn();
|
|
8053
9028
|
} finally {
|
|
8054
|
-
|
|
8055
|
-
|
|
9029
|
+
koloristOptions3.enabled = prevEnabled;
|
|
9030
|
+
koloristOptions3.supportLevel = prevLevel;
|
|
8056
9031
|
}
|
|
8057
9032
|
}
|
|
8058
9033
|
function hasCliSubcommand(rawArgs) {
|
|
@@ -8079,7 +9054,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8079
9054
|
const version = opts.version ?? KIT_VERSION;
|
|
8080
9055
|
const color = shouldUseWelcomeColor(opts);
|
|
8081
9056
|
const title = color ? white2("Mission Kit") : "Mission Kit";
|
|
8082
|
-
const product = color ?
|
|
9057
|
+
const product = color ? cyan6("agent-kit") : "agent-kit";
|
|
8083
9058
|
const muted = (s) => color ? gray3(s) : s;
|
|
8084
9059
|
const lines = [
|
|
8085
9060
|
renderHelmetAscii(color),
|
|
@@ -8092,7 +9067,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8092
9067
|
const width = Math.max(...WELCOME_UTILITY_HINTS.map(({ cmd }) => cmd.length));
|
|
8093
9068
|
return WELCOME_UTILITY_HINTS.map(({ cmd, hint }) => {
|
|
8094
9069
|
const pad = " ".repeat(width - cmd.length + 2);
|
|
8095
|
-
const left = color ?
|
|
9070
|
+
const left = color ? cyan6(` ${cmd}`) : ` ${cmd}`;
|
|
8096
9071
|
return `${left}${pad}${muted(hint)}`;
|
|
8097
9072
|
});
|
|
8098
9073
|
})(),
|
|
@@ -8153,7 +9128,7 @@ async function resolveSubMeta(subCommands) {
|
|
|
8153
9128
|
}
|
|
8154
9129
|
async function renderGroupedRootHelp(cmd) {
|
|
8155
9130
|
const color = shouldUseWelcomeColor();
|
|
8156
|
-
const u = (s) => color ? underline(
|
|
9131
|
+
const u = (s) => color ? underline(bold2(s)) : s;
|
|
8157
9132
|
const g = (s) => color ? gray4(s) : s;
|
|
8158
9133
|
const meta = await resolveCommandMeta(cmd.meta);
|
|
8159
9134
|
const name = meta?.name ?? "agent-kit";
|
|
@@ -8198,7 +9173,7 @@ async function renderGroupedRootHelp(cmd) {
|
|
|
8198
9173
|
}
|
|
8199
9174
|
|
|
8200
9175
|
// src/index.ts
|
|
8201
|
-
var main =
|
|
9176
|
+
var main = defineCommand20({
|
|
8202
9177
|
meta: {
|
|
8203
9178
|
name: "agent-kit",
|
|
8204
9179
|
description: "HITL framework for AI-assisted IDEs (Mission Kit family)",
|
|
@@ -8210,6 +9185,7 @@ var main = defineCommand19({
|
|
|
8210
9185
|
scan: scanCommand,
|
|
8211
9186
|
add: addCommand,
|
|
8212
9187
|
doctor: doctorCommand,
|
|
9188
|
+
"setup-global": setupGlobalCommand,
|
|
8213
9189
|
status: statusCommand,
|
|
8214
9190
|
update: updateCommand,
|
|
8215
9191
|
"cursor-awareness": cursorAwarenessCommand,
|