@dadado/agent-kit-cli 5.4.0 → 5.5.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 +2 -1
- package/dashboard/dashboard-data.mjs +72 -1
- package/dashboard/dashboard.html +19 -1
- package/dist/index.js +1218 -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,10 @@ 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"
|
|
220
223
|
]);
|
|
221
224
|
|
|
222
225
|
// src/lifecycle/paths.ts
|
|
@@ -239,7 +242,8 @@ var MANAGED_HASHES_REL = ".cursor/agent-kit.managed-hashes.json";
|
|
|
239
242
|
var CONSUMER_OVERLAY_PREFIXES = [
|
|
240
243
|
".cursor/agents/",
|
|
241
244
|
".cursor/skills/",
|
|
242
|
-
".cursor/commands/"
|
|
245
|
+
".cursor/commands/",
|
|
246
|
+
".claude/commands/"
|
|
243
247
|
];
|
|
244
248
|
function isConsumerOverlayPath(relPath) {
|
|
245
249
|
const norm = relPath.split(path3.sep).join("/");
|
|
@@ -742,8 +746,8 @@ async function hasRegistryIndex(root) {
|
|
|
742
746
|
return fileExists(path5.join(root, "registry", "registry.json"));
|
|
743
747
|
}
|
|
744
748
|
async function cloneRegistry(url, ref, dest) {
|
|
745
|
-
const { mkdir:
|
|
746
|
-
await
|
|
749
|
+
const { mkdir: mkdir8 } = await import("fs/promises");
|
|
750
|
+
await mkdir8(path5.dirname(dest), { recursive: true });
|
|
747
751
|
try {
|
|
748
752
|
await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
|
|
749
753
|
env: gitEnv()
|
|
@@ -2383,7 +2387,7 @@ var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
|
|
|
2383
2387
|
function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
2384
2388
|
const exists2 = fsHooks.existsSync;
|
|
2385
2389
|
const realpath = fsHooks.realpathSync;
|
|
2386
|
-
const
|
|
2390
|
+
const mkdir8 = fsHooks.mkdirSync;
|
|
2387
2391
|
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2388
2392
|
return { ok: false, error: "invalid repo root" };
|
|
2389
2393
|
}
|
|
@@ -2394,8 +2398,8 @@ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
|
2394
2398
|
return { ok: false, error: "path escape" };
|
|
2395
2399
|
}
|
|
2396
2400
|
try {
|
|
2397
|
-
if (typeof
|
|
2398
|
-
|
|
2401
|
+
if (typeof mkdir8 === "function" && typeof exists2 === "function" && !exists2(contextDir)) {
|
|
2402
|
+
mkdir8(contextDir, { recursive: true });
|
|
2399
2403
|
}
|
|
2400
2404
|
if (typeof realpath === "function" && typeof exists2 === "function" && exists2(abs)) {
|
|
2401
2405
|
const fileReal = String(realpath(abs)).replace(/\\/g, "/");
|
|
@@ -2452,13 +2456,13 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2452
2456
|
}
|
|
2453
2457
|
|
|
2454
2458
|
// src/commands/dashboard.ts
|
|
2455
|
-
function readPreferredBrowserFromWorkspace(cwd,
|
|
2459
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile26 = readFileSync2) {
|
|
2456
2460
|
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2457
2461
|
existsSync,
|
|
2458
2462
|
realpathSync
|
|
2459
2463
|
});
|
|
2460
2464
|
if (!resolved.ok) return null;
|
|
2461
|
-
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync:
|
|
2465
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile26 });
|
|
2462
2466
|
return normalizePreferredBrowser(value);
|
|
2463
2467
|
}
|
|
2464
2468
|
function applyDashboardOpenEnv(env, opts) {
|
|
@@ -2876,7 +2880,7 @@ var diffCommand = defineCommand6({
|
|
|
2876
2880
|
});
|
|
2877
2881
|
|
|
2878
2882
|
// src/commands/doctor.ts
|
|
2879
|
-
import
|
|
2883
|
+
import path25 from "path";
|
|
2880
2884
|
import { defineCommand as defineCommand7 } from "citty";
|
|
2881
2885
|
|
|
2882
2886
|
// src/invariants/hooks-health.ts
|
|
@@ -3092,6 +3096,142 @@ async function assessHooksHealth(rootDir) {
|
|
|
3092
3096
|
};
|
|
3093
3097
|
}
|
|
3094
3098
|
|
|
3099
|
+
// src/readiness/env-checks.ts
|
|
3100
|
+
import { constants as constants3, access as access6, readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
3101
|
+
import { homedir as homedir2 } from "os";
|
|
3102
|
+
import path15 from "path";
|
|
3103
|
+
var MIN_NODE_MAJOR = 20;
|
|
3104
|
+
async function checkBinOnPath(binName, env, platform) {
|
|
3105
|
+
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3106
|
+
if (!pathVar) return false;
|
|
3107
|
+
const dirs = pathVar.split(path15.delimiter).filter(Boolean);
|
|
3108
|
+
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3109
|
+
for (const dir of dirs) {
|
|
3110
|
+
for (const candidate2 of candidates) {
|
|
3111
|
+
try {
|
|
3112
|
+
await access6(path15.join(dir, candidate2), platform === "win32" ? void 0 : constants3.X_OK);
|
|
3113
|
+
return true;
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
function isNodeVersionOk(nodeVersion, minMajor = MIN_NODE_MAJOR) {
|
|
3121
|
+
const match = /^v?(\d+)/.exec(nodeVersion);
|
|
3122
|
+
if (!match) return false;
|
|
3123
|
+
const major = Number(match[1]);
|
|
3124
|
+
return Number.isFinite(major) && major >= minMajor;
|
|
3125
|
+
}
|
|
3126
|
+
function detectShellName(env, platform) {
|
|
3127
|
+
if (platform === "win32") {
|
|
3128
|
+
if (env.PSModulePath) return "powershell";
|
|
3129
|
+
if (env.ComSpec) return "cmd";
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
const shellPath = env.SHELL;
|
|
3133
|
+
if (!shellPath) return null;
|
|
3134
|
+
const base = path15.basename(shellPath).trim();
|
|
3135
|
+
return base || null;
|
|
3136
|
+
}
|
|
3137
|
+
function detectShellProfile(env, platform, homeDir) {
|
|
3138
|
+
const shellName = detectShellName(env, platform);
|
|
3139
|
+
if (shellName === "zsh") return path15.join(homeDir, ".zshrc");
|
|
3140
|
+
if (shellName === "bash") return path15.join(homeDir, ".bashrc");
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
function parseNpmrcPrefix(content, homeDir) {
|
|
3144
|
+
const match = /^\s*prefix\s*=\s*(.+?)\s*$/m.exec(content);
|
|
3145
|
+
const captured = match?.[1];
|
|
3146
|
+
if (!captured) return null;
|
|
3147
|
+
let value = captured.trim().replace(/^["']|["']$/g, "");
|
|
3148
|
+
if (value.startsWith("~")) {
|
|
3149
|
+
value = path15.join(homeDir, value.slice(1));
|
|
3150
|
+
}
|
|
3151
|
+
return value || null;
|
|
3152
|
+
}
|
|
3153
|
+
function heuristicPrefixFromExecPath(execPath, platform) {
|
|
3154
|
+
const p = platform === "win32" ? path15.win32 : path15.posix;
|
|
3155
|
+
return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
|
|
3156
|
+
}
|
|
3157
|
+
async function detectNpmPrefix(options = {}) {
|
|
3158
|
+
const env = options.env ?? process.env;
|
|
3159
|
+
const platform = options.platform ?? process.platform;
|
|
3160
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3161
|
+
const execPath = options.execPath ?? process.execPath;
|
|
3162
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile10(filePath, "utf8"));
|
|
3163
|
+
const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
3164
|
+
if (envPrefix?.trim()) {
|
|
3165
|
+
return { prefix: envPrefix.trim(), source: "env" };
|
|
3166
|
+
}
|
|
3167
|
+
const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path15.join(homeDir, ".npmrc");
|
|
3168
|
+
try {
|
|
3169
|
+
const content = await readFileImpl(userconfigPath);
|
|
3170
|
+
const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
|
|
3171
|
+
if (npmrcPrefix) {
|
|
3172
|
+
return { prefix: npmrcPrefix, source: "npmrc" };
|
|
3173
|
+
}
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
return { prefix: heuristicPrefixFromExecPath(execPath, platform), source: "heuristic" };
|
|
3177
|
+
}
|
|
3178
|
+
async function describeUnwritablePrefix(prefix) {
|
|
3179
|
+
try {
|
|
3180
|
+
const info = await stat3(prefix);
|
|
3181
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
3182
|
+
if (process.platform !== "win32" && currentUid !== void 0 && info.uid === 0 && currentUid !== 0) {
|
|
3183
|
+
return `root-owned prefix (${prefix}); the classic fresh-install PATH/EACCES blocker`;
|
|
3184
|
+
}
|
|
3185
|
+
} catch {
|
|
3186
|
+
}
|
|
3187
|
+
return `npm prefix is not writable: ${prefix}`;
|
|
3188
|
+
}
|
|
3189
|
+
async function checkNpmPrefixWritable(options = {}) {
|
|
3190
|
+
let detected;
|
|
3191
|
+
try {
|
|
3192
|
+
detected = await detectNpmPrefix(options);
|
|
3193
|
+
} catch {
|
|
3194
|
+
return { prefix: null, writable: false, reason: "npm prefix could not be determined" };
|
|
3195
|
+
}
|
|
3196
|
+
try {
|
|
3197
|
+
await access6(detected.prefix, constants3.W_OK);
|
|
3198
|
+
return { prefix: detected.prefix, writable: true, source: detected.source };
|
|
3199
|
+
} catch {
|
|
3200
|
+
return {
|
|
3201
|
+
prefix: detected.prefix,
|
|
3202
|
+
writable: false,
|
|
3203
|
+
source: detected.source,
|
|
3204
|
+
reason: await describeUnwritablePrefix(detected.prefix)
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
async function assessEnvironment(options = {}) {
|
|
3209
|
+
const env = options.env ?? process.env;
|
|
3210
|
+
const platform = options.platform ?? process.platform;
|
|
3211
|
+
const nodeVersion = options.nodeVersion ?? process.version;
|
|
3212
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3213
|
+
const binName = options.binName ?? "agent-kit";
|
|
3214
|
+
const [binOnPath, npmPrefix] = await Promise.all([
|
|
3215
|
+
checkBinOnPath(binName, env, platform).catch(() => false),
|
|
3216
|
+
checkNpmPrefixWritable(options).catch(
|
|
3217
|
+
() => ({
|
|
3218
|
+
prefix: null,
|
|
3219
|
+
writable: false,
|
|
3220
|
+
reason: "npm prefix check failed unexpectedly"
|
|
3221
|
+
})
|
|
3222
|
+
)
|
|
3223
|
+
]);
|
|
3224
|
+
return {
|
|
3225
|
+
binOnPath,
|
|
3226
|
+
npmPrefixWritable: npmPrefix.writable,
|
|
3227
|
+
npmPrefix,
|
|
3228
|
+
nodeVersionOk: isNodeVersionOk(nodeVersion),
|
|
3229
|
+
nodeVersion,
|
|
3230
|
+
shell: detectShellName(env, platform),
|
|
3231
|
+
shellProfile: detectShellProfile(env, platform, homeDir)
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3095
3235
|
// src/scanner/readiness.ts
|
|
3096
3236
|
import { createHash as createHash3 } from "crypto";
|
|
3097
3237
|
function action(id, status, recommendation, owner) {
|
|
@@ -3339,12 +3479,12 @@ function createReadinessReport(scan, options) {
|
|
|
3339
3479
|
}
|
|
3340
3480
|
|
|
3341
3481
|
// src/scanner/safe-fixes.ts
|
|
3342
|
-
import { readFile as
|
|
3343
|
-
import
|
|
3482
|
+
import { readFile as readFile13, writeFile as writeFile5 } from "fs/promises";
|
|
3483
|
+
import path23 from "path";
|
|
3344
3484
|
|
|
3345
3485
|
// src/scanner/detect-repository.ts
|
|
3346
|
-
import { readFile as
|
|
3347
|
-
import
|
|
3486
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3487
|
+
import path16 from "path";
|
|
3348
3488
|
var CONTEXT_PATHS = [
|
|
3349
3489
|
["README.md", "README"],
|
|
3350
3490
|
["README", "README"],
|
|
@@ -3363,7 +3503,7 @@ var CONTEXT_PATHS = [
|
|
|
3363
3503
|
async function existingEvidence(rootDir, candidates) {
|
|
3364
3504
|
const evidence = await Promise.all(
|
|
3365
3505
|
candidates.map(
|
|
3366
|
-
async ([relativePath, label]) => await fileExists(
|
|
3506
|
+
async ([relativePath, label]) => await fileExists(path16.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
3367
3507
|
)
|
|
3368
3508
|
);
|
|
3369
3509
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -3384,7 +3524,7 @@ async function detectContext(rootDir) {
|
|
|
3384
3524
|
async function detectPurpose(rootDir, stack) {
|
|
3385
3525
|
const entries = await listDirectory(rootDir);
|
|
3386
3526
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
3387
|
-
const packageJson = await readJson(
|
|
3527
|
+
const packageJson = await readJson(path16.join(rootDir, "package.json"));
|
|
3388
3528
|
const categories = [];
|
|
3389
3529
|
const evidence = [];
|
|
3390
3530
|
const add = (category, value2) => {
|
|
@@ -3424,16 +3564,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
3424
3564
|
}
|
|
3425
3565
|
async function detectAgentKit(rootDir) {
|
|
3426
3566
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
3427
|
-
const manifestPath =
|
|
3567
|
+
const manifestPath = path16.join(rootDir, manifestRelativePath);
|
|
3428
3568
|
const installed = await fileExists(manifestPath);
|
|
3429
3569
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
3430
3570
|
return {
|
|
3431
3571
|
installed,
|
|
3432
3572
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
3433
3573
|
version: manifest?.version,
|
|
3434
|
-
hasPlans: await fileExists(
|
|
3435
|
-
hasHandoff: await fileExists(
|
|
3436
|
-
hasMemory: await fileExists(
|
|
3574
|
+
hasPlans: await fileExists(path16.join(rootDir, ".cursor/plans")),
|
|
3575
|
+
hasHandoff: await fileExists(path16.join(rootDir, ".cursor/HANDOFF.md")),
|
|
3576
|
+
hasMemory: await fileExists(path16.join(rootDir, ".cursor/memory"))
|
|
3437
3577
|
};
|
|
3438
3578
|
}
|
|
3439
3579
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -3447,9 +3587,9 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
3447
3587
|
"*service-account*.json"
|
|
3448
3588
|
];
|
|
3449
3589
|
async function detectSafety(rootDir, trackedFiles) {
|
|
3450
|
-
const gitignorePath =
|
|
3590
|
+
const gitignorePath = path16.join(rootDir, ".gitignore");
|
|
3451
3591
|
const hasGitignore = await fileExists(gitignorePath);
|
|
3452
|
-
const gitignore = hasGitignore ? await
|
|
3592
|
+
const gitignore = hasGitignore ? await readFile11(gitignorePath, "utf8") : "";
|
|
3453
3593
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
3454
3594
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
3455
3595
|
(pattern) => lines.includes(pattern)
|
|
@@ -3458,7 +3598,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3458
3598
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
3459
3599
|
);
|
|
3460
3600
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
3461
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
3601
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path16.join(rootDir, item))))).some(Boolean);
|
|
3462
3602
|
const guardCandidates = [
|
|
3463
3603
|
".husky/pre-commit",
|
|
3464
3604
|
".husky/pre-push",
|
|
@@ -3467,7 +3607,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3467
3607
|
];
|
|
3468
3608
|
const guardContents = await Promise.all(
|
|
3469
3609
|
guardCandidates.map(
|
|
3470
|
-
async (item) => await fileExists(
|
|
3610
|
+
async (item) => await fileExists(path16.join(rootDir, item)) ? readFile11(path16.join(rootDir, item), "utf8") : ""
|
|
3471
3611
|
)
|
|
3472
3612
|
);
|
|
3473
3613
|
return {
|
|
@@ -3485,11 +3625,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3485
3625
|
}
|
|
3486
3626
|
|
|
3487
3627
|
// src/scanner/scan.ts
|
|
3488
|
-
import
|
|
3628
|
+
import path22 from "path";
|
|
3489
3629
|
|
|
3490
3630
|
// src/scanner/detect-git.ts
|
|
3491
3631
|
import { execFile as execFile3 } from "child_process";
|
|
3492
|
-
import
|
|
3632
|
+
import path17 from "path";
|
|
3493
3633
|
import { promisify as promisify3 } from "util";
|
|
3494
3634
|
var exec = promisify3(execFile3);
|
|
3495
3635
|
function remoteHostname(remoteUrl) {
|
|
@@ -3513,7 +3653,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
3513
3653
|
}
|
|
3514
3654
|
async function detectProvider(rootDir, remoteUrl) {
|
|
3515
3655
|
const configuration = await readJson(
|
|
3516
|
-
|
|
3656
|
+
path17.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
3517
3657
|
);
|
|
3518
3658
|
const configuredProvider = configuration?.git?.provider;
|
|
3519
3659
|
if (configuredProvider) {
|
|
@@ -3570,7 +3710,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
3570
3710
|
evidence: remoteEvidence
|
|
3571
3711
|
};
|
|
3572
3712
|
}
|
|
3573
|
-
if (await fileExists(
|
|
3713
|
+
if (await fileExists(path17.join(rootDir, ".gitlab-ci.yml"))) {
|
|
3574
3714
|
return {
|
|
3575
3715
|
provider: "gitlab",
|
|
3576
3716
|
providerKind: "gitlab-self-hosted",
|
|
@@ -3659,11 +3799,11 @@ async function detectGit(rootDir) {
|
|
|
3659
3799
|
}
|
|
3660
3800
|
|
|
3661
3801
|
// src/scanner/detect-ide.ts
|
|
3662
|
-
import
|
|
3802
|
+
import path18 from "path";
|
|
3663
3803
|
async function detectIde(rootDir) {
|
|
3664
|
-
const hasCursor = await fileExists(
|
|
3665
|
-
const hasVSCode = await fileExists(
|
|
3666
|
-
const hasWindsurf = await fileExists(
|
|
3804
|
+
const hasCursor = await fileExists(path18.join(rootDir, ".cursor"));
|
|
3805
|
+
const hasVSCode = await fileExists(path18.join(rootDir, ".vscode"));
|
|
3806
|
+
const hasWindsurf = await fileExists(path18.join(rootDir, ".windsurfrules"));
|
|
3667
3807
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
3668
3808
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
3669
3809
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -3671,7 +3811,7 @@ async function detectIde(rootDir) {
|
|
|
3671
3811
|
}
|
|
3672
3812
|
|
|
3673
3813
|
// src/scanner/detect-infra.ts
|
|
3674
|
-
import
|
|
3814
|
+
import path19 from "path";
|
|
3675
3815
|
|
|
3676
3816
|
// src/types.ts
|
|
3677
3817
|
var GIT_PLATFORM_META = {
|
|
@@ -3727,12 +3867,12 @@ var PM_TOOL_LABELS = {
|
|
|
3727
3867
|
|
|
3728
3868
|
// src/scanner/detect-infra.ts
|
|
3729
3869
|
async function detectInfra(rootDir) {
|
|
3730
|
-
const docker = await fileExists(
|
|
3731
|
-
const kubernetes = await fileExists(
|
|
3870
|
+
const docker = await fileExists(path19.join(rootDir, "Dockerfile")) || await fileExists(path19.join(rootDir, "docker-compose.yml")) || await fileExists(path19.join(rootDir, "docker-compose.yaml"));
|
|
3871
|
+
const kubernetes = await fileExists(path19.join(rootDir, "k8s")) || await fileExists(path19.join(rootDir, "kubernetes"));
|
|
3732
3872
|
let ci = "none";
|
|
3733
3873
|
const ciFiles = [];
|
|
3734
3874
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
3735
|
-
if (await fileExists(
|
|
3875
|
+
if (await fileExists(path19.join(rootDir, filePath))) {
|
|
3736
3876
|
if (ci === "none") ci = platform;
|
|
3737
3877
|
ciFiles.push(filePath);
|
|
3738
3878
|
}
|
|
@@ -3757,30 +3897,30 @@ async function detectInfra(rootDir) {
|
|
|
3757
3897
|
];
|
|
3758
3898
|
const infrastructureFiles = (await Promise.all(
|
|
3759
3899
|
infrastructureCandidates.map(
|
|
3760
|
-
async (file) => await fileExists(
|
|
3900
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3761
3901
|
)
|
|
3762
3902
|
)).filter((file) => file !== void 0);
|
|
3763
3903
|
const deploymentFiles = (await Promise.all(
|
|
3764
3904
|
deploymentCandidates.map(
|
|
3765
|
-
async (file) => await fileExists(
|
|
3905
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3766
3906
|
)
|
|
3767
3907
|
)).filter((file) => file !== void 0);
|
|
3768
3908
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
3769
3909
|
}
|
|
3770
3910
|
|
|
3771
3911
|
// src/scanner/detect-services.ts
|
|
3772
|
-
import { readFile as
|
|
3773
|
-
import
|
|
3912
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
3913
|
+
import path20 from "path";
|
|
3774
3914
|
async function detectProjectManagement(rootDir) {
|
|
3775
3915
|
const tools = [];
|
|
3776
3916
|
const mcpConfigPaths = [
|
|
3777
|
-
|
|
3778
|
-
|
|
3917
|
+
path20.join(rootDir, ".cursor", "mcp.json"),
|
|
3918
|
+
path20.join(rootDir, "mcp.json")
|
|
3779
3919
|
];
|
|
3780
3920
|
for (const configPath of mcpConfigPaths) {
|
|
3781
3921
|
if (!await fileExists(configPath)) continue;
|
|
3782
3922
|
try {
|
|
3783
|
-
const raw = await
|
|
3923
|
+
const raw = await readFile12(configPath, "utf8");
|
|
3784
3924
|
const lower = raw.toLowerCase();
|
|
3785
3925
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
3786
3926
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -3791,20 +3931,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
3791
3931
|
} catch {
|
|
3792
3932
|
}
|
|
3793
3933
|
}
|
|
3794
|
-
if (await fileExists(
|
|
3934
|
+
if (await fileExists(path20.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
3795
3935
|
tools.push("github-issues");
|
|
3796
3936
|
}
|
|
3797
|
-
if (await fileExists(
|
|
3937
|
+
if (await fileExists(path20.join(rootDir, ".github", "projects"))) {
|
|
3798
3938
|
tools.push("github-projects");
|
|
3799
3939
|
}
|
|
3800
3940
|
return [...new Set(tools)];
|
|
3801
3941
|
}
|
|
3802
3942
|
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(
|
|
3943
|
+
const hasPrisma = await fileExists(path20.join(rootDir, "prisma/schema.prisma"));
|
|
3944
|
+
const hasSequelize = await fileExists(path20.join(rootDir, "sequelize"));
|
|
3945
|
+
const hasDrizzle = await fileExists(path20.join(rootDir, "drizzle.config.ts"));
|
|
3946
|
+
const hasKnex = await fileExists(path20.join(rootDir, "knexfile.ts"));
|
|
3947
|
+
const hasTypeorm = await fileExists(path20.join(rootDir, "ormconfig.json"));
|
|
3808
3948
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
3809
3949
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
3810
3950
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -3816,7 +3956,7 @@ async function detectServices(rootDir) {
|
|
|
3816
3956
|
}
|
|
3817
3957
|
|
|
3818
3958
|
// src/scanner/detect-stack.ts
|
|
3819
|
-
import
|
|
3959
|
+
import path21 from "path";
|
|
3820
3960
|
var PROJECT_MARKERS = [
|
|
3821
3961
|
"package.json",
|
|
3822
3962
|
"requirements.txt",
|
|
@@ -3845,7 +3985,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
3845
3985
|
};
|
|
3846
3986
|
}
|
|
3847
3987
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
3848
|
-
if (await fileExists(
|
|
3988
|
+
if (await fileExists(path21.join(rootDir, lockfile))) {
|
|
3849
3989
|
return {
|
|
3850
3990
|
packageManager,
|
|
3851
3991
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -3862,27 +4002,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
3862
4002
|
return { testCommands, validationCommands };
|
|
3863
4003
|
}
|
|
3864
4004
|
async function detectStack(rootDir) {
|
|
3865
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
3866
|
-
const hasPackageJson = await fileExists(
|
|
4005
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path21.join(rootDir, item))))).some(Boolean);
|
|
4006
|
+
const hasPackageJson = await fileExists(path21.join(rootDir, "package.json"));
|
|
3867
4007
|
if (hasPackageJson) {
|
|
3868
|
-
const packageJson = await readJson(
|
|
4008
|
+
const packageJson = await readJson(path21.join(rootDir, "package.json")) ?? {};
|
|
3869
4009
|
const scripts = packageJson.scripts ?? {};
|
|
3870
4010
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
3871
4011
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
3872
|
-
const hasNextConfig = await fileExists(
|
|
3873
|
-
const hasNestConfig = await fileExists(
|
|
4012
|
+
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"));
|
|
4013
|
+
const hasNestConfig = await fileExists(path21.join(rootDir, "nest-cli.json"));
|
|
3874
4014
|
return {
|
|
3875
4015
|
language: "node",
|
|
3876
4016
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
3877
4017
|
packageManager: packageManager.packageManager,
|
|
3878
4018
|
packageManagerEvidence: packageManager.evidence,
|
|
3879
4019
|
scripts,
|
|
3880
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
4020
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path21.join(rootDir, "pnpm-workspace.yaml")),
|
|
3881
4021
|
...commands,
|
|
3882
4022
|
hasProjectFiles: hasAnyProjectMarker
|
|
3883
4023
|
};
|
|
3884
4024
|
}
|
|
3885
|
-
if (await fileExists(
|
|
4025
|
+
if (await fileExists(path21.join(rootDir, "pyproject.toml"))) {
|
|
3886
4026
|
return {
|
|
3887
4027
|
language: "python",
|
|
3888
4028
|
framework: "python",
|
|
@@ -3892,7 +4032,7 @@ async function detectStack(rootDir) {
|
|
|
3892
4032
|
hasProjectFiles: hasAnyProjectMarker
|
|
3893
4033
|
};
|
|
3894
4034
|
}
|
|
3895
|
-
if (await fileExists(
|
|
4035
|
+
if (await fileExists(path21.join(rootDir, "go.mod"))) {
|
|
3896
4036
|
return {
|
|
3897
4037
|
language: "go",
|
|
3898
4038
|
framework: "go",
|
|
@@ -3902,7 +4042,7 @@ async function detectStack(rootDir) {
|
|
|
3902
4042
|
hasProjectFiles: hasAnyProjectMarker
|
|
3903
4043
|
};
|
|
3904
4044
|
}
|
|
3905
|
-
if (await fileExists(
|
|
4045
|
+
if (await fileExists(path21.join(rootDir, "Cargo.toml"))) {
|
|
3906
4046
|
return {
|
|
3907
4047
|
language: "rust",
|
|
3908
4048
|
framework: "rust",
|
|
@@ -3912,7 +4052,7 @@ async function detectStack(rootDir) {
|
|
|
3912
4052
|
hasProjectFiles: hasAnyProjectMarker
|
|
3913
4053
|
};
|
|
3914
4054
|
}
|
|
3915
|
-
if (await fileExists(
|
|
4055
|
+
if (await fileExists(path21.join(rootDir, "composer.json"))) {
|
|
3916
4056
|
return {
|
|
3917
4057
|
language: "php",
|
|
3918
4058
|
framework: "php",
|
|
@@ -3945,7 +4085,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
3945
4085
|
return meaningful.length === 0;
|
|
3946
4086
|
}
|
|
3947
4087
|
async function runScanner(rootDir) {
|
|
3948
|
-
const normalizedRoot =
|
|
4088
|
+
const normalizedRoot = path22.resolve(rootDir);
|
|
3949
4089
|
const entries = await listDirectory(normalizedRoot);
|
|
3950
4090
|
const stack = await detectStack(normalizedRoot);
|
|
3951
4091
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -4149,7 +4289,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4149
4289
|
});
|
|
4150
4290
|
const changes = [];
|
|
4151
4291
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
4152
|
-
const absolutePath =
|
|
4292
|
+
const absolutePath = path23.join(beforeScan.rootDir, relativePath);
|
|
4153
4293
|
const exists2 = await fileExists(absolutePath);
|
|
4154
4294
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
4155
4295
|
recordChange(
|
|
@@ -4162,8 +4302,8 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4162
4302
|
);
|
|
4163
4303
|
}
|
|
4164
4304
|
const gitignoreRelativePath = ".gitignore";
|
|
4165
|
-
const gitignorePath =
|
|
4166
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
4305
|
+
const gitignorePath = path23.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
4306
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile13(gitignorePath, "utf8") : "";
|
|
4167
4307
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
4168
4308
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
4169
4309
|
if (gitignoreChanged && !dryRun) await writeFile5(gitignorePath, mergedGitignore, "utf8");
|
|
@@ -4178,7 +4318,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4178
4318
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
4179
4319
|
)
|
|
4180
4320
|
);
|
|
4181
|
-
const profilePath =
|
|
4321
|
+
const profilePath = path23.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
4182
4322
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
4183
4323
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
4184
4324
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -4200,7 +4340,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4200
4340
|
generatorVersion: options.generatorVersion,
|
|
4201
4341
|
generatedAt
|
|
4202
4342
|
});
|
|
4203
|
-
const contextConfigPath =
|
|
4343
|
+
const contextConfigPath = path23.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
4204
4344
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
4205
4345
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
4206
4346
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -4229,10 +4369,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4229
4369
|
}
|
|
4230
4370
|
|
|
4231
4371
|
// src/scanner/snapshot.ts
|
|
4232
|
-
import
|
|
4372
|
+
import path24 from "path";
|
|
4233
4373
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
4234
4374
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
4235
|
-
const snapshotPath =
|
|
4375
|
+
const snapshotPath = path24.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
4236
4376
|
await writeJson(snapshotPath, report);
|
|
4237
4377
|
return snapshotPath;
|
|
4238
4378
|
}
|
|
@@ -4402,15 +4542,16 @@ async function withCliProgress(label, fn, opts) {
|
|
|
4402
4542
|
|
|
4403
4543
|
// src/commands/doctor.ts
|
|
4404
4544
|
async function runDoctor(cwd, options = {}) {
|
|
4405
|
-
const rootDir =
|
|
4545
|
+
const rootDir = path25.resolve(cwd);
|
|
4406
4546
|
const hooks = await assessHooksHealth(rootDir);
|
|
4547
|
+
const env = await assessEnvironment();
|
|
4407
4548
|
if (options.fixSafe) {
|
|
4408
4549
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
4409
4550
|
generatorVersion: KIT_VERSION,
|
|
4410
4551
|
generatedAt: options.generatedAt
|
|
4411
4552
|
});
|
|
4412
4553
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
4413
|
-
return { report: execution.after, safeChanges: execution.changes, hooks };
|
|
4554
|
+
return { report: execution.after, safeChanges: execution.changes, hooks, env };
|
|
4414
4555
|
}
|
|
4415
4556
|
const scan = await runScanner(rootDir);
|
|
4416
4557
|
const report = createReadinessReport(scan, {
|
|
@@ -4418,7 +4559,7 @@ async function runDoctor(cwd, options = {}) {
|
|
|
4418
4559
|
generatedAt: options.generatedAt
|
|
4419
4560
|
});
|
|
4420
4561
|
await writeReadinessSnapshot(rootDir, report);
|
|
4421
|
-
return { report, safeChanges: [], hooks };
|
|
4562
|
+
return { report, safeChanges: [], hooks, env };
|
|
4422
4563
|
}
|
|
4423
4564
|
function printDoctorSummary(result) {
|
|
4424
4565
|
const { summary, pendingActions } = result.report;
|
|
@@ -4442,6 +4583,20 @@ function printDoctorSummary(result) {
|
|
|
4442
4583
|
console.log(` - ${tip}`);
|
|
4443
4584
|
}
|
|
4444
4585
|
}
|
|
4586
|
+
console.log("environment:");
|
|
4587
|
+
console.log(` - bin on PATH (agent-kit): ${result.env.binOnPath ? "ok" : "MISSING"}`);
|
|
4588
|
+
console.log(
|
|
4589
|
+
` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
|
|
4590
|
+
);
|
|
4591
|
+
if (!result.env.npmPrefixWritable && result.env.npmPrefix.reason) {
|
|
4592
|
+
console.log(` - ${result.env.npmPrefix.reason}`);
|
|
4593
|
+
}
|
|
4594
|
+
console.log(
|
|
4595
|
+
` - node version >= 20: ${result.env.nodeVersionOk ? "ok" : "TOO OLD"} (${result.env.nodeVersion})`
|
|
4596
|
+
);
|
|
4597
|
+
console.log(
|
|
4598
|
+
` - shell profile: ${result.env.shellProfile ?? "not detected (zsh/bash only)"}${result.env.shell ? ` (shell: ${result.env.shell})` : ""}`
|
|
4599
|
+
);
|
|
4445
4600
|
if (process.env.ALLOW_MAIN_PUSH === "1") {
|
|
4446
4601
|
console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
|
|
4447
4602
|
console.log(" This disables main-push protection for agent Shell commands.");
|
|
@@ -4522,13 +4677,21 @@ var SECRET_PATTERNS2 = [
|
|
|
4522
4677
|
id: "github-pat",
|
|
4523
4678
|
re: /\bghp_[A-Za-z0-9_]{36,}\b/
|
|
4524
4679
|
},
|
|
4680
|
+
// Hyphenated vendor keys (`sk-ant-api03-…`, `sk-proj-…`) cannot be matched by
|
|
4681
|
+
// `openai-sk`: its body class excludes `-`, so it stops at the first separator.
|
|
4682
|
+
// Listed before `openai-sk`; the two cannot both hit the same span.
|
|
4525
4683
|
{
|
|
4684
|
+
id: "sk-hyphenated-vendor",
|
|
4685
|
+
re: /\bsk-[A-Za-z0-9]{2,12}-[A-Za-z0-9_-]{16,}\b/
|
|
4686
|
+
},
|
|
4687
|
+
{
|
|
4688
|
+
// Single-segment `sk-` bodies only (no `-` in the class) — see `sk-hyphenated-vendor`.
|
|
4526
4689
|
id: "openai-sk",
|
|
4527
4690
|
re: /\bsk-[A-Za-z0-9]{20,}\b/
|
|
4528
4691
|
}
|
|
4529
4692
|
];
|
|
4530
4693
|
function maskSecretExcerpt(raw) {
|
|
4531
|
-
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_]{4,})/g, (_m, p1, p2) => {
|
|
4694
|
+
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_-]{4,})/g, (_m, p1, p2) => {
|
|
4532
4695
|
return `${p1}${"*".repeat(Math.min(8, p2.length))}`;
|
|
4533
4696
|
}).replace(
|
|
4534
4697
|
/(=\s*['"]?)([^\s'"]{4,})/g,
|
|
@@ -4752,7 +4915,7 @@ var guardCommand = defineCommand8({
|
|
|
4752
4915
|
shell: defineCommand8({
|
|
4753
4916
|
meta: {
|
|
4754
4917
|
name: "shell",
|
|
4755
|
-
description: "Evaluate a shell command against the
|
|
4918
|
+
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
4919
|
},
|
|
4757
4920
|
args: {
|
|
4758
4921
|
json: {
|
|
@@ -4810,8 +4973,8 @@ var guardCommand = defineCommand8({
|
|
|
4810
4973
|
|
|
4811
4974
|
// src/commands/handoff.ts
|
|
4812
4975
|
import { spawn as spawn4 } from "child_process";
|
|
4813
|
-
import { readFile as
|
|
4814
|
-
import
|
|
4976
|
+
import { readFile as readFile14, readdir as readdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4977
|
+
import path26 from "path";
|
|
4815
4978
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4816
4979
|
function parsePlanFrontmatter(raw) {
|
|
4817
4980
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -4831,19 +4994,19 @@ async function findActivePlan(plansDir) {
|
|
|
4831
4994
|
if (!await fileExists(plansDir)) return null;
|
|
4832
4995
|
const files = (await readdir5(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
4833
4996
|
for (const file of files) {
|
|
4834
|
-
const raw = await
|
|
4997
|
+
const raw = await readFile14(path26.join(plansDir, file), "utf8");
|
|
4835
4998
|
const fm = parsePlanFrontmatter(raw);
|
|
4836
4999
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
4837
5000
|
return { file, raw };
|
|
4838
5001
|
}
|
|
4839
5002
|
}
|
|
4840
|
-
return files[0] ? { file: files[0], raw: await
|
|
5003
|
+
return files[0] ? { file: files[0], raw: await readFile14(path26.join(plansDir, files[0]), "utf8") } : null;
|
|
4841
5004
|
}
|
|
4842
5005
|
function now() {
|
|
4843
5006
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
4844
5007
|
}
|
|
4845
5008
|
async function loadProfile(rootDir) {
|
|
4846
|
-
const configPath =
|
|
5009
|
+
const configPath = path26.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
4847
5010
|
try {
|
|
4848
5011
|
return await readJson(configPath);
|
|
4849
5012
|
} catch {
|
|
@@ -4943,13 +5106,13 @@ var handoffCommand = defineCommand9({
|
|
|
4943
5106
|
},
|
|
4944
5107
|
async run({ args }) {
|
|
4945
5108
|
const profile = await loadProfile(args.cwd);
|
|
4946
|
-
const plansDir =
|
|
4947
|
-
const handoffPath =
|
|
5109
|
+
const plansDir = path26.join(args.cwd, ".cursor", "plans");
|
|
5110
|
+
const handoffPath = path26.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
4948
5111
|
const plan = await findActivePlan(plansDir);
|
|
4949
5112
|
if (plan) {
|
|
4950
5113
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
4951
5114
|
if (fm) {
|
|
4952
|
-
await ensureDir(
|
|
5115
|
+
await ensureDir(path26.join(args.cwd, ".cursor"));
|
|
4953
5116
|
const content = buildHandoff(plan.file, fm, profile);
|
|
4954
5117
|
await writeFile6(handoffPath, content, "utf8");
|
|
4955
5118
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
@@ -4969,7 +5132,7 @@ var handoffCommand = defineCommand9({
|
|
|
4969
5132
|
}
|
|
4970
5133
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
4971
5134
|
}
|
|
4972
|
-
const scriptPath =
|
|
5135
|
+
const scriptPath = path26.join(args.cwd, "cursor-handoff");
|
|
4973
5136
|
if (!await fileExists(scriptPath)) {
|
|
4974
5137
|
printV3Guidance();
|
|
4975
5138
|
return;
|
|
@@ -4994,9 +5157,19 @@ var handoffCommand = defineCommand9({
|
|
|
4994
5157
|
});
|
|
4995
5158
|
|
|
4996
5159
|
// src/commands/hook.ts
|
|
4997
|
-
import
|
|
5160
|
+
import path28 from "path";
|
|
4998
5161
|
import { defineCommand as defineCommand10 } from "citty";
|
|
4999
5162
|
|
|
5163
|
+
// src/hooks/format-session-start.ts
|
|
5164
|
+
function resolveSessionStartFormat(value) {
|
|
5165
|
+
return value === "claude" ? "claude" : "cursor";
|
|
5166
|
+
}
|
|
5167
|
+
function formatSessionStartOutput(additionalContext, format) {
|
|
5168
|
+
if (format === "claude") return additionalContext;
|
|
5169
|
+
return JSON.stringify({ additional_context: additionalContext });
|
|
5170
|
+
}
|
|
5171
|
+
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.";
|
|
5172
|
+
|
|
5000
5173
|
// src/hooks/pre-compact.ts
|
|
5001
5174
|
function buildPreCompactUserMessage(payload = {}) {
|
|
5002
5175
|
const pct = payload.context_usage_percent;
|
|
@@ -5008,8 +5181,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
5008
5181
|
|
|
5009
5182
|
// src/hooks/session-start.ts
|
|
5010
5183
|
import { spawn as spawn5 } from "child_process";
|
|
5011
|
-
import { access as
|
|
5012
|
-
import
|
|
5184
|
+
import { access as access7, readFile as readFile15, stat as stat4 } from "fs/promises";
|
|
5185
|
+
import path27 from "path";
|
|
5013
5186
|
|
|
5014
5187
|
// src/invariants/handoff-schema.ts
|
|
5015
5188
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -5083,7 +5256,7 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
5083
5256
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
5084
5257
|
async function readTextLimited(filePath, limit = 60) {
|
|
5085
5258
|
try {
|
|
5086
|
-
const lines = (await
|
|
5259
|
+
const lines = (await readFile15(filePath, "utf8")).split(/\r?\n/);
|
|
5087
5260
|
return lines.slice(0, limit).join("\n").trim();
|
|
5088
5261
|
} catch {
|
|
5089
5262
|
return "";
|
|
@@ -5091,14 +5264,14 @@ async function readTextLimited(filePath, limit = 60) {
|
|
|
5091
5264
|
}
|
|
5092
5265
|
async function readFull(filePath) {
|
|
5093
5266
|
try {
|
|
5094
|
-
return await
|
|
5267
|
+
return await readFile15(filePath, "utf8");
|
|
5095
5268
|
} catch {
|
|
5096
5269
|
return "";
|
|
5097
5270
|
}
|
|
5098
5271
|
}
|
|
5099
5272
|
async function fileExists2(p) {
|
|
5100
5273
|
try {
|
|
5101
|
-
await
|
|
5274
|
+
await access7(p);
|
|
5102
5275
|
return true;
|
|
5103
5276
|
} catch {
|
|
5104
5277
|
return false;
|
|
@@ -5165,8 +5338,8 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
5165
5338
|
return raw;
|
|
5166
5339
|
}
|
|
5167
5340
|
async function l0Present(root) {
|
|
5168
|
-
const cursor =
|
|
5169
|
-
return await fileExists2(
|
|
5341
|
+
const cursor = path27.join(root, ".cursor");
|
|
5342
|
+
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
5343
|
}
|
|
5171
5344
|
function checkLabelAndRecommendation(check2) {
|
|
5172
5345
|
const checkId = check2.id;
|
|
@@ -5205,10 +5378,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
5205
5378
|
return { essential, nonessential };
|
|
5206
5379
|
}
|
|
5207
5380
|
async function readinessSection(root) {
|
|
5208
|
-
const snapshotPath =
|
|
5381
|
+
const snapshotPath = path27.join(root, ".cursor", "context", "readiness.json");
|
|
5209
5382
|
let data;
|
|
5210
5383
|
try {
|
|
5211
|
-
data = JSON.parse(await
|
|
5384
|
+
data = JSON.parse(await readFile15(snapshotPath, "utf8"));
|
|
5212
5385
|
} catch {
|
|
5213
5386
|
return null;
|
|
5214
5387
|
}
|
|
@@ -5239,13 +5412,13 @@ Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not
|
|
|
5239
5412
|
}
|
|
5240
5413
|
async function dogfoodInboxSection(root) {
|
|
5241
5414
|
const candidateReadmes = [
|
|
5242
|
-
|
|
5243
|
-
|
|
5415
|
+
path27.join(root, "dogfood", "README.md"),
|
|
5416
|
+
path27.join(root, ".cursor", "dogfood", "README.md")
|
|
5244
5417
|
];
|
|
5245
5418
|
for (const readme of candidateReadmes) {
|
|
5246
5419
|
if (!await fileExists2(readme)) continue;
|
|
5247
5420
|
try {
|
|
5248
|
-
const text = await
|
|
5421
|
+
const text = await readFile15(readme, "utf8");
|
|
5249
5422
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
5250
5423
|
} catch {
|
|
5251
5424
|
}
|
|
@@ -5255,7 +5428,7 @@ async function dogfoodInboxSection(root) {
|
|
|
5255
5428
|
async function loadUpdateCheckPrefs(root) {
|
|
5256
5429
|
try {
|
|
5257
5430
|
const data = JSON.parse(
|
|
5258
|
-
await
|
|
5431
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5259
5432
|
);
|
|
5260
5433
|
const uc = data.updateCheck;
|
|
5261
5434
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5333,7 +5506,7 @@ async function updateCheckSection(root) {
|
|
|
5333
5506
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
5334
5507
|
try {
|
|
5335
5508
|
const data = JSON.parse(
|
|
5336
|
-
await
|
|
5509
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5337
5510
|
);
|
|
5338
5511
|
const uc = data.cursorUpdateCheck;
|
|
5339
5512
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5432,9 +5605,101 @@ async function cursorAwarenessSection(root, deps = {}) {
|
|
|
5432
5605
|
if (!shouldEmitCursorAwarenessNudge(result)) return null;
|
|
5433
5606
|
return CURSOR_AWARENESS_NUDGE;
|
|
5434
5607
|
}
|
|
5608
|
+
var AUDIT_SESSION_NS_PREFIX = "agent-kit-audit-";
|
|
5609
|
+
var AUDIT_SESSION_LIST_TIMEOUT_MS = 2e3;
|
|
5610
|
+
function runAuditSessionCommand(cmd, args) {
|
|
5611
|
+
return new Promise((resolve2) => {
|
|
5612
|
+
const child = spawn5(cmd, args, {
|
|
5613
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5614
|
+
timeout: AUDIT_SESSION_LIST_TIMEOUT_MS,
|
|
5615
|
+
shell: false
|
|
5616
|
+
});
|
|
5617
|
+
let out = "";
|
|
5618
|
+
child.stdout?.on("data", (chunk) => {
|
|
5619
|
+
out += chunk.toString("utf8");
|
|
5620
|
+
});
|
|
5621
|
+
child.on("error", () => resolve2(null));
|
|
5622
|
+
child.on("close", () => resolve2(out));
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
function parseTmuxDetachedAuditSessions(out, nowEpochSeconds) {
|
|
5626
|
+
const sessions = [];
|
|
5627
|
+
for (const line of out.split(/\r?\n/)) {
|
|
5628
|
+
const m = /^(\S+)\s+(\d+)\s+(\d+)$/.exec(line.trim());
|
|
5629
|
+
if (!m) continue;
|
|
5630
|
+
const [, name, attached, created] = m;
|
|
5631
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5632
|
+
if (Number(attached) > 0) continue;
|
|
5633
|
+
const createdEpoch = Number(created);
|
|
5634
|
+
const ageSeconds = Number.isFinite(createdEpoch) && nowEpochSeconds >= createdEpoch ? nowEpochSeconds - createdEpoch : -1;
|
|
5635
|
+
sessions.push({ channel: "tmux", name, ageSeconds });
|
|
5636
|
+
}
|
|
5637
|
+
return sessions;
|
|
5638
|
+
}
|
|
5639
|
+
function parseScreenDetachedAuditSessions(listing) {
|
|
5640
|
+
const lines = listing.split(/\r?\n/);
|
|
5641
|
+
let sockdir = null;
|
|
5642
|
+
for (const line of lines) {
|
|
5643
|
+
const dirMatch = /^\d+\s+Sockets?\s+in\s+(.+)\.$/.exec(line);
|
|
5644
|
+
if (dirMatch?.[1]) sockdir = dirMatch[1];
|
|
5645
|
+
}
|
|
5646
|
+
const entries = [];
|
|
5647
|
+
for (const line of lines) {
|
|
5648
|
+
const m = /^\s+(\d+)\.(\S+)\s+\((.*)\)/.exec(line);
|
|
5649
|
+
if (!m) continue;
|
|
5650
|
+
const [, pid, name, marker] = m;
|
|
5651
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5652
|
+
if (marker && /[Aa]ttached/.test(marker)) continue;
|
|
5653
|
+
entries.push({ name, socketPath: sockdir ? path27.join(sockdir, `${pid}.${name}`) : null });
|
|
5654
|
+
}
|
|
5655
|
+
return entries;
|
|
5656
|
+
}
|
|
5657
|
+
function formatSessionAge(seconds) {
|
|
5658
|
+
if (seconds >= 86400) return `${Math.floor(seconds / 86400)}d`;
|
|
5659
|
+
if (seconds >= 3600) return `${Math.floor(seconds / 3600)}h`;
|
|
5660
|
+
if (seconds >= 60) return `${Math.floor(seconds / 60)}m`;
|
|
5661
|
+
return `${seconds}s`;
|
|
5662
|
+
}
|
|
5663
|
+
async function detachedAuditSessionsSection(deps = {}) {
|
|
5664
|
+
try {
|
|
5665
|
+
const run = deps.runCommand ?? runAuditSessionCommand;
|
|
5666
|
+
const nowMs = (deps.now ?? Date.now)();
|
|
5667
|
+
const [tmuxOut, screenOut] = await Promise.all([
|
|
5668
|
+
run("tmux", [
|
|
5669
|
+
"list-sessions",
|
|
5670
|
+
"-F",
|
|
5671
|
+
"#{session_name} #{session_attached} #{session_created}"
|
|
5672
|
+
]),
|
|
5673
|
+
run("screen", ["-ls"])
|
|
5674
|
+
]);
|
|
5675
|
+
const sessions = tmuxOut ? parseTmuxDetachedAuditSessions(tmuxOut, Math.floor(nowMs / 1e3)) : [];
|
|
5676
|
+
if (screenOut) {
|
|
5677
|
+
for (const entry of parseScreenDetachedAuditSessions(screenOut)) {
|
|
5678
|
+
let ageSeconds = -1;
|
|
5679
|
+
if (entry.socketPath) {
|
|
5680
|
+
try {
|
|
5681
|
+
const { mtimeMs } = await stat4(entry.socketPath);
|
|
5682
|
+
if (nowMs >= mtimeMs) ageSeconds = Math.floor((nowMs - mtimeMs) / 1e3);
|
|
5683
|
+
} catch {
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
sessions.push({ channel: "screen", name: entry.name, ageSeconds });
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
if (sessions.length === 0) return null;
|
|
5690
|
+
const knownAges = sessions.map((s) => s.ageSeconds).filter((a) => a >= 0);
|
|
5691
|
+
const oldest = knownAges.length ? `oldest ~${formatSessionAge(Math.max(...knownAges))}` : "oldest age unknown";
|
|
5692
|
+
const noun = sessions.length === 1 ? "session" : "sessions";
|
|
5693
|
+
return `## Detached audit sessions (host)
|
|
5694
|
+
|
|
5695
|
+
${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.`;
|
|
5696
|
+
} catch {
|
|
5697
|
+
return null;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5435
5700
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
5436
|
-
const root =
|
|
5437
|
-
const handoffPath =
|
|
5701
|
+
const root = path27.resolve(rootDir);
|
|
5702
|
+
const handoffPath = path27.join(root, ".cursor", "HANDOFF.md");
|
|
5438
5703
|
const handoffFull = await readFull(handoffPath);
|
|
5439
5704
|
const handoff = await readTextLimited(handoffPath);
|
|
5440
5705
|
const parts = [HARD_RULES];
|
|
@@ -5448,6 +5713,8 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
5448
5713
|
if (updateNudge) parts.push(updateNudge);
|
|
5449
5714
|
const cursorNudge = await cursorAwarenessSection(root);
|
|
5450
5715
|
if (cursorNudge) parts.push(cursorNudge);
|
|
5716
|
+
const auditSessions = await detachedAuditSessionsSection().catch(() => null);
|
|
5717
|
+
if (auditSessions) parts.push(auditSessions);
|
|
5451
5718
|
const formatWarnings = validateHandoffText(handoffFull);
|
|
5452
5719
|
if (formatWarnings.length) {
|
|
5453
5720
|
const bullet = formatWarnings.map((w) => `- ${w.message}`).join("\n");
|
|
@@ -5479,29 +5746,44 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
|
5479
5746
|
}
|
|
5480
5747
|
|
|
5481
5748
|
// src/commands/hook.ts
|
|
5749
|
+
async function runSessionStartHook(cwd, formatArg, deps = {}) {
|
|
5750
|
+
const format = resolveSessionStartFormat(formatArg);
|
|
5751
|
+
try {
|
|
5752
|
+
const readStdin = deps.readStdin ?? readStdinJson;
|
|
5753
|
+
const buildContext = deps.buildContext ?? buildSessionStartAdditionalContext;
|
|
5754
|
+
const payload = await readStdin();
|
|
5755
|
+
const root = resolveSessionRoot(payload, path28.resolve(cwd));
|
|
5756
|
+
const out = await buildContext(root, payload);
|
|
5757
|
+
return formatSessionStartOutput(out.additional_context, format);
|
|
5758
|
+
} catch {
|
|
5759
|
+
return formatSessionStartOutput(SESSION_START_DEGRADED_MESSAGE, format);
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5482
5762
|
var hookCommand = defineCommand10({
|
|
5483
5763
|
meta: {
|
|
5484
5764
|
name: "hook",
|
|
5485
|
-
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5765
|
+
description: "Cursor + Claude Code hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5486
5766
|
},
|
|
5487
5767
|
subCommands: {
|
|
5488
5768
|
"session-start": defineCommand10({
|
|
5489
5769
|
meta: {
|
|
5490
5770
|
name: "session-start",
|
|
5491
|
-
description: "Emit sessionStart
|
|
5771
|
+
description: "Emit sessionStart context (stdin: host payload). --format cursor (default, JSON additional_context) | claude (plain stdout)"
|
|
5492
5772
|
},
|
|
5493
5773
|
args: {
|
|
5494
5774
|
cwd: {
|
|
5495
5775
|
type: "string",
|
|
5496
5776
|
default: process.cwd()
|
|
5777
|
+
},
|
|
5778
|
+
format: {
|
|
5779
|
+
type: "string",
|
|
5780
|
+
default: "cursor",
|
|
5781
|
+
description: "cursor (default) | claude"
|
|
5497
5782
|
}
|
|
5498
5783
|
},
|
|
5499
5784
|
async run({ args }) {
|
|
5500
|
-
const payload = await readStdinJson();
|
|
5501
5785
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5502
|
-
|
|
5503
|
-
const out = await buildSessionStartAdditionalContext(root, payload);
|
|
5504
|
-
console.log(JSON.stringify(out));
|
|
5786
|
+
console.log(await runSessionStartHook(cwd, args.format));
|
|
5505
5787
|
}
|
|
5506
5788
|
}),
|
|
5507
5789
|
"pre-compact": defineCommand10({
|
|
@@ -5522,45 +5804,96 @@ import { intro, outro } from "@clack/prompts";
|
|
|
5522
5804
|
import { defineCommand as defineCommand12 } from "citty";
|
|
5523
5805
|
|
|
5524
5806
|
// src/utils/terminal.ts
|
|
5525
|
-
import {
|
|
5526
|
-
import
|
|
5807
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
5808
|
+
import { homedir as homedir3 } from "os";
|
|
5809
|
+
import path29 from "path";
|
|
5527
5810
|
import { confirm, isCancel } from "@clack/prompts";
|
|
5528
5811
|
function isNonInteractive() {
|
|
5529
5812
|
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
5530
5813
|
if (process.env.AGENT_KIT_YES === "1") return true;
|
|
5531
5814
|
return !process.stdin.isTTY;
|
|
5532
5815
|
}
|
|
5816
|
+
var NESTED_REPO_AMBIGUITY_THRESHOLD = 2;
|
|
5817
|
+
var NESTED_REPO_SCAN_LIMIT = 200;
|
|
5818
|
+
async function findNestedRepoChildren(resolved) {
|
|
5819
|
+
let entries;
|
|
5820
|
+
try {
|
|
5821
|
+
entries = await readdir6(resolved, { withFileTypes: true });
|
|
5822
|
+
} catch {
|
|
5823
|
+
return [];
|
|
5824
|
+
}
|
|
5825
|
+
const nested = [];
|
|
5826
|
+
let scanned = 0;
|
|
5827
|
+
for (const entry of entries) {
|
|
5828
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) break;
|
|
5829
|
+
if (scanned >= NESTED_REPO_SCAN_LIMIT) break;
|
|
5830
|
+
if (!entry.isDirectory()) continue;
|
|
5831
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
5832
|
+
scanned += 1;
|
|
5833
|
+
if (await fileExists(path29.join(resolved, entry.name, ".git"))) {
|
|
5834
|
+
nested.push(entry.name);
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
return nested;
|
|
5838
|
+
}
|
|
5533
5839
|
async function validateProjectRoot(resolved) {
|
|
5534
|
-
const home =
|
|
5840
|
+
const home = path29.resolve(homedir3());
|
|
5535
5841
|
if (resolved === "/" || resolved === home) {
|
|
5536
|
-
return {
|
|
5842
|
+
return {
|
|
5843
|
+
ok: false,
|
|
5844
|
+
reason: `Refused to use ${resolved} as a project root.`,
|
|
5845
|
+
recovery: "Change into a project directory and re-run. Agent Kit installs per project."
|
|
5846
|
+
};
|
|
5537
5847
|
}
|
|
5538
|
-
const hasGit = await fileExists(
|
|
5539
|
-
const hasManifest = await fileExists(
|
|
5848
|
+
const hasGit = await fileExists(path29.join(resolved, ".git"));
|
|
5849
|
+
const hasManifest = await fileExists(path29.join(resolved, ".cursor", "agent-kit.json"));
|
|
5540
5850
|
if (!hasGit && !hasManifest) {
|
|
5541
5851
|
return {
|
|
5542
5852
|
ok: false,
|
|
5543
|
-
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json
|
|
5853
|
+
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json.`,
|
|
5854
|
+
recovery: [
|
|
5855
|
+
"Starting from an empty folder? Pick one of these:",
|
|
5856
|
+
" 1. git init - then re-run. Recommended: readiness and the",
|
|
5857
|
+
" staging -> prod flow both want Git.",
|
|
5858
|
+
" 2. --force-root - install without Git. /agent-kit-onboard will",
|
|
5859
|
+
" still offer to initialize it later.",
|
|
5860
|
+
" 3. Answer yes to the 'Proceed anyway?' prompt in an interactive terminal.",
|
|
5861
|
+
"Or re-run from the project directory you actually meant."
|
|
5862
|
+
].join("\n")
|
|
5544
5863
|
};
|
|
5545
5864
|
}
|
|
5865
|
+
if (hasGit && !hasManifest) {
|
|
5866
|
+
const nested = await findNestedRepoChildren(resolved);
|
|
5867
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) {
|
|
5868
|
+
return {
|
|
5869
|
+
ok: false,
|
|
5870
|
+
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.`,
|
|
5871
|
+
recovery: [
|
|
5872
|
+
"L0 belongs in one project, not in the folder that holds several.",
|
|
5873
|
+
" 1. cd into the project you meant, then re-run.",
|
|
5874
|
+
" 2. --force-root - only if this parent folder really is the project root."
|
|
5875
|
+
].join("\n")
|
|
5876
|
+
};
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5546
5879
|
return { ok: true };
|
|
5547
5880
|
}
|
|
5548
5881
|
async function confirmProjectRoot(cwd, opts) {
|
|
5549
|
-
const resolved =
|
|
5882
|
+
const resolved = path29.resolve(cwd);
|
|
5550
5883
|
if (opts.forceRoot) {
|
|
5551
5884
|
return resolved;
|
|
5552
5885
|
}
|
|
5553
5886
|
const validation = await validateProjectRoot(resolved);
|
|
5554
5887
|
if (!validation.ok) {
|
|
5555
5888
|
if (opts.nonInteractive) {
|
|
5556
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5889
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5557
5890
|
}
|
|
5558
5891
|
const ok2 = await confirm({
|
|
5559
5892
|
message: `${validation.reason} Proceed anyway?`,
|
|
5560
5893
|
initialValue: false
|
|
5561
5894
|
});
|
|
5562
5895
|
if (isCancel(ok2) || !ok2) {
|
|
5563
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5896
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5564
5897
|
}
|
|
5565
5898
|
return resolved;
|
|
5566
5899
|
}
|
|
@@ -5577,16 +5910,38 @@ async function confirmProjectRoot(cwd, opts) {
|
|
|
5577
5910
|
return resolved;
|
|
5578
5911
|
}
|
|
5579
5912
|
var RootRefusedError = class extends Error {
|
|
5580
|
-
constructor(root, reason) {
|
|
5913
|
+
constructor(root, reason, recovery) {
|
|
5581
5914
|
super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
|
|
5582
5915
|
this.root = root;
|
|
5916
|
+
this.recovery = recovery;
|
|
5583
5917
|
this.name = "RootRefusedError";
|
|
5584
5918
|
}
|
|
5585
5919
|
root;
|
|
5920
|
+
recovery;
|
|
5586
5921
|
};
|
|
5922
|
+
var NPM_GLOBAL_PREFIX_PATH_RE = /\/lib\/node_modules|Program Files\\nodejs\\node_modules/;
|
|
5923
|
+
var NPM_GLOBAL_NODE_MODULES_RE = /node_modules/;
|
|
5924
|
+
function isNpmGlobalPrefixError(msg, code) {
|
|
5925
|
+
const isPermissionError = code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg);
|
|
5926
|
+
if (!isPermissionError) return false;
|
|
5927
|
+
return NPM_GLOBAL_PREFIX_PATH_RE.test(msg) || NPM_GLOBAL_NODE_MODULES_RE.test(msg);
|
|
5928
|
+
}
|
|
5587
5929
|
function classifyInstallError(err) {
|
|
5588
5930
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5589
5931
|
const code = err?.code;
|
|
5932
|
+
if (isNpmGlobalPrefixError(msg, code)) {
|
|
5933
|
+
return {
|
|
5934
|
+
kind: "npm-global-eacces",
|
|
5935
|
+
message: `Permission error (root-owned npm prefix): ${msg}`,
|
|
5936
|
+
recovery: [
|
|
5937
|
+
"npm's global install prefix (e.g. /usr/local/lib/node_modules) is owned by root, so global installs fail.",
|
|
5938
|
+
"Recovery options:",
|
|
5939
|
+
" 1. Run: npx @dadado/agent-kit-cli setup-global (relocates npm's prefix to a folder you own, fixes PATH, reinstalls)",
|
|
5940
|
+
' 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',
|
|
5941
|
+
" 3. Use Port B fallback: drag install.md into the Cursor chat"
|
|
5942
|
+
].join("\n")
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5590
5945
|
if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
|
|
5591
5946
|
return {
|
|
5592
5947
|
kind: "eperm",
|
|
@@ -5634,16 +5989,108 @@ function classifyInstallError(err) {
|
|
|
5634
5989
|
}
|
|
5635
5990
|
|
|
5636
5991
|
// src/commands/install.ts
|
|
5637
|
-
import
|
|
5992
|
+
import path36 from "path";
|
|
5638
5993
|
import { defineCommand as defineCommand11 } from "citty";
|
|
5994
|
+
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
5639
5995
|
|
|
5640
5996
|
// src/generator/personalization.ts
|
|
5641
|
-
import { readFile as
|
|
5642
|
-
import
|
|
5997
|
+
import { readFile as readFile18, writeFile as writeFile11 } from "fs/promises";
|
|
5998
|
+
import path34 from "path";
|
|
5999
|
+
|
|
6000
|
+
// src/generator/claude-command-adapters.ts
|
|
6001
|
+
import { readFile as readFile16, readdir as readdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6002
|
+
import path30 from "path";
|
|
6003
|
+
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
6004
|
+
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
6005
|
+
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
6006
|
+
function parseCommandFrontmatter(name, raw) {
|
|
6007
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
6008
|
+
if (!match) return null;
|
|
6009
|
+
const body = match[1] ?? "";
|
|
6010
|
+
const descMatch = /^description:\s*(.+)$/m.exec(body);
|
|
6011
|
+
if (!descMatch) return null;
|
|
6012
|
+
const description = (descMatch[1] ?? "").trim();
|
|
6013
|
+
if (!description) return null;
|
|
6014
|
+
return { name, description };
|
|
6015
|
+
}
|
|
6016
|
+
function renderClaudeCommandAdapter(command) {
|
|
6017
|
+
return `---
|
|
6018
|
+
description: ${command.description}
|
|
6019
|
+
---
|
|
6020
|
+
|
|
6021
|
+
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.
|
|
6022
|
+
|
|
6023
|
+
Adapter rules (Claude Code CLI):
|
|
6024
|
+
- 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.
|
|
6025
|
+
- Skip or cancel means stop.
|
|
6026
|
+
- Never \`/git-prod\` without an explicit operator yes.
|
|
6027
|
+
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
6028
|
+
`;
|
|
6029
|
+
}
|
|
6030
|
+
async function discoverInstalledCommands(rootDir) {
|
|
6031
|
+
const dir = path30.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
6032
|
+
let entries;
|
|
6033
|
+
try {
|
|
6034
|
+
entries = (await readdir7(dir)).filter((f) => f.endsWith(".md"));
|
|
6035
|
+
} catch {
|
|
6036
|
+
return [];
|
|
6037
|
+
}
|
|
6038
|
+
const commands = [];
|
|
6039
|
+
for (const file of entries.sort()) {
|
|
6040
|
+
const name = file.slice(0, -3);
|
|
6041
|
+
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
6042
|
+
try {
|
|
6043
|
+
const raw = await readFile16(path30.join(dir, file), "utf8");
|
|
6044
|
+
const parsed = parseCommandFrontmatter(name, raw);
|
|
6045
|
+
if (parsed) commands.push(parsed);
|
|
6046
|
+
} catch {
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
return commands;
|
|
6050
|
+
}
|
|
6051
|
+
async function generateClaudeCommandAdapters(rootDir) {
|
|
6052
|
+
const commands = await discoverInstalledCommands(rootDir);
|
|
6053
|
+
if (commands.length === 0) return [];
|
|
6054
|
+
const ledger = await loadManagedHashLedger(rootDir);
|
|
6055
|
+
const results = [];
|
|
6056
|
+
let ledgerDirty = false;
|
|
6057
|
+
for (const command of commands) {
|
|
6058
|
+
const relPath = path30.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
6059
|
+
const rendered = renderClaudeCommandAdapter(command);
|
|
6060
|
+
const abs = path30.join(rootDir, relPath);
|
|
6061
|
+
if (!await fileExists(abs)) {
|
|
6062
|
+
await ensureDir(path30.dirname(abs));
|
|
6063
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6064
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6065
|
+
ledgerDirty = true;
|
|
6066
|
+
results.push({ relativePath: relPath, status: "applied" });
|
|
6067
|
+
continue;
|
|
6068
|
+
}
|
|
6069
|
+
const localContent = await readFile16(abs, "utf8");
|
|
6070
|
+
if (localContent === rendered) {
|
|
6071
|
+
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
6072
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6073
|
+
ledgerDirty = true;
|
|
6074
|
+
}
|
|
6075
|
+
results.push({ relativePath: relPath, status: "unchanged" });
|
|
6076
|
+
continue;
|
|
6077
|
+
}
|
|
6078
|
+
if (shouldPreserveCustomizedOverlay(localContent, ledger.hashes[relPath])) {
|
|
6079
|
+
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
6080
|
+
continue;
|
|
6081
|
+
}
|
|
6082
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6083
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6084
|
+
ledgerDirty = true;
|
|
6085
|
+
results.push({ relativePath: relPath, status: "refreshed" });
|
|
6086
|
+
}
|
|
6087
|
+
if (ledgerDirty) await saveManagedHashLedger(rootDir, ledger);
|
|
6088
|
+
return results;
|
|
6089
|
+
}
|
|
5643
6090
|
|
|
5644
6091
|
// src/generator/claude-kit-load.ts
|
|
5645
|
-
import { writeFile as
|
|
5646
|
-
import
|
|
6092
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
6093
|
+
import path31 from "path";
|
|
5647
6094
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
5648
6095
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
5649
6096
|
function renderClaudeMd() {
|
|
@@ -5669,7 +6116,7 @@ Cursor Ask questions is not available in this CLI. When a command requires a cho
|
|
|
5669
6116
|
- Not Action A7 (Windsurf / VS Code generator parity)
|
|
5670
6117
|
- Not Claude external plan-review audits (\`/plan-external-review\`)
|
|
5671
6118
|
- Not \`--backend claude\` plan-loop ticks
|
|
5672
|
-
- Not a copy of Cursor \`
|
|
6119
|
+
- 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
6120
|
`;
|
|
5674
6121
|
}
|
|
5675
6122
|
function renderAgentKitCommand() {
|
|
@@ -5693,12 +6140,12 @@ Non-goals: not audits / \`/plan-external-review\`, not \`--backend claude\` tick
|
|
|
5693
6140
|
`;
|
|
5694
6141
|
}
|
|
5695
6142
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
5696
|
-
const target =
|
|
6143
|
+
const target = path31.join(rootDir, relativePath);
|
|
5697
6144
|
if (await fileExists(target)) {
|
|
5698
6145
|
return { relativePath, status: "skipped-customized" };
|
|
5699
6146
|
}
|
|
5700
|
-
await ensureDir(
|
|
5701
|
-
await
|
|
6147
|
+
await ensureDir(path31.dirname(target));
|
|
6148
|
+
await writeFile8(target, content, "utf8");
|
|
5702
6149
|
return { relativePath, status: "applied" };
|
|
5703
6150
|
}
|
|
5704
6151
|
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
@@ -5708,9 +6155,109 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
5708
6155
|
]);
|
|
5709
6156
|
}
|
|
5710
6157
|
|
|
6158
|
+
// src/generator/claude-session-start-hook.ts
|
|
6159
|
+
import { readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
|
|
6160
|
+
import path32 from "path";
|
|
6161
|
+
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
6162
|
+
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
6163
|
+
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
6164
|
+
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.";
|
|
6165
|
+
function buildSessionStartHookCommand() {
|
|
6166
|
+
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)}`;
|
|
6167
|
+
}
|
|
6168
|
+
function shellSingleQuote(text) {
|
|
6169
|
+
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
6170
|
+
}
|
|
6171
|
+
function buildSessionStartHookEntry() {
|
|
6172
|
+
return {
|
|
6173
|
+
type: "command",
|
|
6174
|
+
command: buildSessionStartHookCommand(),
|
|
6175
|
+
timeout: 15,
|
|
6176
|
+
statusMessage: "Loading Agent Kit session context"
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
function isPlainObject2(value) {
|
|
6180
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6181
|
+
}
|
|
6182
|
+
function groupHasMarker(group) {
|
|
6183
|
+
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
6184
|
+
return group.hooks.some(
|
|
6185
|
+
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
6186
|
+
);
|
|
6187
|
+
}
|
|
6188
|
+
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
6189
|
+
const entry = buildSessionStartHookEntry();
|
|
6190
|
+
const newGroup = { hooks: [entry] };
|
|
6191
|
+
let root = {};
|
|
6192
|
+
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
6193
|
+
try {
|
|
6194
|
+
const parsed = JSON.parse(existingRaw);
|
|
6195
|
+
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
6196
|
+
root = parsed;
|
|
6197
|
+
} catch {
|
|
6198
|
+
return {
|
|
6199
|
+
content: null,
|
|
6200
|
+
status: "unavailable",
|
|
6201
|
+
instructions: instructionsBlock(entry)
|
|
6202
|
+
};
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
6206
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
6207
|
+
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
6208
|
+
let status;
|
|
6209
|
+
if (existingIndex === -1) {
|
|
6210
|
+
sessionStart.push(newGroup);
|
|
6211
|
+
status = "applied";
|
|
6212
|
+
} else {
|
|
6213
|
+
const current = sessionStart[existingIndex];
|
|
6214
|
+
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
6215
|
+
status = "unchanged";
|
|
6216
|
+
} else {
|
|
6217
|
+
sessionStart[existingIndex] = newGroup;
|
|
6218
|
+
status = "refreshed";
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
hooks.SessionStart = sessionStart;
|
|
6222
|
+
root.hooks = hooks;
|
|
6223
|
+
return { content: `${JSON.stringify(root, null, 2)}
|
|
6224
|
+
`, status };
|
|
6225
|
+
}
|
|
6226
|
+
function instructionsBlock(entry) {
|
|
6227
|
+
return [
|
|
6228
|
+
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
6229
|
+
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
6230
|
+
"",
|
|
6231
|
+
JSON.stringify(entry, null, 2)
|
|
6232
|
+
].join("\n");
|
|
6233
|
+
}
|
|
6234
|
+
async function writeClaudeSessionStartHook(rootDir) {
|
|
6235
|
+
const abs = path32.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
6236
|
+
let existing = null;
|
|
6237
|
+
try {
|
|
6238
|
+
existing = await readFile17(abs, "utf8");
|
|
6239
|
+
} catch {
|
|
6240
|
+
existing = null;
|
|
6241
|
+
}
|
|
6242
|
+
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
6243
|
+
if (merged.status === "unavailable" || merged.content === null) {
|
|
6244
|
+
return {
|
|
6245
|
+
relativePath: CLAUDE_SETTINGS_REL,
|
|
6246
|
+
status: "unavailable",
|
|
6247
|
+
instructions: merged.instructions
|
|
6248
|
+
};
|
|
6249
|
+
}
|
|
6250
|
+
if (merged.status === "unchanged") {
|
|
6251
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
6252
|
+
}
|
|
6253
|
+
await ensureDir(path32.dirname(abs));
|
|
6254
|
+
await writeFile9(abs, merged.content, "utf8");
|
|
6255
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
6256
|
+
}
|
|
6257
|
+
|
|
5711
6258
|
// src/generator/vscode.ts
|
|
5712
|
-
import { writeFile as
|
|
5713
|
-
import
|
|
6259
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
6260
|
+
import path33 from "path";
|
|
5714
6261
|
|
|
5715
6262
|
// src/generator/platform.ts
|
|
5716
6263
|
function gitProviderLabel(profile) {
|
|
@@ -5734,14 +6281,14 @@ function prTerminology(profile) {
|
|
|
5734
6281
|
// src/generator/vscode.ts
|
|
5735
6282
|
async function generateVSCodeArtifacts(profile) {
|
|
5736
6283
|
const results = [];
|
|
5737
|
-
const vscodeDir =
|
|
5738
|
-
const githubDir =
|
|
6284
|
+
const vscodeDir = path33.join(profile.rootDir, ".vscode");
|
|
6285
|
+
const githubDir = path33.join(profile.rootDir, ".github");
|
|
5739
6286
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
5740
|
-
const settingsPath =
|
|
6287
|
+
const settingsPath = path33.join(vscodeDir, "settings.json");
|
|
5741
6288
|
if (await fileExists(settingsPath)) {
|
|
5742
6289
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
5743
6290
|
} else {
|
|
5744
|
-
await
|
|
6291
|
+
await writeFile10(
|
|
5745
6292
|
settingsPath,
|
|
5746
6293
|
`${JSON.stringify(
|
|
5747
6294
|
{
|
|
@@ -5761,11 +6308,11 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5761
6308
|
}
|
|
5762
6309
|
const provider = gitProviderLabel(profile);
|
|
5763
6310
|
const prTerm = prTerminology(profile);
|
|
5764
|
-
const copilotPath =
|
|
6311
|
+
const copilotPath = path33.join(githubDir, "copilot-instructions.md");
|
|
5765
6312
|
if (await fileExists(copilotPath)) {
|
|
5766
6313
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
5767
6314
|
} else {
|
|
5768
|
-
await
|
|
6315
|
+
await writeFile10(
|
|
5769
6316
|
copilotPath,
|
|
5770
6317
|
`# Copilot Instructions
|
|
5771
6318
|
|
|
@@ -5779,14 +6326,14 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5779
6326
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
5780
6327
|
}
|
|
5781
6328
|
if (profile.ide.plan === "vscode-pro") {
|
|
5782
|
-
const securityPath =
|
|
6329
|
+
const securityPath = path33.join(vscodeDir, "security-review.agent.md");
|
|
5783
6330
|
if (await fileExists(securityPath)) {
|
|
5784
6331
|
results.push({
|
|
5785
6332
|
relativePath: ".vscode/security-review.agent.md",
|
|
5786
6333
|
status: "skipped-customized"
|
|
5787
6334
|
});
|
|
5788
6335
|
} else {
|
|
5789
|
-
await
|
|
6336
|
+
await writeFile10(
|
|
5790
6337
|
securityPath,
|
|
5791
6338
|
"# Security Review Agent\n\nSpecialized mode for security review.\n",
|
|
5792
6339
|
"utf8"
|
|
@@ -5991,7 +6538,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
5991
6538
|
`;
|
|
5992
6539
|
}
|
|
5993
6540
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
5994
|
-
const target =
|
|
6541
|
+
const target = path34.join(rootDir, relativePath);
|
|
5995
6542
|
if (await fileExists(target)) {
|
|
5996
6543
|
return {
|
|
5997
6544
|
kind: "file",
|
|
@@ -6001,8 +6548,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
6001
6548
|
evidence
|
|
6002
6549
|
};
|
|
6003
6550
|
}
|
|
6004
|
-
await ensureDir(
|
|
6005
|
-
await
|
|
6551
|
+
await ensureDir(path34.dirname(target));
|
|
6552
|
+
await writeFile11(target, content, "utf8");
|
|
6006
6553
|
return {
|
|
6007
6554
|
kind: "file",
|
|
6008
6555
|
id: relativePath,
|
|
@@ -6018,7 +6565,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
6018
6565
|
async function existingTargets(projectRoot, targets) {
|
|
6019
6566
|
const checks = await Promise.all(
|
|
6020
6567
|
targets.map(
|
|
6021
|
-
async (target) => await fileExists(
|
|
6568
|
+
async (target) => await fileExists(path34.join(projectRoot, target)) ? target : null
|
|
6022
6569
|
)
|
|
6023
6570
|
);
|
|
6024
6571
|
return checks.filter((target) => target !== null);
|
|
@@ -6040,14 +6587,14 @@ async function applyPersonalization(input) {
|
|
|
6040
6587
|
componentResults.push({ ...item, status: "unavailable" });
|
|
6041
6588
|
continue;
|
|
6042
6589
|
}
|
|
6043
|
-
const target =
|
|
6590
|
+
const target = path34.posix.join(
|
|
6044
6591
|
".cursor",
|
|
6045
6592
|
"skills",
|
|
6046
6593
|
skill.path.includes("/core/") ? "core" : "community",
|
|
6047
6594
|
skill.id,
|
|
6048
6595
|
"SKILL.md"
|
|
6049
6596
|
);
|
|
6050
|
-
if (await fileExists(
|
|
6597
|
+
if (await fileExists(path34.join(input.rootDir, target))) {
|
|
6051
6598
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
6052
6599
|
protectedPaths.add(target);
|
|
6053
6600
|
continue;
|
|
@@ -6106,6 +6653,41 @@ async function applyPersonalization(input) {
|
|
|
6106
6653
|
evidence: profileEvidence
|
|
6107
6654
|
};
|
|
6108
6655
|
});
|
|
6656
|
+
const claudeCommandItems = [];
|
|
6657
|
+
let claudeSessionStartInstructions;
|
|
6658
|
+
if (input.claudeAdapters) {
|
|
6659
|
+
const adapterResults = await generateClaudeCommandAdapters(input.rootDir);
|
|
6660
|
+
for (const artifact of adapterResults) {
|
|
6661
|
+
protectedPaths.add(artifact.relativePath);
|
|
6662
|
+
claudeCommandItems.push({
|
|
6663
|
+
kind: "file",
|
|
6664
|
+
id: artifact.relativePath,
|
|
6665
|
+
path: artifact.relativePath,
|
|
6666
|
+
// Overlay statuses (applied/unchanged/refreshed/preserved-customized)
|
|
6667
|
+
// fold onto the shared PersonalizationStatus union: anything written
|
|
6668
|
+
// or already current reads as "applied"; a hand-edited adapter left
|
|
6669
|
+
// alone reads as "skipped-customized" (same meaning as elsewhere in
|
|
6670
|
+
// this file — never silently clobbered).
|
|
6671
|
+
status: artifact.status === "preserved-customized" ? "skipped-customized" : "applied",
|
|
6672
|
+
evidence: profileEvidence
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6675
|
+
const hookResult = await writeClaudeSessionStartHook(input.rootDir);
|
|
6676
|
+
protectedPaths.add(hookResult.relativePath);
|
|
6677
|
+
claudeCommandItems.push({
|
|
6678
|
+
kind: "file",
|
|
6679
|
+
id: hookResult.relativePath,
|
|
6680
|
+
path: hookResult.relativePath,
|
|
6681
|
+
// "unavailable" (existing .claude/settings.json unparseable) is a real
|
|
6682
|
+
// PersonalizationStatus value already; every other hook status folds
|
|
6683
|
+
// onto "applied" the same way the command-adapter statuses do above.
|
|
6684
|
+
status: hookResult.status === "unavailable" ? "unavailable" : "applied",
|
|
6685
|
+
evidence: profileEvidence
|
|
6686
|
+
});
|
|
6687
|
+
if (hookResult.status === "unavailable" && hookResult.instructions) {
|
|
6688
|
+
claudeSessionStartInstructions = hookResult.instructions;
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6109
6691
|
const ideDetection = await detectIde(input.rootDir);
|
|
6110
6692
|
if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
|
|
6111
6693
|
const git = {
|
|
@@ -6153,10 +6735,11 @@ async function applyPersonalization(input) {
|
|
|
6153
6735
|
contractVersion: PERSONALIZATION_CONTRACT_VERSION,
|
|
6154
6736
|
generatorVersion: input.generatorVersion,
|
|
6155
6737
|
repositoryFingerprint: input.report.repositoryFingerprint,
|
|
6156
|
-
items: [...fileResults, ...claudeItems, ...componentResults],
|
|
6157
|
-
protectedPaths: [...protectedPaths].sort()
|
|
6738
|
+
items: [...fileResults, ...claudeItems, ...claudeCommandItems, ...componentResults],
|
|
6739
|
+
protectedPaths: [...protectedPaths].sort(),
|
|
6740
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6158
6741
|
};
|
|
6159
|
-
await writeJson(
|
|
6742
|
+
await writeJson(path34.join(input.rootDir, RESULT_PATH), result);
|
|
6160
6743
|
return {
|
|
6161
6744
|
result,
|
|
6162
6745
|
manifest: {
|
|
@@ -6174,26 +6757,26 @@ async function applyPersonalization(input) {
|
|
|
6174
6757
|
};
|
|
6175
6758
|
}
|
|
6176
6759
|
async function readRepositoryProfile(rootDir) {
|
|
6177
|
-
const target =
|
|
6760
|
+
const target = path34.join(rootDir, ".cursor/agent-kit.config.json");
|
|
6178
6761
|
if (!await fileExists(target)) return null;
|
|
6179
|
-
return JSON.parse(await
|
|
6762
|
+
return JSON.parse(await readFile18(target, "utf8"));
|
|
6180
6763
|
}
|
|
6181
6764
|
|
|
6182
6765
|
// src/lifecycle/onboard-migration.ts
|
|
6183
6766
|
import { createHash as createHash4 } from "crypto";
|
|
6184
|
-
import { readFile as
|
|
6185
|
-
import
|
|
6767
|
+
import { readFile as readFile19, unlink } from "fs/promises";
|
|
6768
|
+
import path35 from "path";
|
|
6186
6769
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
6187
6770
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
6188
6771
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
6189
6772
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
6190
6773
|
]);
|
|
6191
6774
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
6192
|
-
const legacyPath =
|
|
6775
|
+
const legacyPath = path35.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
6193
6776
|
if (!await fileExists(legacyPath)) return "absent";
|
|
6194
|
-
const namespacedPath =
|
|
6777
|
+
const namespacedPath = path35.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
6195
6778
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
6196
|
-
const content = await
|
|
6779
|
+
const content = await readFile19(legacyPath);
|
|
6197
6780
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
6198
6781
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
6199
6782
|
await unlink(legacyPath);
|
|
@@ -6252,6 +6835,9 @@ function parsePackList(raw) {
|
|
|
6252
6835
|
)
|
|
6253
6836
|
];
|
|
6254
6837
|
}
|
|
6838
|
+
function nextStepAfterInstall(pendingActions) {
|
|
6839
|
+
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.";
|
|
6840
|
+
}
|
|
6255
6841
|
function printReadinessNarrative(result) {
|
|
6256
6842
|
const { summary, pendingActions } = result.readiness;
|
|
6257
6843
|
const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
|
|
@@ -6261,12 +6847,61 @@ function printReadinessNarrative(result) {
|
|
|
6261
6847
|
);
|
|
6262
6848
|
console.log(` safe fixes applied: ${fixed}`);
|
|
6263
6849
|
console.log(` pending actions: ${pendingActions.length}`);
|
|
6264
|
-
console.log(
|
|
6265
|
-
|
|
6266
|
-
|
|
6850
|
+
console.log(nextStepAfterInstall(pendingActions.length));
|
|
6851
|
+
}
|
|
6852
|
+
function paint(fn, text) {
|
|
6853
|
+
const prevEnabled = koloristOptions2.enabled;
|
|
6854
|
+
const prevSupportLevel = koloristOptions2.supportLevel;
|
|
6855
|
+
koloristOptions2.enabled = true;
|
|
6856
|
+
koloristOptions2.supportLevel = 3;
|
|
6857
|
+
try {
|
|
6858
|
+
return fn(text);
|
|
6859
|
+
} finally {
|
|
6860
|
+
koloristOptions2.enabled = prevEnabled;
|
|
6861
|
+
koloristOptions2.supportLevel = prevSupportLevel;
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
function printInstallEpilogue(env, options = {}) {
|
|
6865
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
6866
|
+
const color = options.color ?? shouldUseWelcomeColor();
|
|
6867
|
+
if (env.binOnPath) {
|
|
6868
|
+
const line = "`agent-kit` is on PATH \u2014 run it directly, e.g. `agent-kit doctor`.";
|
|
6869
|
+
print(color ? paint(green2, line) : line);
|
|
6870
|
+
return;
|
|
6871
|
+
}
|
|
6872
|
+
const divider = "\u2500".repeat(60);
|
|
6873
|
+
const body = [
|
|
6874
|
+
"You ran this through npx, so a bare `agent-kit` isn't on PATH yet.",
|
|
6875
|
+
'If you try `agent-kit <subcommand>` next, you will see "command not',
|
|
6876
|
+
'found". Pick one:',
|
|
6877
|
+
"",
|
|
6878
|
+
" 1. Keep using npx \u2014 works right now, no action needed",
|
|
6879
|
+
" npx @dadado/agent-kit-cli <subcommand>",
|
|
6880
|
+
"",
|
|
6881
|
+
" 2. Put a bare `agent-kit` on PATH",
|
|
6882
|
+
" npx @dadado/agent-kit-cli setup-global",
|
|
6883
|
+
" (fixes a root-owned npm prefix if that's the blocker, or just installs)",
|
|
6884
|
+
"",
|
|
6885
|
+
" 3. Manual steps",
|
|
6886
|
+
" See docs/getting-started.md (Troubleshooting npm failures), or:",
|
|
6887
|
+
" mkdir -p ~/.npm-global",
|
|
6888
|
+
' npm config set prefix "~/.npm-global"',
|
|
6889
|
+
' export PATH="~/.npm-global/bin:$PATH"',
|
|
6890
|
+
" npm i -g @dadado/agent-kit-cli"
|
|
6891
|
+
];
|
|
6892
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6893
|
+
const heading = "Heads up: a bare `agent-kit` command won't work yet";
|
|
6894
|
+
print(color ? paint(bold, paint(cyan3, heading)) : heading);
|
|
6895
|
+
for (const line of body) print(line);
|
|
6896
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6897
|
+
}
|
|
6898
|
+
async function printPostInstallSummary(result) {
|
|
6899
|
+
printReadinessNarrative(result);
|
|
6900
|
+
const env = await assessEnvironment();
|
|
6901
|
+
printInstallEpilogue(env);
|
|
6267
6902
|
}
|
|
6268
6903
|
async function performInstall(options) {
|
|
6269
|
-
const projectRoot =
|
|
6904
|
+
const projectRoot = path36.resolve(options.cwd);
|
|
6270
6905
|
const packs = parsePackList(options.pack);
|
|
6271
6906
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
6272
6907
|
const registry = await resolveRegistryFromCli({
|
|
@@ -6294,6 +6929,7 @@ async function performInstall(options) {
|
|
|
6294
6929
|
generatorVersion: KIT_VERSION
|
|
6295
6930
|
});
|
|
6296
6931
|
let readiness = readinessExecution.after;
|
|
6932
|
+
let claudeSessionStartInstructions;
|
|
6297
6933
|
const profile = await readRepositoryProfile(projectRoot);
|
|
6298
6934
|
if (profile) {
|
|
6299
6935
|
const registryIndex = await loadRegistry(registry.root);
|
|
@@ -6304,9 +6940,11 @@ async function performInstall(options) {
|
|
|
6304
6940
|
report: readinessExecution.after,
|
|
6305
6941
|
registry: registryIndex,
|
|
6306
6942
|
manifest: draft,
|
|
6307
|
-
generatorVersion: KIT_VERSION
|
|
6943
|
+
generatorVersion: KIT_VERSION,
|
|
6944
|
+
claudeAdapters: options.claudeAdapters
|
|
6308
6945
|
});
|
|
6309
6946
|
await saveManifest(projectRoot, personalization.manifest);
|
|
6947
|
+
claudeSessionStartInstructions = personalization.result.claudeSessionStartInstructions;
|
|
6310
6948
|
readiness = createReadinessReport(await runScanner(projectRoot), {
|
|
6311
6949
|
generatorVersion: KIT_VERSION
|
|
6312
6950
|
});
|
|
@@ -6318,7 +6956,8 @@ async function performInstall(options) {
|
|
|
6318
6956
|
manifestPath,
|
|
6319
6957
|
stats,
|
|
6320
6958
|
readiness,
|
|
6321
|
-
safeChanges: readinessExecution.changes
|
|
6959
|
+
safeChanges: readinessExecution.changes,
|
|
6960
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6322
6961
|
};
|
|
6323
6962
|
} finally {
|
|
6324
6963
|
await registry.unlock?.();
|
|
@@ -6350,6 +6989,11 @@ var installCommand = defineCommand11({
|
|
|
6350
6989
|
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
6351
6990
|
default: false
|
|
6352
6991
|
},
|
|
6992
|
+
claude: {
|
|
6993
|
+
type: "boolean",
|
|
6994
|
+
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)",
|
|
6995
|
+
default: false
|
|
6996
|
+
},
|
|
6353
6997
|
cwd: {
|
|
6354
6998
|
type: "string",
|
|
6355
6999
|
default: process.cwd()
|
|
@@ -6371,6 +7015,9 @@ var installCommand = defineCommand11({
|
|
|
6371
7015
|
} catch (err) {
|
|
6372
7016
|
if (err instanceof RootRefusedError) {
|
|
6373
7017
|
logger.error(err.message);
|
|
7018
|
+
if (err.recovery) console.error(`
|
|
7019
|
+
${err.recovery}
|
|
7020
|
+
`);
|
|
6374
7021
|
process.exitCode = 1;
|
|
6375
7022
|
return;
|
|
6376
7023
|
}
|
|
@@ -6393,13 +7040,20 @@ var installCommand = defineCommand11({
|
|
|
6393
7040
|
registry: args.registry,
|
|
6394
7041
|
url: args.url,
|
|
6395
7042
|
ref: args.ref,
|
|
6396
|
-
refresh: args.refresh
|
|
7043
|
+
refresh: args.refresh,
|
|
7044
|
+
claudeAdapters: args.claude
|
|
6397
7045
|
})
|
|
6398
7046
|
);
|
|
6399
7047
|
logApplyStats(result.stats);
|
|
6400
7048
|
logger.success(`Manifest written: ${result.manifestPath}`);
|
|
6401
7049
|
logger.success("Readiness snapshot written: .cursor/context/readiness.json");
|
|
6402
|
-
|
|
7050
|
+
await printPostInstallSummary(result);
|
|
7051
|
+
if (result.claudeSessionStartInstructions) {
|
|
7052
|
+
logger.warn("Could not merge the Claude Code SessionStart hook automatically:");
|
|
7053
|
+
console.log(`
|
|
7054
|
+
${result.claudeSessionStartInstructions}
|
|
7055
|
+
`);
|
|
7056
|
+
}
|
|
6403
7057
|
} catch (err) {
|
|
6404
7058
|
const hint = classifyInstallError(err);
|
|
6405
7059
|
logger.error(hint.message);
|
|
@@ -6426,25 +7080,57 @@ var initCommand = defineCommand12({
|
|
|
6426
7080
|
type: "string",
|
|
6427
7081
|
description: "Project root directory",
|
|
6428
7082
|
default: process.cwd()
|
|
7083
|
+
},
|
|
7084
|
+
yes: {
|
|
7085
|
+
type: "boolean",
|
|
7086
|
+
alias: "y",
|
|
7087
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
7088
|
+
default: false
|
|
7089
|
+
},
|
|
7090
|
+
"force-root": {
|
|
7091
|
+
type: "boolean",
|
|
7092
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
7093
|
+
default: false
|
|
6429
7094
|
}
|
|
6430
7095
|
},
|
|
6431
7096
|
async run({ args }) {
|
|
6432
|
-
const nonInteractive = isNonInteractive();
|
|
7097
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
6433
7098
|
if (!nonInteractive) {
|
|
6434
7099
|
intro(`agent-kit v${KIT_VERSION}`);
|
|
6435
7100
|
} else {
|
|
6436
7101
|
logger.info(`agent-kit v${KIT_VERSION} (non-interactive mode)`);
|
|
6437
7102
|
}
|
|
6438
7103
|
logger.info("init now uses the canonical install and readiness workflow.");
|
|
7104
|
+
let projectRoot;
|
|
6439
7105
|
try {
|
|
6440
|
-
|
|
7106
|
+
projectRoot = await confirmProjectRoot(args.cwd, {
|
|
7107
|
+
nonInteractive,
|
|
7108
|
+
command: "install",
|
|
7109
|
+
forceRoot: args["force-root"]
|
|
7110
|
+
});
|
|
7111
|
+
} catch (err) {
|
|
7112
|
+
if (err instanceof RootRefusedError) {
|
|
7113
|
+
logger.error(err.message);
|
|
7114
|
+
if (err.recovery) console.error(`
|
|
7115
|
+
${err.recovery}
|
|
7116
|
+
`);
|
|
7117
|
+
process.exitCode = 1;
|
|
7118
|
+
return;
|
|
7119
|
+
}
|
|
7120
|
+
throw err;
|
|
7121
|
+
}
|
|
7122
|
+
try {
|
|
7123
|
+
const result = await withCliProgress("init", () => runInitCompatibility(projectRoot));
|
|
6441
7124
|
const pending = result.readiness.pendingActions.length;
|
|
6442
7125
|
logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
|
|
6443
|
-
const nextStep = pending
|
|
7126
|
+
const nextStep = nextStepAfterInstall(pending);
|
|
7127
|
+
const env = await assessEnvironment();
|
|
6444
7128
|
if (!nonInteractive) {
|
|
7129
|
+
printInstallEpilogue(env);
|
|
6445
7130
|
outro(nextStep);
|
|
6446
7131
|
} else {
|
|
6447
7132
|
logger.info(nextStep);
|
|
7133
|
+
printInstallEpilogue(env);
|
|
6448
7134
|
}
|
|
6449
7135
|
} catch (err) {
|
|
6450
7136
|
const hint = classifyInstallError(err);
|
|
@@ -6458,13 +7144,13 @@ ${hint.recovery}
|
|
|
6458
7144
|
});
|
|
6459
7145
|
|
|
6460
7146
|
// src/commands/monitors.ts
|
|
6461
|
-
import
|
|
7147
|
+
import path38 from "path";
|
|
6462
7148
|
import { defineCommand as defineCommand13 } from "citty";
|
|
6463
7149
|
|
|
6464
7150
|
// src/invariants/monitors-untriaged.ts
|
|
6465
7151
|
import { execFile as execFile5 } from "child_process";
|
|
6466
|
-
import { readFile as
|
|
6467
|
-
import
|
|
7152
|
+
import { readFile as readFile20, readdir as readdir8, stat as stat5 } from "fs/promises";
|
|
7153
|
+
import path37 from "path";
|
|
6468
7154
|
import { promisify as promisify5 } from "util";
|
|
6469
7155
|
|
|
6470
7156
|
// src/invariants/triage-heading.ts
|
|
@@ -6488,7 +7174,7 @@ function hasOpenGaps(content) {
|
|
|
6488
7174
|
}
|
|
6489
7175
|
async function listMonitorFiles(memoryDir) {
|
|
6490
7176
|
try {
|
|
6491
|
-
const names = await
|
|
7177
|
+
const names = await readdir8(memoryDir);
|
|
6492
7178
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
6493
7179
|
} catch {
|
|
6494
7180
|
return [];
|
|
@@ -6505,7 +7191,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
6505
7191
|
for (const line of stdout.split("\n")) {
|
|
6506
7192
|
if (!line.trim()) continue;
|
|
6507
7193
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
6508
|
-
const base =
|
|
7194
|
+
const base = path37.basename(file);
|
|
6509
7195
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
6510
7196
|
names.add(base);
|
|
6511
7197
|
}
|
|
@@ -6528,15 +7214,15 @@ function monitorSlugFromName(fileName) {
|
|
|
6528
7214
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
6529
7215
|
}
|
|
6530
7216
|
async function selectUntriagedMonitors(rootDir) {
|
|
6531
|
-
const root =
|
|
6532
|
-
const memoryDir =
|
|
7217
|
+
const root = path37.resolve(rootDir);
|
|
7218
|
+
const memoryDir = path37.join(root, ".cursor", "memory");
|
|
6533
7219
|
const allNames = await listMonitorFiles(memoryDir);
|
|
6534
7220
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
6535
7221
|
const byName = /* @__PURE__ */ new Map();
|
|
6536
7222
|
for (const name of allNames) {
|
|
6537
|
-
const abs =
|
|
7223
|
+
const abs = path37.join(memoryDir, name);
|
|
6538
7224
|
try {
|
|
6539
|
-
const [content, st] = await Promise.all([
|
|
7225
|
+
const [content, st] = await Promise.all([readFile20(abs, "utf8"), stat5(abs)]);
|
|
6540
7226
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
6541
7227
|
} catch {
|
|
6542
7228
|
}
|
|
@@ -6549,7 +7235,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6549
7235
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
6550
7236
|
let handoff = "";
|
|
6551
7237
|
try {
|
|
6552
|
-
handoff = await
|
|
7238
|
+
handoff = await readFile20(path37.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
6553
7239
|
} catch {
|
|
6554
7240
|
handoff = "";
|
|
6555
7241
|
}
|
|
@@ -6569,8 +7255,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6569
7255
|
const row = byName.get(name);
|
|
6570
7256
|
if (!row) continue;
|
|
6571
7257
|
entries.push({
|
|
6572
|
-
path:
|
|
6573
|
-
relativePath:
|
|
7258
|
+
path: path37.join(memoryDir, name),
|
|
7259
|
+
relativePath: path37.relative(root, path37.join(memoryDir, name)).split(path37.sep).join("/"),
|
|
6574
7260
|
mtimeMs: row.mtimeMs,
|
|
6575
7261
|
hasTriageHeading: false,
|
|
6576
7262
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -6616,7 +7302,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6616
7302
|
process.exitCode = 2;
|
|
6617
7303
|
return;
|
|
6618
7304
|
}
|
|
6619
|
-
const result = await selectUntriagedMonitors(
|
|
7305
|
+
const result = await selectUntriagedMonitors(path38.resolve(args.cwd));
|
|
6620
7306
|
if (args.json) {
|
|
6621
7307
|
console.log(JSON.stringify(result, null, 2));
|
|
6622
7308
|
return;
|
|
@@ -6632,7 +7318,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6632
7318
|
});
|
|
6633
7319
|
|
|
6634
7320
|
// src/commands/run-plan.ts
|
|
6635
|
-
import
|
|
7321
|
+
import path43 from "path";
|
|
6636
7322
|
import { defineCommand as defineCommand14 } from "citty";
|
|
6637
7323
|
|
|
6638
7324
|
// src/plan-loop/backends.ts
|
|
@@ -6717,14 +7403,14 @@ function listBackendIds() {
|
|
|
6717
7403
|
}
|
|
6718
7404
|
|
|
6719
7405
|
// src/plan-loop/run-loop.ts
|
|
6720
|
-
import { mkdir as mkdir6, readFile as
|
|
6721
|
-
import
|
|
7406
|
+
import { mkdir as mkdir6, readFile as readFile23, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
7407
|
+
import path42 from "path";
|
|
6722
7408
|
|
|
6723
7409
|
// src/plan-loop/external-review.ts
|
|
6724
7410
|
import { spawn as spawn7 } from "child_process";
|
|
6725
|
-
import
|
|
6726
|
-
var CANONICAL_REL =
|
|
6727
|
-
var FALLBACK_REL =
|
|
7411
|
+
import path39 from "path";
|
|
7412
|
+
var CANONICAL_REL = path39.join(".cursor", "scripts", "plan-external-review.sh");
|
|
7413
|
+
var FALLBACK_REL = path39.join("scripts", "plan-external-review.sh");
|
|
6728
7414
|
function isPlanExhaustedReason(reason) {
|
|
6729
7415
|
const r = reason.trim().toLowerCase();
|
|
6730
7416
|
if (!r) return false;
|
|
@@ -6746,8 +7432,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6746
7432
|
const existsFn = options.existsFn ?? fileExists;
|
|
6747
7433
|
const log = options.log ?? ((line) => console.log(line));
|
|
6748
7434
|
const force = options.force === true;
|
|
6749
|
-
const canonicalPath =
|
|
6750
|
-
const fallbackPath =
|
|
7435
|
+
const canonicalPath = path39.join(root, CANONICAL_REL);
|
|
7436
|
+
const fallbackPath = path39.join(root, FALLBACK_REL);
|
|
6751
7437
|
let scriptPath = null;
|
|
6752
7438
|
let scriptRel = CANONICAL_REL;
|
|
6753
7439
|
if (await existsFn(canonicalPath)) {
|
|
@@ -6800,12 +7486,12 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6800
7486
|
}
|
|
6801
7487
|
|
|
6802
7488
|
// src/plan-loop/persona-banners.ts
|
|
6803
|
-
import
|
|
7489
|
+
import path40 from "path";
|
|
6804
7490
|
import {
|
|
6805
7491
|
blue,
|
|
6806
|
-
cyan as
|
|
7492
|
+
cyan as cyan4,
|
|
6807
7493
|
gray as gray2,
|
|
6808
|
-
green as
|
|
7494
|
+
green as green3,
|
|
6809
7495
|
lightGray,
|
|
6810
7496
|
lightGreen,
|
|
6811
7497
|
magenta,
|
|
@@ -6818,8 +7504,8 @@ var DEFAULT_CLI_PERSONA_ID = "ghost-runner";
|
|
|
6818
7504
|
var COLORS = {
|
|
6819
7505
|
white,
|
|
6820
7506
|
gray: gray2,
|
|
6821
|
-
green:
|
|
6822
|
-
cyan:
|
|
7507
|
+
green: green3,
|
|
7508
|
+
cyan: cyan4,
|
|
6823
7509
|
magenta,
|
|
6824
7510
|
yellow: yellow2,
|
|
6825
7511
|
red: red2,
|
|
@@ -6836,7 +7522,7 @@ function resolveColor(name, fallback) {
|
|
|
6836
7522
|
async function resolveCliPersonaId(root) {
|
|
6837
7523
|
try {
|
|
6838
7524
|
const cfg = await readJson(
|
|
6839
|
-
|
|
7525
|
+
path40.join(root, ".cursor", "context", "config.json")
|
|
6840
7526
|
);
|
|
6841
7527
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
6842
7528
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -6847,7 +7533,7 @@ async function resolveCliPersonaId(root) {
|
|
|
6847
7533
|
}
|
|
6848
7534
|
async function loadPersonaPack(root, personaId) {
|
|
6849
7535
|
try {
|
|
6850
|
-
const personaPath =
|
|
7536
|
+
const personaPath = path40.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
6851
7537
|
const pack = await readJson(personaPath);
|
|
6852
7538
|
if (!pack || typeof pack.id !== "string") return null;
|
|
6853
7539
|
return pack;
|
|
@@ -6865,7 +7551,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6865
7551
|
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
6866
7552
|
const primary = resolveColor(persona.ansiPalette?.primary, white);
|
|
6867
7553
|
const secondary = resolveColor(persona.ansiPalette?.secondary, gray2);
|
|
6868
|
-
const accent = resolveColor(persona.ansiPalette?.accent,
|
|
7554
|
+
const accent = resolveColor(persona.ansiPalette?.accent, green3);
|
|
6869
7555
|
return {
|
|
6870
7556
|
tickStart(detail) {
|
|
6871
7557
|
if (banners.tickStart) {
|
|
@@ -6896,8 +7582,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6896
7582
|
}
|
|
6897
7583
|
|
|
6898
7584
|
// src/plan-loop/plan-state.ts
|
|
6899
|
-
import { readFile as
|
|
6900
|
-
import
|
|
7585
|
+
import { readFile as readFile21, readdir as readdir9 } from "fs/promises";
|
|
7586
|
+
import path41 from "path";
|
|
6901
7587
|
function countPendingTodos(raw) {
|
|
6902
7588
|
const lines = raw.split(/\r?\n/);
|
|
6903
7589
|
let inFront = 0;
|
|
@@ -6924,15 +7610,15 @@ function countPendingTodos(raw) {
|
|
|
6924
7610
|
}
|
|
6925
7611
|
async function findActivePlanFile(plansDir) {
|
|
6926
7612
|
if (!await fileExists(plansDir)) return null;
|
|
6927
|
-
const files = (await
|
|
6928
|
-
return files[0] ?
|
|
7613
|
+
const files = (await readdir9(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
7614
|
+
return files[0] ? path41.join(plansDir, files[0]) : null;
|
|
6929
7615
|
}
|
|
6930
7616
|
async function readPlan(planPath) {
|
|
6931
|
-
return
|
|
7617
|
+
return readFile21(planPath, "utf8");
|
|
6932
7618
|
}
|
|
6933
7619
|
|
|
6934
7620
|
// src/plan-loop/sentinel.ts
|
|
6935
|
-
import { readFile as
|
|
7621
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
6936
7622
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
6937
7623
|
function takeFromText(text) {
|
|
6938
7624
|
if (!text) return null;
|
|
@@ -6979,7 +7665,7 @@ function parseSentinelFromLog(content) {
|
|
|
6979
7665
|
}
|
|
6980
7666
|
async function parseSentinelFromLogFile(logPath) {
|
|
6981
7667
|
try {
|
|
6982
|
-
const content = await
|
|
7668
|
+
const content = await readFile22(logPath, "utf8");
|
|
6983
7669
|
return parseSentinelFromLog(content);
|
|
6984
7670
|
} catch {
|
|
6985
7671
|
return { kind: "missing" };
|
|
@@ -7002,9 +7688,9 @@ function sleep(ms) {
|
|
|
7002
7688
|
return new Promise((r) => setTimeout(r, ms));
|
|
7003
7689
|
}
|
|
7004
7690
|
async function runPlanLoop(opts) {
|
|
7005
|
-
const plansDir =
|
|
7006
|
-
const stopFile =
|
|
7007
|
-
const logDir =
|
|
7691
|
+
const plansDir = path42.join(opts.root, ".cursor", "plans");
|
|
7692
|
+
const stopFile = path42.join(opts.root, ".cursor", "loop.stop");
|
|
7693
|
+
const logDir = path42.join(opts.root, ".cursor", "loop-logs");
|
|
7008
7694
|
const planPath = await findActivePlanFile(plansDir);
|
|
7009
7695
|
if (!planPath) {
|
|
7010
7696
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -7025,7 +7711,7 @@ async function runPlanLoop(opts) {
|
|
|
7025
7711
|
try {
|
|
7026
7712
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
7027
7713
|
const banners = createPersonaBannerPrinter(persona);
|
|
7028
|
-
console.log(`Active plan: ${
|
|
7714
|
+
console.log(`Active plan: ${path42.basename(planPath)}`);
|
|
7029
7715
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
7030
7716
|
console.log(`Backend: ${opts.backend.id}`);
|
|
7031
7717
|
if (persona) {
|
|
@@ -7068,8 +7754,8 @@ async function runPlanLoop(opts) {
|
|
|
7068
7754
|
planExhausted = true;
|
|
7069
7755
|
break;
|
|
7070
7756
|
}
|
|
7071
|
-
const logPath =
|
|
7072
|
-
const relLog =
|
|
7757
|
+
const logPath = path42.join(logDir, `tick-${stamp()}.log`);
|
|
7758
|
+
const relLog = path42.relative(opts.root, logPath);
|
|
7073
7759
|
console.log("");
|
|
7074
7760
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
7075
7761
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -7091,7 +7777,7 @@ async function runPlanLoop(opts) {
|
|
|
7091
7777
|
return 1;
|
|
7092
7778
|
}
|
|
7093
7779
|
try {
|
|
7094
|
-
const logText = await
|
|
7780
|
+
const logText = await readFile23(logPath, "utf8");
|
|
7095
7781
|
if (logText.includes("Too many MCP tools")) {
|
|
7096
7782
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
7097
7783
|
if (banners) banners.stop(msg);
|
|
@@ -7148,7 +7834,7 @@ async function runPlanLoop(opts) {
|
|
|
7148
7834
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
7149
7835
|
if (banners) banners.phaseComplete(finishDetail);
|
|
7150
7836
|
console.log(
|
|
7151
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
7837
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path42.relative(opts.root, logDir)}/`
|
|
7152
7838
|
);
|
|
7153
7839
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
7154
7840
|
await armExternalPlanReview(opts.root);
|
|
@@ -7219,7 +7905,7 @@ var runPlanCommand = defineCommand14({
|
|
|
7219
7905
|
return;
|
|
7220
7906
|
}
|
|
7221
7907
|
const code = await runPlanLoop({
|
|
7222
|
-
root:
|
|
7908
|
+
root: path43.resolve(args.cwd),
|
|
7223
7909
|
maxTicks,
|
|
7224
7910
|
sleepSeconds,
|
|
7225
7911
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -7252,9 +7938,286 @@ var scanCommand = defineCommand15({
|
|
|
7252
7938
|
}
|
|
7253
7939
|
});
|
|
7254
7940
|
|
|
7255
|
-
// src/commands/
|
|
7256
|
-
import
|
|
7941
|
+
// src/commands/setup-global.ts
|
|
7942
|
+
import { spawn as spawn8 } from "child_process";
|
|
7943
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile24, writeFile as writeFile12 } from "fs/promises";
|
|
7944
|
+
import { homedir as homedir4 } from "os";
|
|
7945
|
+
import path44 from "path";
|
|
7946
|
+
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
7257
7947
|
import { defineCommand as defineCommand16 } from "citty";
|
|
7948
|
+
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
7949
|
+
var NPM_GLOBAL_DIR_NAME = ".npm-global";
|
|
7950
|
+
var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
7951
|
+
var DEFAULT_PACKAGE_SPEC = "@dadado/agent-kit-cli";
|
|
7952
|
+
function planSetupGlobalSteps(env, options = {}) {
|
|
7953
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
7954
|
+
const packageSpec = options.packageSpec ?? DEFAULT_PACKAGE_SPEC;
|
|
7955
|
+
const npmGlobalDir = path44.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
7956
|
+
const npmGlobalBin = path44.join(npmGlobalDir, "bin");
|
|
7957
|
+
const npmrcPath = path44.join(homeDir, ".npmrc");
|
|
7958
|
+
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
7959
|
+
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
7960
|
+
const shellProfile = env.shellProfile;
|
|
7961
|
+
const shellSupported = shellProfile != null;
|
|
7962
|
+
const steps = [
|
|
7963
|
+
{
|
|
7964
|
+
id: "set-prefix",
|
|
7965
|
+
title: "Set npm's global install prefix to a folder you own",
|
|
7966
|
+
detail: [
|
|
7967
|
+
`mkdir -p ${npmGlobalDir}`,
|
|
7968
|
+
`npm config set prefix "${npmrcPrefixValue}" (writes "prefix = ${npmrcPrefixValue}" to ${npmrcPath})`
|
|
7969
|
+
]
|
|
7970
|
+
},
|
|
7971
|
+
{
|
|
7972
|
+
id: "append-path",
|
|
7973
|
+
title: shellSupported ? `Put ${npmGlobalBin} on PATH via ${shellProfile}` : "Put npm's global bin on PATH (manual \u2014 shell not auto-detected)",
|
|
7974
|
+
detail: shellSupported ? [`Append to ${shellProfile}:`, ` ${SETUP_GLOBAL_MARKER}`, ` ${pathExportLine}`] : [
|
|
7975
|
+
`Shell could not be auto-detected as zsh or bash (detected: ${env.shell ?? "unknown"}).`,
|
|
7976
|
+
"You'll need to add this line to your shell's startup file yourself:",
|
|
7977
|
+
` ${pathExportLine}`
|
|
7978
|
+
]
|
|
7979
|
+
},
|
|
7980
|
+
{
|
|
7981
|
+
id: "npm-install",
|
|
7982
|
+
title: `Reinstall ${packageSpec} globally, now into the new prefix`,
|
|
7983
|
+
detail: [`npm i -g ${packageSpec}`]
|
|
7984
|
+
},
|
|
7985
|
+
{
|
|
7986
|
+
id: "verify",
|
|
7987
|
+
title: "Verify `agent-kit` resolves on PATH",
|
|
7988
|
+
detail: [
|
|
7989
|
+
"Re-check whether a bare `agent-kit` resolves on PATH.",
|
|
7990
|
+
"Needs a new shell session (or `source` the profile) to take effect \u2014 this process's own PATH can't reflect it."
|
|
7991
|
+
]
|
|
7992
|
+
}
|
|
7993
|
+
];
|
|
7994
|
+
return {
|
|
7995
|
+
packageSpec,
|
|
7996
|
+
homeDir,
|
|
7997
|
+
npmGlobalDir,
|
|
7998
|
+
npmGlobalBin,
|
|
7999
|
+
npmrcPath,
|
|
8000
|
+
npmrcPrefixValue,
|
|
8001
|
+
pathExportLine,
|
|
8002
|
+
markerComment: SETUP_GLOBAL_MARKER,
|
|
8003
|
+
shellProfile,
|
|
8004
|
+
shellSupported,
|
|
8005
|
+
alreadyWritable: env.npmPrefixWritable,
|
|
8006
|
+
currentPrefix: env.npmPrefix.prefix,
|
|
8007
|
+
steps
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
8010
|
+
function upsertNpmrcPrefix(content, prefixValue) {
|
|
8011
|
+
const line = `prefix = ${prefixValue}`;
|
|
8012
|
+
const prefixLineRe = /^\s*prefix\s*=.*$/m;
|
|
8013
|
+
if (prefixLineRe.test(content)) {
|
|
8014
|
+
return content.replace(prefixLineRe, line);
|
|
8015
|
+
}
|
|
8016
|
+
const withTrailingNewline = content.length > 0 && !content.endsWith("\n") ? `${content}
|
|
8017
|
+
` : content;
|
|
8018
|
+
return `${withTrailingNewline}${line}
|
|
8019
|
+
`;
|
|
8020
|
+
}
|
|
8021
|
+
var defaultFsImpl = {
|
|
8022
|
+
mkdir: async (dir) => {
|
|
8023
|
+
await mkdir7(dir, { recursive: true });
|
|
8024
|
+
},
|
|
8025
|
+
readFile: (filePath) => readFile24(filePath, "utf8"),
|
|
8026
|
+
writeFile: (filePath, content) => writeFile12(filePath, content, "utf8"),
|
|
8027
|
+
appendFile: (filePath, content) => appendFile(filePath, content, "utf8")
|
|
8028
|
+
};
|
|
8029
|
+
async function safeReadFile(fs, filePath) {
|
|
8030
|
+
try {
|
|
8031
|
+
return await fs.readFile(filePath);
|
|
8032
|
+
} catch {
|
|
8033
|
+
return "";
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
var defaultNpmInstallImpl = (packageSpec) => new Promise((resolve2) => {
|
|
8037
|
+
const child = spawn8("npm", ["i", "-g", packageSpec], { stdio: "inherit" });
|
|
8038
|
+
child.on("error", (error) => resolve2({ ok: false, error }));
|
|
8039
|
+
child.on("close", (code) => {
|
|
8040
|
+
if (code === 0) resolve2({ ok: true });
|
|
8041
|
+
else resolve2({ ok: false, error: new Error(`npm exited with code ${code ?? "unknown"}`) });
|
|
8042
|
+
});
|
|
8043
|
+
});
|
|
8044
|
+
var defaultConfirmImpl = async (message) => {
|
|
8045
|
+
const answer = await confirm2({ message, initialValue: true });
|
|
8046
|
+
if (isCancel2(answer)) return false;
|
|
8047
|
+
return Boolean(answer);
|
|
8048
|
+
};
|
|
8049
|
+
function printHeader(env, print) {
|
|
8050
|
+
print(cyan5("agent-kit setup-global"));
|
|
8051
|
+
print(
|
|
8052
|
+
` npm prefix: ${env.npmPrefix.prefix ?? "unknown"} (${env.npmPrefixWritable ? "writable" : "NOT writable"})`
|
|
8053
|
+
);
|
|
8054
|
+
if (!env.npmPrefixWritable && env.npmPrefix.reason) {
|
|
8055
|
+
print(` ${env.npmPrefix.reason}`);
|
|
8056
|
+
}
|
|
8057
|
+
print(
|
|
8058
|
+
` shell: ${env.shell ?? "unknown"}${env.shellProfile ? ` (profile: ${env.shellProfile})` : " (profile not auto-detected: zsh/bash only)"}`
|
|
8059
|
+
);
|
|
8060
|
+
}
|
|
8061
|
+
function printPlanSteps(plan, print) {
|
|
8062
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
8063
|
+
print(`${index + 1}. ${step.title}`);
|
|
8064
|
+
for (const line of step.detail) print(` ${line}`);
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
function printManualInstructions(plan, print) {
|
|
8068
|
+
print("No changes made. Same fix, as commands you can run yourself:");
|
|
8069
|
+
print(` mkdir -p ${plan.npmGlobalDir}`);
|
|
8070
|
+
print(` npm config set prefix "${plan.npmrcPrefixValue}"`);
|
|
8071
|
+
if (plan.shellSupported) {
|
|
8072
|
+
print(` echo '${plan.markerComment}' >> ${plan.shellProfile}`);
|
|
8073
|
+
print(` echo '${plan.pathExportLine}' >> ${plan.shellProfile}`);
|
|
8074
|
+
print(` source ${plan.shellProfile}`);
|
|
8075
|
+
} else {
|
|
8076
|
+
print(` # add this line to your shell's startup file:`);
|
|
8077
|
+
print(` ${plan.pathExportLine}`);
|
|
8078
|
+
}
|
|
8079
|
+
print(` npm i -g ${plan.packageSpec}`);
|
|
8080
|
+
print(" agent-kit --version # verify, in a new shell session");
|
|
8081
|
+
}
|
|
8082
|
+
async function runSetupGlobal(options = {}) {
|
|
8083
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
8084
|
+
const assessEnvironmentImpl = options.assessEnvironmentImpl ?? assessEnvironment;
|
|
8085
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
8086
|
+
const env = await assessEnvironmentImpl(options);
|
|
8087
|
+
const plan = planSetupGlobalSteps(env, { homeDir, packageSpec: options.packageSpec });
|
|
8088
|
+
if (plan.alreadyWritable) {
|
|
8089
|
+
printHeader(env, print);
|
|
8090
|
+
print(green4("npm's global prefix is already writable \u2014 nothing to fix."));
|
|
8091
|
+
return { exitCode: 0, mutated: false, outcome: "already-ok", env, plan };
|
|
8092
|
+
}
|
|
8093
|
+
if (options.dryRun) {
|
|
8094
|
+
printHeader(env, print);
|
|
8095
|
+
print("Dry run \u2014 no changes will be made. Steps that would run:");
|
|
8096
|
+
printPlanSteps(plan, print);
|
|
8097
|
+
return { exitCode: 0, mutated: false, outcome: "dry-run", env, plan };
|
|
8098
|
+
}
|
|
8099
|
+
const nonInteractive = options.nonInteractive ?? isNonInteractive();
|
|
8100
|
+
if (nonInteractive) {
|
|
8101
|
+
printHeader(env, print);
|
|
8102
|
+
printManualInstructions(plan, print);
|
|
8103
|
+
return { exitCode: 0, mutated: false, outcome: "manual-instructions", env, plan };
|
|
8104
|
+
}
|
|
8105
|
+
printHeader(env, print);
|
|
8106
|
+
print("You just hit the classic 'command not found' / EACCES fresh-install blocker.");
|
|
8107
|
+
print("The following steps need your confirmation, one at a time:");
|
|
8108
|
+
printPlanSteps(plan, print);
|
|
8109
|
+
const fs = options.fsImpl ?? defaultFsImpl;
|
|
8110
|
+
const confirmStep = options.confirmImpl ?? defaultConfirmImpl;
|
|
8111
|
+
const npmInstall = options.npmInstallImpl ?? defaultNpmInstallImpl;
|
|
8112
|
+
let mutated = false;
|
|
8113
|
+
const setPrefixStep = plan.steps[0];
|
|
8114
|
+
print(`
|
|
8115
|
+
${setPrefixStep.title}`);
|
|
8116
|
+
for (const line of setPrefixStep.detail) print(` ${line}`);
|
|
8117
|
+
const proceedPrefix = await confirmStep(`Set npm's global prefix to ${plan.npmGlobalDir}?`);
|
|
8118
|
+
if (!proceedPrefix) {
|
|
8119
|
+
print(yellow3("Cancelled \u2014 no changes made."));
|
|
8120
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8121
|
+
}
|
|
8122
|
+
await fs.mkdir(plan.npmGlobalDir);
|
|
8123
|
+
const npmrcContent = await safeReadFile(fs, plan.npmrcPath);
|
|
8124
|
+
await fs.writeFile(plan.npmrcPath, upsertNpmrcPrefix(npmrcContent, plan.npmrcPrefixValue));
|
|
8125
|
+
mutated = true;
|
|
8126
|
+
print(green4(` done: prefix set (${plan.npmrcPath}).`));
|
|
8127
|
+
const appendPathStep = plan.steps[1];
|
|
8128
|
+
print(`
|
|
8129
|
+
${appendPathStep.title}`);
|
|
8130
|
+
for (const line of appendPathStep.detail) print(` ${line}`);
|
|
8131
|
+
if (!plan.shellSupported) {
|
|
8132
|
+
print(yellow3(" shell not auto-detected as zsh/bash \u2014 add the line above yourself."));
|
|
8133
|
+
} else {
|
|
8134
|
+
const profilePath = plan.shellProfile;
|
|
8135
|
+
const profileContent = await safeReadFile(fs, profilePath);
|
|
8136
|
+
if (profileContent.includes(plan.markerComment)) {
|
|
8137
|
+
print(` already present in ${profilePath} (marker found) \u2014 skipping.`);
|
|
8138
|
+
} else {
|
|
8139
|
+
const proceedPath = await confirmStep(`Append the PATH export to ${profilePath}?`);
|
|
8140
|
+
if (!proceedPath) {
|
|
8141
|
+
print(yellow3("Cancelled \u2014 prefix was set, PATH export was not appended."));
|
|
8142
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8143
|
+
}
|
|
8144
|
+
await fs.appendFile(profilePath, `
|
|
8145
|
+
${plan.markerComment}
|
|
8146
|
+
${plan.pathExportLine}
|
|
8147
|
+
`);
|
|
8148
|
+
mutated = true;
|
|
8149
|
+
print(green4(` done: PATH export appended to ${profilePath}.`));
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
const installStep = plan.steps[2];
|
|
8153
|
+
print(`
|
|
8154
|
+
${installStep.title}`);
|
|
8155
|
+
for (const line of installStep.detail) print(` ${line}`);
|
|
8156
|
+
const proceedInstall = await confirmStep(`Run: npm i -g ${plan.packageSpec}?`);
|
|
8157
|
+
if (!proceedInstall) {
|
|
8158
|
+
print(yellow3("Cancelled \u2014 prefix/PATH changes above are still in place."));
|
|
8159
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8160
|
+
}
|
|
8161
|
+
const installResult = await npmInstall(plan.packageSpec);
|
|
8162
|
+
if (!installResult.ok) {
|
|
8163
|
+
const hint = classifyInstallError(installResult.error);
|
|
8164
|
+
print(` npm install failed: ${hint.message}`);
|
|
8165
|
+
print(hint.recovery);
|
|
8166
|
+
return { exitCode: 1, mutated, outcome: "error", env, plan };
|
|
8167
|
+
}
|
|
8168
|
+
mutated = true;
|
|
8169
|
+
print(green4(` done: ${plan.packageSpec} installed globally.`));
|
|
8170
|
+
const verifyStep = plan.steps[3];
|
|
8171
|
+
print(`
|
|
8172
|
+
${verifyStep.title}`);
|
|
8173
|
+
for (const line of verifyStep.detail) print(` ${line}`);
|
|
8174
|
+
const proceedVerify = await confirmStep("Verify now (re-check PATH in this process)?");
|
|
8175
|
+
if (proceedVerify) {
|
|
8176
|
+
const verifyEnv = await assessEnvironmentImpl(options);
|
|
8177
|
+
if (verifyEnv.binOnPath) {
|
|
8178
|
+
print(green4(" verify: `agent-kit` resolves on PATH."));
|
|
8179
|
+
} else {
|
|
8180
|
+
print(" verify: `agent-kit` isn't resolvable in THIS process's PATH yet \u2014 that's expected.");
|
|
8181
|
+
print(
|
|
8182
|
+
` Open a new terminal (or run: source ${plan.shellProfile ?? "<your shell profile>"}) and re-check with: agent-kit --version`
|
|
8183
|
+
);
|
|
8184
|
+
}
|
|
8185
|
+
} else {
|
|
8186
|
+
print(" skipped verification. Open a new terminal and run: agent-kit --version");
|
|
8187
|
+
}
|
|
8188
|
+
return { exitCode: 0, mutated, outcome: "completed", env, plan };
|
|
8189
|
+
}
|
|
8190
|
+
var setupGlobalCommand = defineCommand16({
|
|
8191
|
+
meta: {
|
|
8192
|
+
name: "setup-global",
|
|
8193
|
+
description: "Self-heal a root-owned npm prefix: relocate to ~/.npm-global, fix PATH, reinstall globally."
|
|
8194
|
+
},
|
|
8195
|
+
args: {
|
|
8196
|
+
"dry-run": {
|
|
8197
|
+
type: "boolean",
|
|
8198
|
+
description: "Print the resolved plan; mutate nothing.",
|
|
8199
|
+
default: false
|
|
8200
|
+
},
|
|
8201
|
+
yes: {
|
|
8202
|
+
type: "boolean",
|
|
8203
|
+
alias: "y",
|
|
8204
|
+
description: "Treat as non-interactive: print the manual steps instead of prompting (never mutates).",
|
|
8205
|
+
default: false
|
|
8206
|
+
}
|
|
8207
|
+
},
|
|
8208
|
+
async run({ args }) {
|
|
8209
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
8210
|
+
const result = await runSetupGlobal({
|
|
8211
|
+
dryRun: Boolean(args["dry-run"]),
|
|
8212
|
+
nonInteractive
|
|
8213
|
+
});
|
|
8214
|
+
process.exitCode = result.exitCode;
|
|
8215
|
+
}
|
|
8216
|
+
});
|
|
8217
|
+
|
|
8218
|
+
// src/commands/status.ts
|
|
8219
|
+
import path45 from "path";
|
|
8220
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
7258
8221
|
function profileStatus(profile) {
|
|
7259
8222
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
7260
8223
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -7267,7 +8230,7 @@ function profileStatus(profile) {
|
|
|
7267
8230
|
}
|
|
7268
8231
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
7269
8232
|
}
|
|
7270
|
-
var statusCommand =
|
|
8233
|
+
var statusCommand = defineCommand17({
|
|
7271
8234
|
meta: {
|
|
7272
8235
|
name: "status",
|
|
7273
8236
|
description: "Show installed kit version, manifest, and optional profile."
|
|
@@ -7284,11 +8247,11 @@ var statusCommand = defineCommand16({
|
|
|
7284
8247
|
}
|
|
7285
8248
|
},
|
|
7286
8249
|
async run({ args }) {
|
|
7287
|
-
const rootDir =
|
|
8250
|
+
const rootDir = path45.resolve(args.cwd);
|
|
7288
8251
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
7289
8252
|
loadAgentKitManifest(rootDir),
|
|
7290
8253
|
readJson(
|
|
7291
|
-
|
|
8254
|
+
path45.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
7292
8255
|
),
|
|
7293
8256
|
runScanner(rootDir)
|
|
7294
8257
|
]);
|
|
@@ -7343,11 +8306,11 @@ var statusCommand = defineCommand16({
|
|
|
7343
8306
|
});
|
|
7344
8307
|
|
|
7345
8308
|
// src/commands/update.ts
|
|
7346
|
-
import { defineCommand as
|
|
8309
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
7347
8310
|
|
|
7348
8311
|
// src/lifecycle/check-updates.ts
|
|
7349
8312
|
import { execFile as execFile6 } from "child_process";
|
|
7350
|
-
import
|
|
8313
|
+
import path46 from "path";
|
|
7351
8314
|
import { promisify as promisify6 } from "util";
|
|
7352
8315
|
var execFileAsync5 = promisify6(execFile6);
|
|
7353
8316
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -7438,11 +8401,11 @@ function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
|
7438
8401
|
return Date.now() - last >= ms;
|
|
7439
8402
|
}
|
|
7440
8403
|
async function loadContextConfig2(cwd) {
|
|
7441
|
-
const configPath =
|
|
8404
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7442
8405
|
return readJson(configPath);
|
|
7443
8406
|
}
|
|
7444
8407
|
async function stampLastCheckedAt(cwd) {
|
|
7445
|
-
const configPath =
|
|
8408
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7446
8409
|
const existing = await loadContextConfig2(cwd) ?? {};
|
|
7447
8410
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
7448
8411
|
existing.updateCheck = {
|
|
@@ -7454,7 +8417,7 @@ async function stampLastCheckedAt(cwd) {
|
|
|
7454
8417
|
}
|
|
7455
8418
|
async function readLocalKitVersion(registryRoot) {
|
|
7456
8419
|
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
7457
|
-
const data = await readJson(
|
|
8420
|
+
const data = await readJson(path46.join(registryRoot, rel));
|
|
7458
8421
|
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
7459
8422
|
return data.version;
|
|
7460
8423
|
}
|
|
@@ -7475,7 +8438,7 @@ async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
|
7475
8438
|
status: "error",
|
|
7476
8439
|
installedVersion: manifest.version,
|
|
7477
8440
|
latestVersion: null,
|
|
7478
|
-
registryUrl:
|
|
8441
|
+
registryUrl: path46.resolve(registryPath),
|
|
7479
8442
|
registryRef: "local",
|
|
7480
8443
|
applyRecommended: false,
|
|
7481
8444
|
message: `Failed to resolve --registry: ${msg}`
|
|
@@ -7697,7 +8660,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
7697
8660
|
}
|
|
7698
8661
|
|
|
7699
8662
|
// src/commands/update.ts
|
|
7700
|
-
var updateCommand =
|
|
8663
|
+
var updateCommand = defineCommand18({
|
|
7701
8664
|
meta: {
|
|
7702
8665
|
name: "update",
|
|
7703
8666
|
description: "Re-apply L0/packs/skills from the registry (never overwrites L3). --check = notify-only."
|
|
@@ -7776,6 +8739,9 @@ var updateCommand = defineCommand17({
|
|
|
7776
8739
|
} catch (err) {
|
|
7777
8740
|
if (err instanceof RootRefusedError) {
|
|
7778
8741
|
logger.error(err.message);
|
|
8742
|
+
if (err.recovery) console.error(`
|
|
8743
|
+
${err.recovery}
|
|
8744
|
+
`);
|
|
7779
8745
|
process.exitCode = 1;
|
|
7780
8746
|
return;
|
|
7781
8747
|
}
|
|
@@ -7828,9 +8794,9 @@ var updateCommand = defineCommand17({
|
|
|
7828
8794
|
});
|
|
7829
8795
|
|
|
7830
8796
|
// src/commands/validate.ts
|
|
7831
|
-
import { readFile as
|
|
7832
|
-
import
|
|
7833
|
-
import { defineCommand as
|
|
8797
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
8798
|
+
import path47 from "path";
|
|
8799
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
7834
8800
|
|
|
7835
8801
|
// src/invariants/plan-schema.ts
|
|
7836
8802
|
var CITE5 = "agent-kit validate plan (.cursor/context/templates/plan.md)";
|
|
@@ -7876,9 +8842,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
7876
8842
|
// src/commands/validate.ts
|
|
7877
8843
|
async function resolveEditedPath(cwd, explicit) {
|
|
7878
8844
|
if (explicit) {
|
|
7879
|
-
const filePath2 =
|
|
8845
|
+
const filePath2 = path47.resolve(cwd, explicit);
|
|
7880
8846
|
try {
|
|
7881
|
-
return { filePath: filePath2, content: await
|
|
8847
|
+
return { filePath: filePath2, content: await readFile25(filePath2, "utf8") };
|
|
7882
8848
|
} catch {
|
|
7883
8849
|
return null;
|
|
7884
8850
|
}
|
|
@@ -7886,9 +8852,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
7886
8852
|
const payload = await readStdinJson();
|
|
7887
8853
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
7888
8854
|
if (!rel) return null;
|
|
7889
|
-
const filePath =
|
|
8855
|
+
const filePath = path47.isAbsolute(rel) ? rel : path47.resolve(cwd, rel);
|
|
7890
8856
|
try {
|
|
7891
|
-
return { filePath, content: await
|
|
8857
|
+
return { filePath, content: await readFile25(filePath, "utf8") };
|
|
7892
8858
|
} catch {
|
|
7893
8859
|
return null;
|
|
7894
8860
|
}
|
|
@@ -7900,13 +8866,13 @@ function isPlanPath(filePath) {
|
|
|
7900
8866
|
const norm = filePath.replace(/\\/g, "/");
|
|
7901
8867
|
return norm.includes("/.cursor/plans/") && norm.endsWith(".plan.md");
|
|
7902
8868
|
}
|
|
7903
|
-
var validateCommand =
|
|
8869
|
+
var validateCommand = defineCommand19({
|
|
7904
8870
|
meta: {
|
|
7905
8871
|
name: "validate",
|
|
7906
8872
|
description: "Advisory validators for HANDOFF / plan frontmatter (hook adapter)."
|
|
7907
8873
|
},
|
|
7908
8874
|
subCommands: {
|
|
7909
|
-
handoff:
|
|
8875
|
+
handoff: defineCommand19({
|
|
7910
8876
|
meta: { name: "handoff", description: "Validate HANDOFF machine fields" },
|
|
7911
8877
|
args: {
|
|
7912
8878
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7916,10 +8882,10 @@ var validateCommand = defineCommand18({
|
|
|
7916
8882
|
async run({ args }) {
|
|
7917
8883
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7918
8884
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
7919
|
-
const filePath = fileArg ?
|
|
8885
|
+
const filePath = fileArg ? path47.resolve(cwd, fileArg) : path47.join(path47.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
7920
8886
|
let content = "";
|
|
7921
8887
|
try {
|
|
7922
|
-
content = await
|
|
8888
|
+
content = await readFile25(filePath, "utf8");
|
|
7923
8889
|
} catch {
|
|
7924
8890
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7925
8891
|
return;
|
|
@@ -7928,7 +8894,7 @@ var validateCommand = defineCommand18({
|
|
|
7928
8894
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7929
8895
|
}
|
|
7930
8896
|
}),
|
|
7931
|
-
plan:
|
|
8897
|
+
plan: defineCommand19({
|
|
7932
8898
|
meta: { name: "plan", description: "Validate plan frontmatter" },
|
|
7933
8899
|
args: {
|
|
7934
8900
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7943,10 +8909,10 @@ var validateCommand = defineCommand18({
|
|
|
7943
8909
|
process.exitCode = 2;
|
|
7944
8910
|
return;
|
|
7945
8911
|
}
|
|
7946
|
-
const filePath =
|
|
8912
|
+
const filePath = path47.resolve(cwd, fileArg);
|
|
7947
8913
|
let content = "";
|
|
7948
8914
|
try {
|
|
7949
|
-
content = await
|
|
8915
|
+
content = await readFile25(filePath, "utf8");
|
|
7950
8916
|
} catch {
|
|
7951
8917
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7952
8918
|
return;
|
|
@@ -7955,7 +8921,7 @@ var validateCommand = defineCommand18({
|
|
|
7955
8921
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7956
8922
|
}
|
|
7957
8923
|
}),
|
|
7958
|
-
"after-edit":
|
|
8924
|
+
"after-edit": defineCommand19({
|
|
7959
8925
|
meta: {
|
|
7960
8926
|
name: "after-edit",
|
|
7961
8927
|
description: "Advisory afterFileEdit: annotate HANDOFF/plan issues (never block)"
|
|
@@ -7965,7 +8931,7 @@ var validateCommand = defineCommand18({
|
|
|
7965
8931
|
},
|
|
7966
8932
|
async run({ args }) {
|
|
7967
8933
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7968
|
-
const resolved = await resolveEditedPath(
|
|
8934
|
+
const resolved = await resolveEditedPath(path47.resolve(cwd));
|
|
7969
8935
|
if (!resolved) {
|
|
7970
8936
|
console.log(JSON.stringify({}));
|
|
7971
8937
|
return;
|
|
@@ -8008,14 +8974,14 @@ var validateCommand = defineCommand18({
|
|
|
8008
8974
|
});
|
|
8009
8975
|
|
|
8010
8976
|
// src/welcome/help-groups.ts
|
|
8011
|
-
import { bold, gray as gray4, underline } from "kolorist";
|
|
8977
|
+
import { bold as bold2, gray as gray4, underline } from "kolorist";
|
|
8012
8978
|
|
|
8013
8979
|
// src/welcome/screen.ts
|
|
8014
8980
|
import {
|
|
8015
8981
|
blue as blue2,
|
|
8016
|
-
cyan as
|
|
8982
|
+
cyan as cyan6,
|
|
8017
8983
|
gray as gray3,
|
|
8018
|
-
options as
|
|
8984
|
+
options as koloristOptions3,
|
|
8019
8985
|
lightCyan,
|
|
8020
8986
|
trueColor,
|
|
8021
8987
|
white as white2
|
|
@@ -8044,15 +9010,15 @@ function outlineAnsi(line) {
|
|
|
8044
9010
|
return trueColor(r, g, b)(line);
|
|
8045
9011
|
}
|
|
8046
9012
|
function withKoloristColor(fn) {
|
|
8047
|
-
const prevEnabled =
|
|
8048
|
-
const prevLevel =
|
|
8049
|
-
|
|
8050
|
-
|
|
9013
|
+
const prevEnabled = koloristOptions3.enabled;
|
|
9014
|
+
const prevLevel = koloristOptions3.supportLevel;
|
|
9015
|
+
koloristOptions3.enabled = true;
|
|
9016
|
+
koloristOptions3.supportLevel = KOLORIST_TRUECOLOR;
|
|
8051
9017
|
try {
|
|
8052
9018
|
return fn();
|
|
8053
9019
|
} finally {
|
|
8054
|
-
|
|
8055
|
-
|
|
9020
|
+
koloristOptions3.enabled = prevEnabled;
|
|
9021
|
+
koloristOptions3.supportLevel = prevLevel;
|
|
8056
9022
|
}
|
|
8057
9023
|
}
|
|
8058
9024
|
function hasCliSubcommand(rawArgs) {
|
|
@@ -8079,7 +9045,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8079
9045
|
const version = opts.version ?? KIT_VERSION;
|
|
8080
9046
|
const color = shouldUseWelcomeColor(opts);
|
|
8081
9047
|
const title = color ? white2("Mission Kit") : "Mission Kit";
|
|
8082
|
-
const product = color ?
|
|
9048
|
+
const product = color ? cyan6("agent-kit") : "agent-kit";
|
|
8083
9049
|
const muted = (s) => color ? gray3(s) : s;
|
|
8084
9050
|
const lines = [
|
|
8085
9051
|
renderHelmetAscii(color),
|
|
@@ -8092,7 +9058,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8092
9058
|
const width = Math.max(...WELCOME_UTILITY_HINTS.map(({ cmd }) => cmd.length));
|
|
8093
9059
|
return WELCOME_UTILITY_HINTS.map(({ cmd, hint }) => {
|
|
8094
9060
|
const pad = " ".repeat(width - cmd.length + 2);
|
|
8095
|
-
const left = color ?
|
|
9061
|
+
const left = color ? cyan6(` ${cmd}`) : ` ${cmd}`;
|
|
8096
9062
|
return `${left}${pad}${muted(hint)}`;
|
|
8097
9063
|
});
|
|
8098
9064
|
})(),
|
|
@@ -8153,7 +9119,7 @@ async function resolveSubMeta(subCommands) {
|
|
|
8153
9119
|
}
|
|
8154
9120
|
async function renderGroupedRootHelp(cmd) {
|
|
8155
9121
|
const color = shouldUseWelcomeColor();
|
|
8156
|
-
const u = (s) => color ? underline(
|
|
9122
|
+
const u = (s) => color ? underline(bold2(s)) : s;
|
|
8157
9123
|
const g = (s) => color ? gray4(s) : s;
|
|
8158
9124
|
const meta = await resolveCommandMeta(cmd.meta);
|
|
8159
9125
|
const name = meta?.name ?? "agent-kit";
|
|
@@ -8198,7 +9164,7 @@ async function renderGroupedRootHelp(cmd) {
|
|
|
8198
9164
|
}
|
|
8199
9165
|
|
|
8200
9166
|
// src/index.ts
|
|
8201
|
-
var main =
|
|
9167
|
+
var main = defineCommand20({
|
|
8202
9168
|
meta: {
|
|
8203
9169
|
name: "agent-kit",
|
|
8204
9170
|
description: "HITL framework for AI-assisted IDEs (Mission Kit family)",
|
|
@@ -8210,6 +9176,7 @@ var main = defineCommand19({
|
|
|
8210
9176
|
scan: scanCommand,
|
|
8211
9177
|
add: addCommand,
|
|
8212
9178
|
doctor: doctorCommand,
|
|
9179
|
+
"setup-global": setupGlobalCommand,
|
|
8213
9180
|
status: statusCommand,
|
|
8214
9181
|
update: updateCommand,
|
|
8215
9182
|
"cursor-awareness": cursorAwarenessCommand,
|