@node9/proxy 1.10.1 → 1.10.3
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 +105 -4
- package/dist/cli.js +2300 -480
- package/dist/cli.mjs +2279 -458
- package/dist/index.js +52 -8
- package/dist/index.mjs +52 -8
- package/dist/shields/builtin/bash-safe.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
const lines = result.error.issues.map((issue) => {
|
|
171
|
-
const
|
|
172
|
-
return ` \u2022 ${
|
|
171
|
+
const path39 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path39}: ${issue.message}`;
|
|
173
173
|
});
|
|
174
174
|
return {
|
|
175
175
|
sanitized,
|
|
@@ -254,7 +254,8 @@ var init_config_schema = __esm({
|
|
|
254
254
|
enableTrustSessions: import_zod.z.boolean().optional(),
|
|
255
255
|
allowGlobalPause: import_zod.z.boolean().optional(),
|
|
256
256
|
auditHashArgs: import_zod.z.boolean().optional(),
|
|
257
|
-
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
|
|
257
|
+
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
258
|
+
cloudSyncIntervalHours: import_zod.z.number().positive().optional()
|
|
258
259
|
}).optional(),
|
|
259
260
|
policy: import_zod.z.object({
|
|
260
261
|
sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -514,11 +515,11 @@ function getGlobalSettings() {
|
|
|
514
515
|
};
|
|
515
516
|
}
|
|
516
517
|
function getCredentials() {
|
|
517
|
-
const
|
|
518
|
+
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
518
519
|
if (process.env.NODE9_API_KEY) {
|
|
519
520
|
return {
|
|
520
521
|
apiKey: process.env.NODE9_API_KEY,
|
|
521
|
-
apiUrl: process.env.NODE9_API_URL ||
|
|
522
|
+
apiUrl: process.env.NODE9_API_URL || DEFAULT_API_URL2
|
|
522
523
|
};
|
|
523
524
|
}
|
|
524
525
|
try {
|
|
@@ -530,13 +531,13 @@ function getCredentials() {
|
|
|
530
531
|
if (profile?.apiKey) {
|
|
531
532
|
return {
|
|
532
533
|
apiKey: profile.apiKey,
|
|
533
|
-
apiUrl: profile.apiUrl ||
|
|
534
|
+
apiUrl: profile.apiUrl || DEFAULT_API_URL2
|
|
534
535
|
};
|
|
535
536
|
}
|
|
536
537
|
if (creds.apiKey) {
|
|
537
538
|
return {
|
|
538
539
|
apiKey: creds.apiKey,
|
|
539
|
-
apiUrl: creds.apiUrl ||
|
|
540
|
+
apiUrl: creds.apiUrl || DEFAULT_API_URL2
|
|
540
541
|
};
|
|
541
542
|
}
|
|
542
543
|
}
|
|
@@ -587,6 +588,8 @@ function getConfig(cwd) {
|
|
|
587
588
|
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
588
589
|
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
589
590
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
591
|
+
if (s.cloudSyncIntervalHours !== void 0)
|
|
592
|
+
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
590
593
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
591
594
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
592
595
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -596,7 +599,12 @@ function getConfig(cwd) {
|
|
|
596
599
|
if (p.smartRules) {
|
|
597
600
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
598
601
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
599
|
-
|
|
602
|
+
const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
|
|
603
|
+
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
604
|
+
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
605
|
+
(r) => !r.name || !userRuleNames.has(r.name)
|
|
606
|
+
);
|
|
607
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
|
|
600
608
|
}
|
|
601
609
|
if (p.snapshot) {
|
|
602
610
|
const s2 = p.snapshot;
|
|
@@ -630,6 +638,16 @@ function getConfig(cwd) {
|
|
|
630
638
|
};
|
|
631
639
|
applyLayer(globalConfig);
|
|
632
640
|
applyLayer(projectConfig);
|
|
641
|
+
{
|
|
642
|
+
const cacheFile = import_path3.default.join(import_os3.default.homedir(), ".node9", "rules-cache.json");
|
|
643
|
+
try {
|
|
644
|
+
const raw = JSON.parse(import_fs3.default.readFileSync(cacheFile, "utf-8"));
|
|
645
|
+
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
646
|
+
applyLayer({ policy: { smartRules: raw.rules } });
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
633
651
|
const shieldOverrides = readShieldOverrides();
|
|
634
652
|
for (const shieldName of readActiveShields()) {
|
|
635
653
|
const shield = getShield(shieldName);
|
|
@@ -748,7 +766,8 @@ var init_config = __esm({
|
|
|
748
766
|
// 120-second auto-deny timeout
|
|
749
767
|
flightRecorder: true,
|
|
750
768
|
auditHashArgs: true,
|
|
751
|
-
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
769
|
+
approvers: { native: true, browser: true, cloud: false, terminal: true },
|
|
770
|
+
cloudSyncIntervalHours: 5
|
|
752
771
|
},
|
|
753
772
|
policy: {
|
|
754
773
|
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
@@ -1727,9 +1746,9 @@ function matchesPattern(text, patterns) {
|
|
|
1727
1746
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1728
1747
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1729
1748
|
}
|
|
1730
|
-
function getNestedValue(obj,
|
|
1749
|
+
function getNestedValue(obj, path39) {
|
|
1731
1750
|
if (!obj || typeof obj !== "object") return null;
|
|
1732
|
-
return
|
|
1751
|
+
return path39.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1733
1752
|
}
|
|
1734
1753
|
function shouldSnapshot(toolName, args, config) {
|
|
1735
1754
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2204,8 +2223,8 @@ async function explainPolicy(toolName, args) {
|
|
|
2204
2223
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
2205
2224
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
2206
2225
|
allTokens.push(...extraTokens);
|
|
2207
|
-
const
|
|
2208
|
-
detail += ` + deep scan of args: [${
|
|
2226
|
+
const preview2 = extraTokens.slice(0, 8).join(", ") + (extraTokens.length > 8 ? "\u2026" : "");
|
|
2227
|
+
detail += ` + deep scan of args: [${preview2}]`;
|
|
2209
2228
|
}
|
|
2210
2229
|
steps.push({ name: "Input parsing", outcome: "checked", detail });
|
|
2211
2230
|
}
|
|
@@ -2457,19 +2476,44 @@ function getInternalToken() {
|
|
|
2457
2476
|
function isDaemonRunning() {
|
|
2458
2477
|
const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
|
|
2459
2478
|
if (import_fs9.default.existsSync(pidFile)) {
|
|
2479
|
+
let pid;
|
|
2480
|
+
let port;
|
|
2460
2481
|
try {
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
return true;
|
|
2482
|
+
const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
2483
|
+
pid = data.pid;
|
|
2484
|
+
port = data.port;
|
|
2465
2485
|
} catch {
|
|
2466
2486
|
return false;
|
|
2467
2487
|
}
|
|
2488
|
+
if (port !== DAEMON_PORT) {
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
const MAX_PID2 = 4194304;
|
|
2492
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID2) {
|
|
2493
|
+
return false;
|
|
2494
|
+
}
|
|
2495
|
+
try {
|
|
2496
|
+
process.kill(pid, 0);
|
|
2497
|
+
} catch (err2) {
|
|
2498
|
+
if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
|
|
2499
|
+
try {
|
|
2500
|
+
import_fs9.default.unlinkSync(pidFile);
|
|
2501
|
+
} catch {
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
2507
|
+
encoding: "utf8",
|
|
2508
|
+
timeout: 300
|
|
2509
|
+
});
|
|
2510
|
+
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
|
|
2511
|
+
return false;
|
|
2468
2512
|
}
|
|
2469
2513
|
try {
|
|
2470
2514
|
const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
2471
2515
|
encoding: "utf8",
|
|
2472
|
-
timeout:
|
|
2516
|
+
timeout: 300
|
|
2473
2517
|
});
|
|
2474
2518
|
return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
|
|
2475
2519
|
} catch {
|
|
@@ -6241,9 +6285,146 @@ var init_costSync = __esm({
|
|
|
6241
6285
|
}
|
|
6242
6286
|
});
|
|
6243
6287
|
|
|
6288
|
+
// src/daemon/sync.ts
|
|
6289
|
+
function readCredentials() {
|
|
6290
|
+
if (process.env.NODE9_API_KEY) {
|
|
6291
|
+
return {
|
|
6292
|
+
apiKey: process.env.NODE9_API_KEY,
|
|
6293
|
+
apiUrl: process.env.NODE9_API_URL ?? DEFAULT_API_URL
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
6296
|
+
try {
|
|
6297
|
+
const credPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "credentials.json");
|
|
6298
|
+
const creds = JSON.parse(import_fs17.default.readFileSync(credPath, "utf-8"));
|
|
6299
|
+
const profileName = process.env.NODE9_PROFILE ?? "default";
|
|
6300
|
+
const profile = creds[profileName];
|
|
6301
|
+
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
6302
|
+
return {
|
|
6303
|
+
apiKey: profile.apiKey,
|
|
6304
|
+
apiUrl: typeof profile.apiUrl === "string" ? profile.apiUrl.replace(/\/intercept$/, "/policy") : DEFAULT_API_URL
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
6308
|
+
return { apiKey: creds.apiKey, apiUrl: DEFAULT_API_URL };
|
|
6309
|
+
}
|
|
6310
|
+
} catch {
|
|
6311
|
+
}
|
|
6312
|
+
return null;
|
|
6313
|
+
}
|
|
6314
|
+
function fetchCloudRules(apiKey, apiUrl) {
|
|
6315
|
+
const parsed = new URL(apiUrl);
|
|
6316
|
+
return new Promise((resolve, reject) => {
|
|
6317
|
+
const req = import_https.default.request(
|
|
6318
|
+
{
|
|
6319
|
+
hostname: parsed.hostname,
|
|
6320
|
+
port: parsed.port ? parseInt(parsed.port, 10) : void 0,
|
|
6321
|
+
path: parsed.pathname + parsed.search,
|
|
6322
|
+
method: "GET",
|
|
6323
|
+
headers: {
|
|
6324
|
+
Authorization: `Bearer ${apiKey}`,
|
|
6325
|
+
"Content-Type": "application/json"
|
|
6326
|
+
},
|
|
6327
|
+
timeout: 1e4
|
|
6328
|
+
},
|
|
6329
|
+
(res) => {
|
|
6330
|
+
const chunks = [];
|
|
6331
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
6332
|
+
res.on("end", () => {
|
|
6333
|
+
if (res.statusCode !== 200) {
|
|
6334
|
+
reject(new Error(`API returned ${res.statusCode ?? "unknown"}`));
|
|
6335
|
+
return;
|
|
6336
|
+
}
|
|
6337
|
+
try {
|
|
6338
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
6339
|
+
const rules = Array.isArray(body) ? body : Array.isArray(body.rules) ? body.rules : [];
|
|
6340
|
+
resolve(rules);
|
|
6341
|
+
} catch (e) {
|
|
6342
|
+
reject(e);
|
|
6343
|
+
}
|
|
6344
|
+
});
|
|
6345
|
+
}
|
|
6346
|
+
);
|
|
6347
|
+
req.on("error", reject);
|
|
6348
|
+
req.on("timeout", () => {
|
|
6349
|
+
req.destroy(new Error("Cloud policy fetch timed out"));
|
|
6350
|
+
});
|
|
6351
|
+
req.end();
|
|
6352
|
+
});
|
|
6353
|
+
}
|
|
6354
|
+
async function syncOnce() {
|
|
6355
|
+
const creds = readCredentials();
|
|
6356
|
+
if (!creds) return;
|
|
6357
|
+
try {
|
|
6358
|
+
const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
|
|
6359
|
+
const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
|
|
6360
|
+
const dir = import_path20.default.dirname(rulesCacheFile());
|
|
6361
|
+
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
6362
|
+
import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
6363
|
+
} catch {
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
async function runCloudSync() {
|
|
6367
|
+
const creds = readCredentials();
|
|
6368
|
+
if (!creds) {
|
|
6369
|
+
return { ok: false, reason: "No API key configured. Add credentials with: node9 login" };
|
|
6370
|
+
}
|
|
6371
|
+
try {
|
|
6372
|
+
const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
|
|
6373
|
+
const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
|
|
6374
|
+
const dir = import_path20.default.dirname(rulesCacheFile());
|
|
6375
|
+
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
6376
|
+
import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
6377
|
+
return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
|
|
6378
|
+
} catch (err2) {
|
|
6379
|
+
return { ok: false, reason: err2 instanceof Error ? err2.message : String(err2) };
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
function getCloudSyncStatus() {
|
|
6383
|
+
try {
|
|
6384
|
+
const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
|
|
6385
|
+
if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
|
|
6386
|
+
return { cached: true, rules: raw.rules.length, fetchedAt: raw.fetchedAt };
|
|
6387
|
+
} catch {
|
|
6388
|
+
return { cached: false };
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
function getCloudRules() {
|
|
6392
|
+
try {
|
|
6393
|
+
const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
|
|
6394
|
+
return Array.isArray(raw.rules) ? raw.rules : null;
|
|
6395
|
+
} catch {
|
|
6396
|
+
return null;
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
function startCloudSync() {
|
|
6400
|
+
const rawHours = getConfig().settings.cloudSyncIntervalHours ?? DEFAULT_INTERVAL_HOURS;
|
|
6401
|
+
const intervalHours = Math.max(rawHours, MIN_INTERVAL_HOURS);
|
|
6402
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
6403
|
+
const initial = setTimeout(() => void syncOnce(), 3e4);
|
|
6404
|
+
initial.unref();
|
|
6405
|
+
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
6406
|
+
recurring.unref();
|
|
6407
|
+
}
|
|
6408
|
+
var import_fs17, import_https, import_os15, import_path20, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
|
|
6409
|
+
var init_sync = __esm({
|
|
6410
|
+
"src/daemon/sync.ts"() {
|
|
6411
|
+
"use strict";
|
|
6412
|
+
import_fs17 = __toESM(require("fs"));
|
|
6413
|
+
import_https = __toESM(require("https"));
|
|
6414
|
+
import_os15 = __toESM(require("os"));
|
|
6415
|
+
import_path20 = __toESM(require("path"));
|
|
6416
|
+
init_config();
|
|
6417
|
+
rulesCacheFile = () => import_path20.default.join(import_os15.default.homedir(), ".node9", "rules-cache.json");
|
|
6418
|
+
DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
|
|
6419
|
+
DEFAULT_INTERVAL_HOURS = 5;
|
|
6420
|
+
MIN_INTERVAL_HOURS = 1;
|
|
6421
|
+
}
|
|
6422
|
+
});
|
|
6423
|
+
|
|
6244
6424
|
// src/daemon/server.ts
|
|
6245
6425
|
function startDaemon() {
|
|
6246
6426
|
startCostSync();
|
|
6427
|
+
startCloudSync();
|
|
6247
6428
|
loadInsightCounts();
|
|
6248
6429
|
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6249
6430
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -6259,7 +6440,7 @@ function startDaemon() {
|
|
|
6259
6440
|
idleTimer = setTimeout(() => {
|
|
6260
6441
|
if (autoStarted) {
|
|
6261
6442
|
try {
|
|
6262
|
-
|
|
6443
|
+
import_fs18.default.unlinkSync(DAEMON_PID_FILE);
|
|
6263
6444
|
} catch {
|
|
6264
6445
|
}
|
|
6265
6446
|
}
|
|
@@ -6422,7 +6603,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6422
6603
|
status: "pending"
|
|
6423
6604
|
});
|
|
6424
6605
|
}
|
|
6425
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6606
|
+
const projectCwd = typeof cwd === "string" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6426
6607
|
const projectConfig = getConfig(projectCwd);
|
|
6427
6608
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6428
6609
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6812,8 +6993,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6812
6993
|
const body = await readBody(req);
|
|
6813
6994
|
const data = body ? JSON.parse(body) : {};
|
|
6814
6995
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6815
|
-
const node9Dir =
|
|
6816
|
-
if (!
|
|
6996
|
+
const node9Dir = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6997
|
+
if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
|
|
6817
6998
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6818
6999
|
return res.end(
|
|
6819
7000
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6924,14 +7105,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6924
7105
|
server.on("error", (e) => {
|
|
6925
7106
|
if (e.code === "EADDRINUSE") {
|
|
6926
7107
|
try {
|
|
6927
|
-
if (
|
|
6928
|
-
const { pid } = JSON.parse(
|
|
7108
|
+
if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
|
|
7109
|
+
const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6929
7110
|
process.kill(pid, 0);
|
|
6930
7111
|
return process.exit(0);
|
|
6931
7112
|
}
|
|
6932
7113
|
} catch {
|
|
6933
7114
|
try {
|
|
6934
|
-
|
|
7115
|
+
import_fs18.default.unlinkSync(DAEMON_PID_FILE);
|
|
6935
7116
|
} catch {
|
|
6936
7117
|
}
|
|
6937
7118
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6990,13 +7171,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
6990
7171
|
}
|
|
6991
7172
|
startActivitySocket();
|
|
6992
7173
|
}
|
|
6993
|
-
var import_http,
|
|
7174
|
+
var import_http, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
|
|
6994
7175
|
var init_server = __esm({
|
|
6995
7176
|
"src/daemon/server.ts"() {
|
|
6996
7177
|
"use strict";
|
|
6997
7178
|
import_http = __toESM(require("http"));
|
|
6998
|
-
|
|
6999
|
-
|
|
7179
|
+
import_fs18 = __toESM(require("fs"));
|
|
7180
|
+
import_path21 = __toESM(require("path"));
|
|
7000
7181
|
import_crypto7 = require("crypto");
|
|
7001
7182
|
import_child_process4 = require("child_process");
|
|
7002
7183
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -7007,57 +7188,327 @@ var init_server = __esm({
|
|
|
7007
7188
|
init_patch();
|
|
7008
7189
|
init_config_schema();
|
|
7009
7190
|
init_costSync();
|
|
7191
|
+
init_sync();
|
|
7192
|
+
}
|
|
7193
|
+
});
|
|
7194
|
+
|
|
7195
|
+
// src/daemon/service.ts
|
|
7196
|
+
function resolveNode9Binary() {
|
|
7197
|
+
try {
|
|
7198
|
+
const script = process.argv[1];
|
|
7199
|
+
if (typeof script === "string" && import_path22.default.isAbsolute(script) && import_fs19.default.existsSync(script)) {
|
|
7200
|
+
return import_fs19.default.realpathSync(script);
|
|
7201
|
+
}
|
|
7202
|
+
} catch {
|
|
7203
|
+
}
|
|
7204
|
+
try {
|
|
7205
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
7206
|
+
const r = (0, import_child_process5.spawnSync)(cmd, ["node9"], { encoding: "utf8", timeout: 3e3 });
|
|
7207
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
7208
|
+
return r.stdout.trim().split("\n")[0].trim();
|
|
7209
|
+
}
|
|
7210
|
+
} catch {
|
|
7211
|
+
}
|
|
7212
|
+
return null;
|
|
7213
|
+
}
|
|
7214
|
+
function xmlEscape(s) {
|
|
7215
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7216
|
+
}
|
|
7217
|
+
function launchdPlist(binaryPath) {
|
|
7218
|
+
const logDir = import_path22.default.join(import_os16.default.homedir(), ".node9");
|
|
7219
|
+
const nodePath = xmlEscape(process.execPath);
|
|
7220
|
+
const scriptPath = xmlEscape(binaryPath);
|
|
7221
|
+
const outLog = xmlEscape(import_path22.default.join(logDir, "daemon.log"));
|
|
7222
|
+
const errLog = xmlEscape(import_path22.default.join(logDir, "daemon-error.log"));
|
|
7223
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7224
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7225
|
+
<plist version="1.0">
|
|
7226
|
+
<dict>
|
|
7227
|
+
<key>Label</key>
|
|
7228
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
7229
|
+
<key>ProgramArguments</key>
|
|
7230
|
+
<array>
|
|
7231
|
+
<string>${nodePath}</string>
|
|
7232
|
+
<string>${scriptPath}</string>
|
|
7233
|
+
<string>daemon</string>
|
|
7234
|
+
</array>
|
|
7235
|
+
<key>RunAtLoad</key>
|
|
7236
|
+
<true/>
|
|
7237
|
+
<key>KeepAlive</key>
|
|
7238
|
+
<true/>
|
|
7239
|
+
<key>ThrottleInterval</key>
|
|
7240
|
+
<integer>10</integer>
|
|
7241
|
+
<key>StandardOutPath</key>
|
|
7242
|
+
<string>${outLog}</string>
|
|
7243
|
+
<key>StandardErrorPath</key>
|
|
7244
|
+
<string>${errLog}</string>
|
|
7245
|
+
<key>EnvironmentVariables</key>
|
|
7246
|
+
<dict>
|
|
7247
|
+
<key>NODE9_AUTO_STARTED</key>
|
|
7248
|
+
<string>1</string>
|
|
7249
|
+
<key>NODE9_BROWSER_OPENED</key>
|
|
7250
|
+
<string>1</string>
|
|
7251
|
+
</dict>
|
|
7252
|
+
</dict>
|
|
7253
|
+
</plist>
|
|
7254
|
+
`;
|
|
7255
|
+
}
|
|
7256
|
+
function installLaunchd(binaryPath) {
|
|
7257
|
+
const dir = import_path22.default.dirname(LAUNCHD_PLIST);
|
|
7258
|
+
if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
|
|
7259
|
+
import_fs19.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
7260
|
+
(0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
7261
|
+
const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
7262
|
+
encoding: "utf8",
|
|
7263
|
+
timeout: 5e3
|
|
7264
|
+
});
|
|
7265
|
+
if (r.status !== 0) {
|
|
7266
|
+
throw new Error(`launchctl load failed: ${r.stderr || r.stdout || "unknown error"}`);
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
function uninstallLaunchd() {
|
|
7270
|
+
if (import_fs19.default.existsSync(LAUNCHD_PLIST)) {
|
|
7271
|
+
(0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
7272
|
+
import_fs19.default.unlinkSync(LAUNCHD_PLIST);
|
|
7273
|
+
}
|
|
7274
|
+
}
|
|
7275
|
+
function isLaunchdInstalled() {
|
|
7276
|
+
return import_fs19.default.existsSync(LAUNCHD_PLIST);
|
|
7277
|
+
}
|
|
7278
|
+
function systemdUnit(binaryPath) {
|
|
7279
|
+
return `[Unit]
|
|
7280
|
+
Description=node9 approval daemon
|
|
7281
|
+
After=network.target
|
|
7282
|
+
|
|
7283
|
+
[Service]
|
|
7284
|
+
Type=simple
|
|
7285
|
+
ExecStart=${process.execPath} ${binaryPath} daemon
|
|
7286
|
+
Restart=on-failure
|
|
7287
|
+
RestartSec=10s
|
|
7288
|
+
Environment=NODE9_AUTO_STARTED=1
|
|
7289
|
+
Environment=NODE9_BROWSER_OPENED=1
|
|
7290
|
+
|
|
7291
|
+
[Install]
|
|
7292
|
+
WantedBy=default.target
|
|
7293
|
+
`;
|
|
7294
|
+
}
|
|
7295
|
+
function installSystemd(binaryPath) {
|
|
7296
|
+
if (!import_fs19.default.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
7297
|
+
import_fs19.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
7298
|
+
}
|
|
7299
|
+
import_fs19.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
7300
|
+
try {
|
|
7301
|
+
(0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os16.default.userInfo().username], { timeout: 3e3 });
|
|
7302
|
+
} catch {
|
|
7303
|
+
}
|
|
7304
|
+
const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
|
|
7305
|
+
encoding: "utf8",
|
|
7306
|
+
timeout: 5e3
|
|
7307
|
+
});
|
|
7308
|
+
if (reload.status !== 0) {
|
|
7309
|
+
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
7310
|
+
}
|
|
7311
|
+
(0, import_child_process5.spawnSync)("systemctl", ["--user", "stop", "node9-daemon"], { encoding: "utf8", timeout: 3e3 });
|
|
7312
|
+
const enable = (0, import_child_process5.spawnSync)("systemctl", ["--user", "enable", "--now", "node9-daemon"], {
|
|
7313
|
+
encoding: "utf8",
|
|
7314
|
+
timeout: 5e3
|
|
7315
|
+
});
|
|
7316
|
+
if (enable.status !== 0) {
|
|
7317
|
+
throw new Error(`systemctl enable failed: ${enable.stderr}`);
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
function uninstallSystemd() {
|
|
7321
|
+
if (import_fs19.default.existsSync(SYSTEMD_UNIT)) {
|
|
7322
|
+
(0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
7323
|
+
encoding: "utf8",
|
|
7324
|
+
timeout: 5e3
|
|
7325
|
+
});
|
|
7326
|
+
(0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
7327
|
+
import_fs19.default.unlinkSync(SYSTEMD_UNIT);
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
function isSystemdInstalled() {
|
|
7331
|
+
return import_fs19.default.existsSync(SYSTEMD_UNIT);
|
|
7332
|
+
}
|
|
7333
|
+
function stopRunningDaemon() {
|
|
7334
|
+
const pidFile = import_path22.default.join(import_os16.default.homedir(), ".node9", "daemon.pid");
|
|
7335
|
+
if (!import_fs19.default.existsSync(pidFile)) return;
|
|
7336
|
+
try {
|
|
7337
|
+
const data = JSON.parse(import_fs19.default.readFileSync(pidFile, "utf-8"));
|
|
7338
|
+
const pid = data.pid;
|
|
7339
|
+
const MAX_PID2 = 4194304;
|
|
7340
|
+
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
7341
|
+
try {
|
|
7342
|
+
process.kill(pid, "SIGTERM");
|
|
7343
|
+
const deadline = Date.now() + 3e3;
|
|
7344
|
+
const pollStop = (0, import_child_process5.spawnSync)(
|
|
7345
|
+
"sh",
|
|
7346
|
+
["-c", `while kill -0 ${pid} 2>/dev/null; do sleep 0.1; done`],
|
|
7347
|
+
{
|
|
7348
|
+
timeout: 3100
|
|
7349
|
+
}
|
|
7350
|
+
);
|
|
7351
|
+
void pollStop;
|
|
7352
|
+
void deadline;
|
|
7353
|
+
} catch {
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
try {
|
|
7357
|
+
import_fs19.default.unlinkSync(pidFile);
|
|
7358
|
+
} catch {
|
|
7359
|
+
}
|
|
7360
|
+
} catch {
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
function installDaemonService() {
|
|
7364
|
+
const binary = resolveNode9Binary();
|
|
7365
|
+
if (!binary) {
|
|
7366
|
+
return { ok: false, reason: "Could not locate the node9 binary. Is it in your PATH?" };
|
|
7367
|
+
}
|
|
7368
|
+
stopRunningDaemon();
|
|
7369
|
+
try {
|
|
7370
|
+
if (process.platform === "darwin") {
|
|
7371
|
+
const alreadyInstalled = isLaunchdInstalled();
|
|
7372
|
+
installLaunchd(binary);
|
|
7373
|
+
return { ok: true, platform: "launchd", alreadyInstalled };
|
|
7374
|
+
}
|
|
7375
|
+
if (process.platform === "linux") {
|
|
7376
|
+
const check = (0, import_child_process5.spawnSync)("systemctl", ["--user", "--version"], {
|
|
7377
|
+
encoding: "utf8",
|
|
7378
|
+
timeout: 2e3
|
|
7379
|
+
});
|
|
7380
|
+
if (check.status !== 0) {
|
|
7381
|
+
return {
|
|
7382
|
+
ok: false,
|
|
7383
|
+
reason: "systemd not available. Start the daemon manually with: node9 daemon start"
|
|
7384
|
+
};
|
|
7385
|
+
}
|
|
7386
|
+
const alreadyInstalled = isSystemdInstalled();
|
|
7387
|
+
installSystemd(binary);
|
|
7388
|
+
return { ok: true, platform: "systemd", alreadyInstalled };
|
|
7389
|
+
}
|
|
7390
|
+
return {
|
|
7391
|
+
ok: false,
|
|
7392
|
+
reason: `Automatic service install is not supported on ${process.platform}. Start the daemon manually with: node9 daemon start`
|
|
7393
|
+
};
|
|
7394
|
+
} catch (err2) {
|
|
7395
|
+
return {
|
|
7396
|
+
ok: false,
|
|
7397
|
+
reason: err2 instanceof Error ? err2.message : String(err2)
|
|
7398
|
+
};
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
function uninstallDaemonService() {
|
|
7402
|
+
try {
|
|
7403
|
+
if (process.platform === "darwin") {
|
|
7404
|
+
uninstallLaunchd();
|
|
7405
|
+
return { ok: true, platform: "launchd", alreadyInstalled: false };
|
|
7406
|
+
}
|
|
7407
|
+
if (process.platform === "linux") {
|
|
7408
|
+
uninstallSystemd();
|
|
7409
|
+
return { ok: true, platform: "systemd", alreadyInstalled: false };
|
|
7410
|
+
}
|
|
7411
|
+
return {
|
|
7412
|
+
ok: false,
|
|
7413
|
+
reason: `Service management not supported on ${process.platform}.`
|
|
7414
|
+
};
|
|
7415
|
+
} catch (err2) {
|
|
7416
|
+
return {
|
|
7417
|
+
ok: false,
|
|
7418
|
+
reason: err2 instanceof Error ? err2.message : String(err2)
|
|
7419
|
+
};
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
function isDaemonServiceInstalled() {
|
|
7423
|
+
if (process.platform === "darwin") return isLaunchdInstalled();
|
|
7424
|
+
if (process.platform === "linux") return isSystemdInstalled();
|
|
7425
|
+
return false;
|
|
7426
|
+
}
|
|
7427
|
+
var import_fs19, import_path22, import_os16, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
|
|
7428
|
+
var init_service = __esm({
|
|
7429
|
+
"src/daemon/service.ts"() {
|
|
7430
|
+
"use strict";
|
|
7431
|
+
import_fs19 = __toESM(require("fs"));
|
|
7432
|
+
import_path22 = __toESM(require("path"));
|
|
7433
|
+
import_os16 = __toESM(require("os"));
|
|
7434
|
+
import_child_process5 = require("child_process");
|
|
7435
|
+
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
7436
|
+
LAUNCHD_PLIST = import_path22.default.join(import_os16.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
7437
|
+
SYSTEMD_UNIT_DIR = import_path22.default.join(import_os16.default.homedir(), ".config", "systemd", "user");
|
|
7438
|
+
SYSTEMD_UNIT = import_path22.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
7010
7439
|
}
|
|
7011
7440
|
});
|
|
7012
7441
|
|
|
7013
7442
|
// src/daemon/index.ts
|
|
7014
7443
|
function stopDaemon() {
|
|
7015
|
-
if (!
|
|
7444
|
+
if (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
7016
7445
|
try {
|
|
7017
|
-
const
|
|
7446
|
+
const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7447
|
+
const pid = data.pid;
|
|
7448
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
7449
|
+
console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7018
7452
|
process.kill(pid, "SIGTERM");
|
|
7019
7453
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
7020
7454
|
} catch {
|
|
7021
7455
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
7022
7456
|
} finally {
|
|
7023
7457
|
try {
|
|
7024
|
-
|
|
7458
|
+
import_fs20.default.unlinkSync(DAEMON_PID_FILE);
|
|
7025
7459
|
} catch {
|
|
7026
7460
|
}
|
|
7027
7461
|
}
|
|
7028
7462
|
}
|
|
7029
7463
|
function daemonStatus() {
|
|
7030
|
-
|
|
7464
|
+
const serviceInstalled = isDaemonServiceInstalled();
|
|
7465
|
+
const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
|
|
7466
|
+
let processStatus;
|
|
7467
|
+
if (import_fs20.default.existsSync(DAEMON_PID_FILE)) {
|
|
7031
7468
|
try {
|
|
7032
|
-
const
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7469
|
+
const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7470
|
+
const pid = data.pid;
|
|
7471
|
+
const port = data.port;
|
|
7472
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
7473
|
+
processStatus = import_chalk3.default.yellow("not running (invalid PID file)");
|
|
7474
|
+
} else {
|
|
7475
|
+
process.kill(pid, 0);
|
|
7476
|
+
processStatus = import_chalk3.default.green(
|
|
7477
|
+
`running (PID ${pid}, port ${typeof port === "number" ? port : DAEMON_PORT})`
|
|
7478
|
+
);
|
|
7479
|
+
}
|
|
7036
7480
|
} catch {
|
|
7037
|
-
|
|
7038
|
-
return;
|
|
7481
|
+
processStatus = import_chalk3.default.yellow("not running (stale PID file)");
|
|
7039
7482
|
}
|
|
7040
|
-
}
|
|
7041
|
-
const r = (0, import_child_process5.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
7042
|
-
encoding: "utf8",
|
|
7043
|
-
timeout: 500
|
|
7044
|
-
});
|
|
7045
|
-
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
|
|
7046
|
-
console.log(import_chalk3.default.yellow("Node9 daemon: running (no PID file \u2014 orphaned)"));
|
|
7047
7483
|
} else {
|
|
7048
|
-
|
|
7484
|
+
const r = (0, import_child_process6.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
7485
|
+
encoding: "utf8",
|
|
7486
|
+
timeout: 500
|
|
7487
|
+
});
|
|
7488
|
+
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
|
|
7489
|
+
processStatus = import_chalk3.default.yellow(`running (orphaned \u2014 no PID file)`);
|
|
7490
|
+
} else {
|
|
7491
|
+
processStatus = import_chalk3.default.yellow("not running");
|
|
7492
|
+
}
|
|
7049
7493
|
}
|
|
7494
|
+
console.log(`
|
|
7495
|
+
Process : ${processStatus}`);
|
|
7496
|
+
console.log(` Service : ${serviceLabel}
|
|
7497
|
+
`);
|
|
7050
7498
|
}
|
|
7051
|
-
var
|
|
7499
|
+
var import_fs20, import_chalk3, import_child_process6, MAX_PID;
|
|
7052
7500
|
var init_daemon2 = __esm({
|
|
7053
7501
|
"src/daemon/index.ts"() {
|
|
7054
7502
|
"use strict";
|
|
7055
|
-
|
|
7503
|
+
import_fs20 = __toESM(require("fs"));
|
|
7056
7504
|
import_chalk3 = __toESM(require("chalk"));
|
|
7057
|
-
|
|
7505
|
+
import_child_process6 = require("child_process");
|
|
7058
7506
|
init_server();
|
|
7059
7507
|
init_state2();
|
|
7508
|
+
init_service();
|
|
7060
7509
|
init_state2();
|
|
7510
|
+
init_service();
|
|
7511
|
+
MAX_PID = 4194304;
|
|
7061
7512
|
}
|
|
7062
7513
|
});
|
|
7063
7514
|
|
|
@@ -7086,22 +7537,22 @@ function formatBase(activity) {
|
|
|
7086
7537
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7087
7538
|
const icon = getIcon(activity.tool);
|
|
7088
7539
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7089
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7540
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os29.default.homedir(), "~");
|
|
7090
7541
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7091
|
-
return `${
|
|
7542
|
+
return `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
|
|
7092
7543
|
}
|
|
7093
7544
|
function renderResult(activity, result) {
|
|
7094
7545
|
const base = formatBase(activity);
|
|
7095
7546
|
let status;
|
|
7096
7547
|
if (result.status === "allow") {
|
|
7097
|
-
status =
|
|
7548
|
+
status = import_chalk23.default.green("\u2713 ALLOW");
|
|
7098
7549
|
} else if (result.status === "dlp") {
|
|
7099
|
-
status =
|
|
7550
|
+
status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7100
7551
|
} else {
|
|
7101
|
-
status =
|
|
7552
|
+
status = import_chalk23.default.red("\u2717 BLOCK");
|
|
7102
7553
|
}
|
|
7103
7554
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7104
|
-
const costSuffix = cost == null ? "" :
|
|
7555
|
+
const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7105
7556
|
if (process.stdout.isTTY) {
|
|
7106
7557
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7107
7558
|
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7118,19 +7569,19 @@ function renderResult(activity, result) {
|
|
|
7118
7569
|
}
|
|
7119
7570
|
function renderPending(activity) {
|
|
7120
7571
|
if (!process.stdout.isTTY) return;
|
|
7121
|
-
const line = `${formatBase(activity)} ${
|
|
7572
|
+
const line = `${formatBase(activity)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
|
|
7122
7573
|
pendingShownForId = activity.id;
|
|
7123
7574
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7124
7575
|
process.stdout.write(`${line}\r`);
|
|
7125
7576
|
}
|
|
7126
7577
|
async function ensureDaemon() {
|
|
7127
7578
|
let pidPort = null;
|
|
7128
|
-
if (
|
|
7579
|
+
if (import_fs33.default.existsSync(PID_FILE)) {
|
|
7129
7580
|
try {
|
|
7130
|
-
const { port } = JSON.parse(
|
|
7581
|
+
const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
|
|
7131
7582
|
pidPort = port;
|
|
7132
7583
|
} catch {
|
|
7133
|
-
console.error(
|
|
7584
|
+
console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7134
7585
|
}
|
|
7135
7586
|
}
|
|
7136
7587
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7141,8 +7592,8 @@ async function ensureDaemon() {
|
|
|
7141
7592
|
if (res.ok) return checkPort;
|
|
7142
7593
|
} catch {
|
|
7143
7594
|
}
|
|
7144
|
-
console.log(
|
|
7145
|
-
const child = (0,
|
|
7595
|
+
console.log(import_chalk23.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7596
|
+
const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
7146
7597
|
detached: true,
|
|
7147
7598
|
stdio: "ignore",
|
|
7148
7599
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -7158,7 +7609,7 @@ async function ensureDaemon() {
|
|
|
7158
7609
|
} catch {
|
|
7159
7610
|
}
|
|
7160
7611
|
}
|
|
7161
|
-
console.error(
|
|
7612
|
+
console.error(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7162
7613
|
process.exit(1);
|
|
7163
7614
|
}
|
|
7164
7615
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7190,27 +7641,56 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
|
7190
7641
|
req.end(body);
|
|
7191
7642
|
});
|
|
7192
7643
|
}
|
|
7644
|
+
function extractArgsSummary(toolName, args) {
|
|
7645
|
+
const a = args;
|
|
7646
|
+
if (!a) return "";
|
|
7647
|
+
const cmd = a["command"] ?? a["cmd"];
|
|
7648
|
+
if (typeof cmd === "string") return cmd.replace(/\s+/g, " ").trim();
|
|
7649
|
+
const fp = a["file_path"] ?? a["path"] ?? a["filepath"];
|
|
7650
|
+
if (typeof fp === "string") return fp;
|
|
7651
|
+
const q = a["query"] ?? a["sql"];
|
|
7652
|
+
if (typeof q === "string") return q.replace(/\s+/g, " ").trim();
|
|
7653
|
+
const s = JSON.stringify(a).replace(/\s+/g, " ");
|
|
7654
|
+
return s.length > 80 ? s.slice(0, 80) + "\u2026" : s;
|
|
7655
|
+
}
|
|
7656
|
+
function cleanReason(raw) {
|
|
7657
|
+
return raw.replace(/\s*[—–-]+\s*(blocked by|requires human approval)[^)]*(\([^)]*\))?\.?\s*$/i, "").trim();
|
|
7658
|
+
}
|
|
7659
|
+
function cleanBlockedBy(raw) {
|
|
7660
|
+
const shieldMatch = raw.match(/shield:([^:]+):/i);
|
|
7661
|
+
if (shieldMatch) return shieldMatch[1];
|
|
7662
|
+
const smartMatch = raw.match(/^Smart Rule:\s*(.+)$/i);
|
|
7663
|
+
if (smartMatch) return smartMatch[1];
|
|
7664
|
+
return raw;
|
|
7665
|
+
}
|
|
7193
7666
|
function buildCardLines(req, localCount = 0) {
|
|
7194
7667
|
if (req.recoveryCommand) {
|
|
7195
7668
|
return buildRecoveryCardLines(req);
|
|
7196
7669
|
}
|
|
7197
|
-
const
|
|
7198
|
-
const argsPreview =
|
|
7199
|
-
const
|
|
7200
|
-
const blockedBy =
|
|
7670
|
+
const argsSummary = extractArgsSummary(req.toolName, req.args);
|
|
7671
|
+
const argsPreview = argsSummary.length > 72 ? argsSummary.slice(0, 72) + "\u2026" : argsSummary;
|
|
7672
|
+
const rawBlockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
|
|
7673
|
+
const blockedBy = cleanBlockedBy(rawBlockedBy);
|
|
7674
|
+
const isBlock = req.riskMetadata?.tier != null && req.riskMetadata.tier <= 2;
|
|
7675
|
+
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
7676
|
+
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
7677
|
+
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
7201
7678
|
const lines = [
|
|
7202
7679
|
``,
|
|
7203
7680
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
7204
7681
|
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
7205
|
-
`${CYAN}\u2551${RESET2}
|
|
7682
|
+
`${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
|
|
7206
7683
|
];
|
|
7207
|
-
if (
|
|
7208
|
-
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}
|
|
7684
|
+
if (description) {
|
|
7685
|
+
lines.push(`${CYAN}\u2551${RESET2} Why: ${YELLOW}${description}${RESET2}`);
|
|
7209
7686
|
}
|
|
7210
|
-
if (req.riskMetadata?.ruleName &&
|
|
7687
|
+
if (req.riskMetadata?.ruleName && rawBlockedBy.includes("Taint")) {
|
|
7211
7688
|
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
7212
7689
|
}
|
|
7213
|
-
|
|
7690
|
+
if (argsPreview) {
|
|
7691
|
+
const argLabel = req.toolName.toLowerCase().includes("bash") ? "Command" : "Args ";
|
|
7692
|
+
lines.push(`${CYAN}\u2551${RESET2} ${argLabel}: ${GRAY}${argsPreview}${RESET2}`);
|
|
7693
|
+
}
|
|
7214
7694
|
if (localCount >= 2) {
|
|
7215
7695
|
lines.push(
|
|
7216
7696
|
`${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
|
|
@@ -7250,9 +7730,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7250
7730
|
];
|
|
7251
7731
|
}
|
|
7252
7732
|
function readApproversFromDisk() {
|
|
7253
|
-
const configPath =
|
|
7733
|
+
const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
|
|
7254
7734
|
try {
|
|
7255
|
-
const raw = JSON.parse(
|
|
7735
|
+
const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
|
|
7256
7736
|
const settings = raw.settings ?? {};
|
|
7257
7737
|
return settings.approvers ?? {};
|
|
7258
7738
|
} catch {
|
|
@@ -7263,20 +7743,20 @@ function approverStatusLine() {
|
|
|
7263
7743
|
const a = readApproversFromDisk();
|
|
7264
7744
|
const fmt = (label, key) => {
|
|
7265
7745
|
const on = a[key] !== false;
|
|
7266
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
7746
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk23.default.green("\u2713") : import_chalk23.default.dim("\u2717")}`;
|
|
7267
7747
|
};
|
|
7268
7748
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7269
7749
|
}
|
|
7270
7750
|
function toggleApprover(channel) {
|
|
7271
|
-
const configPath =
|
|
7751
|
+
const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
|
|
7272
7752
|
try {
|
|
7273
|
-
const raw = JSON.parse(
|
|
7753
|
+
const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
|
|
7274
7754
|
const settings = raw.settings ?? {};
|
|
7275
7755
|
const approvers = settings.approvers ?? {};
|
|
7276
7756
|
approvers[channel] = approvers[channel] === false;
|
|
7277
7757
|
settings.approvers = approvers;
|
|
7278
7758
|
raw.settings = settings;
|
|
7279
|
-
|
|
7759
|
+
import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7280
7760
|
} catch (err2) {
|
|
7281
7761
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7282
7762
|
`);
|
|
@@ -7308,7 +7788,7 @@ async function startTail(options = {}) {
|
|
|
7308
7788
|
req2.end();
|
|
7309
7789
|
});
|
|
7310
7790
|
if (result.ok) {
|
|
7311
|
-
console.log(
|
|
7791
|
+
console.log(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7312
7792
|
} else if (result.code === "ECONNREFUSED") {
|
|
7313
7793
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7314
7794
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7352,7 +7832,7 @@ async function startTail(options = {}) {
|
|
|
7352
7832
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7353
7833
|
if (channel) {
|
|
7354
7834
|
toggleApprover(channel);
|
|
7355
|
-
console.log(
|
|
7835
|
+
console.log(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7356
7836
|
}
|
|
7357
7837
|
};
|
|
7358
7838
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7418,7 +7898,7 @@ async function startTail(options = {}) {
|
|
|
7418
7898
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7419
7899
|
)
|
|
7420
7900
|
);
|
|
7421
|
-
const decisionStamp = action === "always-allow" ?
|
|
7901
|
+
const decisionStamp = action === "always-allow" ? import_chalk23.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk23.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk23.default.yellow("\u21A9 REDIRECT AI") : import_chalk23.default.red("\u2717 DENIED");
|
|
7422
7902
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7423
7903
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7424
7904
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7446,8 +7926,8 @@ async function startTail(options = {}) {
|
|
|
7446
7926
|
}
|
|
7447
7927
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7448
7928
|
try {
|
|
7449
|
-
|
|
7450
|
-
|
|
7929
|
+
import_fs33.default.appendFileSync(
|
|
7930
|
+
import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
|
|
7451
7931
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7452
7932
|
`
|
|
7453
7933
|
);
|
|
@@ -7469,7 +7949,7 @@ async function startTail(options = {}) {
|
|
|
7469
7949
|
);
|
|
7470
7950
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7471
7951
|
if (externalDecision) {
|
|
7472
|
-
const source = externalDecision === "allow" ?
|
|
7952
|
+
const source = externalDecision === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
|
|
7473
7953
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7474
7954
|
}
|
|
7475
7955
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7515,10 +7995,10 @@ async function startTail(options = {}) {
|
|
|
7515
7995
|
try {
|
|
7516
7996
|
const browserEnabled = getConfig().settings.approvers?.browser !== false;
|
|
7517
7997
|
if (browserEnabled) {
|
|
7518
|
-
if (process.platform === "darwin") (0,
|
|
7998
|
+
if (process.platform === "darwin") (0, import_child_process15.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7519
7999
|
else if (process.platform === "win32")
|
|
7520
|
-
(0,
|
|
7521
|
-
else (0,
|
|
8000
|
+
(0, import_child_process15.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
|
|
8001
|
+
else (0, import_child_process15.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7522
8002
|
const intToken = getInternalToken();
|
|
7523
8003
|
fetch(`http://127.0.0.1:${port}/browser-opened`, {
|
|
7524
8004
|
method: "POST",
|
|
@@ -7528,16 +8008,16 @@ async function startTail(options = {}) {
|
|
|
7528
8008
|
}
|
|
7529
8009
|
} catch {
|
|
7530
8010
|
}
|
|
7531
|
-
console.log(
|
|
7532
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
8011
|
+
console.log(import_chalk23.default.cyan.bold(`
|
|
8012
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk23.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7533
8013
|
if (canApprove) {
|
|
7534
|
-
console.log(
|
|
7535
|
-
console.log(
|
|
8014
|
+
console.log(import_chalk23.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8015
|
+
console.log(import_chalk23.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7536
8016
|
}
|
|
7537
8017
|
if (options.history) {
|
|
7538
|
-
console.log(
|
|
8018
|
+
console.log(import_chalk23.default.dim("Showing history + live events.\n"));
|
|
7539
8019
|
} else {
|
|
7540
|
-
console.log(
|
|
8020
|
+
console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7541
8021
|
}
|
|
7542
8022
|
process.on("SIGINT", () => {
|
|
7543
8023
|
exitIdleMode();
|
|
@@ -7547,13 +8027,13 @@ async function startTail(options = {}) {
|
|
|
7547
8027
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7548
8028
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7549
8029
|
}
|
|
7550
|
-
console.log(
|
|
8030
|
+
console.log(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7551
8031
|
process.exit(0);
|
|
7552
8032
|
});
|
|
7553
8033
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7554
8034
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7555
8035
|
if (res.statusCode !== 200) {
|
|
7556
|
-
console.error(
|
|
8036
|
+
console.error(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7557
8037
|
process.exit(1);
|
|
7558
8038
|
}
|
|
7559
8039
|
if (canApprove) enterIdleMode();
|
|
@@ -7584,7 +8064,7 @@ async function startTail(options = {}) {
|
|
|
7584
8064
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7585
8065
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7586
8066
|
}
|
|
7587
|
-
console.log(
|
|
8067
|
+
console.log(import_chalk23.default.red("\n\u274C Daemon disconnected."));
|
|
7588
8068
|
process.exit(1);
|
|
7589
8069
|
});
|
|
7590
8070
|
});
|
|
@@ -7676,9 +8156,9 @@ async function startTail(options = {}) {
|
|
|
7676
8156
|
const hash = data.hash ?? "";
|
|
7677
8157
|
const summary = data.argsSummary ?? data.tool;
|
|
7678
8158
|
const fileCount = data.fileCount ?? 0;
|
|
7679
|
-
const files = fileCount > 0 ?
|
|
8159
|
+
const files = fileCount > 0 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7680
8160
|
process.stdout.write(
|
|
7681
|
-
`${
|
|
8161
|
+
`${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
|
|
7682
8162
|
`
|
|
7683
8163
|
);
|
|
7684
8164
|
return;
|
|
@@ -7695,26 +8175,26 @@ async function startTail(options = {}) {
|
|
|
7695
8175
|
}
|
|
7696
8176
|
req.on("error", (err2) => {
|
|
7697
8177
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7698
|
-
console.error(
|
|
8178
|
+
console.error(import_chalk23.default.red(`
|
|
7699
8179
|
\u274C ${msg}`));
|
|
7700
8180
|
process.exit(1);
|
|
7701
8181
|
});
|
|
7702
8182
|
}
|
|
7703
|
-
var import_http2,
|
|
8183
|
+
var import_http2, import_chalk23, import_fs33, import_os29, import_path36, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
7704
8184
|
var init_tail = __esm({
|
|
7705
8185
|
"src/tui/tail.ts"() {
|
|
7706
8186
|
"use strict";
|
|
7707
8187
|
import_http2 = __toESM(require("http"));
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
8188
|
+
import_chalk23 = __toESM(require("chalk"));
|
|
8189
|
+
import_fs33 = __toESM(require("fs"));
|
|
8190
|
+
import_os29 = __toESM(require("os"));
|
|
8191
|
+
import_path36 = __toESM(require("path"));
|
|
7712
8192
|
import_readline5 = __toESM(require("readline"));
|
|
7713
|
-
|
|
8193
|
+
import_child_process15 = require("child_process");
|
|
7714
8194
|
init_daemon2();
|
|
7715
8195
|
init_daemon();
|
|
7716
8196
|
init_core();
|
|
7717
|
-
PID_FILE =
|
|
8197
|
+
PID_FILE = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
|
|
7718
8198
|
ICONS = {
|
|
7719
8199
|
bash: "\u{1F4BB}",
|
|
7720
8200
|
shell: "\u{1F4BB}",
|
|
@@ -7829,9 +8309,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7829
8309
|
return ` (${m}m left)`;
|
|
7830
8310
|
}
|
|
7831
8311
|
function safeReadJson(filePath) {
|
|
7832
|
-
if (!
|
|
8312
|
+
if (!import_fs34.default.existsSync(filePath)) return null;
|
|
7833
8313
|
try {
|
|
7834
|
-
return JSON.parse(
|
|
8314
|
+
return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
|
|
7835
8315
|
} catch {
|
|
7836
8316
|
return null;
|
|
7837
8317
|
}
|
|
@@ -7852,12 +8332,12 @@ function countHooksInFile(filePath) {
|
|
|
7852
8332
|
return Object.keys(cfg.hooks).length;
|
|
7853
8333
|
}
|
|
7854
8334
|
function countRulesInDir(rulesDir) {
|
|
7855
|
-
if (!
|
|
8335
|
+
if (!import_fs34.default.existsSync(rulesDir)) return 0;
|
|
7856
8336
|
let count = 0;
|
|
7857
8337
|
try {
|
|
7858
|
-
for (const entry of
|
|
8338
|
+
for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7859
8339
|
if (entry.isDirectory()) {
|
|
7860
|
-
count += countRulesInDir(
|
|
8340
|
+
count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
|
|
7861
8341
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7862
8342
|
count++;
|
|
7863
8343
|
}
|
|
@@ -7868,46 +8348,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7868
8348
|
}
|
|
7869
8349
|
function isSamePath(a, b) {
|
|
7870
8350
|
try {
|
|
7871
|
-
return
|
|
8351
|
+
return import_path37.default.resolve(a) === import_path37.default.resolve(b);
|
|
7872
8352
|
} catch {
|
|
7873
8353
|
return false;
|
|
7874
8354
|
}
|
|
7875
8355
|
}
|
|
7876
8356
|
function countConfigs(cwd) {
|
|
7877
|
-
const homeDir2 =
|
|
7878
|
-
const claudeDir =
|
|
8357
|
+
const homeDir2 = import_os30.default.homedir();
|
|
8358
|
+
const claudeDir = import_path37.default.join(homeDir2, ".claude");
|
|
7879
8359
|
let claudeMdCount = 0;
|
|
7880
8360
|
let rulesCount = 0;
|
|
7881
8361
|
let hooksCount = 0;
|
|
7882
8362
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7883
8363
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7884
|
-
if (
|
|
7885
|
-
rulesCount += countRulesInDir(
|
|
7886
|
-
const userSettings =
|
|
8364
|
+
if (import_fs34.default.existsSync(import_path37.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8365
|
+
rulesCount += countRulesInDir(import_path37.default.join(claudeDir, "rules"));
|
|
8366
|
+
const userSettings = import_path37.default.join(claudeDir, "settings.json");
|
|
7887
8367
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7888
8368
|
hooksCount += countHooksInFile(userSettings);
|
|
7889
|
-
const userClaudeJson =
|
|
8369
|
+
const userClaudeJson = import_path37.default.join(homeDir2, ".claude.json");
|
|
7890
8370
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7891
8371
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7892
8372
|
userMcpServers.delete(name);
|
|
7893
8373
|
}
|
|
7894
8374
|
if (cwd) {
|
|
7895
|
-
if (
|
|
7896
|
-
if (
|
|
7897
|
-
const projectClaudeDir =
|
|
8375
|
+
if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8376
|
+
if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8377
|
+
const projectClaudeDir = import_path37.default.join(cwd, ".claude");
|
|
7898
8378
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7899
8379
|
if (!overlapsUserScope) {
|
|
7900
|
-
if (
|
|
7901
|
-
rulesCount += countRulesInDir(
|
|
7902
|
-
const projSettings =
|
|
8380
|
+
if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8381
|
+
rulesCount += countRulesInDir(import_path37.default.join(projectClaudeDir, "rules"));
|
|
8382
|
+
const projSettings = import_path37.default.join(projectClaudeDir, "settings.json");
|
|
7903
8383
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7904
8384
|
hooksCount += countHooksInFile(projSettings);
|
|
7905
8385
|
}
|
|
7906
|
-
if (
|
|
7907
|
-
const localSettings =
|
|
8386
|
+
if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8387
|
+
const localSettings = import_path37.default.join(projectClaudeDir, "settings.local.json");
|
|
7908
8388
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7909
8389
|
hooksCount += countHooksInFile(localSettings);
|
|
7910
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8390
|
+
const mcpJsonServers = getMcpServerNames(import_path37.default.join(cwd, ".mcp.json"));
|
|
7911
8391
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7912
8392
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7913
8393
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7940,12 +8420,12 @@ function readActiveShieldsHud() {
|
|
|
7940
8420
|
return shieldsCache.value;
|
|
7941
8421
|
}
|
|
7942
8422
|
try {
|
|
7943
|
-
const shieldsPath =
|
|
7944
|
-
if (!
|
|
8423
|
+
const shieldsPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
|
|
8424
|
+
if (!import_fs34.default.existsSync(shieldsPath)) {
|
|
7945
8425
|
shieldsCache = { value: [], ts: now };
|
|
7946
8426
|
return [];
|
|
7947
8427
|
}
|
|
7948
|
-
const parsed = JSON.parse(
|
|
8428
|
+
const parsed = JSON.parse(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
|
|
7949
8429
|
if (!Array.isArray(parsed.active)) {
|
|
7950
8430
|
shieldsCache = { value: [], ts: now };
|
|
7951
8431
|
return [];
|
|
@@ -8047,17 +8527,17 @@ function renderContextLine(stdin) {
|
|
|
8047
8527
|
async function main() {
|
|
8048
8528
|
try {
|
|
8049
8529
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8050
|
-
if (
|
|
8530
|
+
if (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
|
|
8051
8531
|
try {
|
|
8052
|
-
const logPath =
|
|
8532
|
+
const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
|
|
8053
8533
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8054
8534
|
let size = 0;
|
|
8055
8535
|
try {
|
|
8056
|
-
size =
|
|
8536
|
+
size = import_fs34.default.statSync(logPath).size;
|
|
8057
8537
|
} catch {
|
|
8058
8538
|
}
|
|
8059
8539
|
if (size < MAX_LOG_SIZE) {
|
|
8060
|
-
|
|
8540
|
+
import_fs34.default.appendFileSync(
|
|
8061
8541
|
logPath,
|
|
8062
8542
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8063
8543
|
);
|
|
@@ -8078,11 +8558,11 @@ async function main() {
|
|
|
8078
8558
|
try {
|
|
8079
8559
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8080
8560
|
for (const configPath of [
|
|
8081
|
-
|
|
8082
|
-
|
|
8561
|
+
import_path37.default.join(cwd, "node9.config.json"),
|
|
8562
|
+
import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
|
|
8083
8563
|
]) {
|
|
8084
|
-
if (!
|
|
8085
|
-
const cfg = JSON.parse(
|
|
8564
|
+
if (!import_fs34.default.existsSync(configPath)) continue;
|
|
8565
|
+
const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
|
|
8086
8566
|
const hud = cfg.settings?.hud;
|
|
8087
8567
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8088
8568
|
}
|
|
@@ -8100,13 +8580,13 @@ async function main() {
|
|
|
8100
8580
|
renderOffline();
|
|
8101
8581
|
}
|
|
8102
8582
|
}
|
|
8103
|
-
var
|
|
8583
|
+
var import_fs34, import_path37, import_os30, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
8104
8584
|
var init_hud = __esm({
|
|
8105
8585
|
"src/cli/hud.ts"() {
|
|
8106
8586
|
"use strict";
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8587
|
+
import_fs34 = __toESM(require("fs"));
|
|
8588
|
+
import_path37 = __toESM(require("path"));
|
|
8589
|
+
import_os30 = __toESM(require("os"));
|
|
8110
8590
|
import_http3 = __toESM(require("http"));
|
|
8111
8591
|
init_daemon();
|
|
8112
8592
|
RESET3 = "\x1B[0m";
|
|
@@ -8517,7 +8997,9 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
|
8517
8997
|
claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
|
|
8518
8998
|
gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
|
|
8519
8999
|
cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
|
|
8520
|
-
codex: exists(import_path15.default.join(homeDir2, ".codex"))
|
|
9000
|
+
codex: exists(import_path15.default.join(homeDir2, ".codex")),
|
|
9001
|
+
windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
|
|
9002
|
+
vscode: exists(import_path15.default.join(homeDir2, ".vscode"))
|
|
8521
9003
|
};
|
|
8522
9004
|
}
|
|
8523
9005
|
async function setupCursor() {
|
|
@@ -8666,6 +9148,38 @@ async function setupCodex() {
|
|
|
8666
9148
|
printDaemonTip();
|
|
8667
9149
|
}
|
|
8668
9150
|
}
|
|
9151
|
+
function teardownCodex() {
|
|
9152
|
+
const homeDir2 = import_os11.default.homedir();
|
|
9153
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
9154
|
+
const config = readToml(configPath);
|
|
9155
|
+
if (!config?.mcp_servers) {
|
|
9156
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
|
|
9157
|
+
return;
|
|
9158
|
+
}
|
|
9159
|
+
let changed = false;
|
|
9160
|
+
if (removeNode9McpServer(config.mcp_servers)) {
|
|
9161
|
+
changed = true;
|
|
9162
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codex/config.toml"));
|
|
9163
|
+
}
|
|
9164
|
+
for (const [name, server] of Object.entries(config.mcp_servers)) {
|
|
9165
|
+
const args = server.args;
|
|
9166
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
9167
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
9168
|
+
config.mcp_servers[name] = {
|
|
9169
|
+
...server,
|
|
9170
|
+
command: originalCmd,
|
|
9171
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
9172
|
+
};
|
|
9173
|
+
changed = true;
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
9176
|
+
if (changed) {
|
|
9177
|
+
writeToml(configPath, config);
|
|
9178
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
|
|
9179
|
+
} else {
|
|
9180
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
8669
9183
|
function setupHud() {
|
|
8670
9184
|
const homeDir2 = import_os11.default.homedir();
|
|
8671
9185
|
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
@@ -8712,38 +9226,310 @@ function teardownHud() {
|
|
|
8712
9226
|
console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
|
|
8713
9227
|
console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
|
|
8714
9228
|
}
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
if (!m) return null;
|
|
8728
|
-
const n = parseFloat(m[1]);
|
|
8729
|
-
switch ((m[2] ?? "m").toLowerCase()) {
|
|
8730
|
-
case "s":
|
|
8731
|
-
return Math.round(n * 1e3);
|
|
8732
|
-
case "m":
|
|
8733
|
-
return Math.round(n * 6e4);
|
|
8734
|
-
case "h":
|
|
8735
|
-
return Math.round(n * 36e5);
|
|
8736
|
-
case "d":
|
|
8737
|
-
return Math.round(n * 864e5);
|
|
8738
|
-
default:
|
|
8739
|
-
return null;
|
|
9229
|
+
async function setupWindsurf() {
|
|
9230
|
+
const homeDir2 = import_os11.default.homedir();
|
|
9231
|
+
const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
|
|
9232
|
+
const mcpConfig = readJson(mcpPath) ?? {};
|
|
9233
|
+
const servers = mcpConfig.mcpServers ?? {};
|
|
9234
|
+
let anythingChanged = false;
|
|
9235
|
+
if (!hasNode9McpServer(servers)) {
|
|
9236
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
9237
|
+
mcpConfig.mcpServers = servers;
|
|
9238
|
+
writeJson(mcpPath, mcpConfig);
|
|
9239
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
9240
|
+
anythingChanged = true;
|
|
8740
9241
|
}
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
9242
|
+
const serversToWrap = [];
|
|
9243
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
9244
|
+
if (!server.command || server.command === "node9") continue;
|
|
9245
|
+
serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
|
|
9246
|
+
}
|
|
9247
|
+
if (serversToWrap.length > 0) {
|
|
9248
|
+
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
9249
|
+
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
9250
|
+
for (const { name, upstream } of serversToWrap) {
|
|
9251
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
9252
|
+
}
|
|
9253
|
+
console.log("");
|
|
9254
|
+
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
9255
|
+
if (proceed) {
|
|
9256
|
+
for (const { name, upstream } of serversToWrap) {
|
|
9257
|
+
servers[name] = {
|
|
9258
|
+
...servers[name],
|
|
9259
|
+
command: "node9",
|
|
9260
|
+
args: ["mcp", "--upstream", upstream]
|
|
9261
|
+
};
|
|
9262
|
+
}
|
|
9263
|
+
mcpConfig.mcpServers = servers;
|
|
9264
|
+
writeJson(mcpPath, mcpConfig);
|
|
9265
|
+
console.log(import_chalk.default.green(`
|
|
9266
|
+
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
9267
|
+
anythingChanged = true;
|
|
9268
|
+
} else {
|
|
9269
|
+
console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
|
|
9270
|
+
}
|
|
9271
|
+
console.log("");
|
|
9272
|
+
}
|
|
9273
|
+
console.log(
|
|
9274
|
+
import_chalk.default.yellow(
|
|
9275
|
+
" \u26A0\uFE0F Note: Windsurf does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Windsurf."
|
|
9276
|
+
)
|
|
9277
|
+
);
|
|
9278
|
+
console.log("");
|
|
9279
|
+
if (!anythingChanged && serversToWrap.length === 0) {
|
|
9280
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Windsurf."));
|
|
9281
|
+
printDaemonTip();
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
if (anythingChanged) {
|
|
9285
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Windsurf via MCP proxy!"));
|
|
9286
|
+
console.log(import_chalk.default.gray(" Restart Windsurf for changes to take effect."));
|
|
9287
|
+
printDaemonTip();
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
function teardownWindsurf() {
|
|
9291
|
+
const homeDir2 = import_os11.default.homedir();
|
|
9292
|
+
const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
|
|
9293
|
+
const mcpConfig = readJson(mcpPath);
|
|
9294
|
+
if (!mcpConfig?.mcpServers) {
|
|
9295
|
+
console.log(
|
|
9296
|
+
import_chalk.default.blue(" \u2139\uFE0F ~/.codeium/windsurf/mcp_config.json not found \u2014 nothing to remove")
|
|
9297
|
+
);
|
|
9298
|
+
return;
|
|
9299
|
+
}
|
|
9300
|
+
let changed = false;
|
|
9301
|
+
if (removeNode9McpServer(mcpConfig.mcpServers)) {
|
|
9302
|
+
changed = true;
|
|
9303
|
+
console.log(
|
|
9304
|
+
import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codeium/windsurf/mcp_config.json")
|
|
9305
|
+
);
|
|
9306
|
+
}
|
|
9307
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
9308
|
+
const args = server.args;
|
|
9309
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
9310
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
9311
|
+
mcpConfig.mcpServers[name] = {
|
|
9312
|
+
...server,
|
|
9313
|
+
command: originalCmd,
|
|
9314
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
9315
|
+
};
|
|
9316
|
+
changed = true;
|
|
9317
|
+
}
|
|
9318
|
+
}
|
|
9319
|
+
if (changed) {
|
|
9320
|
+
writeJson(mcpPath, mcpConfig);
|
|
9321
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codeium/windsurf/mcp_config.json"));
|
|
9322
|
+
} else {
|
|
9323
|
+
console.log(
|
|
9324
|
+
import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codeium/windsurf/mcp_config.json")
|
|
9325
|
+
);
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
function hasNode9McpServerVSCode(servers) {
|
|
9329
|
+
const entry = servers["node9"];
|
|
9330
|
+
return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
|
|
9331
|
+
}
|
|
9332
|
+
async function setupVSCode() {
|
|
9333
|
+
const homeDir2 = import_os11.default.homedir();
|
|
9334
|
+
const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
|
|
9335
|
+
const mcpConfig = readJson(mcpPath) ?? {};
|
|
9336
|
+
const servers = mcpConfig.servers ?? {};
|
|
9337
|
+
let anythingChanged = false;
|
|
9338
|
+
if (!hasNode9McpServerVSCode(servers)) {
|
|
9339
|
+
servers["node9"] = { type: "stdio", command: "node9", args: ["mcp-server"] };
|
|
9340
|
+
mcpConfig.servers = servers;
|
|
9341
|
+
writeJson(mcpPath, mcpConfig);
|
|
9342
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
9343
|
+
anythingChanged = true;
|
|
9344
|
+
}
|
|
9345
|
+
const serversToWrap = [];
|
|
9346
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
9347
|
+
if (!server.command || server.command === "node9") continue;
|
|
9348
|
+
serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
|
|
9349
|
+
}
|
|
9350
|
+
if (serversToWrap.length > 0) {
|
|
9351
|
+
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
9352
|
+
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
9353
|
+
for (const { name, upstream } of serversToWrap) {
|
|
9354
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
9355
|
+
}
|
|
9356
|
+
console.log("");
|
|
9357
|
+
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
9358
|
+
if (proceed) {
|
|
9359
|
+
for (const { name, upstream } of serversToWrap) {
|
|
9360
|
+
servers[name] = {
|
|
9361
|
+
...servers[name],
|
|
9362
|
+
type: "stdio",
|
|
9363
|
+
command: "node9",
|
|
9364
|
+
args: ["mcp", "--upstream", upstream]
|
|
9365
|
+
};
|
|
9366
|
+
}
|
|
9367
|
+
mcpConfig.servers = servers;
|
|
9368
|
+
writeJson(mcpPath, mcpConfig);
|
|
9369
|
+
console.log(import_chalk.default.green(`
|
|
9370
|
+
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
9371
|
+
anythingChanged = true;
|
|
9372
|
+
} else {
|
|
9373
|
+
console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
|
|
9374
|
+
}
|
|
9375
|
+
console.log("");
|
|
9376
|
+
}
|
|
9377
|
+
console.log(
|
|
9378
|
+
import_chalk.default.yellow(
|
|
9379
|
+
" \u26A0\uFE0F Note: VSCode MCP support requires the GitHub Copilot extension (v1.99+).\n Pre-execution hooks are not supported \u2014 MCP proxy wrapping only."
|
|
9380
|
+
)
|
|
9381
|
+
);
|
|
9382
|
+
console.log("");
|
|
9383
|
+
if (!anythingChanged && serversToWrap.length === 0) {
|
|
9384
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for VSCode."));
|
|
9385
|
+
printDaemonTip();
|
|
9386
|
+
return;
|
|
9387
|
+
}
|
|
9388
|
+
if (anythingChanged) {
|
|
9389
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting VSCode via MCP proxy!"));
|
|
9390
|
+
console.log(import_chalk.default.gray(" Restart VSCode for changes to take effect."));
|
|
9391
|
+
printDaemonTip();
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
function teardownVSCode() {
|
|
9395
|
+
const homeDir2 = import_os11.default.homedir();
|
|
9396
|
+
const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
|
|
9397
|
+
const mcpConfig = readJson(mcpPath);
|
|
9398
|
+
if (!mcpConfig?.servers) {
|
|
9399
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.vscode/mcp.json not found \u2014 nothing to remove"));
|
|
9400
|
+
return;
|
|
9401
|
+
}
|
|
9402
|
+
let changed = false;
|
|
9403
|
+
if (hasNode9McpServerVSCode(mcpConfig.servers)) {
|
|
9404
|
+
delete mcpConfig.servers["node9"];
|
|
9405
|
+
changed = true;
|
|
9406
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.vscode/mcp.json"));
|
|
9407
|
+
}
|
|
9408
|
+
for (const [name, server] of Object.entries(mcpConfig.servers)) {
|
|
9409
|
+
const args = server.args;
|
|
9410
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
9411
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
9412
|
+
mcpConfig.servers[name] = {
|
|
9413
|
+
...server,
|
|
9414
|
+
type: "stdio",
|
|
9415
|
+
command: originalCmd,
|
|
9416
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
9417
|
+
};
|
|
9418
|
+
changed = true;
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
if (changed) {
|
|
9422
|
+
writeJson(mcpPath, mcpConfig);
|
|
9423
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.vscode/mcp.json"));
|
|
9424
|
+
} else {
|
|
9425
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.vscode/mcp.json"));
|
|
9426
|
+
}
|
|
9427
|
+
}
|
|
9428
|
+
function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
|
|
9429
|
+
const detected = detectAgents(homeDir2);
|
|
9430
|
+
const claudeWired = (() => {
|
|
9431
|
+
const settings = readJson(import_path15.default.join(homeDir2, ".claude", "settings.json"));
|
|
9432
|
+
return !!settings?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
|
|
9433
|
+
})();
|
|
9434
|
+
const geminiWired = (() => {
|
|
9435
|
+
const settings = readJson(import_path15.default.join(homeDir2, ".gemini", "settings.json"));
|
|
9436
|
+
return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
|
|
9437
|
+
})();
|
|
9438
|
+
const cursorWired = (() => {
|
|
9439
|
+
const cfg = readJson(import_path15.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
9440
|
+
return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
|
|
9441
|
+
})();
|
|
9442
|
+
const codexWired = (() => {
|
|
9443
|
+
const cfg = readToml(import_path15.default.join(homeDir2, ".codex", "config.toml"));
|
|
9444
|
+
return !!(cfg?.mcp_servers && hasNode9McpServer(cfg.mcp_servers));
|
|
9445
|
+
})();
|
|
9446
|
+
const windsurfWired = (() => {
|
|
9447
|
+
const cfg = readJson(
|
|
9448
|
+
import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json")
|
|
9449
|
+
);
|
|
9450
|
+
return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
|
|
9451
|
+
})();
|
|
9452
|
+
const vscodeWired = (() => {
|
|
9453
|
+
const cfg = readJson(import_path15.default.join(homeDir2, ".vscode", "mcp.json"));
|
|
9454
|
+
return !!(cfg?.servers && hasNode9McpServerVSCode(cfg.servers));
|
|
9455
|
+
})();
|
|
9456
|
+
return [
|
|
9457
|
+
{
|
|
9458
|
+
name: "claude",
|
|
9459
|
+
label: "Claude Code",
|
|
9460
|
+
installed: detected.claude,
|
|
9461
|
+
wired: claudeWired,
|
|
9462
|
+
mode: detected.claude ? "hooks" : null
|
|
9463
|
+
},
|
|
9464
|
+
{
|
|
9465
|
+
name: "gemini",
|
|
9466
|
+
label: "Gemini CLI",
|
|
9467
|
+
installed: detected.gemini,
|
|
9468
|
+
wired: geminiWired,
|
|
9469
|
+
mode: detected.gemini ? "hooks" : null
|
|
9470
|
+
},
|
|
9471
|
+
{
|
|
9472
|
+
name: "cursor",
|
|
9473
|
+
label: "Cursor",
|
|
9474
|
+
installed: detected.cursor,
|
|
9475
|
+
wired: cursorWired,
|
|
9476
|
+
mode: detected.cursor ? "mcp" : null
|
|
9477
|
+
},
|
|
9478
|
+
{
|
|
9479
|
+
name: "windsurf",
|
|
9480
|
+
label: "Windsurf",
|
|
9481
|
+
installed: detected.windsurf,
|
|
9482
|
+
wired: windsurfWired,
|
|
9483
|
+
mode: detected.windsurf ? "mcp" : null
|
|
9484
|
+
},
|
|
9485
|
+
{
|
|
9486
|
+
name: "vscode",
|
|
9487
|
+
label: "VSCode",
|
|
9488
|
+
installed: detected.vscode,
|
|
9489
|
+
wired: vscodeWired,
|
|
9490
|
+
mode: detected.vscode ? "mcp" : null
|
|
9491
|
+
},
|
|
9492
|
+
{
|
|
9493
|
+
name: "codex",
|
|
9494
|
+
label: "Codex",
|
|
9495
|
+
installed: detected.codex,
|
|
9496
|
+
wired: codexWired,
|
|
9497
|
+
mode: detected.codex ? "mcp" : null
|
|
9498
|
+
}
|
|
9499
|
+
];
|
|
9500
|
+
}
|
|
9501
|
+
|
|
9502
|
+
// src/cli.ts
|
|
9503
|
+
init_daemon2();
|
|
9504
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
9505
|
+
var import_fs35 = __toESM(require("fs"));
|
|
9506
|
+
var import_path38 = __toESM(require("path"));
|
|
9507
|
+
var import_os31 = __toESM(require("os"));
|
|
9508
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
9509
|
+
|
|
9510
|
+
// src/utils/duration.ts
|
|
9511
|
+
function parseDuration(str) {
|
|
9512
|
+
const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
9513
|
+
if (!m) return null;
|
|
9514
|
+
const n = parseFloat(m[1]);
|
|
9515
|
+
switch ((m[2] ?? "m").toLowerCase()) {
|
|
9516
|
+
case "s":
|
|
9517
|
+
return Math.round(n * 1e3);
|
|
9518
|
+
case "m":
|
|
9519
|
+
return Math.round(n * 6e4);
|
|
9520
|
+
case "h":
|
|
9521
|
+
return Math.round(n * 36e5);
|
|
9522
|
+
case "d":
|
|
9523
|
+
return Math.round(n * 864e5);
|
|
9524
|
+
default:
|
|
9525
|
+
return null;
|
|
9526
|
+
}
|
|
9527
|
+
}
|
|
9528
|
+
|
|
9529
|
+
// src/proxy/index.ts
|
|
9530
|
+
var import_readline = __toESM(require("readline"));
|
|
8745
9531
|
var import_chalk4 = __toESM(require("chalk"));
|
|
8746
|
-
var
|
|
9532
|
+
var import_child_process7 = require("child_process");
|
|
8747
9533
|
var import_execa = require("execa");
|
|
8748
9534
|
var import_execa2 = require("execa");
|
|
8749
9535
|
init_orchestrator();
|
|
@@ -8831,11 +9617,11 @@ async function runProxy(targetCommand) {
|
|
|
8831
9617
|
}
|
|
8832
9618
|
console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
8833
9619
|
const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
|
|
8834
|
-
const child = useShell ? (0,
|
|
9620
|
+
const child = useShell ? (0, import_child_process7.spawn)("/bin/bash", ["-c", targetCommand], {
|
|
8835
9621
|
stdio: ["pipe", "pipe", "inherit"],
|
|
8836
9622
|
shell: false,
|
|
8837
9623
|
env: spawnEnv
|
|
8838
|
-
}) : (0,
|
|
9624
|
+
}) : (0, import_child_process7.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
|
|
8839
9625
|
const agentIn = import_readline.default.createInterface({ input: process.stdin, terminal: false });
|
|
8840
9626
|
agentIn.on("line", async (line) => {
|
|
8841
9627
|
let message;
|
|
@@ -8899,22 +9685,22 @@ async function runProxy(targetCommand) {
|
|
|
8899
9685
|
}
|
|
8900
9686
|
|
|
8901
9687
|
// src/cli/daemon-starter.ts
|
|
8902
|
-
var
|
|
9688
|
+
var import_child_process8 = require("child_process");
|
|
8903
9689
|
init_daemon();
|
|
8904
9690
|
function openBrowserLocal() {
|
|
8905
9691
|
const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
|
|
8906
9692
|
try {
|
|
8907
9693
|
const opts = { stdio: "ignore" };
|
|
8908
|
-
if (process.platform === "darwin") (0,
|
|
8909
|
-
else if (process.platform === "win32") (0,
|
|
8910
|
-
else (0,
|
|
9694
|
+
if (process.platform === "darwin") (0, import_child_process8.execSync)(`open "${url}"`, opts);
|
|
9695
|
+
else if (process.platform === "win32") (0, import_child_process8.execSync)(`cmd /c start "" "${url}"`, opts);
|
|
9696
|
+
else (0, import_child_process8.execSync)(`xdg-open "${url}"`, opts);
|
|
8911
9697
|
} catch {
|
|
8912
9698
|
}
|
|
8913
9699
|
}
|
|
8914
9700
|
async function autoStartDaemonAndWait() {
|
|
8915
9701
|
if (process.env.NODE9_TESTING === "1") return false;
|
|
8916
9702
|
try {
|
|
8917
|
-
const child = (0,
|
|
9703
|
+
const child = (0, import_child_process8.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
8918
9704
|
detached: true,
|
|
8919
9705
|
stdio: "ignore",
|
|
8920
9706
|
// NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
|
|
@@ -8943,23 +9729,23 @@ async function autoStartDaemonAndWait() {
|
|
|
8943
9729
|
|
|
8944
9730
|
// src/cli/commands/check.ts
|
|
8945
9731
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8946
|
-
var
|
|
8947
|
-
var
|
|
8948
|
-
var
|
|
8949
|
-
var
|
|
9732
|
+
var import_fs22 = __toESM(require("fs"));
|
|
9733
|
+
var import_child_process10 = require("child_process");
|
|
9734
|
+
var import_path24 = __toESM(require("path"));
|
|
9735
|
+
var import_os18 = __toESM(require("os"));
|
|
8950
9736
|
init_orchestrator();
|
|
8951
9737
|
init_daemon();
|
|
8952
9738
|
init_config();
|
|
8953
9739
|
init_policy();
|
|
8954
9740
|
|
|
8955
9741
|
// src/undo.ts
|
|
8956
|
-
var
|
|
9742
|
+
var import_child_process9 = require("child_process");
|
|
8957
9743
|
var import_crypto8 = __toESM(require("crypto"));
|
|
8958
|
-
var
|
|
9744
|
+
var import_fs21 = __toESM(require("fs"));
|
|
8959
9745
|
var import_net3 = __toESM(require("net"));
|
|
8960
|
-
var
|
|
8961
|
-
var
|
|
8962
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
9746
|
+
var import_path23 = __toESM(require("path"));
|
|
9747
|
+
var import_os17 = __toESM(require("os"));
|
|
9748
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path23.default.join(import_os17.default.tmpdir(), "node9-activity.sock");
|
|
8963
9749
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8964
9750
|
try {
|
|
8965
9751
|
const payload = JSON.stringify({
|
|
@@ -8979,22 +9765,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8979
9765
|
} catch {
|
|
8980
9766
|
}
|
|
8981
9767
|
}
|
|
8982
|
-
var SNAPSHOT_STACK_PATH =
|
|
8983
|
-
var UNDO_LATEST_PATH =
|
|
9768
|
+
var SNAPSHOT_STACK_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots.json");
|
|
9769
|
+
var UNDO_LATEST_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "undo_latest.txt");
|
|
8984
9770
|
var MAX_SNAPSHOTS = 10;
|
|
8985
9771
|
var GIT_TIMEOUT = 15e3;
|
|
8986
9772
|
function readStack() {
|
|
8987
9773
|
try {
|
|
8988
|
-
if (
|
|
8989
|
-
return JSON.parse(
|
|
9774
|
+
if (import_fs21.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
9775
|
+
return JSON.parse(import_fs21.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
8990
9776
|
} catch {
|
|
8991
9777
|
}
|
|
8992
9778
|
return [];
|
|
8993
9779
|
}
|
|
8994
9780
|
function writeStack(stack) {
|
|
8995
|
-
const dir =
|
|
8996
|
-
if (!
|
|
8997
|
-
|
|
9781
|
+
const dir = import_path23.default.dirname(SNAPSHOT_STACK_PATH);
|
|
9782
|
+
if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
|
|
9783
|
+
import_fs21.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8998
9784
|
}
|
|
8999
9785
|
function extractFilePath(args) {
|
|
9000
9786
|
if (!args || typeof args !== "object") return null;
|
|
@@ -9014,12 +9800,12 @@ function buildArgsSummary(tool, args) {
|
|
|
9014
9800
|
return "";
|
|
9015
9801
|
}
|
|
9016
9802
|
function findProjectRoot(filePath) {
|
|
9017
|
-
let dir =
|
|
9803
|
+
let dir = import_path23.default.dirname(filePath);
|
|
9018
9804
|
while (true) {
|
|
9019
|
-
if (
|
|
9805
|
+
if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
|
|
9020
9806
|
return dir;
|
|
9021
9807
|
}
|
|
9022
|
-
const parent =
|
|
9808
|
+
const parent = import_path23.default.dirname(dir);
|
|
9023
9809
|
if (parent === dir) return process.cwd();
|
|
9024
9810
|
dir = parent;
|
|
9025
9811
|
}
|
|
@@ -9027,7 +9813,7 @@ function findProjectRoot(filePath) {
|
|
|
9027
9813
|
function normalizeCwdForHash(cwd) {
|
|
9028
9814
|
let normalized;
|
|
9029
9815
|
try {
|
|
9030
|
-
normalized =
|
|
9816
|
+
normalized = import_fs21.default.realpathSync(cwd);
|
|
9031
9817
|
} catch {
|
|
9032
9818
|
normalized = cwd;
|
|
9033
9819
|
}
|
|
@@ -9037,16 +9823,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
9037
9823
|
}
|
|
9038
9824
|
function getShadowRepoDir(cwd) {
|
|
9039
9825
|
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9040
|
-
return
|
|
9826
|
+
return import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
|
|
9041
9827
|
}
|
|
9042
9828
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
9043
9829
|
try {
|
|
9044
9830
|
const cutoff = Date.now() - 6e4;
|
|
9045
|
-
for (const f of
|
|
9831
|
+
for (const f of import_fs21.default.readdirSync(shadowDir)) {
|
|
9046
9832
|
if (f.startsWith("index_")) {
|
|
9047
|
-
const fp =
|
|
9833
|
+
const fp = import_path23.default.join(shadowDir, f);
|
|
9048
9834
|
try {
|
|
9049
|
-
if (
|
|
9835
|
+
if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
|
|
9050
9836
|
} catch {
|
|
9051
9837
|
}
|
|
9052
9838
|
}
|
|
@@ -9058,7 +9844,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
9058
9844
|
const hardcoded = [".git", ".node9"];
|
|
9059
9845
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
9060
9846
|
try {
|
|
9061
|
-
|
|
9847
|
+
import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
9062
9848
|
} catch {
|
|
9063
9849
|
}
|
|
9064
9850
|
}
|
|
@@ -9066,54 +9852,54 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9066
9852
|
cleanOrphanedIndexFiles(shadowDir);
|
|
9067
9853
|
const normalizedCwd = normalizeCwdForHash(cwd);
|
|
9068
9854
|
const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
9069
|
-
const check = (0,
|
|
9855
|
+
const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
9070
9856
|
env: shadowEnvBase,
|
|
9071
9857
|
timeout: 3e3
|
|
9072
9858
|
});
|
|
9073
9859
|
if (check.status === 0) {
|
|
9074
|
-
const ptPath =
|
|
9860
|
+
const ptPath = import_path23.default.join(shadowDir, "project-path.txt");
|
|
9075
9861
|
try {
|
|
9076
|
-
const stored =
|
|
9862
|
+
const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
|
|
9077
9863
|
if (stored === normalizedCwd) return true;
|
|
9078
9864
|
if (process.env.NODE9_DEBUG === "1")
|
|
9079
9865
|
console.error(
|
|
9080
9866
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
9081
9867
|
);
|
|
9082
|
-
|
|
9868
|
+
import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
9083
9869
|
} catch {
|
|
9084
9870
|
try {
|
|
9085
|
-
|
|
9871
|
+
import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
9086
9872
|
} catch {
|
|
9087
9873
|
}
|
|
9088
9874
|
return true;
|
|
9089
9875
|
}
|
|
9090
9876
|
}
|
|
9091
9877
|
try {
|
|
9092
|
-
|
|
9878
|
+
import_fs21.default.mkdirSync(shadowDir, { recursive: true });
|
|
9093
9879
|
} catch {
|
|
9094
9880
|
}
|
|
9095
|
-
const init = (0,
|
|
9881
|
+
const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
9096
9882
|
if (init.status !== 0 || init.error) {
|
|
9097
9883
|
const reason = init.error ? init.error.message : init.stderr?.toString();
|
|
9098
9884
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
9099
9885
|
return false;
|
|
9100
9886
|
}
|
|
9101
|
-
const configFile =
|
|
9102
|
-
(0,
|
|
9887
|
+
const configFile = import_path23.default.join(shadowDir, "config");
|
|
9888
|
+
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
9103
9889
|
timeout: 3e3
|
|
9104
9890
|
});
|
|
9105
|
-
(0,
|
|
9891
|
+
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
|
|
9106
9892
|
timeout: 3e3
|
|
9107
9893
|
});
|
|
9108
9894
|
try {
|
|
9109
|
-
|
|
9895
|
+
import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
9110
9896
|
} catch {
|
|
9111
9897
|
}
|
|
9112
9898
|
return true;
|
|
9113
9899
|
}
|
|
9114
9900
|
function buildGitEnv(cwd) {
|
|
9115
9901
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9116
|
-
const check = (0,
|
|
9902
|
+
const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
9117
9903
|
env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
|
|
9118
9904
|
timeout: 2e3
|
|
9119
9905
|
});
|
|
@@ -9126,23 +9912,23 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9126
9912
|
let indexFile = null;
|
|
9127
9913
|
try {
|
|
9128
9914
|
const rawFilePath = extractFilePath(args);
|
|
9129
|
-
const absFilePath = rawFilePath &&
|
|
9915
|
+
const absFilePath = rawFilePath && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
9130
9916
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
9131
9917
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9132
9918
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
9133
9919
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
9134
|
-
indexFile =
|
|
9920
|
+
indexFile = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
9135
9921
|
const shadowEnv = {
|
|
9136
9922
|
...process.env,
|
|
9137
9923
|
GIT_DIR: shadowDir,
|
|
9138
9924
|
GIT_WORK_TREE: cwd,
|
|
9139
9925
|
GIT_INDEX_FILE: indexFile
|
|
9140
9926
|
};
|
|
9141
|
-
(0,
|
|
9142
|
-
const treeRes = (0,
|
|
9927
|
+
(0, import_child_process9.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
9928
|
+
const treeRes = (0, import_child_process9.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
9143
9929
|
const treeHash = treeRes.stdout?.toString().trim();
|
|
9144
9930
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
9145
|
-
const commitRes = (0,
|
|
9931
|
+
const commitRes = (0, import_child_process9.spawnSync)(
|
|
9146
9932
|
"git",
|
|
9147
9933
|
["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
|
|
9148
9934
|
{ env: shadowEnv, timeout: GIT_TIMEOUT }
|
|
@@ -9154,7 +9940,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9154
9940
|
let capturedFiles = [];
|
|
9155
9941
|
let capturedDiff = null;
|
|
9156
9942
|
if (prevEntry) {
|
|
9157
|
-
const filesRes = (0,
|
|
9943
|
+
const filesRes = (0, import_child_process9.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
|
|
9158
9944
|
env: shadowEnv,
|
|
9159
9945
|
timeout: GIT_TIMEOUT
|
|
9160
9946
|
});
|
|
@@ -9164,7 +9950,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9164
9950
|
if (capturedFiles.length === 0) {
|
|
9165
9951
|
return prevEntry.hash;
|
|
9166
9952
|
}
|
|
9167
|
-
const diffRes = (0,
|
|
9953
|
+
const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
|
|
9168
9954
|
env: shadowEnv,
|
|
9169
9955
|
timeout: GIT_TIMEOUT
|
|
9170
9956
|
});
|
|
@@ -9172,7 +9958,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9172
9958
|
capturedDiff = diffRes.stdout?.toString() || null;
|
|
9173
9959
|
}
|
|
9174
9960
|
} else {
|
|
9175
|
-
const filesRes = (0,
|
|
9961
|
+
const filesRes = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
|
|
9176
9962
|
env: shadowEnv,
|
|
9177
9963
|
timeout: GIT_TIMEOUT
|
|
9178
9964
|
});
|
|
@@ -9203,9 +9989,9 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9203
9989
|
writeStack(stack);
|
|
9204
9990
|
const entry = stack[stack.length - 1];
|
|
9205
9991
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
9206
|
-
|
|
9992
|
+
import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
9207
9993
|
if (shouldGc) {
|
|
9208
|
-
(0,
|
|
9994
|
+
(0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
9209
9995
|
}
|
|
9210
9996
|
return commitHash;
|
|
9211
9997
|
} catch (err2) {
|
|
@@ -9214,7 +10000,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9214
10000
|
} finally {
|
|
9215
10001
|
if (indexFile) {
|
|
9216
10002
|
try {
|
|
9217
|
-
|
|
10003
|
+
import_fs21.default.unlinkSync(indexFile);
|
|
9218
10004
|
} catch {
|
|
9219
10005
|
}
|
|
9220
10006
|
}
|
|
@@ -9226,14 +10012,14 @@ function getSnapshotHistory() {
|
|
|
9226
10012
|
function computeUndoDiff(hash, cwd) {
|
|
9227
10013
|
try {
|
|
9228
10014
|
const env = buildGitEnv(cwd);
|
|
9229
|
-
const statRes = (0,
|
|
10015
|
+
const statRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
|
|
9230
10016
|
cwd,
|
|
9231
10017
|
env,
|
|
9232
10018
|
timeout: GIT_TIMEOUT
|
|
9233
10019
|
});
|
|
9234
10020
|
const stat = statRes.stdout?.toString().trim();
|
|
9235
10021
|
if (!stat || statRes.status !== 0) return null;
|
|
9236
|
-
const diffRes = (0,
|
|
10022
|
+
const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--", "."], {
|
|
9237
10023
|
cwd,
|
|
9238
10024
|
env,
|
|
9239
10025
|
timeout: GIT_TIMEOUT
|
|
@@ -9252,7 +10038,7 @@ function applyUndo(hash, cwd) {
|
|
|
9252
10038
|
try {
|
|
9253
10039
|
const dir = cwd ?? process.cwd();
|
|
9254
10040
|
const env = buildGitEnv(dir);
|
|
9255
|
-
const restore = (0,
|
|
10041
|
+
const restore = (0, import_child_process9.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
9256
10042
|
cwd: dir,
|
|
9257
10043
|
env,
|
|
9258
10044
|
timeout: GIT_TIMEOUT
|
|
@@ -9264,7 +10050,7 @@ function applyUndo(hash, cwd) {
|
|
|
9264
10050
|
}
|
|
9265
10051
|
return false;
|
|
9266
10052
|
}
|
|
9267
|
-
const lsTree = (0,
|
|
10053
|
+
const lsTree = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
9268
10054
|
cwd: dir,
|
|
9269
10055
|
env,
|
|
9270
10056
|
timeout: GIT_TIMEOUT
|
|
@@ -9283,16 +10069,16 @@ function applyUndo(hash, cwd) {
|
|
|
9283
10069
|
`);
|
|
9284
10070
|
return false;
|
|
9285
10071
|
}
|
|
9286
|
-
const tracked = (0,
|
|
9287
|
-
const untracked = (0,
|
|
10072
|
+
const tracked = (0, import_child_process9.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
10073
|
+
const untracked = (0, import_child_process9.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
9288
10074
|
cwd: dir,
|
|
9289
10075
|
env,
|
|
9290
10076
|
timeout: GIT_TIMEOUT
|
|
9291
10077
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
9292
10078
|
for (const file of [...tracked, ...untracked]) {
|
|
9293
|
-
const fullPath =
|
|
9294
|
-
if (!snapshotFiles.has(file) &&
|
|
9295
|
-
|
|
10079
|
+
const fullPath = import_path23.default.join(dir, file);
|
|
10080
|
+
if (!snapshotFiles.has(file) && import_fs21.default.existsSync(fullPath)) {
|
|
10081
|
+
import_fs21.default.unlinkSync(fullPath);
|
|
9296
10082
|
}
|
|
9297
10083
|
}
|
|
9298
10084
|
return true;
|
|
@@ -9316,9 +10102,9 @@ function registerCheckCommand(program2) {
|
|
|
9316
10102
|
} catch (err2) {
|
|
9317
10103
|
const tempConfig = getConfig();
|
|
9318
10104
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9319
|
-
const logPath =
|
|
10105
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
9320
10106
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9321
|
-
|
|
10107
|
+
import_fs22.default.appendFileSync(
|
|
9322
10108
|
logPath,
|
|
9323
10109
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9324
10110
|
RAW: ${raw}
|
|
@@ -9331,13 +10117,13 @@ RAW: ${raw}
|
|
|
9331
10117
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9332
10118
|
try {
|
|
9333
10119
|
const scriptPath = process.argv[1];
|
|
9334
|
-
if (typeof scriptPath !== "string" || !
|
|
10120
|
+
if (typeof scriptPath !== "string" || !import_path24.default.isAbsolute(scriptPath))
|
|
9335
10121
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9336
|
-
const resolvedScript =
|
|
9337
|
-
const
|
|
9338
|
-
if (resolvedScript !==
|
|
10122
|
+
const resolvedScript = import_fs22.default.realpathSync(scriptPath);
|
|
10123
|
+
const packageDist = import_fs22.default.realpathSync(import_path24.default.resolve(__dirname, "../.."));
|
|
10124
|
+
if (!resolvedScript.startsWith(packageDist + import_path24.default.sep) && resolvedScript !== packageDist)
|
|
9339
10125
|
throw new Error(
|
|
9340
|
-
|
|
10126
|
+
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
9341
10127
|
);
|
|
9342
10128
|
const safeEnv = { ...process.env };
|
|
9343
10129
|
for (const key of [
|
|
@@ -9350,20 +10136,30 @@ RAW: ${raw}
|
|
|
9350
10136
|
]) {
|
|
9351
10137
|
delete safeEnv[key];
|
|
9352
10138
|
}
|
|
9353
|
-
const d = (0,
|
|
10139
|
+
const d = (0, import_child_process10.spawn)(process.execPath, [scriptPath, "daemon"], {
|
|
9354
10140
|
detached: true,
|
|
9355
10141
|
stdio: "ignore",
|
|
9356
10142
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
9357
10143
|
});
|
|
9358
10144
|
d.unref();
|
|
9359
|
-
} catch {
|
|
10145
|
+
} catch (spawnErr) {
|
|
10146
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
10147
|
+
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10148
|
+
try {
|
|
10149
|
+
import_fs22.default.appendFileSync(
|
|
10150
|
+
logPath,
|
|
10151
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10152
|
+
`
|
|
10153
|
+
);
|
|
10154
|
+
} catch {
|
|
10155
|
+
}
|
|
9360
10156
|
}
|
|
9361
10157
|
}
|
|
9362
10158
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9363
|
-
const logPath =
|
|
9364
|
-
if (!
|
|
9365
|
-
|
|
9366
|
-
|
|
10159
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
10160
|
+
if (!import_fs22.default.existsSync(import_path24.default.dirname(logPath)))
|
|
10161
|
+
import_fs22.default.mkdirSync(import_path24.default.dirname(logPath), { recursive: true });
|
|
10162
|
+
import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
9367
10163
|
`);
|
|
9368
10164
|
}
|
|
9369
10165
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9376,8 +10172,8 @@ RAW: ${raw}
|
|
|
9376
10172
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9377
10173
|
let ttyFd = null;
|
|
9378
10174
|
try {
|
|
9379
|
-
ttyFd =
|
|
9380
|
-
const writeTty = (line) =>
|
|
10175
|
+
ttyFd = import_fs22.default.openSync("/dev/tty", "w");
|
|
10176
|
+
const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
|
|
9381
10177
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9382
10178
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
9383
10179
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9396,7 +10192,7 @@ RAW: ${raw}
|
|
|
9396
10192
|
} finally {
|
|
9397
10193
|
if (ttyFd !== null)
|
|
9398
10194
|
try {
|
|
9399
|
-
|
|
10195
|
+
import_fs22.default.closeSync(ttyFd);
|
|
9400
10196
|
} catch {
|
|
9401
10197
|
}
|
|
9402
10198
|
}
|
|
@@ -9428,7 +10224,7 @@ RAW: ${raw}
|
|
|
9428
10224
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9429
10225
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9430
10226
|
}
|
|
9431
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10227
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9432
10228
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9433
10229
|
cwd: safeCwdForAuth
|
|
9434
10230
|
});
|
|
@@ -9440,12 +10236,12 @@ RAW: ${raw}
|
|
|
9440
10236
|
}
|
|
9441
10237
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9442
10238
|
try {
|
|
9443
|
-
const tty =
|
|
9444
|
-
|
|
10239
|
+
const tty = import_fs22.default.openSync("/dev/tty", "w");
|
|
10240
|
+
import_fs22.default.writeSync(
|
|
9445
10241
|
tty,
|
|
9446
10242
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9447
10243
|
);
|
|
9448
|
-
|
|
10244
|
+
import_fs22.default.closeSync(tty);
|
|
9449
10245
|
} catch {
|
|
9450
10246
|
}
|
|
9451
10247
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9472,9 +10268,9 @@ RAW: ${raw}
|
|
|
9472
10268
|
});
|
|
9473
10269
|
} catch (err2) {
|
|
9474
10270
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9475
|
-
const logPath =
|
|
10271
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
9476
10272
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9477
|
-
|
|
10273
|
+
import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9478
10274
|
`);
|
|
9479
10275
|
}
|
|
9480
10276
|
process.exit(0);
|
|
@@ -9508,9 +10304,9 @@ RAW: ${raw}
|
|
|
9508
10304
|
}
|
|
9509
10305
|
|
|
9510
10306
|
// src/cli/commands/log.ts
|
|
9511
|
-
var
|
|
9512
|
-
var
|
|
9513
|
-
var
|
|
10307
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10308
|
+
var import_path25 = __toESM(require("path"));
|
|
10309
|
+
var import_os19 = __toESM(require("os"));
|
|
9514
10310
|
init_audit();
|
|
9515
10311
|
init_config();
|
|
9516
10312
|
init_policy();
|
|
@@ -9586,10 +10382,10 @@ function registerLogCommand(program2) {
|
|
|
9586
10382
|
decision: "allowed",
|
|
9587
10383
|
source: "post-hook"
|
|
9588
10384
|
};
|
|
9589
|
-
const logPath =
|
|
9590
|
-
if (!
|
|
9591
|
-
|
|
9592
|
-
|
|
10385
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
|
|
10386
|
+
if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
|
|
10387
|
+
import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
|
|
10388
|
+
import_fs23.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
9593
10389
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9594
10390
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9595
10391
|
if (command) {
|
|
@@ -9622,7 +10418,7 @@ function registerLogCommand(program2) {
|
|
|
9622
10418
|
}
|
|
9623
10419
|
}
|
|
9624
10420
|
}
|
|
9625
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
10421
|
+
const safeCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9626
10422
|
const config = getConfig(safeCwd);
|
|
9627
10423
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9628
10424
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9631,9 +10427,9 @@ function registerLogCommand(program2) {
|
|
|
9631
10427
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9632
10428
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9633
10429
|
`);
|
|
9634
|
-
const debugPath =
|
|
10430
|
+
const debugPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
9635
10431
|
try {
|
|
9636
|
-
|
|
10432
|
+
import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9637
10433
|
`);
|
|
9638
10434
|
} catch {
|
|
9639
10435
|
}
|
|
@@ -9663,10 +10459,10 @@ init_audit();
|
|
|
9663
10459
|
init_config();
|
|
9664
10460
|
|
|
9665
10461
|
// src/utils/https-fetch.ts
|
|
9666
|
-
var
|
|
10462
|
+
var import_https2 = __toESM(require("https"));
|
|
9667
10463
|
function httpsFetch(url) {
|
|
9668
10464
|
return new Promise((resolve, reject) => {
|
|
9669
|
-
|
|
10465
|
+
import_https2.default.get(url, (res) => {
|
|
9670
10466
|
if (res.statusCode !== 200) {
|
|
9671
10467
|
reject(new Error(`HTTP ${String(res.statusCode)} for ${url}`));
|
|
9672
10468
|
res.resume();
|
|
@@ -10033,14 +10829,14 @@ function registerConfigShowCommand(program2) {
|
|
|
10033
10829
|
|
|
10034
10830
|
// src/cli/commands/doctor.ts
|
|
10035
10831
|
var import_chalk7 = __toESM(require("chalk"));
|
|
10036
|
-
var
|
|
10037
|
-
var
|
|
10038
|
-
var
|
|
10039
|
-
var
|
|
10832
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10833
|
+
var import_path26 = __toESM(require("path"));
|
|
10834
|
+
var import_os20 = __toESM(require("os"));
|
|
10835
|
+
var import_child_process11 = require("child_process");
|
|
10040
10836
|
init_daemon();
|
|
10041
10837
|
function registerDoctorCommand(program2, version2) {
|
|
10042
10838
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
10043
|
-
const homeDir2 =
|
|
10839
|
+
const homeDir2 = import_os20.default.homedir();
|
|
10044
10840
|
let failures = 0;
|
|
10045
10841
|
function pass(msg) {
|
|
10046
10842
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -10062,7 +10858,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10062
10858
|
`));
|
|
10063
10859
|
section("Binary");
|
|
10064
10860
|
try {
|
|
10065
|
-
const which = (0,
|
|
10861
|
+
const which = (0, import_child_process11.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
10066
10862
|
pass(`node9 found at ${which}`);
|
|
10067
10863
|
} catch {
|
|
10068
10864
|
warn(
|
|
@@ -10080,7 +10876,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10080
10876
|
);
|
|
10081
10877
|
}
|
|
10082
10878
|
try {
|
|
10083
|
-
const gitVersion = (0,
|
|
10879
|
+
const gitVersion = (0, import_child_process11.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
10084
10880
|
pass(gitVersion);
|
|
10085
10881
|
} catch {
|
|
10086
10882
|
warn(
|
|
@@ -10089,10 +10885,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10089
10885
|
);
|
|
10090
10886
|
}
|
|
10091
10887
|
section("Configuration");
|
|
10092
|
-
const globalConfigPath =
|
|
10093
|
-
if (
|
|
10888
|
+
const globalConfigPath = import_path26.default.join(homeDir2, ".node9", "config.json");
|
|
10889
|
+
if (import_fs24.default.existsSync(globalConfigPath)) {
|
|
10094
10890
|
try {
|
|
10095
|
-
JSON.parse(
|
|
10891
|
+
JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
|
|
10096
10892
|
pass("~/.node9/config.json found and valid");
|
|
10097
10893
|
} catch {
|
|
10098
10894
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -10100,10 +10896,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10100
10896
|
} else {
|
|
10101
10897
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
10102
10898
|
}
|
|
10103
|
-
const projectConfigPath =
|
|
10104
|
-
if (
|
|
10899
|
+
const projectConfigPath = import_path26.default.join(process.cwd(), "node9.config.json");
|
|
10900
|
+
if (import_fs24.default.existsSync(projectConfigPath)) {
|
|
10105
10901
|
try {
|
|
10106
|
-
JSON.parse(
|
|
10902
|
+
JSON.parse(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
|
|
10107
10903
|
pass("node9.config.json found and valid (project)");
|
|
10108
10904
|
} catch {
|
|
10109
10905
|
fail(
|
|
@@ -10112,8 +10908,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10112
10908
|
);
|
|
10113
10909
|
}
|
|
10114
10910
|
}
|
|
10115
|
-
const credsPath =
|
|
10116
|
-
if (
|
|
10911
|
+
const credsPath = import_path26.default.join(homeDir2, ".node9", "credentials.json");
|
|
10912
|
+
if (import_fs24.default.existsSync(credsPath)) {
|
|
10117
10913
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
10118
10914
|
} else {
|
|
10119
10915
|
warn(
|
|
@@ -10122,10 +10918,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10122
10918
|
);
|
|
10123
10919
|
}
|
|
10124
10920
|
section("Agent Hooks");
|
|
10125
|
-
const claudeSettingsPath =
|
|
10126
|
-
if (
|
|
10921
|
+
const claudeSettingsPath = import_path26.default.join(homeDir2, ".claude", "settings.json");
|
|
10922
|
+
if (import_fs24.default.existsSync(claudeSettingsPath)) {
|
|
10127
10923
|
try {
|
|
10128
|
-
const cs = JSON.parse(
|
|
10924
|
+
const cs = JSON.parse(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
10129
10925
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
10130
10926
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10131
10927
|
);
|
|
@@ -10141,10 +10937,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10141
10937
|
} else {
|
|
10142
10938
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
10143
10939
|
}
|
|
10144
|
-
const geminiSettingsPath =
|
|
10145
|
-
if (
|
|
10940
|
+
const geminiSettingsPath = import_path26.default.join(homeDir2, ".gemini", "settings.json");
|
|
10941
|
+
if (import_fs24.default.existsSync(geminiSettingsPath)) {
|
|
10146
10942
|
try {
|
|
10147
|
-
const gs = JSON.parse(
|
|
10943
|
+
const gs = JSON.parse(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
10148
10944
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
10149
10945
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10150
10946
|
);
|
|
@@ -10160,10 +10956,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10160
10956
|
} else {
|
|
10161
10957
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
10162
10958
|
}
|
|
10163
|
-
const cursorHooksPath =
|
|
10164
|
-
if (
|
|
10959
|
+
const cursorHooksPath = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
|
|
10960
|
+
if (import_fs24.default.existsSync(cursorHooksPath)) {
|
|
10165
10961
|
try {
|
|
10166
|
-
const cur = JSON.parse(
|
|
10962
|
+
const cur = JSON.parse(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
10167
10963
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
10168
10964
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
10169
10965
|
);
|
|
@@ -10201,9 +10997,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10201
10997
|
|
|
10202
10998
|
// src/cli/commands/audit.ts
|
|
10203
10999
|
var import_chalk8 = __toESM(require("chalk"));
|
|
10204
|
-
var
|
|
10205
|
-
var
|
|
10206
|
-
var
|
|
11000
|
+
var import_fs25 = __toESM(require("fs"));
|
|
11001
|
+
var import_path27 = __toESM(require("path"));
|
|
11002
|
+
var import_os21 = __toESM(require("os"));
|
|
10207
11003
|
function formatRelativeTime(timestamp) {
|
|
10208
11004
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
10209
11005
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -10216,14 +11012,14 @@ function formatRelativeTime(timestamp) {
|
|
|
10216
11012
|
}
|
|
10217
11013
|
function registerAuditCommand(program2) {
|
|
10218
11014
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
10219
|
-
const logPath =
|
|
10220
|
-
if (!
|
|
11015
|
+
const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
|
|
11016
|
+
if (!import_fs25.default.existsSync(logPath)) {
|
|
10221
11017
|
console.log(
|
|
10222
11018
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
10223
11019
|
);
|
|
10224
11020
|
return;
|
|
10225
11021
|
}
|
|
10226
|
-
const raw =
|
|
11022
|
+
const raw = import_fs25.default.readFileSync(logPath, "utf-8");
|
|
10227
11023
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
10228
11024
|
let entries = lines.flatMap((line) => {
|
|
10229
11025
|
try {
|
|
@@ -10277,9 +11073,9 @@ function registerAuditCommand(program2) {
|
|
|
10277
11073
|
|
|
10278
11074
|
// src/cli/commands/report.ts
|
|
10279
11075
|
var import_chalk9 = __toESM(require("chalk"));
|
|
10280
|
-
var
|
|
10281
|
-
var
|
|
10282
|
-
var
|
|
11076
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11077
|
+
var import_path28 = __toESM(require("path"));
|
|
11078
|
+
var import_os22 = __toESM(require("os"));
|
|
10283
11079
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
10284
11080
|
function buildTestTimestamps(allEntries) {
|
|
10285
11081
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -10326,8 +11122,8 @@ function getDateRange(period) {
|
|
|
10326
11122
|
}
|
|
10327
11123
|
}
|
|
10328
11124
|
function parseAuditLog(logPath) {
|
|
10329
|
-
if (!
|
|
10330
|
-
const raw =
|
|
11125
|
+
if (!import_fs26.default.existsSync(logPath)) return [];
|
|
11126
|
+
const raw = import_fs26.default.readFileSync(logPath, "utf-8");
|
|
10331
11127
|
return raw.split("\n").flatMap((line) => {
|
|
10332
11128
|
if (!line.trim()) return [];
|
|
10333
11129
|
try {
|
|
@@ -10353,9 +11149,9 @@ function colorBar(value, max, width) {
|
|
|
10353
11149
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
10354
11150
|
return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
|
|
10355
11151
|
}
|
|
10356
|
-
function pct(
|
|
11152
|
+
function pct(num3, total) {
|
|
10357
11153
|
if (total === 0) return "\u2013";
|
|
10358
|
-
return Math.round(
|
|
11154
|
+
return Math.round(num3 / total * 100) + "%";
|
|
10359
11155
|
}
|
|
10360
11156
|
function fmtDate(d) {
|
|
10361
11157
|
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
@@ -10396,11 +11192,11 @@ function loadClaudeCost(start, end) {
|
|
|
10396
11192
|
inputTokens: 0,
|
|
10397
11193
|
cacheReadTokens: 0
|
|
10398
11194
|
};
|
|
10399
|
-
const projectsDir =
|
|
10400
|
-
if (!
|
|
11195
|
+
const projectsDir = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
|
|
11196
|
+
if (!import_fs26.default.existsSync(projectsDir)) return empty;
|
|
10401
11197
|
let dirs;
|
|
10402
11198
|
try {
|
|
10403
|
-
dirs =
|
|
11199
|
+
dirs = import_fs26.default.readdirSync(projectsDir);
|
|
10404
11200
|
} catch {
|
|
10405
11201
|
return empty;
|
|
10406
11202
|
}
|
|
@@ -10410,18 +11206,18 @@ function loadClaudeCost(start, end) {
|
|
|
10410
11206
|
const byDay = /* @__PURE__ */ new Map();
|
|
10411
11207
|
const byModel = /* @__PURE__ */ new Map();
|
|
10412
11208
|
for (const proj of dirs) {
|
|
10413
|
-
const projPath =
|
|
11209
|
+
const projPath = import_path28.default.join(projectsDir, proj);
|
|
10414
11210
|
let files;
|
|
10415
11211
|
try {
|
|
10416
|
-
const stat =
|
|
11212
|
+
const stat = import_fs26.default.statSync(projPath);
|
|
10417
11213
|
if (!stat.isDirectory()) continue;
|
|
10418
|
-
files =
|
|
11214
|
+
files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10419
11215
|
} catch {
|
|
10420
11216
|
continue;
|
|
10421
11217
|
}
|
|
10422
11218
|
for (const file of files) {
|
|
10423
11219
|
try {
|
|
10424
|
-
const raw =
|
|
11220
|
+
const raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
|
|
10425
11221
|
for (const line of raw.split("\n")) {
|
|
10426
11222
|
if (!line.trim()) continue;
|
|
10427
11223
|
let entry;
|
|
@@ -10464,7 +11260,7 @@ function registerReportCommand(program2) {
|
|
|
10464
11260
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
10465
11261
|
options.period
|
|
10466
11262
|
) ? options.period : "7d";
|
|
10467
|
-
const logPath =
|
|
11263
|
+
const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
|
|
10468
11264
|
const allEntries = parseAuditLog(logPath);
|
|
10469
11265
|
if (allEntries.length === 0) {
|
|
10470
11266
|
console.log(
|
|
@@ -10712,22 +11508,63 @@ function registerReportCommand(program2) {
|
|
|
10712
11508
|
|
|
10713
11509
|
// src/cli/commands/daemon-cmd.ts
|
|
10714
11510
|
var import_chalk10 = __toESM(require("chalk"));
|
|
10715
|
-
var
|
|
11511
|
+
var import_child_process12 = require("child_process");
|
|
10716
11512
|
init_daemon2();
|
|
10717
11513
|
init_daemon();
|
|
11514
|
+
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
10718
11515
|
function registerDaemonCommand(program2) {
|
|
10719
|
-
program2.command("daemon").description("
|
|
11516
|
+
program2.command("daemon").description("Manage the local approval daemon").argument("[action]", `${VALID_ACTIONS} (default: start)`).option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
|
|
10720
11517
|
"-w, --watch",
|
|
10721
11518
|
"Start daemon + open browser, stay alive permanently (Flight Recorder mode)"
|
|
10722
11519
|
).action(
|
|
10723
11520
|
async (action, options) => {
|
|
10724
11521
|
const cmd = (action ?? "start").toLowerCase();
|
|
11522
|
+
if (cmd === "install") {
|
|
11523
|
+
const result = installDaemonService();
|
|
11524
|
+
if (!result.ok) {
|
|
11525
|
+
console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
|
|
11526
|
+
process.exit(1);
|
|
11527
|
+
}
|
|
11528
|
+
if (result.alreadyInstalled) {
|
|
11529
|
+
console.log(import_chalk10.default.green(`\u2713 Daemon service reinstalled (${result.platform})`));
|
|
11530
|
+
} else {
|
|
11531
|
+
console.log(import_chalk10.default.green(`\u2713 Daemon installed as login service (${result.platform})`));
|
|
11532
|
+
console.log(import_chalk10.default.gray(" The daemon will now start automatically on login."));
|
|
11533
|
+
}
|
|
11534
|
+
process.exit(0);
|
|
11535
|
+
}
|
|
11536
|
+
if (cmd === "uninstall") {
|
|
11537
|
+
const result = uninstallDaemonService();
|
|
11538
|
+
if (!result.ok) {
|
|
11539
|
+
console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
|
|
11540
|
+
process.exit(1);
|
|
11541
|
+
}
|
|
11542
|
+
console.log(import_chalk10.default.green(`\u2713 Daemon service removed (${result.platform})`));
|
|
11543
|
+
console.log(import_chalk10.default.gray(" The daemon will no longer start automatically on login."));
|
|
11544
|
+
console.log(import_chalk10.default.gray(" To stop the running daemon: node9 daemon stop"));
|
|
11545
|
+
process.exit(0);
|
|
11546
|
+
}
|
|
10725
11547
|
if (cmd === "stop") return stopDaemon();
|
|
11548
|
+
if (cmd === "restart") {
|
|
11549
|
+
stopDaemon();
|
|
11550
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
11551
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
11552
|
+
detached: true,
|
|
11553
|
+
stdio: "ignore",
|
|
11554
|
+
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
11555
|
+
});
|
|
11556
|
+
child.unref();
|
|
11557
|
+
if (child.pid) {
|
|
11558
|
+
console.log(import_chalk10.default.green(`\u2713 Daemon restarted (PID ${child.pid})`));
|
|
11559
|
+
} else {
|
|
11560
|
+
console.error(import_chalk10.default.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
11561
|
+
process.exit(1);
|
|
11562
|
+
}
|
|
11563
|
+
process.exit(0);
|
|
11564
|
+
}
|
|
10726
11565
|
if (cmd === "status") return daemonStatus();
|
|
10727
11566
|
if (cmd !== "start" && action !== void 0) {
|
|
10728
|
-
console.error(
|
|
10729
|
-
import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
|
|
10730
|
-
);
|
|
11567
|
+
console.error(import_chalk10.default.red(`Unknown daemon action: "${action}". Use: ${VALID_ACTIONS}`));
|
|
10731
11568
|
process.exit(1);
|
|
10732
11569
|
}
|
|
10733
11570
|
if (options.watch) {
|
|
@@ -10745,7 +11582,7 @@ function registerDaemonCommand(program2) {
|
|
|
10745
11582
|
console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
10746
11583
|
process.exit(0);
|
|
10747
11584
|
}
|
|
10748
|
-
const child = (0,
|
|
11585
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10749
11586
|
detached: true,
|
|
10750
11587
|
stdio: "ignore"
|
|
10751
11588
|
});
|
|
@@ -10760,7 +11597,7 @@ function registerDaemonCommand(program2) {
|
|
|
10760
11597
|
process.exit(0);
|
|
10761
11598
|
}
|
|
10762
11599
|
if (options.background) {
|
|
10763
|
-
const child = (0,
|
|
11600
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10764
11601
|
detached: true,
|
|
10765
11602
|
stdio: "ignore"
|
|
10766
11603
|
});
|
|
@@ -10776,14 +11613,14 @@ function registerDaemonCommand(program2) {
|
|
|
10776
11613
|
|
|
10777
11614
|
// src/cli/commands/status.ts
|
|
10778
11615
|
var import_chalk11 = __toESM(require("chalk"));
|
|
10779
|
-
var
|
|
10780
|
-
var
|
|
10781
|
-
var
|
|
11616
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11617
|
+
var import_path29 = __toESM(require("path"));
|
|
11618
|
+
var import_os23 = __toESM(require("os"));
|
|
10782
11619
|
init_core();
|
|
10783
11620
|
init_daemon();
|
|
10784
11621
|
function readJson2(filePath) {
|
|
10785
11622
|
try {
|
|
10786
|
-
if (
|
|
11623
|
+
if (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
|
|
10787
11624
|
} catch {
|
|
10788
11625
|
}
|
|
10789
11626
|
return null;
|
|
@@ -10848,28 +11685,28 @@ function registerStatusCommand(program2) {
|
|
|
10848
11685
|
console.log("");
|
|
10849
11686
|
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
10850
11687
|
console.log(` Mode: ${modeLabel}`);
|
|
10851
|
-
const projectConfig =
|
|
10852
|
-
const globalConfig =
|
|
11688
|
+
const projectConfig = import_path29.default.join(process.cwd(), "node9.config.json");
|
|
11689
|
+
const globalConfig = import_path29.default.join(import_os23.default.homedir(), ".node9", "config.json");
|
|
10853
11690
|
console.log(
|
|
10854
|
-
` Local: ${
|
|
11691
|
+
` Local: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10855
11692
|
);
|
|
10856
11693
|
console.log(
|
|
10857
|
-
` Global: ${
|
|
11694
|
+
` Global: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10858
11695
|
);
|
|
10859
11696
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10860
11697
|
console.log(
|
|
10861
11698
|
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10862
11699
|
);
|
|
10863
11700
|
}
|
|
10864
|
-
const homeDir2 =
|
|
11701
|
+
const homeDir2 = import_os23.default.homedir();
|
|
10865
11702
|
const claudeSettings = readJson2(
|
|
10866
|
-
|
|
11703
|
+
import_path29.default.join(homeDir2, ".claude", "settings.json")
|
|
10867
11704
|
);
|
|
10868
|
-
const claudeConfig = readJson2(
|
|
11705
|
+
const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
|
|
10869
11706
|
const geminiSettings = readJson2(
|
|
10870
|
-
|
|
11707
|
+
import_path29.default.join(homeDir2, ".gemini", "settings.json")
|
|
10871
11708
|
);
|
|
10872
|
-
const cursorConfig = readJson2(
|
|
11709
|
+
const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
10873
11710
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10874
11711
|
if (agentFound) {
|
|
10875
11712
|
console.log("");
|
|
@@ -10928,12 +11765,13 @@ function registerStatusCommand(program2) {
|
|
|
10928
11765
|
|
|
10929
11766
|
// src/cli/commands/init.ts
|
|
10930
11767
|
var import_chalk12 = __toESM(require("chalk"));
|
|
10931
|
-
var
|
|
10932
|
-
var
|
|
10933
|
-
var
|
|
10934
|
-
var
|
|
11768
|
+
var import_fs28 = __toESM(require("fs"));
|
|
11769
|
+
var import_path30 = __toESM(require("path"));
|
|
11770
|
+
var import_os24 = __toESM(require("os"));
|
|
11771
|
+
var import_https3 = __toESM(require("https"));
|
|
10935
11772
|
init_core();
|
|
10936
11773
|
init_shields();
|
|
11774
|
+
init_service();
|
|
10937
11775
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
|
|
10938
11776
|
function fireTelemetryPing(agents) {
|
|
10939
11777
|
try {
|
|
@@ -10943,7 +11781,7 @@ function fireTelemetryPing(agents) {
|
|
|
10943
11781
|
os: process.platform,
|
|
10944
11782
|
node9_version: process.env.npm_package_version ?? "unknown"
|
|
10945
11783
|
});
|
|
10946
|
-
const req =
|
|
11784
|
+
const req = import_https3.default.request(
|
|
10947
11785
|
{
|
|
10948
11786
|
hostname: "api.node9.ai",
|
|
10949
11787
|
path: "/api/v1/telemetry",
|
|
@@ -10990,15 +11828,15 @@ function registerInitCommand(program2) {
|
|
|
10990
11828
|
}
|
|
10991
11829
|
console.log("");
|
|
10992
11830
|
}
|
|
10993
|
-
const configPath =
|
|
10994
|
-
if (
|
|
11831
|
+
const configPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11832
|
+
if (import_fs28.default.existsSync(configPath) && !options.force) {
|
|
10995
11833
|
try {
|
|
10996
|
-
const existing = JSON.parse(
|
|
11834
|
+
const existing = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
10997
11835
|
const settings = existing.settings ?? {};
|
|
10998
11836
|
if (settings.mode !== chosenMode) {
|
|
10999
11837
|
settings.mode = chosenMode;
|
|
11000
11838
|
existing.settings = settings;
|
|
11001
|
-
|
|
11839
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11002
11840
|
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
11003
11841
|
} else {
|
|
11004
11842
|
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -11011,9 +11849,9 @@ function registerInitCommand(program2) {
|
|
|
11011
11849
|
...DEFAULT_CONFIG,
|
|
11012
11850
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
11013
11851
|
};
|
|
11014
|
-
const dir =
|
|
11015
|
-
if (!
|
|
11016
|
-
|
|
11852
|
+
const dir = import_path30.default.dirname(configPath);
|
|
11853
|
+
if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
|
|
11854
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
11017
11855
|
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
11018
11856
|
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
11019
11857
|
}
|
|
@@ -11025,9 +11863,13 @@ function registerInitCommand(program2) {
|
|
|
11025
11863
|
);
|
|
11026
11864
|
if (found.length === 0) {
|
|
11027
11865
|
console.log(
|
|
11028
|
-
import_chalk12.default.gray(
|
|
11866
|
+
import_chalk12.default.gray(
|
|
11867
|
+
"No AI agents detected. Install Claude Code, Gemini CLI, Cursor, Windsurf, VSCode, or Codex"
|
|
11868
|
+
)
|
|
11869
|
+
);
|
|
11870
|
+
console.log(
|
|
11871
|
+
import_chalk12.default.gray("then run: node9 agents add <claude|gemini|cursor|windsurf|vscode|codex>")
|
|
11029
11872
|
);
|
|
11030
|
-
console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
|
|
11031
11873
|
return;
|
|
11032
11874
|
}
|
|
11033
11875
|
console.log(import_chalk12.default.bold("Detected agents:"));
|
|
@@ -11041,6 +11883,32 @@ function registerInitCommand(program2) {
|
|
|
11041
11883
|
else if (agent === "gemini") await setupGemini();
|
|
11042
11884
|
else if (agent === "cursor") await setupCursor();
|
|
11043
11885
|
else if (agent === "codex") await setupCodex();
|
|
11886
|
+
else if (agent === "windsurf") await setupWindsurf();
|
|
11887
|
+
else if (agent === "vscode") await setupVSCode();
|
|
11888
|
+
console.log("");
|
|
11889
|
+
}
|
|
11890
|
+
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
11891
|
+
const alreadyInstalled = isDaemonServiceInstalled();
|
|
11892
|
+
if (!alreadyInstalled) {
|
|
11893
|
+
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
11894
|
+
const installService = await confirm3({
|
|
11895
|
+
message: "Install daemon as a login service? (starts automatically on login)",
|
|
11896
|
+
default: true
|
|
11897
|
+
});
|
|
11898
|
+
if (installService) {
|
|
11899
|
+
const result = installDaemonService();
|
|
11900
|
+
if (result.ok) {
|
|
11901
|
+
console.log(
|
|
11902
|
+
import_chalk12.default.green(` \u2713 Daemon installed as login service (${result.platform})`)
|
|
11903
|
+
);
|
|
11904
|
+
} else {
|
|
11905
|
+
console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not install service: ${result.reason}`));
|
|
11906
|
+
console.log(import_chalk12.default.gray(" You can try again later with: node9 daemon install"));
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
} else {
|
|
11910
|
+
console.log(import_chalk12.default.green(" \u2713 Daemon login service already installed"));
|
|
11911
|
+
}
|
|
11044
11912
|
console.log("");
|
|
11045
11913
|
}
|
|
11046
11914
|
{
|
|
@@ -11067,7 +11935,7 @@ function registerInitCommand(program2) {
|
|
|
11067
11935
|
}
|
|
11068
11936
|
|
|
11069
11937
|
// src/cli/commands/undo.ts
|
|
11070
|
-
var
|
|
11938
|
+
var import_path31 = __toESM(require("path"));
|
|
11071
11939
|
var import_chalk14 = __toESM(require("chalk"));
|
|
11072
11940
|
|
|
11073
11941
|
// src/tui/undo-navigator.ts
|
|
@@ -11226,7 +12094,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
11226
12094
|
let dir = startDir;
|
|
11227
12095
|
while (true) {
|
|
11228
12096
|
if (cwds.has(dir)) return dir;
|
|
11229
|
-
const parent =
|
|
12097
|
+
const parent = import_path31.default.dirname(dir);
|
|
11230
12098
|
if (parent === dir) return null;
|
|
11231
12099
|
dir = parent;
|
|
11232
12100
|
}
|
|
@@ -11354,7 +12222,7 @@ function registerUndoCommand(program2) {
|
|
|
11354
12222
|
|
|
11355
12223
|
// src/cli/commands/watch.ts
|
|
11356
12224
|
var import_chalk15 = __toESM(require("chalk"));
|
|
11357
|
-
var
|
|
12225
|
+
var import_child_process13 = require("child_process");
|
|
11358
12226
|
init_daemon();
|
|
11359
12227
|
function registerWatchCommand(program2) {
|
|
11360
12228
|
program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
|
|
@@ -11371,7 +12239,7 @@ function registerWatchCommand(program2) {
|
|
|
11371
12239
|
}
|
|
11372
12240
|
} catch {
|
|
11373
12241
|
console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
11374
|
-
const child = (0,
|
|
12242
|
+
const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
11375
12243
|
detached: true,
|
|
11376
12244
|
stdio: "ignore",
|
|
11377
12245
|
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
@@ -11401,7 +12269,7 @@ function registerWatchCommand(program2) {
|
|
|
11401
12269
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
11402
12270
|
)
|
|
11403
12271
|
);
|
|
11404
|
-
const result = (0,
|
|
12272
|
+
const result = (0, import_child_process13.spawnSync)(cmd, args, {
|
|
11405
12273
|
stdio: "inherit",
|
|
11406
12274
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
11407
12275
|
});
|
|
@@ -11416,18 +12284,18 @@ function registerWatchCommand(program2) {
|
|
|
11416
12284
|
// src/mcp-gateway/index.ts
|
|
11417
12285
|
var import_readline3 = __toESM(require("readline"));
|
|
11418
12286
|
var import_chalk16 = __toESM(require("chalk"));
|
|
11419
|
-
var
|
|
12287
|
+
var import_child_process14 = require("child_process");
|
|
11420
12288
|
var import_execa3 = require("execa");
|
|
11421
12289
|
init_orchestrator();
|
|
11422
12290
|
init_provenance();
|
|
11423
12291
|
|
|
11424
12292
|
// src/mcp-pin.ts
|
|
11425
|
-
var
|
|
11426
|
-
var
|
|
11427
|
-
var
|
|
12293
|
+
var import_fs29 = __toESM(require("fs"));
|
|
12294
|
+
var import_path32 = __toESM(require("path"));
|
|
12295
|
+
var import_os25 = __toESM(require("os"));
|
|
11428
12296
|
var import_crypto9 = __toESM(require("crypto"));
|
|
11429
12297
|
function getPinsFilePath() {
|
|
11430
|
-
return
|
|
12298
|
+
return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
|
|
11431
12299
|
}
|
|
11432
12300
|
function hashToolDefinitions(tools) {
|
|
11433
12301
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -11444,7 +12312,7 @@ function getServerKey(upstreamCommand) {
|
|
|
11444
12312
|
function readMcpPinsSafe() {
|
|
11445
12313
|
const filePath = getPinsFilePath();
|
|
11446
12314
|
try {
|
|
11447
|
-
const raw =
|
|
12315
|
+
const raw = import_fs29.default.readFileSync(filePath, "utf-8");
|
|
11448
12316
|
if (!raw.trim()) {
|
|
11449
12317
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
11450
12318
|
}
|
|
@@ -11468,10 +12336,10 @@ function readMcpPins() {
|
|
|
11468
12336
|
}
|
|
11469
12337
|
function writeMcpPins(data) {
|
|
11470
12338
|
const filePath = getPinsFilePath();
|
|
11471
|
-
|
|
12339
|
+
import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
|
|
11472
12340
|
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
11473
|
-
|
|
11474
|
-
|
|
12341
|
+
import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
12342
|
+
import_fs29.default.renameSync(tmp, filePath);
|
|
11475
12343
|
}
|
|
11476
12344
|
function checkPin(serverKey, currentHash) {
|
|
11477
12345
|
const result = readMcpPinsSafe();
|
|
@@ -11588,7 +12456,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11588
12456
|
const safeEnv = Object.fromEntries(
|
|
11589
12457
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
11590
12458
|
);
|
|
11591
|
-
const child = (0,
|
|
12459
|
+
const child = (0, import_child_process14.spawn)(executable, cmdArgs, {
|
|
11592
12460
|
stdio: ["pipe", "pipe", "inherit"],
|
|
11593
12461
|
// control stdin/stdout; inherit stderr
|
|
11594
12462
|
shell: false,
|
|
@@ -11843,9 +12711,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11843
12711
|
|
|
11844
12712
|
// src/mcp-server/index.ts
|
|
11845
12713
|
var import_readline4 = __toESM(require("readline"));
|
|
11846
|
-
var
|
|
11847
|
-
var
|
|
11848
|
-
var
|
|
12714
|
+
var import_fs30 = __toESM(require("fs"));
|
|
12715
|
+
var import_os26 = __toESM(require("os"));
|
|
12716
|
+
var import_path33 = __toESM(require("path"));
|
|
11849
12717
|
init_core();
|
|
11850
12718
|
init_daemon();
|
|
11851
12719
|
init_shields();
|
|
@@ -12020,13 +12888,13 @@ function handleStatus() {
|
|
|
12020
12888
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
12021
12889
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
12022
12890
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
12023
|
-
const projectConfig =
|
|
12024
|
-
const globalConfig =
|
|
12891
|
+
const projectConfig = import_path33.default.join(process.cwd(), "node9.config.json");
|
|
12892
|
+
const globalConfig = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
12025
12893
|
lines.push(
|
|
12026
|
-
`Project config (node9.config.json): ${
|
|
12894
|
+
`Project config (node9.config.json): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
12027
12895
|
);
|
|
12028
12896
|
lines.push(
|
|
12029
|
-
`Global config (~/.node9/config.json): ${
|
|
12897
|
+
`Global config (~/.node9/config.json): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
12030
12898
|
);
|
|
12031
12899
|
return lines.join("\n");
|
|
12032
12900
|
}
|
|
@@ -12100,21 +12968,21 @@ function handleShieldDisable(args) {
|
|
|
12100
12968
|
writeActiveShields(active.filter((s) => s !== name));
|
|
12101
12969
|
return `Shield "${name}" disabled.`;
|
|
12102
12970
|
}
|
|
12103
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
12971
|
+
var GLOBAL_CONFIG_PATH2 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
12104
12972
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
12105
12973
|
function readGlobalConfigRaw() {
|
|
12106
12974
|
try {
|
|
12107
|
-
if (
|
|
12108
|
-
return JSON.parse(
|
|
12975
|
+
if (import_fs30.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
12976
|
+
return JSON.parse(import_fs30.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
12109
12977
|
}
|
|
12110
12978
|
} catch {
|
|
12111
12979
|
}
|
|
12112
12980
|
return {};
|
|
12113
12981
|
}
|
|
12114
12982
|
function writeGlobalConfigRaw(data) {
|
|
12115
|
-
const dir =
|
|
12116
|
-
if (!
|
|
12117
|
-
|
|
12983
|
+
const dir = import_path33.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
12984
|
+
if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
|
|
12985
|
+
import_fs30.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
12118
12986
|
}
|
|
12119
12987
|
function handleApproverList() {
|
|
12120
12988
|
const config = getConfig();
|
|
@@ -12157,9 +13025,9 @@ function handleApproverSet(args) {
|
|
|
12157
13025
|
}
|
|
12158
13026
|
function handleAuditGet(args) {
|
|
12159
13027
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
12160
|
-
const auditPath =
|
|
12161
|
-
if (!
|
|
12162
|
-
const lines =
|
|
13028
|
+
const auditPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "audit.log");
|
|
13029
|
+
if (!import_fs30.default.existsSync(auditPath)) return "No audit log found.";
|
|
13030
|
+
const lines = import_fs30.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
12163
13031
|
const recent = lines.slice(-limit);
|
|
12164
13032
|
const entries = recent.map((line) => {
|
|
12165
13033
|
try {
|
|
@@ -12477,25 +13345,950 @@ function registerMcpPinCommand(program2) {
|
|
|
12477
13345
|
});
|
|
12478
13346
|
}
|
|
12479
13347
|
|
|
13348
|
+
// src/cli/commands/sync.ts
|
|
13349
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
13350
|
+
init_sync();
|
|
13351
|
+
function registerSyncCommand(program2) {
|
|
13352
|
+
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
13353
|
+
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
13354
|
+
process.stdout.write(import_chalk19.default.cyan("Syncing cloud policy rules\u2026"));
|
|
13355
|
+
const result = await runCloudSync();
|
|
13356
|
+
process.stdout.write("\n");
|
|
13357
|
+
if (!result.ok) {
|
|
13358
|
+
console.error(import_chalk19.default.red(`\u2717 ${result.reason}`));
|
|
13359
|
+
process.exit(1);
|
|
13360
|
+
}
|
|
13361
|
+
console.log(
|
|
13362
|
+
import_chalk19.default.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
13363
|
+
);
|
|
13364
|
+
console.log(import_chalk19.default.gray(` Cached at: ${result.fetchedAt}`));
|
|
13365
|
+
console.log(import_chalk19.default.gray(` File: ~/.node9/rules-cache.json`));
|
|
13366
|
+
});
|
|
13367
|
+
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
13368
|
+
const status = getCloudSyncStatus();
|
|
13369
|
+
if (!status.cached) {
|
|
13370
|
+
console.log(import_chalk19.default.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
13371
|
+
return;
|
|
13372
|
+
}
|
|
13373
|
+
const rules = getCloudRules() ?? [];
|
|
13374
|
+
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
13375
|
+
console.log(
|
|
13376
|
+
import_chalk19.default.bold(`
|
|
13377
|
+
Cloud policy rules`) + import_chalk19.default.gray(
|
|
13378
|
+
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
13379
|
+
`
|
|
13380
|
+
)
|
|
13381
|
+
);
|
|
13382
|
+
if (rules.length === 0) {
|
|
13383
|
+
console.log(import_chalk19.default.gray(" No rules defined in cloud policy.\n"));
|
|
13384
|
+
return;
|
|
13385
|
+
}
|
|
13386
|
+
for (const rule of rules) {
|
|
13387
|
+
const r = rule;
|
|
13388
|
+
const verdictColor = r.verdict === "block" ? import_chalk19.default.red : r.verdict === "allow" ? import_chalk19.default.green : import_chalk19.default.yellow;
|
|
13389
|
+
console.log(
|
|
13390
|
+
` ${verdictColor(
|
|
13391
|
+
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
13392
|
+
)} ${import_chalk19.default.white(String(r.name ?? "(unnamed)"))}`
|
|
13393
|
+
);
|
|
13394
|
+
if (r.reason) console.log(import_chalk19.default.gray(` ${String(r.reason)}`));
|
|
13395
|
+
}
|
|
13396
|
+
console.log("");
|
|
13397
|
+
});
|
|
13398
|
+
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
13399
|
+
const s = getCloudSyncStatus();
|
|
13400
|
+
if (!s.cached) {
|
|
13401
|
+
console.log(import_chalk19.default.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
13402
|
+
} else {
|
|
13403
|
+
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
13404
|
+
console.log(`
|
|
13405
|
+
Rules : ${import_chalk19.default.green(String(s.rules))} cloud rules loaded`);
|
|
13406
|
+
console.log(
|
|
13407
|
+
` Synced : ${import_chalk19.default.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})
|
|
13408
|
+
`
|
|
13409
|
+
);
|
|
13410
|
+
}
|
|
13411
|
+
});
|
|
13412
|
+
}
|
|
13413
|
+
|
|
13414
|
+
// src/cli/commands/agents.ts
|
|
13415
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
13416
|
+
var SETUP_FN = {
|
|
13417
|
+
claude: setupClaude,
|
|
13418
|
+
gemini: setupGemini,
|
|
13419
|
+
cursor: setupCursor,
|
|
13420
|
+
codex: setupCodex,
|
|
13421
|
+
windsurf: setupWindsurf,
|
|
13422
|
+
vscode: setupVSCode
|
|
13423
|
+
};
|
|
13424
|
+
var TEARDOWN_FN = {
|
|
13425
|
+
claude: teardownClaude,
|
|
13426
|
+
gemini: teardownGemini,
|
|
13427
|
+
cursor: teardownCursor,
|
|
13428
|
+
codex: teardownCodex,
|
|
13429
|
+
windsurf: teardownWindsurf,
|
|
13430
|
+
vscode: teardownVSCode
|
|
13431
|
+
};
|
|
13432
|
+
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
13433
|
+
function registerAgentsCommand(program2) {
|
|
13434
|
+
const agents = program2.command("agents").description("List and manage AI agent integrations");
|
|
13435
|
+
agents.command("list").description("Show all supported agents and their Node9 status").action(() => {
|
|
13436
|
+
const statuses = getAgentsStatus();
|
|
13437
|
+
const anyInstalled = statuses.some((s) => s.installed);
|
|
13438
|
+
console.log("");
|
|
13439
|
+
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
13440
|
+
console.log(" " + "\u2500".repeat(44));
|
|
13441
|
+
for (const s of statuses) {
|
|
13442
|
+
const installed = s.installed ? import_chalk20.default.green("\u2713") : import_chalk20.default.gray("\u2717");
|
|
13443
|
+
const wired = !s.installed ? import_chalk20.default.gray("\u2014") : s.wired ? import_chalk20.default.green("\u2713") : import_chalk20.default.yellow("\u2717");
|
|
13444
|
+
const mode = s.mode ? import_chalk20.default.gray(s.mode) : import_chalk20.default.gray("\u2014");
|
|
13445
|
+
const hint = s.installed && !s.wired ? import_chalk20.default.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
13446
|
+
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
13447
|
+
}
|
|
13448
|
+
console.log("");
|
|
13449
|
+
if (!anyInstalled) {
|
|
13450
|
+
console.log(
|
|
13451
|
+
import_chalk20.default.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + import_chalk20.default.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
|
|
13452
|
+
);
|
|
13453
|
+
return;
|
|
13454
|
+
}
|
|
13455
|
+
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
13456
|
+
if (unwired.length > 0) {
|
|
13457
|
+
console.log(
|
|
13458
|
+
import_chalk20.default.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + import_chalk20.default.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
13459
|
+
);
|
|
13460
|
+
}
|
|
13461
|
+
});
|
|
13462
|
+
agents.command("add").description("Wire Node9 into an agent").argument("<agent>", `Agent to wire: ${AGENT_NAMES.join(" | ")}`).action(async (agent) => {
|
|
13463
|
+
const name = agent.toLowerCase();
|
|
13464
|
+
const fn = SETUP_FN[name];
|
|
13465
|
+
if (!fn) {
|
|
13466
|
+
console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
13467
|
+
process.exit(1);
|
|
13468
|
+
}
|
|
13469
|
+
await fn();
|
|
13470
|
+
});
|
|
13471
|
+
agents.command("remove").description("Remove Node9 from an agent").argument("<agent>", `Agent to unwire: ${AGENT_NAMES.join(" | ")}`).action((agent) => {
|
|
13472
|
+
const name = agent.toLowerCase();
|
|
13473
|
+
const fn = TEARDOWN_FN[name];
|
|
13474
|
+
if (!fn) {
|
|
13475
|
+
console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
13476
|
+
process.exit(1);
|
|
13477
|
+
}
|
|
13478
|
+
console.log(import_chalk20.default.cyan(`
|
|
13479
|
+
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
13480
|
+
`));
|
|
13481
|
+
fn();
|
|
13482
|
+
console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
|
|
13483
|
+
});
|
|
13484
|
+
}
|
|
13485
|
+
|
|
13486
|
+
// src/cli/commands/scan.ts
|
|
13487
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
13488
|
+
var import_fs31 = __toESM(require("fs"));
|
|
13489
|
+
var import_path34 = __toESM(require("path"));
|
|
13490
|
+
var import_os27 = __toESM(require("os"));
|
|
13491
|
+
init_shields();
|
|
13492
|
+
init_config();
|
|
13493
|
+
init_policy();
|
|
13494
|
+
init_dlp();
|
|
13495
|
+
var CLAUDE_PRICING2 = {
|
|
13496
|
+
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13497
|
+
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13498
|
+
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
13499
|
+
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13500
|
+
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13501
|
+
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13502
|
+
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13503
|
+
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13504
|
+
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
13505
|
+
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
13506
|
+
};
|
|
13507
|
+
function claudeModelPrice2(model) {
|
|
13508
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
13509
|
+
for (const [key, p] of Object.entries(CLAUDE_PRICING2)) {
|
|
13510
|
+
if (base === key || base.startsWith(key)) return p;
|
|
13511
|
+
}
|
|
13512
|
+
return null;
|
|
13513
|
+
}
|
|
13514
|
+
function num2(n) {
|
|
13515
|
+
return n.toLocaleString();
|
|
13516
|
+
}
|
|
13517
|
+
function fmtCost2(usd) {
|
|
13518
|
+
if (usd < 1e-3) return "< $0.001";
|
|
13519
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
13520
|
+
return "$" + usd.toFixed(2);
|
|
13521
|
+
}
|
|
13522
|
+
function fmtTs(ts) {
|
|
13523
|
+
try {
|
|
13524
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
13525
|
+
month: "short",
|
|
13526
|
+
day: "numeric",
|
|
13527
|
+
year: "numeric"
|
|
13528
|
+
});
|
|
13529
|
+
} catch {
|
|
13530
|
+
return ts.slice(0, 10);
|
|
13531
|
+
}
|
|
13532
|
+
}
|
|
13533
|
+
function preview(input, max) {
|
|
13534
|
+
const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
13535
|
+
const s = String(cmd).replace(/\s+/g, " ").trim();
|
|
13536
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
13537
|
+
}
|
|
13538
|
+
function buildRuleSources() {
|
|
13539
|
+
const sources = [];
|
|
13540
|
+
for (const [shieldName, shield] of Object.entries(SHIELDS)) {
|
|
13541
|
+
for (const rule of shield.smartRules) {
|
|
13542
|
+
sources.push({ shieldName, shieldLabel: shieldName, rule });
|
|
13543
|
+
}
|
|
13544
|
+
}
|
|
13545
|
+
try {
|
|
13546
|
+
const config = getConfig();
|
|
13547
|
+
for (const rule of config.policy.smartRules) {
|
|
13548
|
+
if (!rule.name) continue;
|
|
13549
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
13550
|
+
const isCloud = rule.name.startsWith("cloud:");
|
|
13551
|
+
sources.push({
|
|
13552
|
+
shieldName: isCloud ? "cloud" : "custom",
|
|
13553
|
+
shieldLabel: isCloud ? "Cloud Policy" : "Your Rules",
|
|
13554
|
+
rule
|
|
13555
|
+
});
|
|
13556
|
+
}
|
|
13557
|
+
} catch {
|
|
13558
|
+
}
|
|
13559
|
+
return sources;
|
|
13560
|
+
}
|
|
13561
|
+
function scanClaudeHistory(startDate) {
|
|
13562
|
+
const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
|
|
13563
|
+
const result = {
|
|
13564
|
+
filesScanned: 0,
|
|
13565
|
+
sessions: 0,
|
|
13566
|
+
totalToolCalls: 0,
|
|
13567
|
+
bashCalls: 0,
|
|
13568
|
+
findings: [],
|
|
13569
|
+
dlpFindings: [],
|
|
13570
|
+
totalCostUSD: 0,
|
|
13571
|
+
firstDate: null,
|
|
13572
|
+
lastDate: null
|
|
13573
|
+
};
|
|
13574
|
+
if (!import_fs31.default.existsSync(projectsDir)) return result;
|
|
13575
|
+
let projDirs;
|
|
13576
|
+
try {
|
|
13577
|
+
projDirs = import_fs31.default.readdirSync(projectsDir);
|
|
13578
|
+
} catch {
|
|
13579
|
+
return result;
|
|
13580
|
+
}
|
|
13581
|
+
const ruleSources = buildRuleSources();
|
|
13582
|
+
for (const proj of projDirs) {
|
|
13583
|
+
const projPath = import_path34.default.join(projectsDir, proj);
|
|
13584
|
+
try {
|
|
13585
|
+
if (!import_fs31.default.statSync(projPath).isDirectory()) continue;
|
|
13586
|
+
} catch {
|
|
13587
|
+
continue;
|
|
13588
|
+
}
|
|
13589
|
+
const projLabel = decodeURIComponent(proj).replace(import_os27.default.homedir(), "~").slice(0, 40);
|
|
13590
|
+
let files;
|
|
13591
|
+
try {
|
|
13592
|
+
files = import_fs31.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13593
|
+
} catch {
|
|
13594
|
+
continue;
|
|
13595
|
+
}
|
|
13596
|
+
for (const file of files) {
|
|
13597
|
+
result.filesScanned++;
|
|
13598
|
+
result.sessions++;
|
|
13599
|
+
let raw;
|
|
13600
|
+
try {
|
|
13601
|
+
raw = import_fs31.default.readFileSync(import_path34.default.join(projPath, file), "utf-8");
|
|
13602
|
+
} catch {
|
|
13603
|
+
continue;
|
|
13604
|
+
}
|
|
13605
|
+
for (const line of raw.split("\n")) {
|
|
13606
|
+
if (!line.trim()) continue;
|
|
13607
|
+
let entry;
|
|
13608
|
+
try {
|
|
13609
|
+
entry = JSON.parse(line);
|
|
13610
|
+
} catch {
|
|
13611
|
+
continue;
|
|
13612
|
+
}
|
|
13613
|
+
if (entry.type !== "assistant") continue;
|
|
13614
|
+
if (startDate && entry.timestamp) {
|
|
13615
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
13616
|
+
}
|
|
13617
|
+
if (entry.timestamp) {
|
|
13618
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
13619
|
+
result.firstDate = entry.timestamp;
|
|
13620
|
+
if (!result.lastDate || entry.timestamp > result.lastDate)
|
|
13621
|
+
result.lastDate = entry.timestamp;
|
|
13622
|
+
}
|
|
13623
|
+
const usage = entry.message?.usage;
|
|
13624
|
+
const model = entry.message?.model;
|
|
13625
|
+
if (usage && model) {
|
|
13626
|
+
const p = claudeModelPrice2(model);
|
|
13627
|
+
if (p) {
|
|
13628
|
+
result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
const content = entry.message?.content;
|
|
13632
|
+
if (!Array.isArray(content)) continue;
|
|
13633
|
+
for (const block of content) {
|
|
13634
|
+
if (block.type !== "tool_use") continue;
|
|
13635
|
+
result.totalToolCalls++;
|
|
13636
|
+
const toolName = block.name ?? "";
|
|
13637
|
+
const toolNameLower = toolName.toLowerCase();
|
|
13638
|
+
const input = block.input ?? {};
|
|
13639
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
13640
|
+
result.bashCalls++;
|
|
13641
|
+
}
|
|
13642
|
+
const dlpMatch = scanArgs(input);
|
|
13643
|
+
if (dlpMatch) {
|
|
13644
|
+
const isDupe = result.dlpFindings.some(
|
|
13645
|
+
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
13646
|
+
);
|
|
13647
|
+
if (!isDupe) {
|
|
13648
|
+
result.dlpFindings.push({
|
|
13649
|
+
patternName: dlpMatch.patternName,
|
|
13650
|
+
redactedSample: dlpMatch.redactedSample,
|
|
13651
|
+
toolName,
|
|
13652
|
+
timestamp: entry.timestamp ?? "",
|
|
13653
|
+
project: projLabel
|
|
13654
|
+
});
|
|
13655
|
+
}
|
|
13656
|
+
}
|
|
13657
|
+
for (const source of ruleSources) {
|
|
13658
|
+
const { rule } = source;
|
|
13659
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
13660
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
13661
|
+
const inputPreview = preview(input, 120);
|
|
13662
|
+
const isDupe = result.findings.some(
|
|
13663
|
+
(f) => f.source.rule.name === rule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
13664
|
+
);
|
|
13665
|
+
if (!isDupe) {
|
|
13666
|
+
result.findings.push({
|
|
13667
|
+
source,
|
|
13668
|
+
toolName,
|
|
13669
|
+
input,
|
|
13670
|
+
timestamp: entry.timestamp ?? "",
|
|
13671
|
+
project: projLabel
|
|
13672
|
+
});
|
|
13673
|
+
}
|
|
13674
|
+
break;
|
|
13675
|
+
}
|
|
13676
|
+
}
|
|
13677
|
+
}
|
|
13678
|
+
}
|
|
13679
|
+
}
|
|
13680
|
+
return result;
|
|
13681
|
+
}
|
|
13682
|
+
function registerScanCommand(program2) {
|
|
13683
|
+
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per shield", "5").action((options) => {
|
|
13684
|
+
const topN = Math.max(1, parseInt(options.top, 10) || 5);
|
|
13685
|
+
const startDate = options.all ? null : (() => {
|
|
13686
|
+
const d = /* @__PURE__ */ new Date();
|
|
13687
|
+
d.setDate(d.getDate() - (parseInt(options.days, 10) || 90));
|
|
13688
|
+
d.setHours(0, 0, 0, 0);
|
|
13689
|
+
return d;
|
|
13690
|
+
})();
|
|
13691
|
+
console.log("");
|
|
13692
|
+
console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
|
|
13693
|
+
console.log("");
|
|
13694
|
+
const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
|
|
13695
|
+
if (!import_fs31.default.existsSync(projectsDir)) {
|
|
13696
|
+
console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
13697
|
+
console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
13698
|
+
return;
|
|
13699
|
+
}
|
|
13700
|
+
process.stdout.write(import_chalk21.default.dim(" Scanning\u2026"));
|
|
13701
|
+
const scan = scanClaudeHistory(startDate);
|
|
13702
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
13703
|
+
if (scan.filesScanned === 0) {
|
|
13704
|
+
console.log(import_chalk21.default.yellow(" No JSONL session files found.\n"));
|
|
13705
|
+
return;
|
|
13706
|
+
}
|
|
13707
|
+
const rangeLabel = options.all ? import_chalk21.default.dim("all time") : import_chalk21.default.dim(`last ${options.days ?? 90} days`);
|
|
13708
|
+
const dateRange = scan.firstDate && scan.lastDate ? import_chalk21.default.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
|
|
13709
|
+
console.log(
|
|
13710
|
+
" " + import_chalk21.default.white(num2(scan.sessions)) + import_chalk21.default.dim(" sessions ") + import_chalk21.default.white(num2(scan.totalToolCalls)) + import_chalk21.default.dim(" tool calls ") + import_chalk21.default.white(num2(scan.bashCalls)) + import_chalk21.default.dim(" bash commands ") + rangeLabel + dateRange
|
|
13711
|
+
);
|
|
13712
|
+
console.log("");
|
|
13713
|
+
const byShield = /* @__PURE__ */ new Map();
|
|
13714
|
+
for (const f of scan.findings) {
|
|
13715
|
+
const key = f.source.shieldName;
|
|
13716
|
+
const entry = byShield.get(key) ?? { label: f.source.shieldLabel, findings: [] };
|
|
13717
|
+
entry.findings.push(f);
|
|
13718
|
+
byShield.set(key, entry);
|
|
13719
|
+
}
|
|
13720
|
+
const totalFindings = scan.findings.length;
|
|
13721
|
+
if (totalFindings === 0 && scan.dlpFindings.length === 0) {
|
|
13722
|
+
console.log(import_chalk21.default.green(" \u2705 No findings across all shields and rules."));
|
|
13723
|
+
console.log(import_chalk21.default.dim(" node9 is still worth running \u2014 it monitors in real time.\n"));
|
|
13724
|
+
} else {
|
|
13725
|
+
if (totalFindings > 0) {
|
|
13726
|
+
console.log(
|
|
13727
|
+
" " + import_chalk21.default.bold("If node9 had been installed:") + " " + import_chalk21.default.yellow.bold(
|
|
13728
|
+
`${num2(totalFindings)} command${totalFindings !== 1 ? "s" : ""} flagged for review`
|
|
13729
|
+
)
|
|
13730
|
+
);
|
|
13731
|
+
console.log("");
|
|
13732
|
+
const sorted = [...byShield.entries()].sort(
|
|
13733
|
+
(a, b) => b[1].findings.length - a[1].findings.length
|
|
13734
|
+
);
|
|
13735
|
+
for (const [shieldName, { label, findings }] of sorted) {
|
|
13736
|
+
const count = findings.length;
|
|
13737
|
+
const isUserRule = shieldName === "custom" || shieldName === "cloud";
|
|
13738
|
+
const shieldBadge = isUserRule ? import_chalk21.default.magenta(label) : import_chalk21.default.cyan(label);
|
|
13739
|
+
console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
|
|
13740
|
+
console.log(
|
|
13741
|
+
" " + shieldBadge + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.yellow(`${num2(count)} finding${count !== 1 ? "s" : ""}`) + (isUserRule ? "" : import_chalk21.default.dim(` \u2192 node9 shield enable ${shieldName}`))
|
|
13742
|
+
);
|
|
13743
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
13744
|
+
for (const f of findings) {
|
|
13745
|
+
const ruleKey = f.source.rule.name ?? "unnamed";
|
|
13746
|
+
const arr = byRule.get(ruleKey) ?? [];
|
|
13747
|
+
arr.push(f);
|
|
13748
|
+
byRule.set(ruleKey, arr);
|
|
13749
|
+
}
|
|
13750
|
+
for (const [, ruleFindings] of byRule) {
|
|
13751
|
+
const rule = ruleFindings[0].source.rule;
|
|
13752
|
+
const ruleCount = ruleFindings.length;
|
|
13753
|
+
const countBadge = ruleCount > 1 ? import_chalk21.default.white(` \xD7${ruleCount}`) : "";
|
|
13754
|
+
const shortName = (rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
13755
|
+
console.log(
|
|
13756
|
+
" " + import_chalk21.default.white(shortName) + countBadge + (rule.reason ? import_chalk21.default.dim(` \u2014 ${rule.reason}`) : "")
|
|
13757
|
+
);
|
|
13758
|
+
const shown = ruleFindings.slice(0, topN);
|
|
13759
|
+
for (const f of shown) {
|
|
13760
|
+
const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
|
|
13761
|
+
const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
13762
|
+
const cmd = import_chalk21.default.gray(preview(f.input, 55));
|
|
13763
|
+
console.log(` ${ts}${proj}${cmd}`);
|
|
13764
|
+
}
|
|
13765
|
+
if (ruleFindings.length > topN) {
|
|
13766
|
+
console.log(
|
|
13767
|
+
import_chalk21.default.dim(
|
|
13768
|
+
` \u2026 and ${ruleFindings.length - topN} more (--top ${ruleFindings.length})`
|
|
13769
|
+
)
|
|
13770
|
+
);
|
|
13771
|
+
}
|
|
13772
|
+
}
|
|
13773
|
+
console.log("");
|
|
13774
|
+
}
|
|
13775
|
+
}
|
|
13776
|
+
if (scan.dlpFindings.length > 0) {
|
|
13777
|
+
console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
|
|
13778
|
+
console.log(
|
|
13779
|
+
" " + import_chalk21.default.red.bold("Secrets / DLP") + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.red(
|
|
13780
|
+
`${num2(scan.dlpFindings.length)} potential secret leak${scan.dlpFindings.length !== 1 ? "s" : ""}`
|
|
13781
|
+
)
|
|
13782
|
+
);
|
|
13783
|
+
const shownDlp = scan.dlpFindings.slice(0, topN);
|
|
13784
|
+
for (const f of shownDlp) {
|
|
13785
|
+
const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
|
|
13786
|
+
const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
13787
|
+
console.log(
|
|
13788
|
+
` ${ts}${proj}` + import_chalk21.default.yellow(f.patternName) + import_chalk21.default.dim(" ") + import_chalk21.default.gray(f.redactedSample)
|
|
13789
|
+
);
|
|
13790
|
+
}
|
|
13791
|
+
if (scan.dlpFindings.length > topN) {
|
|
13792
|
+
console.log(
|
|
13793
|
+
import_chalk21.default.dim(
|
|
13794
|
+
` \u2026 and ${scan.dlpFindings.length - topN} more (--top ${scan.dlpFindings.length})`
|
|
13795
|
+
)
|
|
13796
|
+
);
|
|
13797
|
+
}
|
|
13798
|
+
console.log("");
|
|
13799
|
+
}
|
|
13800
|
+
}
|
|
13801
|
+
if (scan.totalCostUSD > 0) {
|
|
13802
|
+
console.log(
|
|
13803
|
+
" " + import_chalk21.default.bold("Claude spend:") + " " + import_chalk21.default.yellow(fmtCost2(scan.totalCostUSD)) + import_chalk21.default.dim(" (for per-period breakdown: node9 report)")
|
|
13804
|
+
);
|
|
13805
|
+
console.log("");
|
|
13806
|
+
}
|
|
13807
|
+
const auditLog = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
|
|
13808
|
+
if (import_fs31.default.existsSync(auditLog)) {
|
|
13809
|
+
console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
13810
|
+
console.log(
|
|
13811
|
+
import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
|
|
13812
|
+
);
|
|
13813
|
+
} else {
|
|
13814
|
+
console.log(import_chalk21.default.yellow.bold(" \u26A1 node9 was not running during these sessions."));
|
|
13815
|
+
console.log(
|
|
13816
|
+
" " + import_chalk21.default.white("Run ") + import_chalk21.default.cyan("node9 init") + import_chalk21.default.white(" to start protecting your AI agents.")
|
|
13817
|
+
);
|
|
13818
|
+
}
|
|
13819
|
+
console.log("");
|
|
13820
|
+
});
|
|
13821
|
+
}
|
|
13822
|
+
|
|
13823
|
+
// src/cli/commands/sessions.ts
|
|
13824
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
13825
|
+
var import_fs32 = __toESM(require("fs"));
|
|
13826
|
+
var import_path35 = __toESM(require("path"));
|
|
13827
|
+
var import_os28 = __toESM(require("os"));
|
|
13828
|
+
var CLAUDE_PRICING3 = {
|
|
13829
|
+
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13830
|
+
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13831
|
+
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
13832
|
+
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13833
|
+
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13834
|
+
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13835
|
+
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13836
|
+
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13837
|
+
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
13838
|
+
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
13839
|
+
};
|
|
13840
|
+
function modelPrice(model) {
|
|
13841
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
13842
|
+
for (const [key, p] of Object.entries(CLAUDE_PRICING3)) {
|
|
13843
|
+
if (base === key || base.startsWith(key)) return p;
|
|
13844
|
+
}
|
|
13845
|
+
return null;
|
|
13846
|
+
}
|
|
13847
|
+
function encodeProjectPath(projectPath) {
|
|
13848
|
+
return projectPath.replace(/\//g, "-");
|
|
13849
|
+
}
|
|
13850
|
+
function sessionJsonlPath(projectPath, sessionId) {
|
|
13851
|
+
const encoded = encodeProjectPath(projectPath);
|
|
13852
|
+
return import_path35.default.join(import_os28.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
13853
|
+
}
|
|
13854
|
+
function projectLabel(projectPath) {
|
|
13855
|
+
return projectPath.replace(import_os28.default.homedir(), "~");
|
|
13856
|
+
}
|
|
13857
|
+
function parseHistoryLines(lines) {
|
|
13858
|
+
const entries = [];
|
|
13859
|
+
for (const line of lines) {
|
|
13860
|
+
if (!line.trim()) continue;
|
|
13861
|
+
try {
|
|
13862
|
+
const obj = JSON.parse(line);
|
|
13863
|
+
if (typeof obj["display"] === "string" && (typeof obj["timestamp"] === "string" || typeof obj["timestamp"] === "number") && typeof obj["project"] === "string" && typeof obj["sessionId"] === "string") {
|
|
13864
|
+
const ts = typeof obj["timestamp"] === "number" ? new Date(obj["timestamp"]).toISOString() : obj["timestamp"];
|
|
13865
|
+
entries.push({
|
|
13866
|
+
display: obj["display"],
|
|
13867
|
+
timestamp: ts,
|
|
13868
|
+
project: obj["project"],
|
|
13869
|
+
sessionId: obj["sessionId"]
|
|
13870
|
+
});
|
|
13871
|
+
}
|
|
13872
|
+
} catch {
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13875
|
+
return entries;
|
|
13876
|
+
}
|
|
13877
|
+
function parseSessionLines(lines) {
|
|
13878
|
+
const toolCalls = [];
|
|
13879
|
+
let costUSD = 0;
|
|
13880
|
+
let hasSnapshot = false;
|
|
13881
|
+
const modifiedFiles = [];
|
|
13882
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
13883
|
+
for (const line of lines) {
|
|
13884
|
+
if (!line.trim()) continue;
|
|
13885
|
+
let entry;
|
|
13886
|
+
try {
|
|
13887
|
+
entry = JSON.parse(line);
|
|
13888
|
+
} catch {
|
|
13889
|
+
continue;
|
|
13890
|
+
}
|
|
13891
|
+
if (entry.type === "file-history-snapshot") {
|
|
13892
|
+
hasSnapshot = true;
|
|
13893
|
+
continue;
|
|
13894
|
+
}
|
|
13895
|
+
if (entry.type !== "assistant") continue;
|
|
13896
|
+
const usage = entry.message?.usage;
|
|
13897
|
+
const model = entry.message?.model;
|
|
13898
|
+
if (usage && model) {
|
|
13899
|
+
const p = modelPrice(model);
|
|
13900
|
+
if (p) {
|
|
13901
|
+
costUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
13902
|
+
}
|
|
13903
|
+
}
|
|
13904
|
+
const content = entry.message?.content;
|
|
13905
|
+
if (!Array.isArray(content)) continue;
|
|
13906
|
+
for (const block of content) {
|
|
13907
|
+
if (block.type !== "tool_use") continue;
|
|
13908
|
+
const tool = block.name ?? "";
|
|
13909
|
+
const input = block.input ?? {};
|
|
13910
|
+
toolCalls.push({ tool, input, timestamp: entry.timestamp ?? "" });
|
|
13911
|
+
const toolLower = tool.toLowerCase();
|
|
13912
|
+
if (toolLower === "write" || toolLower === "edit" || toolLower === "notebookedit") {
|
|
13913
|
+
const fp = input.file_path ?? input.path;
|
|
13914
|
+
if (typeof fp === "string" && !seenFiles.has(fp)) {
|
|
13915
|
+
seenFiles.add(fp);
|
|
13916
|
+
modifiedFiles.push(fp);
|
|
13917
|
+
}
|
|
13918
|
+
}
|
|
13919
|
+
}
|
|
13920
|
+
}
|
|
13921
|
+
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
13922
|
+
}
|
|
13923
|
+
function loadAuditEntries(auditPath) {
|
|
13924
|
+
const aPath = auditPath ?? import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
|
|
13925
|
+
let raw;
|
|
13926
|
+
try {
|
|
13927
|
+
raw = import_fs32.default.readFileSync(aPath, "utf-8");
|
|
13928
|
+
} catch {
|
|
13929
|
+
return [];
|
|
13930
|
+
}
|
|
13931
|
+
const entries = [];
|
|
13932
|
+
for (const line of raw.split("\n")) {
|
|
13933
|
+
if (!line.trim()) continue;
|
|
13934
|
+
try {
|
|
13935
|
+
const e = JSON.parse(line);
|
|
13936
|
+
if (!e.ts || !e.tool || !e.decision) continue;
|
|
13937
|
+
if (e.decision === "allow" || e.decision === "allowed") continue;
|
|
13938
|
+
entries.push(e);
|
|
13939
|
+
} catch {
|
|
13940
|
+
}
|
|
13941
|
+
}
|
|
13942
|
+
return entries;
|
|
13943
|
+
}
|
|
13944
|
+
function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
13945
|
+
const start = new Date(windowStart).getTime();
|
|
13946
|
+
const end = new Date(windowEnd).getTime();
|
|
13947
|
+
const result = [];
|
|
13948
|
+
for (const e of entries) {
|
|
13949
|
+
const t = new Date(e.ts).getTime();
|
|
13950
|
+
if (t < start || t > end) continue;
|
|
13951
|
+
result.push({
|
|
13952
|
+
tool: e.tool,
|
|
13953
|
+
args: e.args,
|
|
13954
|
+
argsHash: e.argsHash,
|
|
13955
|
+
timestamp: e.ts,
|
|
13956
|
+
decision: e.decision,
|
|
13957
|
+
checkedBy: e.checkedBy
|
|
13958
|
+
});
|
|
13959
|
+
}
|
|
13960
|
+
return result;
|
|
13961
|
+
}
|
|
13962
|
+
function buildSessions(days, historyPath) {
|
|
13963
|
+
const hPath = historyPath ?? import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
|
|
13964
|
+
let historyRaw;
|
|
13965
|
+
try {
|
|
13966
|
+
historyRaw = import_fs32.default.readFileSync(hPath, "utf-8");
|
|
13967
|
+
} catch {
|
|
13968
|
+
return [];
|
|
13969
|
+
}
|
|
13970
|
+
const cutoff = days !== null ? (() => {
|
|
13971
|
+
const d = /* @__PURE__ */ new Date();
|
|
13972
|
+
d.setDate(d.getDate() - days);
|
|
13973
|
+
d.setHours(0, 0, 0, 0);
|
|
13974
|
+
return d;
|
|
13975
|
+
})() : null;
|
|
13976
|
+
const entries = parseHistoryLines(historyRaw.split("\n"));
|
|
13977
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
13978
|
+
for (const e of entries) {
|
|
13979
|
+
if (cutoff && new Date(e.timestamp) < cutoff) continue;
|
|
13980
|
+
const existing = bySession.get(e.sessionId);
|
|
13981
|
+
if (!existing || e.timestamp < existing.timestamp) {
|
|
13982
|
+
bySession.set(e.sessionId, e);
|
|
13983
|
+
}
|
|
13984
|
+
}
|
|
13985
|
+
const allAuditEntries = loadAuditEntries();
|
|
13986
|
+
const summaries = [];
|
|
13987
|
+
for (const entry of bySession.values()) {
|
|
13988
|
+
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
13989
|
+
let sessionLines = [];
|
|
13990
|
+
try {
|
|
13991
|
+
sessionLines = import_fs32.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
13992
|
+
} catch {
|
|
13993
|
+
}
|
|
13994
|
+
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
13995
|
+
const windowStart = entry.timestamp;
|
|
13996
|
+
const lastToolTs = toolCalls.length > 0 ? toolCalls[toolCalls.length - 1].timestamp : "";
|
|
13997
|
+
const windowEnd = new Date(
|
|
13998
|
+
Math.max(new Date(windowStart).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
13999
|
+
// 5 min buffer
|
|
14000
|
+
).toISOString();
|
|
14001
|
+
const blockedCalls = auditEntriesInWindow(allAuditEntries, windowStart, windowEnd);
|
|
14002
|
+
summaries.push({
|
|
14003
|
+
sessionId: entry.sessionId,
|
|
14004
|
+
project: entry.project,
|
|
14005
|
+
projectLabel: projectLabel(entry.project),
|
|
14006
|
+
firstPrompt: entry.display,
|
|
14007
|
+
startTime: entry.timestamp,
|
|
14008
|
+
toolCalls,
|
|
14009
|
+
blockedCalls,
|
|
14010
|
+
costUSD,
|
|
14011
|
+
hasSnapshot,
|
|
14012
|
+
modifiedFiles
|
|
14013
|
+
});
|
|
14014
|
+
}
|
|
14015
|
+
summaries.sort((a, b) => a.startTime > b.startTime ? -1 : 1);
|
|
14016
|
+
return summaries;
|
|
14017
|
+
}
|
|
14018
|
+
function fmtCost3(usd) {
|
|
14019
|
+
if (usd === 0) return "";
|
|
14020
|
+
if (usd < 1e-3) return "< $0.001";
|
|
14021
|
+
if (usd < 1) return "$" + usd.toFixed(3);
|
|
14022
|
+
return "$" + usd.toFixed(2);
|
|
14023
|
+
}
|
|
14024
|
+
function fmtDate2(iso) {
|
|
14025
|
+
try {
|
|
14026
|
+
return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
14027
|
+
} catch {
|
|
14028
|
+
return iso.slice(0, 10);
|
|
14029
|
+
}
|
|
14030
|
+
}
|
|
14031
|
+
function fmtTime(iso) {
|
|
14032
|
+
try {
|
|
14033
|
+
return new Date(iso).toLocaleTimeString("en-US", {
|
|
14034
|
+
hour: "2-digit",
|
|
14035
|
+
minute: "2-digit",
|
|
14036
|
+
hour12: false
|
|
14037
|
+
});
|
|
14038
|
+
} catch {
|
|
14039
|
+
return iso.slice(11, 16);
|
|
14040
|
+
}
|
|
14041
|
+
}
|
|
14042
|
+
function fmtDateTime(iso) {
|
|
14043
|
+
try {
|
|
14044
|
+
return new Date(iso).toLocaleString("en-US", {
|
|
14045
|
+
month: "short",
|
|
14046
|
+
day: "numeric",
|
|
14047
|
+
year: "numeric",
|
|
14048
|
+
hour: "2-digit",
|
|
14049
|
+
minute: "2-digit",
|
|
14050
|
+
hour12: false
|
|
14051
|
+
});
|
|
14052
|
+
} catch {
|
|
14053
|
+
return iso;
|
|
14054
|
+
}
|
|
14055
|
+
}
|
|
14056
|
+
function truncate(s, max) {
|
|
14057
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
14058
|
+
}
|
|
14059
|
+
function toolInputSummary(tool, input) {
|
|
14060
|
+
const fp = input.file_path ?? input.path;
|
|
14061
|
+
if (typeof fp === "string") return fp;
|
|
14062
|
+
const cmd = input.command;
|
|
14063
|
+
if (typeof cmd === "string") return truncate(cmd.replace(/\s+/g, " "), 60);
|
|
14064
|
+
const q = input.query ?? input.url;
|
|
14065
|
+
if (typeof q === "string") return truncate(String(q), 60);
|
|
14066
|
+
return "";
|
|
14067
|
+
}
|
|
14068
|
+
function toolColor(tool) {
|
|
14069
|
+
const t = tool.toLowerCase();
|
|
14070
|
+
if (t === "bash" || t === "execute_bash") return import_chalk22.default.red;
|
|
14071
|
+
if (t === "write") return import_chalk22.default.green;
|
|
14072
|
+
if (t === "edit" || t === "notebookedit") return import_chalk22.default.yellow;
|
|
14073
|
+
if (t === "read") return import_chalk22.default.cyan;
|
|
14074
|
+
return import_chalk22.default.gray;
|
|
14075
|
+
}
|
|
14076
|
+
function barStr2(value, max, width) {
|
|
14077
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
14078
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
14079
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
14080
|
+
}
|
|
14081
|
+
function colorBar2(value, max, width) {
|
|
14082
|
+
const s = barStr2(value, max, width);
|
|
14083
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
14084
|
+
return import_chalk22.default.cyan(s.slice(0, filled)) + import_chalk22.default.dim(s.slice(filled));
|
|
14085
|
+
}
|
|
14086
|
+
function renderSummary(summaries) {
|
|
14087
|
+
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
14088
|
+
const totalCost = summaries.reduce((n, s) => n + s.costUSD, 0);
|
|
14089
|
+
const totalFiles = summaries.reduce((n, s) => n + s.modifiedFiles.length, 0);
|
|
14090
|
+
const totalBlocked = summaries.reduce((n, s) => n + s.blockedCalls.length, 0);
|
|
14091
|
+
const snapshots = summaries.filter((s) => s.hasSnapshot).length;
|
|
14092
|
+
const avgCost = summaries.length > 0 ? totalCost / summaries.length : 0;
|
|
14093
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
14094
|
+
for (const s of summaries) {
|
|
14095
|
+
for (const tc of s.toolCalls) {
|
|
14096
|
+
const key = tc.tool.toLowerCase();
|
|
14097
|
+
toolCounts.set(key, (toolCounts.get(key) ?? 0) + 1);
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
const groups = { Bash: 0, Read: 0, Write: 0, Edit: 0, Other: 0 };
|
|
14101
|
+
for (const [tool, count] of toolCounts) {
|
|
14102
|
+
if (tool === "bash" || tool === "execute_bash") groups["Bash"] += count;
|
|
14103
|
+
else if (tool === "read") groups["Read"] += count;
|
|
14104
|
+
else if (tool === "write") groups["Write"] += count;
|
|
14105
|
+
else if (tool === "edit" || tool === "notebookedit") groups["Edit"] += count;
|
|
14106
|
+
else groups["Other"] += count;
|
|
14107
|
+
}
|
|
14108
|
+
const projCosts = /* @__PURE__ */ new Map();
|
|
14109
|
+
for (const s of summaries) {
|
|
14110
|
+
projCosts.set(s.projectLabel, (projCosts.get(s.projectLabel) ?? 0) + s.costUSD);
|
|
14111
|
+
}
|
|
14112
|
+
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
14113
|
+
const W = 20;
|
|
14114
|
+
console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
|
|
14115
|
+
console.log(
|
|
14116
|
+
" " + import_chalk22.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk22.default.dim("sessions ") + import_chalk22.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk22.default.dim("total ") + import_chalk22.default.bold.white(String(totalTools).padEnd(6)) + import_chalk22.default.dim("tool calls ") + import_chalk22.default.bold.white(String(totalFiles)) + import_chalk22.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk22.default.dim(" ") + import_chalk22.default.red.bold(String(totalBlocked)) + import_chalk22.default.dim(" blocked by node9") : "")
|
|
14117
|
+
);
|
|
14118
|
+
console.log(
|
|
14119
|
+
" " + import_chalk22.default.dim("avg ") + import_chalk22.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk22.default.dim("/session ") + import_chalk22.default.green(String(snapshots)) + import_chalk22.default.dim(` of ${summaries.length} sessions had snapshots`)
|
|
14120
|
+
);
|
|
14121
|
+
console.log("");
|
|
14122
|
+
console.log(" " + import_chalk22.default.dim("Tool breakdown:"));
|
|
14123
|
+
const maxGroup = Math.max(...Object.values(groups));
|
|
14124
|
+
for (const [label, count] of Object.entries(groups)) {
|
|
14125
|
+
if (count === 0) continue;
|
|
14126
|
+
const pct2 = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
14127
|
+
console.log(
|
|
14128
|
+
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk22.default.white(String(count).padStart(4)) + import_chalk22.default.dim(` (${String(pct2)}%)`)
|
|
14129
|
+
);
|
|
14130
|
+
}
|
|
14131
|
+
console.log("");
|
|
14132
|
+
if (topProjects.length > 1) {
|
|
14133
|
+
console.log(" " + import_chalk22.default.dim("Cost by project:"));
|
|
14134
|
+
const maxProjCost = topProjects[0][1];
|
|
14135
|
+
for (const [proj, cost] of topProjects) {
|
|
14136
|
+
console.log(
|
|
14137
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk22.default.yellow(fmtCost3(cost))
|
|
14138
|
+
);
|
|
14139
|
+
}
|
|
14140
|
+
console.log("");
|
|
14141
|
+
}
|
|
14142
|
+
console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
|
|
14143
|
+
console.log("");
|
|
14144
|
+
}
|
|
14145
|
+
function renderList(summaries, totalCost) {
|
|
14146
|
+
if (summaries.length === 0) {
|
|
14147
|
+
console.log(import_chalk22.default.yellow(" No sessions found in the requested range.\n"));
|
|
14148
|
+
return;
|
|
14149
|
+
}
|
|
14150
|
+
const totalLabel = totalCost > 0 ? import_chalk22.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
14151
|
+
console.log(
|
|
14152
|
+
" " + import_chalk22.default.white(String(summaries.length)) + import_chalk22.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
14153
|
+
);
|
|
14154
|
+
console.log("");
|
|
14155
|
+
let lastGroup = "";
|
|
14156
|
+
for (const s of summaries) {
|
|
14157
|
+
const group = fmtDate2(s.startTime) + " " + s.projectLabel;
|
|
14158
|
+
if (group !== lastGroup) {
|
|
14159
|
+
console.log(
|
|
14160
|
+
import_chalk22.default.dim(" \u2500\u2500\u2500 ") + import_chalk22.default.bold(fmtDate2(s.startTime)) + import_chalk22.default.dim(" " + s.projectLabel)
|
|
14161
|
+
);
|
|
14162
|
+
lastGroup = group;
|
|
14163
|
+
}
|
|
14164
|
+
const timeStr = import_chalk22.default.dim(fmtTime(s.startTime));
|
|
14165
|
+
const prompt = import_chalk22.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
14166
|
+
const tools = s.toolCalls.length > 0 ? import_chalk22.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk22.default.dim(" 0 tools");
|
|
14167
|
+
const cost = s.costUSD > 0 ? import_chalk22.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
14168
|
+
const blocked = s.blockedCalls.length > 0 ? import_chalk22.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
14169
|
+
const snap = s.hasSnapshot ? import_chalk22.default.green(" \u{1F4F8}") : "";
|
|
14170
|
+
const sid = import_chalk22.default.dim(" " + s.sessionId.slice(0, 8));
|
|
14171
|
+
console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${sid}`);
|
|
14172
|
+
}
|
|
14173
|
+
console.log("");
|
|
14174
|
+
console.log(
|
|
14175
|
+
import_chalk22.default.dim(" Run") + " " + import_chalk22.default.cyan("node9 sessions --detail <session-id>") + import_chalk22.default.dim(" for full tool trace.")
|
|
14176
|
+
);
|
|
14177
|
+
console.log("");
|
|
14178
|
+
}
|
|
14179
|
+
function renderDetail(s) {
|
|
14180
|
+
console.log("");
|
|
14181
|
+
console.log(import_chalk22.default.bold(" Session ") + import_chalk22.default.dim(s.sessionId));
|
|
14182
|
+
console.log(
|
|
14183
|
+
import_chalk22.default.bold(" Prompt ") + import_chalk22.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
14184
|
+
);
|
|
14185
|
+
console.log(import_chalk22.default.bold(" Project ") + import_chalk22.default.white(s.projectLabel));
|
|
14186
|
+
console.log(import_chalk22.default.bold(" When ") + import_chalk22.default.white(fmtDateTime(s.startTime)));
|
|
14187
|
+
if (s.costUSD > 0)
|
|
14188
|
+
console.log(import_chalk22.default.bold(" Cost ") + import_chalk22.default.yellow("~" + fmtCost3(s.costUSD)));
|
|
14189
|
+
console.log(
|
|
14190
|
+
import_chalk22.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk22.default.green("\u2713 taken") : import_chalk22.default.dim("none"))
|
|
14191
|
+
);
|
|
14192
|
+
console.log("");
|
|
14193
|
+
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
14194
|
+
console.log(import_chalk22.default.dim(" No tool calls recorded.\n"));
|
|
14195
|
+
return;
|
|
14196
|
+
}
|
|
14197
|
+
const timeline = [
|
|
14198
|
+
...s.toolCalls.map((tc) => ({ kind: "tool", tc })),
|
|
14199
|
+
...s.blockedCalls.map((bc) => ({ kind: "blocked", bc }))
|
|
14200
|
+
].sort((a, b) => {
|
|
14201
|
+
const ta = a.kind === "tool" ? a.tc.timestamp : a.bc.timestamp;
|
|
14202
|
+
const tb = b.kind === "tool" ? b.tc.timestamp : b.bc.timestamp;
|
|
14203
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
14204
|
+
});
|
|
14205
|
+
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
14206
|
+
if (s.blockedCalls.length > 0)
|
|
14207
|
+
headerParts.push(import_chalk22.default.red(`${s.blockedCalls.length} blocked by node9`));
|
|
14208
|
+
console.log(import_chalk22.default.bold(" " + headerParts.join(" \xB7 ")));
|
|
14209
|
+
console.log("");
|
|
14210
|
+
for (const entry of timeline) {
|
|
14211
|
+
if (entry.kind === "tool") {
|
|
14212
|
+
const tc = entry.tc;
|
|
14213
|
+
const colorFn = toolColor(tc.tool);
|
|
14214
|
+
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
14215
|
+
const detail = import_chalk22.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
14216
|
+
const ts = tc.timestamp ? import_chalk22.default.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
14217
|
+
console.log(` ${ts}${toolPad} ${detail}`);
|
|
14218
|
+
} else {
|
|
14219
|
+
const bc = entry.bc;
|
|
14220
|
+
const ts = bc.timestamp ? import_chalk22.default.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
14221
|
+
const label = import_chalk22.default.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
14222
|
+
const toolName = import_chalk22.default.red(bc.tool.padEnd(10));
|
|
14223
|
+
const argsSummary = bc.args ? import_chalk22.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk22.default.dim("[args not logged]");
|
|
14224
|
+
const reason = bc.checkedBy ? import_chalk22.default.dim(" \u2190 " + bc.checkedBy) : "";
|
|
14225
|
+
console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
|
|
14226
|
+
}
|
|
14227
|
+
}
|
|
14228
|
+
console.log("");
|
|
14229
|
+
if (s.modifiedFiles.length > 0) {
|
|
14230
|
+
console.log(import_chalk22.default.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
14231
|
+
for (const f of s.modifiedFiles) {
|
|
14232
|
+
console.log(" " + import_chalk22.default.yellow(f));
|
|
14233
|
+
}
|
|
14234
|
+
console.log("");
|
|
14235
|
+
}
|
|
14236
|
+
}
|
|
14237
|
+
function registerSessionsCommand(program2) {
|
|
14238
|
+
program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
|
|
14239
|
+
console.log("");
|
|
14240
|
+
console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
|
|
14241
|
+
console.log("");
|
|
14242
|
+
const historyPath = import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
|
|
14243
|
+
if (!import_fs32.default.existsSync(historyPath)) {
|
|
14244
|
+
console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14245
|
+
console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14246
|
+
return;
|
|
14247
|
+
}
|
|
14248
|
+
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
14249
|
+
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
14250
|
+
console.log(import_chalk22.default.dim(" " + rangeLabel));
|
|
14251
|
+
console.log("");
|
|
14252
|
+
process.stdout.write(import_chalk22.default.dim(" Loading\u2026"));
|
|
14253
|
+
const summaries = buildSessions(days);
|
|
14254
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
14255
|
+
if (options.detail) {
|
|
14256
|
+
const target = summaries.find(
|
|
14257
|
+
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
14258
|
+
);
|
|
14259
|
+
if (!target) {
|
|
14260
|
+
console.log(import_chalk22.default.red(` Session not found: ${options.detail}`));
|
|
14261
|
+
console.log(import_chalk22.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
14262
|
+
return;
|
|
14263
|
+
}
|
|
14264
|
+
renderDetail(target);
|
|
14265
|
+
return;
|
|
14266
|
+
}
|
|
14267
|
+
const totalCost = summaries.reduce((sum, s) => sum + s.costUSD, 0);
|
|
14268
|
+
if (summaries.length > 0) renderSummary(summaries);
|
|
14269
|
+
renderList(summaries, totalCost);
|
|
14270
|
+
});
|
|
14271
|
+
}
|
|
14272
|
+
|
|
12480
14273
|
// src/cli.ts
|
|
12481
14274
|
var { version } = JSON.parse(
|
|
12482
|
-
|
|
14275
|
+
import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
|
|
12483
14276
|
);
|
|
12484
14277
|
var program = new import_commander.Command();
|
|
12485
14278
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
12486
14279
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
12487
|
-
const
|
|
12488
|
-
const credPath =
|
|
12489
|
-
if (!
|
|
12490
|
-
|
|
14280
|
+
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14281
|
+
const credPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "credentials.json");
|
|
14282
|
+
if (!import_fs35.default.existsSync(import_path38.default.dirname(credPath)))
|
|
14283
|
+
import_fs35.default.mkdirSync(import_path38.default.dirname(credPath), { recursive: true });
|
|
12491
14284
|
const profileName = options.profile || "default";
|
|
12492
14285
|
let existingCreds = {};
|
|
12493
14286
|
try {
|
|
12494
|
-
if (
|
|
12495
|
-
const raw = JSON.parse(
|
|
14287
|
+
if (import_fs35.default.existsSync(credPath)) {
|
|
14288
|
+
const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
|
|
12496
14289
|
if (raw.apiKey) {
|
|
12497
14290
|
existingCreds = {
|
|
12498
|
-
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl ||
|
|
14291
|
+
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
12499
14292
|
};
|
|
12500
14293
|
} else {
|
|
12501
14294
|
existingCreds = raw;
|
|
@@ -12503,14 +14296,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12503
14296
|
}
|
|
12504
14297
|
} catch {
|
|
12505
14298
|
}
|
|
12506
|
-
existingCreds[profileName] = { apiKey, apiUrl:
|
|
12507
|
-
|
|
14299
|
+
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14300
|
+
import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
12508
14301
|
if (profileName === "default") {
|
|
12509
|
-
const configPath =
|
|
14302
|
+
const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
|
|
12510
14303
|
let config = {};
|
|
12511
14304
|
try {
|
|
12512
|
-
if (
|
|
12513
|
-
config = JSON.parse(
|
|
14305
|
+
if (import_fs35.default.existsSync(configPath))
|
|
14306
|
+
config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
|
|
12514
14307
|
} catch {
|
|
12515
14308
|
}
|
|
12516
14309
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -12525,39 +14318,47 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12525
14318
|
approvers.cloud = false;
|
|
12526
14319
|
}
|
|
12527
14320
|
s.approvers = approvers;
|
|
12528
|
-
if (!
|
|
12529
|
-
|
|
12530
|
-
|
|
14321
|
+
if (!import_fs35.default.existsSync(import_path38.default.dirname(configPath)))
|
|
14322
|
+
import_fs35.default.mkdirSync(import_path38.default.dirname(configPath), { recursive: true });
|
|
14323
|
+
import_fs35.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
12531
14324
|
}
|
|
12532
14325
|
if (options.profile && profileName !== "default") {
|
|
12533
|
-
console.log(
|
|
12534
|
-
console.log(
|
|
14326
|
+
console.log(import_chalk24.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
14327
|
+
console.log(import_chalk24.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
12535
14328
|
} else if (options.local) {
|
|
12536
|
-
console.log(
|
|
12537
|
-
console.log(
|
|
14329
|
+
console.log(import_chalk24.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
14330
|
+
console.log(import_chalk24.default.gray(` All decisions stay on this machine.`));
|
|
12538
14331
|
} else {
|
|
12539
|
-
console.log(
|
|
12540
|
-
console.log(
|
|
14332
|
+
console.log(import_chalk24.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
14333
|
+
console.log(import_chalk24.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
12541
14334
|
}
|
|
12542
14335
|
});
|
|
12543
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
14336
|
+
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
|
|
12544
14337
|
if (target === "gemini") return await setupGemini();
|
|
12545
14338
|
if (target === "claude") return await setupClaude();
|
|
12546
14339
|
if (target === "cursor") return await setupCursor();
|
|
14340
|
+
if (target === "windsurf") return await setupWindsurf();
|
|
14341
|
+
if (target === "vscode") return await setupVSCode();
|
|
12547
14342
|
if (target === "hud") return setupHud();
|
|
12548
|
-
console.error(
|
|
14343
|
+
console.error(
|
|
14344
|
+
import_chalk24.default.red(
|
|
14345
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14346
|
+
)
|
|
14347
|
+
);
|
|
12549
14348
|
process.exit(1);
|
|
12550
14349
|
});
|
|
12551
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
14350
|
+
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
|
|
12552
14351
|
if (!target) {
|
|
12553
|
-
console.log(
|
|
12554
|
-
console.log(" Usage: " +
|
|
14352
|
+
console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
14353
|
+
console.log(" Usage: " + import_chalk24.default.white("node9 setup <target>") + "\n");
|
|
12555
14354
|
console.log(" Targets:");
|
|
12556
|
-
console.log(" " +
|
|
12557
|
-
console.log(" " +
|
|
12558
|
-
console.log(" " +
|
|
14355
|
+
console.log(" " + import_chalk24.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
14356
|
+
console.log(" " + import_chalk24.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
14357
|
+
console.log(" " + import_chalk24.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
14358
|
+
console.log(" " + import_chalk24.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
14359
|
+
console.log(" " + import_chalk24.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
12559
14360
|
process.stdout.write(
|
|
12560
|
-
" " +
|
|
14361
|
+
" " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
12561
14362
|
);
|
|
12562
14363
|
console.log("");
|
|
12563
14364
|
return;
|
|
@@ -12566,93 +14367,108 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
12566
14367
|
if (t === "gemini") return await setupGemini();
|
|
12567
14368
|
if (t === "claude") return await setupClaude();
|
|
12568
14369
|
if (t === "cursor") return await setupCursor();
|
|
14370
|
+
if (t === "windsurf") return await setupWindsurf();
|
|
14371
|
+
if (t === "vscode") return await setupVSCode();
|
|
12569
14372
|
if (t === "hud") return setupHud();
|
|
12570
|
-
console.error(
|
|
14373
|
+
console.error(
|
|
14374
|
+
import_chalk24.default.red(
|
|
14375
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14376
|
+
)
|
|
14377
|
+
);
|
|
12571
14378
|
process.exit(1);
|
|
12572
14379
|
});
|
|
12573
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument(
|
|
14380
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
|
|
14381
|
+
"<target>",
|
|
14382
|
+
"The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
|
|
14383
|
+
).action((target) => {
|
|
12574
14384
|
let fn;
|
|
12575
14385
|
if (target === "claude") fn = teardownClaude;
|
|
12576
14386
|
else if (target === "gemini") fn = teardownGemini;
|
|
12577
14387
|
else if (target === "cursor") fn = teardownCursor;
|
|
14388
|
+
else if (target === "windsurf") fn = teardownWindsurf;
|
|
14389
|
+
else if (target === "vscode") fn = teardownVSCode;
|
|
12578
14390
|
else if (target === "hud") fn = teardownHud;
|
|
12579
14391
|
else {
|
|
12580
14392
|
console.error(
|
|
12581
|
-
|
|
14393
|
+
import_chalk24.default.red(
|
|
14394
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14395
|
+
)
|
|
12582
14396
|
);
|
|
12583
14397
|
process.exit(1);
|
|
12584
14398
|
}
|
|
12585
|
-
console.log(
|
|
14399
|
+
console.log(import_chalk24.default.cyan(`
|
|
12586
14400
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
12587
14401
|
`));
|
|
12588
14402
|
try {
|
|
12589
14403
|
fn();
|
|
12590
14404
|
} catch (err2) {
|
|
12591
|
-
console.error(
|
|
14405
|
+
console.error(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
12592
14406
|
process.exit(1);
|
|
12593
14407
|
}
|
|
12594
|
-
console.log(
|
|
14408
|
+
console.log(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
|
|
12595
14409
|
});
|
|
12596
14410
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
12597
|
-
console.log(
|
|
12598
|
-
console.log(
|
|
14411
|
+
console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
14412
|
+
console.log(import_chalk24.default.bold("Stopping daemon..."));
|
|
12599
14413
|
try {
|
|
12600
14414
|
stopDaemon();
|
|
12601
|
-
console.log(
|
|
14415
|
+
console.log(import_chalk24.default.green(" \u2705 Daemon stopped"));
|
|
12602
14416
|
} catch {
|
|
12603
|
-
console.log(
|
|
14417
|
+
console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
12604
14418
|
}
|
|
12605
|
-
console.log(
|
|
14419
|
+
console.log(import_chalk24.default.bold("\nRemoving hooks..."));
|
|
12606
14420
|
let teardownFailed = false;
|
|
12607
14421
|
for (const [label, fn] of [
|
|
12608
14422
|
["Claude", teardownClaude],
|
|
12609
14423
|
["Gemini", teardownGemini],
|
|
12610
|
-
["Cursor", teardownCursor]
|
|
14424
|
+
["Cursor", teardownCursor],
|
|
14425
|
+
["Windsurf", teardownWindsurf],
|
|
14426
|
+
["VSCode", teardownVSCode]
|
|
12611
14427
|
]) {
|
|
12612
14428
|
try {
|
|
12613
14429
|
fn();
|
|
12614
14430
|
} catch (err2) {
|
|
12615
14431
|
teardownFailed = true;
|
|
12616
14432
|
console.error(
|
|
12617
|
-
|
|
14433
|
+
import_chalk24.default.red(
|
|
12618
14434
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
12619
14435
|
)
|
|
12620
14436
|
);
|
|
12621
14437
|
}
|
|
12622
14438
|
}
|
|
12623
14439
|
if (options.purge) {
|
|
12624
|
-
const node9Dir =
|
|
12625
|
-
if (
|
|
14440
|
+
const node9Dir = import_path38.default.join(import_os31.default.homedir(), ".node9");
|
|
14441
|
+
if (import_fs35.default.existsSync(node9Dir)) {
|
|
12626
14442
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
12627
14443
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
12628
14444
|
default: false
|
|
12629
14445
|
});
|
|
12630
14446
|
if (confirmed) {
|
|
12631
|
-
|
|
12632
|
-
if (
|
|
14447
|
+
import_fs35.default.rmSync(node9Dir, { recursive: true });
|
|
14448
|
+
if (import_fs35.default.existsSync(node9Dir)) {
|
|
12633
14449
|
console.error(
|
|
12634
|
-
|
|
14450
|
+
import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
12635
14451
|
);
|
|
12636
14452
|
} else {
|
|
12637
|
-
console.log(
|
|
14453
|
+
console.log(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
12638
14454
|
}
|
|
12639
14455
|
} else {
|
|
12640
|
-
console.log(
|
|
14456
|
+
console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
12641
14457
|
}
|
|
12642
14458
|
} else {
|
|
12643
|
-
console.log(
|
|
14459
|
+
console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
12644
14460
|
}
|
|
12645
14461
|
} else {
|
|
12646
14462
|
console.log(
|
|
12647
|
-
|
|
14463
|
+
import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
12648
14464
|
);
|
|
12649
14465
|
}
|
|
12650
14466
|
if (teardownFailed) {
|
|
12651
|
-
console.error(
|
|
14467
|
+
console.error(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
12652
14468
|
process.exit(1);
|
|
12653
14469
|
}
|
|
12654
|
-
console.log(
|
|
12655
|
-
console.log(
|
|
14470
|
+
console.log(import_chalk24.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
14471
|
+
console.log(import_chalk24.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
12656
14472
|
});
|
|
12657
14473
|
registerDoctorCommand(program, version);
|
|
12658
14474
|
program.command("explain").description(
|
|
@@ -12665,7 +14481,7 @@ program.command("explain").description(
|
|
|
12665
14481
|
try {
|
|
12666
14482
|
args = JSON.parse(trimmed);
|
|
12667
14483
|
} catch {
|
|
12668
|
-
console.error(
|
|
14484
|
+
console.error(import_chalk24.default.red(`
|
|
12669
14485
|
\u274C Invalid JSON: ${trimmed}
|
|
12670
14486
|
`));
|
|
12671
14487
|
process.exit(1);
|
|
@@ -12676,54 +14492,54 @@ program.command("explain").description(
|
|
|
12676
14492
|
}
|
|
12677
14493
|
const result = await explainPolicy(tool, args);
|
|
12678
14494
|
console.log("");
|
|
12679
|
-
console.log(
|
|
14495
|
+
console.log(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
12680
14496
|
console.log("");
|
|
12681
|
-
console.log(` ${
|
|
14497
|
+
console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
|
|
12682
14498
|
if (argsRaw) {
|
|
12683
|
-
const
|
|
12684
|
-
console.log(` ${
|
|
14499
|
+
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14500
|
+
console.log(` ${import_chalk24.default.bold("Input:")} ${import_chalk24.default.gray(preview2)}`);
|
|
12685
14501
|
}
|
|
12686
14502
|
console.log("");
|
|
12687
|
-
console.log(
|
|
14503
|
+
console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
|
|
12688
14504
|
for (const tier of result.waterfall) {
|
|
12689
|
-
const
|
|
14505
|
+
const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
|
|
12690
14506
|
const label = tier.label.padEnd(16);
|
|
12691
14507
|
let statusStr;
|
|
12692
14508
|
if (tier.tier === 1) {
|
|
12693
|
-
statusStr =
|
|
14509
|
+
statusStr = import_chalk24.default.gray(tier.note ?? "");
|
|
12694
14510
|
} else if (tier.status === "active") {
|
|
12695
|
-
const loc = tier.path ?
|
|
12696
|
-
const note = tier.note ?
|
|
12697
|
-
statusStr =
|
|
14511
|
+
const loc = tier.path ? import_chalk24.default.gray(tier.path) : "";
|
|
14512
|
+
const note = tier.note ? import_chalk24.default.gray(`(${tier.note})`) : "";
|
|
14513
|
+
statusStr = import_chalk24.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
12698
14514
|
} else {
|
|
12699
|
-
statusStr =
|
|
14515
|
+
statusStr = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
12700
14516
|
}
|
|
12701
|
-
console.log(`${
|
|
14517
|
+
console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
|
|
12702
14518
|
}
|
|
12703
14519
|
console.log("");
|
|
12704
|
-
console.log(
|
|
14520
|
+
console.log(import_chalk24.default.bold("Policy Evaluation:"));
|
|
12705
14521
|
for (const step of result.steps) {
|
|
12706
14522
|
const isFinal = step.isFinal;
|
|
12707
14523
|
let icon;
|
|
12708
|
-
if (step.outcome === "allow") icon =
|
|
12709
|
-
else if (step.outcome === "review") icon =
|
|
12710
|
-
else if (step.outcome === "skip") icon =
|
|
12711
|
-
else icon =
|
|
14524
|
+
if (step.outcome === "allow") icon = import_chalk24.default.green(" \u2705");
|
|
14525
|
+
else if (step.outcome === "review") icon = import_chalk24.default.red(" \u{1F534}");
|
|
14526
|
+
else if (step.outcome === "skip") icon = import_chalk24.default.gray(" \u2500 ");
|
|
14527
|
+
else icon = import_chalk24.default.gray(" \u25CB ");
|
|
12712
14528
|
const name = step.name.padEnd(18);
|
|
12713
|
-
const nameStr = isFinal ?
|
|
12714
|
-
const detail = isFinal ?
|
|
12715
|
-
const arrow = isFinal ?
|
|
14529
|
+
const nameStr = isFinal ? import_chalk24.default.white.bold(name) : import_chalk24.default.white(name);
|
|
14530
|
+
const detail = isFinal ? import_chalk24.default.white(step.detail) : import_chalk24.default.gray(step.detail);
|
|
14531
|
+
const arrow = isFinal ? import_chalk24.default.yellow(" \u2190 STOP") : "";
|
|
12716
14532
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
12717
14533
|
}
|
|
12718
14534
|
console.log("");
|
|
12719
14535
|
if (result.decision === "allow") {
|
|
12720
|
-
console.log(
|
|
14536
|
+
console.log(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
|
|
12721
14537
|
} else {
|
|
12722
14538
|
console.log(
|
|
12723
|
-
|
|
14539
|
+
import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
|
|
12724
14540
|
);
|
|
12725
14541
|
if (result.blockedByLabel) {
|
|
12726
|
-
console.log(
|
|
14542
|
+
console.log(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
12727
14543
|
}
|
|
12728
14544
|
}
|
|
12729
14545
|
console.log("");
|
|
@@ -12738,7 +14554,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
12738
14554
|
try {
|
|
12739
14555
|
await startTail2(options);
|
|
12740
14556
|
} catch (err2) {
|
|
12741
|
-
console.error(
|
|
14557
|
+
console.error(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
12742
14558
|
process.exit(1);
|
|
12743
14559
|
}
|
|
12744
14560
|
});
|
|
@@ -12770,14 +14586,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12770
14586
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12771
14587
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
12772
14588
|
if (subcommand === "debug") {
|
|
12773
|
-
const flagFile =
|
|
14589
|
+
const flagFile = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
|
|
12774
14590
|
if (state === "on") {
|
|
12775
|
-
|
|
12776
|
-
|
|
14591
|
+
import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
|
|
14592
|
+
import_fs35.default.writeFileSync(flagFile, "");
|
|
12777
14593
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12778
14594
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12779
14595
|
} else if (state === "off") {
|
|
12780
|
-
if (
|
|
14596
|
+
if (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
|
|
12781
14597
|
console.log("HUD debug logging disabled.");
|
|
12782
14598
|
} else {
|
|
12783
14599
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12792,7 +14608,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12792
14608
|
const ms = parseDuration(options.duration);
|
|
12793
14609
|
if (ms === null) {
|
|
12794
14610
|
console.error(
|
|
12795
|
-
|
|
14611
|
+
import_chalk24.default.red(`
|
|
12796
14612
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
12797
14613
|
`)
|
|
12798
14614
|
);
|
|
@@ -12800,20 +14616,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12800
14616
|
}
|
|
12801
14617
|
pauseNode9(ms, options.duration);
|
|
12802
14618
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
12803
|
-
console.log(
|
|
14619
|
+
console.log(import_chalk24.default.yellow(`
|
|
12804
14620
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
12805
|
-
console.log(
|
|
12806
|
-
console.log(
|
|
14621
|
+
console.log(import_chalk24.default.gray(` All tool calls will be allowed without review.`));
|
|
14622
|
+
console.log(import_chalk24.default.gray(` Run "node9 resume" to re-enable early.
|
|
12807
14623
|
`));
|
|
12808
14624
|
});
|
|
12809
14625
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
12810
14626
|
const { paused } = checkPause();
|
|
12811
14627
|
if (!paused) {
|
|
12812
|
-
console.log(
|
|
14628
|
+
console.log(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
12813
14629
|
return;
|
|
12814
14630
|
}
|
|
12815
14631
|
resumeNode9();
|
|
12816
|
-
console.log(
|
|
14632
|
+
console.log(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
12817
14633
|
});
|
|
12818
14634
|
var HOOK_BASED_AGENTS = {
|
|
12819
14635
|
claude: "claude",
|
|
@@ -12826,15 +14642,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12826
14642
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
12827
14643
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
12828
14644
|
console.error(
|
|
12829
|
-
|
|
14645
|
+
import_chalk24.default.yellow(`
|
|
12830
14646
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
12831
14647
|
);
|
|
12832
|
-
console.error(
|
|
14648
|
+
console.error(import_chalk24.default.white(`
|
|
12833
14649
|
"${target}" uses its own hook system. Use:`));
|
|
12834
14650
|
console.error(
|
|
12835
|
-
|
|
14651
|
+
import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
|
|
12836
14652
|
);
|
|
12837
|
-
console.error(
|
|
14653
|
+
console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
|
|
12838
14654
|
process.exit(1);
|
|
12839
14655
|
}
|
|
12840
14656
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -12851,7 +14667,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12851
14667
|
}
|
|
12852
14668
|
);
|
|
12853
14669
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
12854
|
-
console.error(
|
|
14670
|
+
console.error(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
12855
14671
|
const daemonReady = await autoStartDaemonAndWait();
|
|
12856
14672
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
12857
14673
|
}
|
|
@@ -12864,12 +14680,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12864
14680
|
}
|
|
12865
14681
|
if (!result.approved) {
|
|
12866
14682
|
console.error(
|
|
12867
|
-
|
|
14683
|
+
import_chalk24.default.red(`
|
|
12868
14684
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
12869
14685
|
);
|
|
12870
14686
|
process.exit(1);
|
|
12871
14687
|
}
|
|
12872
|
-
console.error(
|
|
14688
|
+
console.error(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
12873
14689
|
await runProxy(fullCommand);
|
|
12874
14690
|
} else {
|
|
12875
14691
|
program.help();
|
|
@@ -12879,14 +14695,18 @@ registerUndoCommand(program);
|
|
|
12879
14695
|
registerShieldCommand(program);
|
|
12880
14696
|
registerConfigShowCommand(program);
|
|
12881
14697
|
registerTrustCommand(program);
|
|
14698
|
+
registerSyncCommand(program);
|
|
14699
|
+
registerAgentsCommand(program);
|
|
14700
|
+
registerScanCommand(program);
|
|
14701
|
+
registerSessionsCommand(program);
|
|
12882
14702
|
if (process.argv[2] !== "daemon") {
|
|
12883
14703
|
process.on("unhandledRejection", (reason) => {
|
|
12884
14704
|
const isCheckHook = process.argv[2] === "check";
|
|
12885
14705
|
if (isCheckHook) {
|
|
12886
14706
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12887
|
-
const logPath =
|
|
14707
|
+
const logPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
|
|
12888
14708
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12889
|
-
|
|
14709
|
+
import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12890
14710
|
`);
|
|
12891
14711
|
}
|
|
12892
14712
|
process.exit(0);
|