@node9/proxy 1.10.2 → 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 +2284 -499
- package/dist/cli.mjs +2242 -456
- package/dist/index.js +22 -3
- package/dist/index.mjs +22 -3
- 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
|
}
|
|
@@ -2469,8 +2488,8 @@ function isDaemonRunning() {
|
|
|
2469
2488
|
if (port !== DAEMON_PORT) {
|
|
2470
2489
|
return false;
|
|
2471
2490
|
}
|
|
2472
|
-
const
|
|
2473
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid >
|
|
2491
|
+
const MAX_PID2 = 4194304;
|
|
2492
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID2) {
|
|
2474
2493
|
return false;
|
|
2475
2494
|
}
|
|
2476
2495
|
try {
|
|
@@ -6266,9 +6285,146 @@ var init_costSync = __esm({
|
|
|
6266
6285
|
}
|
|
6267
6286
|
});
|
|
6268
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
|
+
|
|
6269
6424
|
// src/daemon/server.ts
|
|
6270
6425
|
function startDaemon() {
|
|
6271
6426
|
startCostSync();
|
|
6427
|
+
startCloudSync();
|
|
6272
6428
|
loadInsightCounts();
|
|
6273
6429
|
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6274
6430
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -6284,7 +6440,7 @@ function startDaemon() {
|
|
|
6284
6440
|
idleTimer = setTimeout(() => {
|
|
6285
6441
|
if (autoStarted) {
|
|
6286
6442
|
try {
|
|
6287
|
-
|
|
6443
|
+
import_fs18.default.unlinkSync(DAEMON_PID_FILE);
|
|
6288
6444
|
} catch {
|
|
6289
6445
|
}
|
|
6290
6446
|
}
|
|
@@ -6447,7 +6603,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6447
6603
|
status: "pending"
|
|
6448
6604
|
});
|
|
6449
6605
|
}
|
|
6450
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6606
|
+
const projectCwd = typeof cwd === "string" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6451
6607
|
const projectConfig = getConfig(projectCwd);
|
|
6452
6608
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6453
6609
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6837,8 +6993,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6837
6993
|
const body = await readBody(req);
|
|
6838
6994
|
const data = body ? JSON.parse(body) : {};
|
|
6839
6995
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6840
|
-
const node9Dir =
|
|
6841
|
-
if (!
|
|
6996
|
+
const node9Dir = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6997
|
+
if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
|
|
6842
6998
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6843
6999
|
return res.end(
|
|
6844
7000
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6949,14 +7105,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6949
7105
|
server.on("error", (e) => {
|
|
6950
7106
|
if (e.code === "EADDRINUSE") {
|
|
6951
7107
|
try {
|
|
6952
|
-
if (
|
|
6953
|
-
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"));
|
|
6954
7110
|
process.kill(pid, 0);
|
|
6955
7111
|
return process.exit(0);
|
|
6956
7112
|
}
|
|
6957
7113
|
} catch {
|
|
6958
7114
|
try {
|
|
6959
|
-
|
|
7115
|
+
import_fs18.default.unlinkSync(DAEMON_PID_FILE);
|
|
6960
7116
|
} catch {
|
|
6961
7117
|
}
|
|
6962
7118
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -7015,13 +7171,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
7015
7171
|
}
|
|
7016
7172
|
startActivitySocket();
|
|
7017
7173
|
}
|
|
7018
|
-
var import_http,
|
|
7174
|
+
var import_http, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
|
|
7019
7175
|
var init_server = __esm({
|
|
7020
7176
|
"src/daemon/server.ts"() {
|
|
7021
7177
|
"use strict";
|
|
7022
7178
|
import_http = __toESM(require("http"));
|
|
7023
|
-
|
|
7024
|
-
|
|
7179
|
+
import_fs18 = __toESM(require("fs"));
|
|
7180
|
+
import_path21 = __toESM(require("path"));
|
|
7025
7181
|
import_crypto7 = require("crypto");
|
|
7026
7182
|
import_child_process4 = require("child_process");
|
|
7027
7183
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -7032,57 +7188,327 @@ var init_server = __esm({
|
|
|
7032
7188
|
init_patch();
|
|
7033
7189
|
init_config_schema();
|
|
7034
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");
|
|
7035
7439
|
}
|
|
7036
7440
|
});
|
|
7037
7441
|
|
|
7038
7442
|
// src/daemon/index.ts
|
|
7039
7443
|
function stopDaemon() {
|
|
7040
|
-
if (!
|
|
7444
|
+
if (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
7041
7445
|
try {
|
|
7042
|
-
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
|
+
}
|
|
7043
7452
|
process.kill(pid, "SIGTERM");
|
|
7044
7453
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
7045
7454
|
} catch {
|
|
7046
7455
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
7047
7456
|
} finally {
|
|
7048
7457
|
try {
|
|
7049
|
-
|
|
7458
|
+
import_fs20.default.unlinkSync(DAEMON_PID_FILE);
|
|
7050
7459
|
} catch {
|
|
7051
7460
|
}
|
|
7052
7461
|
}
|
|
7053
7462
|
}
|
|
7054
7463
|
function daemonStatus() {
|
|
7055
|
-
|
|
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)) {
|
|
7056
7468
|
try {
|
|
7057
|
-
const
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
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
|
+
}
|
|
7061
7480
|
} catch {
|
|
7062
|
-
|
|
7063
|
-
return;
|
|
7481
|
+
processStatus = import_chalk3.default.yellow("not running (stale PID file)");
|
|
7064
7482
|
}
|
|
7065
|
-
}
|
|
7066
|
-
const r = (0, import_child_process5.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
7067
|
-
encoding: "utf8",
|
|
7068
|
-
timeout: 500
|
|
7069
|
-
});
|
|
7070
|
-
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
|
|
7071
|
-
console.log(import_chalk3.default.yellow("Node9 daemon: running (no PID file \u2014 orphaned)"));
|
|
7072
7483
|
} else {
|
|
7073
|
-
|
|
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
|
+
}
|
|
7074
7493
|
}
|
|
7494
|
+
console.log(`
|
|
7495
|
+
Process : ${processStatus}`);
|
|
7496
|
+
console.log(` Service : ${serviceLabel}
|
|
7497
|
+
`);
|
|
7075
7498
|
}
|
|
7076
|
-
var
|
|
7499
|
+
var import_fs20, import_chalk3, import_child_process6, MAX_PID;
|
|
7077
7500
|
var init_daemon2 = __esm({
|
|
7078
7501
|
"src/daemon/index.ts"() {
|
|
7079
7502
|
"use strict";
|
|
7080
|
-
|
|
7503
|
+
import_fs20 = __toESM(require("fs"));
|
|
7081
7504
|
import_chalk3 = __toESM(require("chalk"));
|
|
7082
|
-
|
|
7505
|
+
import_child_process6 = require("child_process");
|
|
7083
7506
|
init_server();
|
|
7084
7507
|
init_state2();
|
|
7508
|
+
init_service();
|
|
7085
7509
|
init_state2();
|
|
7510
|
+
init_service();
|
|
7511
|
+
MAX_PID = 4194304;
|
|
7086
7512
|
}
|
|
7087
7513
|
});
|
|
7088
7514
|
|
|
@@ -7111,22 +7537,22 @@ function formatBase(activity) {
|
|
|
7111
7537
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7112
7538
|
const icon = getIcon(activity.tool);
|
|
7113
7539
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7114
|
-
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(), "~");
|
|
7115
7541
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7116
|
-
return `${
|
|
7542
|
+
return `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
|
|
7117
7543
|
}
|
|
7118
7544
|
function renderResult(activity, result) {
|
|
7119
7545
|
const base = formatBase(activity);
|
|
7120
7546
|
let status;
|
|
7121
7547
|
if (result.status === "allow") {
|
|
7122
|
-
status =
|
|
7548
|
+
status = import_chalk23.default.green("\u2713 ALLOW");
|
|
7123
7549
|
} else if (result.status === "dlp") {
|
|
7124
|
-
status =
|
|
7550
|
+
status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7125
7551
|
} else {
|
|
7126
|
-
status =
|
|
7552
|
+
status = import_chalk23.default.red("\u2717 BLOCK");
|
|
7127
7553
|
}
|
|
7128
7554
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7129
|
-
const costSuffix = cost == null ? "" :
|
|
7555
|
+
const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7130
7556
|
if (process.stdout.isTTY) {
|
|
7131
7557
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7132
7558
|
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7143,19 +7569,19 @@ function renderResult(activity, result) {
|
|
|
7143
7569
|
}
|
|
7144
7570
|
function renderPending(activity) {
|
|
7145
7571
|
if (!process.stdout.isTTY) return;
|
|
7146
|
-
const line = `${formatBase(activity)} ${
|
|
7572
|
+
const line = `${formatBase(activity)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
|
|
7147
7573
|
pendingShownForId = activity.id;
|
|
7148
7574
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7149
7575
|
process.stdout.write(`${line}\r`);
|
|
7150
7576
|
}
|
|
7151
7577
|
async function ensureDaemon() {
|
|
7152
7578
|
let pidPort = null;
|
|
7153
|
-
if (
|
|
7579
|
+
if (import_fs33.default.existsSync(PID_FILE)) {
|
|
7154
7580
|
try {
|
|
7155
|
-
const { port } = JSON.parse(
|
|
7581
|
+
const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
|
|
7156
7582
|
pidPort = port;
|
|
7157
7583
|
} catch {
|
|
7158
|
-
console.error(
|
|
7584
|
+
console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7159
7585
|
}
|
|
7160
7586
|
}
|
|
7161
7587
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7166,8 +7592,8 @@ async function ensureDaemon() {
|
|
|
7166
7592
|
if (res.ok) return checkPort;
|
|
7167
7593
|
} catch {
|
|
7168
7594
|
}
|
|
7169
|
-
console.log(
|
|
7170
|
-
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"], {
|
|
7171
7597
|
detached: true,
|
|
7172
7598
|
stdio: "ignore",
|
|
7173
7599
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -7183,7 +7609,7 @@ async function ensureDaemon() {
|
|
|
7183
7609
|
} catch {
|
|
7184
7610
|
}
|
|
7185
7611
|
}
|
|
7186
|
-
console.error(
|
|
7612
|
+
console.error(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7187
7613
|
process.exit(1);
|
|
7188
7614
|
}
|
|
7189
7615
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7215,27 +7641,56 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
|
7215
7641
|
req.end(body);
|
|
7216
7642
|
});
|
|
7217
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
|
+
}
|
|
7218
7666
|
function buildCardLines(req, localCount = 0) {
|
|
7219
7667
|
if (req.recoveryCommand) {
|
|
7220
7668
|
return buildRecoveryCardLines(req);
|
|
7221
7669
|
}
|
|
7222
|
-
const
|
|
7223
|
-
const argsPreview =
|
|
7224
|
-
const
|
|
7225
|
-
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) : "";
|
|
7226
7678
|
const lines = [
|
|
7227
7679
|
``,
|
|
7228
7680
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
7229
7681
|
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
7230
|
-
`${CYAN}\u2551${RESET2}
|
|
7682
|
+
`${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
|
|
7231
7683
|
];
|
|
7232
|
-
if (
|
|
7233
|
-
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}
|
|
7684
|
+
if (description) {
|
|
7685
|
+
lines.push(`${CYAN}\u2551${RESET2} Why: ${YELLOW}${description}${RESET2}`);
|
|
7234
7686
|
}
|
|
7235
|
-
if (req.riskMetadata?.ruleName &&
|
|
7687
|
+
if (req.riskMetadata?.ruleName && rawBlockedBy.includes("Taint")) {
|
|
7236
7688
|
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
7237
7689
|
}
|
|
7238
|
-
|
|
7690
|
+
if (argsPreview) {
|
|
7691
|
+
const argLabel = req.toolName.toLowerCase().includes("bash") ? "Command" : "Args ";
|
|
7692
|
+
lines.push(`${CYAN}\u2551${RESET2} ${argLabel}: ${GRAY}${argsPreview}${RESET2}`);
|
|
7693
|
+
}
|
|
7239
7694
|
if (localCount >= 2) {
|
|
7240
7695
|
lines.push(
|
|
7241
7696
|
`${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
|
|
@@ -7275,9 +7730,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7275
7730
|
];
|
|
7276
7731
|
}
|
|
7277
7732
|
function readApproversFromDisk() {
|
|
7278
|
-
const configPath =
|
|
7733
|
+
const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
|
|
7279
7734
|
try {
|
|
7280
|
-
const raw = JSON.parse(
|
|
7735
|
+
const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
|
|
7281
7736
|
const settings = raw.settings ?? {};
|
|
7282
7737
|
return settings.approvers ?? {};
|
|
7283
7738
|
} catch {
|
|
@@ -7288,20 +7743,20 @@ function approverStatusLine() {
|
|
|
7288
7743
|
const a = readApproversFromDisk();
|
|
7289
7744
|
const fmt = (label, key) => {
|
|
7290
7745
|
const on = a[key] !== false;
|
|
7291
|
-
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")}`;
|
|
7292
7747
|
};
|
|
7293
7748
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7294
7749
|
}
|
|
7295
7750
|
function toggleApprover(channel) {
|
|
7296
|
-
const configPath =
|
|
7751
|
+
const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
|
|
7297
7752
|
try {
|
|
7298
|
-
const raw = JSON.parse(
|
|
7753
|
+
const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
|
|
7299
7754
|
const settings = raw.settings ?? {};
|
|
7300
7755
|
const approvers = settings.approvers ?? {};
|
|
7301
7756
|
approvers[channel] = approvers[channel] === false;
|
|
7302
7757
|
settings.approvers = approvers;
|
|
7303
7758
|
raw.settings = settings;
|
|
7304
|
-
|
|
7759
|
+
import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7305
7760
|
} catch (err2) {
|
|
7306
7761
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7307
7762
|
`);
|
|
@@ -7333,7 +7788,7 @@ async function startTail(options = {}) {
|
|
|
7333
7788
|
req2.end();
|
|
7334
7789
|
});
|
|
7335
7790
|
if (result.ok) {
|
|
7336
|
-
console.log(
|
|
7791
|
+
console.log(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7337
7792
|
} else if (result.code === "ECONNREFUSED") {
|
|
7338
7793
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7339
7794
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7377,7 +7832,7 @@ async function startTail(options = {}) {
|
|
|
7377
7832
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7378
7833
|
if (channel) {
|
|
7379
7834
|
toggleApprover(channel);
|
|
7380
|
-
console.log(
|
|
7835
|
+
console.log(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7381
7836
|
}
|
|
7382
7837
|
};
|
|
7383
7838
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7443,7 +7898,7 @@ async function startTail(options = {}) {
|
|
|
7443
7898
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7444
7899
|
)
|
|
7445
7900
|
);
|
|
7446
|
-
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");
|
|
7447
7902
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7448
7903
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7449
7904
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7471,8 +7926,8 @@ async function startTail(options = {}) {
|
|
|
7471
7926
|
}
|
|
7472
7927
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7473
7928
|
try {
|
|
7474
|
-
|
|
7475
|
-
|
|
7929
|
+
import_fs33.default.appendFileSync(
|
|
7930
|
+
import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
|
|
7476
7931
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7477
7932
|
`
|
|
7478
7933
|
);
|
|
@@ -7494,7 +7949,7 @@ async function startTail(options = {}) {
|
|
|
7494
7949
|
);
|
|
7495
7950
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7496
7951
|
if (externalDecision) {
|
|
7497
|
-
const source = externalDecision === "allow" ?
|
|
7952
|
+
const source = externalDecision === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
|
|
7498
7953
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7499
7954
|
}
|
|
7500
7955
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7540,10 +7995,10 @@ async function startTail(options = {}) {
|
|
|
7540
7995
|
try {
|
|
7541
7996
|
const browserEnabled = getConfig().settings.approvers?.browser !== false;
|
|
7542
7997
|
if (browserEnabled) {
|
|
7543
|
-
if (process.platform === "darwin") (0,
|
|
7998
|
+
if (process.platform === "darwin") (0, import_child_process15.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7544
7999
|
else if (process.platform === "win32")
|
|
7545
|
-
(0,
|
|
7546
|
-
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" });
|
|
7547
8002
|
const intToken = getInternalToken();
|
|
7548
8003
|
fetch(`http://127.0.0.1:${port}/browser-opened`, {
|
|
7549
8004
|
method: "POST",
|
|
@@ -7553,16 +8008,16 @@ async function startTail(options = {}) {
|
|
|
7553
8008
|
}
|
|
7554
8009
|
} catch {
|
|
7555
8010
|
}
|
|
7556
|
-
console.log(
|
|
7557
|
-
\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}`));
|
|
7558
8013
|
if (canApprove) {
|
|
7559
|
-
console.log(
|
|
7560
|
-
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`));
|
|
7561
8016
|
}
|
|
7562
8017
|
if (options.history) {
|
|
7563
|
-
console.log(
|
|
8018
|
+
console.log(import_chalk23.default.dim("Showing history + live events.\n"));
|
|
7564
8019
|
} else {
|
|
7565
|
-
console.log(
|
|
8020
|
+
console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7566
8021
|
}
|
|
7567
8022
|
process.on("SIGINT", () => {
|
|
7568
8023
|
exitIdleMode();
|
|
@@ -7572,13 +8027,13 @@ async function startTail(options = {}) {
|
|
|
7572
8027
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7573
8028
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7574
8029
|
}
|
|
7575
|
-
console.log(
|
|
8030
|
+
console.log(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7576
8031
|
process.exit(0);
|
|
7577
8032
|
});
|
|
7578
8033
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7579
8034
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7580
8035
|
if (res.statusCode !== 200) {
|
|
7581
|
-
console.error(
|
|
8036
|
+
console.error(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7582
8037
|
process.exit(1);
|
|
7583
8038
|
}
|
|
7584
8039
|
if (canApprove) enterIdleMode();
|
|
@@ -7609,7 +8064,7 @@ async function startTail(options = {}) {
|
|
|
7609
8064
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7610
8065
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7611
8066
|
}
|
|
7612
|
-
console.log(
|
|
8067
|
+
console.log(import_chalk23.default.red("\n\u274C Daemon disconnected."));
|
|
7613
8068
|
process.exit(1);
|
|
7614
8069
|
});
|
|
7615
8070
|
});
|
|
@@ -7701,9 +8156,9 @@ async function startTail(options = {}) {
|
|
|
7701
8156
|
const hash = data.hash ?? "";
|
|
7702
8157
|
const summary = data.argsSummary ?? data.tool;
|
|
7703
8158
|
const fileCount = data.fileCount ?? 0;
|
|
7704
|
-
const files = fileCount > 0 ?
|
|
8159
|
+
const files = fileCount > 0 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7705
8160
|
process.stdout.write(
|
|
7706
|
-
`${
|
|
8161
|
+
`${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
|
|
7707
8162
|
`
|
|
7708
8163
|
);
|
|
7709
8164
|
return;
|
|
@@ -7720,26 +8175,26 @@ async function startTail(options = {}) {
|
|
|
7720
8175
|
}
|
|
7721
8176
|
req.on("error", (err2) => {
|
|
7722
8177
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7723
|
-
console.error(
|
|
8178
|
+
console.error(import_chalk23.default.red(`
|
|
7724
8179
|
\u274C ${msg}`));
|
|
7725
8180
|
process.exit(1);
|
|
7726
8181
|
});
|
|
7727
8182
|
}
|
|
7728
|
-
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;
|
|
7729
8184
|
var init_tail = __esm({
|
|
7730
8185
|
"src/tui/tail.ts"() {
|
|
7731
8186
|
"use strict";
|
|
7732
8187
|
import_http2 = __toESM(require("http"));
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
8188
|
+
import_chalk23 = __toESM(require("chalk"));
|
|
8189
|
+
import_fs33 = __toESM(require("fs"));
|
|
8190
|
+
import_os29 = __toESM(require("os"));
|
|
8191
|
+
import_path36 = __toESM(require("path"));
|
|
7737
8192
|
import_readline5 = __toESM(require("readline"));
|
|
7738
|
-
|
|
8193
|
+
import_child_process15 = require("child_process");
|
|
7739
8194
|
init_daemon2();
|
|
7740
8195
|
init_daemon();
|
|
7741
8196
|
init_core();
|
|
7742
|
-
PID_FILE =
|
|
8197
|
+
PID_FILE = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
|
|
7743
8198
|
ICONS = {
|
|
7744
8199
|
bash: "\u{1F4BB}",
|
|
7745
8200
|
shell: "\u{1F4BB}",
|
|
@@ -7854,9 +8309,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7854
8309
|
return ` (${m}m left)`;
|
|
7855
8310
|
}
|
|
7856
8311
|
function safeReadJson(filePath) {
|
|
7857
|
-
if (!
|
|
8312
|
+
if (!import_fs34.default.existsSync(filePath)) return null;
|
|
7858
8313
|
try {
|
|
7859
|
-
return JSON.parse(
|
|
8314
|
+
return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
|
|
7860
8315
|
} catch {
|
|
7861
8316
|
return null;
|
|
7862
8317
|
}
|
|
@@ -7877,12 +8332,12 @@ function countHooksInFile(filePath) {
|
|
|
7877
8332
|
return Object.keys(cfg.hooks).length;
|
|
7878
8333
|
}
|
|
7879
8334
|
function countRulesInDir(rulesDir) {
|
|
7880
|
-
if (!
|
|
8335
|
+
if (!import_fs34.default.existsSync(rulesDir)) return 0;
|
|
7881
8336
|
let count = 0;
|
|
7882
8337
|
try {
|
|
7883
|
-
for (const entry of
|
|
8338
|
+
for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7884
8339
|
if (entry.isDirectory()) {
|
|
7885
|
-
count += countRulesInDir(
|
|
8340
|
+
count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
|
|
7886
8341
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7887
8342
|
count++;
|
|
7888
8343
|
}
|
|
@@ -7893,46 +8348,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7893
8348
|
}
|
|
7894
8349
|
function isSamePath(a, b) {
|
|
7895
8350
|
try {
|
|
7896
|
-
return
|
|
8351
|
+
return import_path37.default.resolve(a) === import_path37.default.resolve(b);
|
|
7897
8352
|
} catch {
|
|
7898
8353
|
return false;
|
|
7899
8354
|
}
|
|
7900
8355
|
}
|
|
7901
8356
|
function countConfigs(cwd) {
|
|
7902
|
-
const homeDir2 =
|
|
7903
|
-
const claudeDir =
|
|
8357
|
+
const homeDir2 = import_os30.default.homedir();
|
|
8358
|
+
const claudeDir = import_path37.default.join(homeDir2, ".claude");
|
|
7904
8359
|
let claudeMdCount = 0;
|
|
7905
8360
|
let rulesCount = 0;
|
|
7906
8361
|
let hooksCount = 0;
|
|
7907
8362
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7908
8363
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7909
|
-
if (
|
|
7910
|
-
rulesCount += countRulesInDir(
|
|
7911
|
-
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");
|
|
7912
8367
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7913
8368
|
hooksCount += countHooksInFile(userSettings);
|
|
7914
|
-
const userClaudeJson =
|
|
8369
|
+
const userClaudeJson = import_path37.default.join(homeDir2, ".claude.json");
|
|
7915
8370
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7916
8371
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7917
8372
|
userMcpServers.delete(name);
|
|
7918
8373
|
}
|
|
7919
8374
|
if (cwd) {
|
|
7920
|
-
if (
|
|
7921
|
-
if (
|
|
7922
|
-
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");
|
|
7923
8378
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7924
8379
|
if (!overlapsUserScope) {
|
|
7925
|
-
if (
|
|
7926
|
-
rulesCount += countRulesInDir(
|
|
7927
|
-
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");
|
|
7928
8383
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7929
8384
|
hooksCount += countHooksInFile(projSettings);
|
|
7930
8385
|
}
|
|
7931
|
-
if (
|
|
7932
|
-
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");
|
|
7933
8388
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7934
8389
|
hooksCount += countHooksInFile(localSettings);
|
|
7935
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8390
|
+
const mcpJsonServers = getMcpServerNames(import_path37.default.join(cwd, ".mcp.json"));
|
|
7936
8391
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7937
8392
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7938
8393
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7965,12 +8420,12 @@ function readActiveShieldsHud() {
|
|
|
7965
8420
|
return shieldsCache.value;
|
|
7966
8421
|
}
|
|
7967
8422
|
try {
|
|
7968
|
-
const shieldsPath =
|
|
7969
|
-
if (!
|
|
8423
|
+
const shieldsPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
|
|
8424
|
+
if (!import_fs34.default.existsSync(shieldsPath)) {
|
|
7970
8425
|
shieldsCache = { value: [], ts: now };
|
|
7971
8426
|
return [];
|
|
7972
8427
|
}
|
|
7973
|
-
const parsed = JSON.parse(
|
|
8428
|
+
const parsed = JSON.parse(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
|
|
7974
8429
|
if (!Array.isArray(parsed.active)) {
|
|
7975
8430
|
shieldsCache = { value: [], ts: now };
|
|
7976
8431
|
return [];
|
|
@@ -8072,17 +8527,17 @@ function renderContextLine(stdin) {
|
|
|
8072
8527
|
async function main() {
|
|
8073
8528
|
try {
|
|
8074
8529
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8075
|
-
if (
|
|
8530
|
+
if (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
|
|
8076
8531
|
try {
|
|
8077
|
-
const logPath =
|
|
8532
|
+
const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
|
|
8078
8533
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8079
8534
|
let size = 0;
|
|
8080
8535
|
try {
|
|
8081
|
-
size =
|
|
8536
|
+
size = import_fs34.default.statSync(logPath).size;
|
|
8082
8537
|
} catch {
|
|
8083
8538
|
}
|
|
8084
8539
|
if (size < MAX_LOG_SIZE) {
|
|
8085
|
-
|
|
8540
|
+
import_fs34.default.appendFileSync(
|
|
8086
8541
|
logPath,
|
|
8087
8542
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8088
8543
|
);
|
|
@@ -8103,11 +8558,11 @@ async function main() {
|
|
|
8103
8558
|
try {
|
|
8104
8559
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8105
8560
|
for (const configPath of [
|
|
8106
|
-
|
|
8107
|
-
|
|
8561
|
+
import_path37.default.join(cwd, "node9.config.json"),
|
|
8562
|
+
import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
|
|
8108
8563
|
]) {
|
|
8109
|
-
if (!
|
|
8110
|
-
const cfg = JSON.parse(
|
|
8564
|
+
if (!import_fs34.default.existsSync(configPath)) continue;
|
|
8565
|
+
const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
|
|
8111
8566
|
const hud = cfg.settings?.hud;
|
|
8112
8567
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8113
8568
|
}
|
|
@@ -8125,13 +8580,13 @@ async function main() {
|
|
|
8125
8580
|
renderOffline();
|
|
8126
8581
|
}
|
|
8127
8582
|
}
|
|
8128
|
-
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;
|
|
8129
8584
|
var init_hud = __esm({
|
|
8130
8585
|
"src/cli/hud.ts"() {
|
|
8131
8586
|
"use strict";
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8587
|
+
import_fs34 = __toESM(require("fs"));
|
|
8588
|
+
import_path37 = __toESM(require("path"));
|
|
8589
|
+
import_os30 = __toESM(require("os"));
|
|
8135
8590
|
import_http3 = __toESM(require("http"));
|
|
8136
8591
|
init_daemon();
|
|
8137
8592
|
RESET3 = "\x1B[0m";
|
|
@@ -8542,7 +8997,9 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
|
8542
8997
|
claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
|
|
8543
8998
|
gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
|
|
8544
8999
|
cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
|
|
8545
|
-
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"))
|
|
8546
9003
|
};
|
|
8547
9004
|
}
|
|
8548
9005
|
async function setupCursor() {
|
|
@@ -8691,6 +9148,38 @@ async function setupCodex() {
|
|
|
8691
9148
|
printDaemonTip();
|
|
8692
9149
|
}
|
|
8693
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
|
+
}
|
|
8694
9183
|
function setupHud() {
|
|
8695
9184
|
const homeDir2 = import_os11.default.homedir();
|
|
8696
9185
|
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
@@ -8737,54 +9226,326 @@ function teardownHud() {
|
|
|
8737
9226
|
console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
|
|
8738
9227
|
console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
|
|
8739
9228
|
}
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
if (!m) return null;
|
|
8753
|
-
const n = parseFloat(m[1]);
|
|
8754
|
-
switch ((m[2] ?? "m").toLowerCase()) {
|
|
8755
|
-
case "s":
|
|
8756
|
-
return Math.round(n * 1e3);
|
|
8757
|
-
case "m":
|
|
8758
|
-
return Math.round(n * 6e4);
|
|
8759
|
-
case "h":
|
|
8760
|
-
return Math.round(n * 36e5);
|
|
8761
|
-
case "d":
|
|
8762
|
-
return Math.round(n * 864e5);
|
|
8763
|
-
default:
|
|
8764
|
-
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;
|
|
8765
9241
|
}
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
var import_chalk4 = __toESM(require("chalk"));
|
|
8771
|
-
var import_child_process6 = require("child_process");
|
|
8772
|
-
var import_execa = require("execa");
|
|
8773
|
-
var import_execa2 = require("execa");
|
|
8774
|
-
init_orchestrator();
|
|
8775
|
-
|
|
8776
|
-
// src/policy/negotiation.ts
|
|
8777
|
-
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
|
|
8778
|
-
if (isHumanDecision) {
|
|
8779
|
-
return `NODE9: The human user rejected this action.
|
|
8780
|
-
REASON: ${humanReason || "No specific reason provided."}
|
|
8781
|
-
INSTRUCTIONS:
|
|
8782
|
-
- Do NOT retry this exact command.
|
|
8783
|
-
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
8784
|
-
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
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(" ") });
|
|
8785
9246
|
}
|
|
8786
|
-
|
|
8787
|
-
|
|
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"));
|
|
9531
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
9532
|
+
var import_child_process7 = require("child_process");
|
|
9533
|
+
var import_execa = require("execa");
|
|
9534
|
+
var import_execa2 = require("execa");
|
|
9535
|
+
init_orchestrator();
|
|
9536
|
+
|
|
9537
|
+
// src/policy/negotiation.ts
|
|
9538
|
+
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
|
|
9539
|
+
if (isHumanDecision) {
|
|
9540
|
+
return `NODE9: The human user rejected this action.
|
|
9541
|
+
REASON: ${humanReason || "No specific reason provided."}
|
|
9542
|
+
INSTRUCTIONS:
|
|
9543
|
+
- Do NOT retry this exact command.
|
|
9544
|
+
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
9545
|
+
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
9546
|
+
}
|
|
9547
|
+
const label = blockedByLabel.toLowerCase();
|
|
9548
|
+
if (label.includes("dlp") || label.includes("secret detected") || label.includes("credential review")) {
|
|
8788
9549
|
return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
|
|
8789
9550
|
CRITICAL INSTRUCTION: Do NOT retry this action.
|
|
8790
9551
|
REQUIRED ACTIONS:
|
|
@@ -8856,11 +9617,11 @@ async function runProxy(targetCommand) {
|
|
|
8856
9617
|
}
|
|
8857
9618
|
console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
8858
9619
|
const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
|
|
8859
|
-
const child = useShell ? (0,
|
|
9620
|
+
const child = useShell ? (0, import_child_process7.spawn)("/bin/bash", ["-c", targetCommand], {
|
|
8860
9621
|
stdio: ["pipe", "pipe", "inherit"],
|
|
8861
9622
|
shell: false,
|
|
8862
9623
|
env: spawnEnv
|
|
8863
|
-
}) : (0,
|
|
9624
|
+
}) : (0, import_child_process7.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
|
|
8864
9625
|
const agentIn = import_readline.default.createInterface({ input: process.stdin, terminal: false });
|
|
8865
9626
|
agentIn.on("line", async (line) => {
|
|
8866
9627
|
let message;
|
|
@@ -8924,22 +9685,22 @@ async function runProxy(targetCommand) {
|
|
|
8924
9685
|
}
|
|
8925
9686
|
|
|
8926
9687
|
// src/cli/daemon-starter.ts
|
|
8927
|
-
var
|
|
9688
|
+
var import_child_process8 = require("child_process");
|
|
8928
9689
|
init_daemon();
|
|
8929
9690
|
function openBrowserLocal() {
|
|
8930
9691
|
const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
|
|
8931
9692
|
try {
|
|
8932
9693
|
const opts = { stdio: "ignore" };
|
|
8933
|
-
if (process.platform === "darwin") (0,
|
|
8934
|
-
else if (process.platform === "win32") (0,
|
|
8935
|
-
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);
|
|
8936
9697
|
} catch {
|
|
8937
9698
|
}
|
|
8938
9699
|
}
|
|
8939
9700
|
async function autoStartDaemonAndWait() {
|
|
8940
9701
|
if (process.env.NODE9_TESTING === "1") return false;
|
|
8941
9702
|
try {
|
|
8942
|
-
const child = (0,
|
|
9703
|
+
const child = (0, import_child_process8.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
8943
9704
|
detached: true,
|
|
8944
9705
|
stdio: "ignore",
|
|
8945
9706
|
// NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
|
|
@@ -8968,23 +9729,23 @@ async function autoStartDaemonAndWait() {
|
|
|
8968
9729
|
|
|
8969
9730
|
// src/cli/commands/check.ts
|
|
8970
9731
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8971
|
-
var
|
|
8972
|
-
var
|
|
8973
|
-
var
|
|
8974
|
-
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"));
|
|
8975
9736
|
init_orchestrator();
|
|
8976
9737
|
init_daemon();
|
|
8977
9738
|
init_config();
|
|
8978
9739
|
init_policy();
|
|
8979
9740
|
|
|
8980
9741
|
// src/undo.ts
|
|
8981
|
-
var
|
|
9742
|
+
var import_child_process9 = require("child_process");
|
|
8982
9743
|
var import_crypto8 = __toESM(require("crypto"));
|
|
8983
|
-
var
|
|
9744
|
+
var import_fs21 = __toESM(require("fs"));
|
|
8984
9745
|
var import_net3 = __toESM(require("net"));
|
|
8985
|
-
var
|
|
8986
|
-
var
|
|
8987
|
-
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");
|
|
8988
9749
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8989
9750
|
try {
|
|
8990
9751
|
const payload = JSON.stringify({
|
|
@@ -9004,22 +9765,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
9004
9765
|
} catch {
|
|
9005
9766
|
}
|
|
9006
9767
|
}
|
|
9007
|
-
var SNAPSHOT_STACK_PATH =
|
|
9008
|
-
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");
|
|
9009
9770
|
var MAX_SNAPSHOTS = 10;
|
|
9010
9771
|
var GIT_TIMEOUT = 15e3;
|
|
9011
9772
|
function readStack() {
|
|
9012
9773
|
try {
|
|
9013
|
-
if (
|
|
9014
|
-
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"));
|
|
9015
9776
|
} catch {
|
|
9016
9777
|
}
|
|
9017
9778
|
return [];
|
|
9018
9779
|
}
|
|
9019
9780
|
function writeStack(stack) {
|
|
9020
|
-
const dir =
|
|
9021
|
-
if (!
|
|
9022
|
-
|
|
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));
|
|
9023
9784
|
}
|
|
9024
9785
|
function extractFilePath(args) {
|
|
9025
9786
|
if (!args || typeof args !== "object") return null;
|
|
@@ -9039,12 +9800,12 @@ function buildArgsSummary(tool, args) {
|
|
|
9039
9800
|
return "";
|
|
9040
9801
|
}
|
|
9041
9802
|
function findProjectRoot(filePath) {
|
|
9042
|
-
let dir =
|
|
9803
|
+
let dir = import_path23.default.dirname(filePath);
|
|
9043
9804
|
while (true) {
|
|
9044
|
-
if (
|
|
9805
|
+
if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
|
|
9045
9806
|
return dir;
|
|
9046
9807
|
}
|
|
9047
|
-
const parent =
|
|
9808
|
+
const parent = import_path23.default.dirname(dir);
|
|
9048
9809
|
if (parent === dir) return process.cwd();
|
|
9049
9810
|
dir = parent;
|
|
9050
9811
|
}
|
|
@@ -9052,7 +9813,7 @@ function findProjectRoot(filePath) {
|
|
|
9052
9813
|
function normalizeCwdForHash(cwd) {
|
|
9053
9814
|
let normalized;
|
|
9054
9815
|
try {
|
|
9055
|
-
normalized =
|
|
9816
|
+
normalized = import_fs21.default.realpathSync(cwd);
|
|
9056
9817
|
} catch {
|
|
9057
9818
|
normalized = cwd;
|
|
9058
9819
|
}
|
|
@@ -9062,16 +9823,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
9062
9823
|
}
|
|
9063
9824
|
function getShadowRepoDir(cwd) {
|
|
9064
9825
|
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9065
|
-
return
|
|
9826
|
+
return import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
|
|
9066
9827
|
}
|
|
9067
9828
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
9068
9829
|
try {
|
|
9069
9830
|
const cutoff = Date.now() - 6e4;
|
|
9070
|
-
for (const f of
|
|
9831
|
+
for (const f of import_fs21.default.readdirSync(shadowDir)) {
|
|
9071
9832
|
if (f.startsWith("index_")) {
|
|
9072
|
-
const fp =
|
|
9833
|
+
const fp = import_path23.default.join(shadowDir, f);
|
|
9073
9834
|
try {
|
|
9074
|
-
if (
|
|
9835
|
+
if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
|
|
9075
9836
|
} catch {
|
|
9076
9837
|
}
|
|
9077
9838
|
}
|
|
@@ -9083,7 +9844,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
9083
9844
|
const hardcoded = [".git", ".node9"];
|
|
9084
9845
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
9085
9846
|
try {
|
|
9086
|
-
|
|
9847
|
+
import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
9087
9848
|
} catch {
|
|
9088
9849
|
}
|
|
9089
9850
|
}
|
|
@@ -9091,54 +9852,54 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9091
9852
|
cleanOrphanedIndexFiles(shadowDir);
|
|
9092
9853
|
const normalizedCwd = normalizeCwdForHash(cwd);
|
|
9093
9854
|
const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
9094
|
-
const check = (0,
|
|
9855
|
+
const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
9095
9856
|
env: shadowEnvBase,
|
|
9096
9857
|
timeout: 3e3
|
|
9097
9858
|
});
|
|
9098
9859
|
if (check.status === 0) {
|
|
9099
|
-
const ptPath =
|
|
9860
|
+
const ptPath = import_path23.default.join(shadowDir, "project-path.txt");
|
|
9100
9861
|
try {
|
|
9101
|
-
const stored =
|
|
9862
|
+
const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
|
|
9102
9863
|
if (stored === normalizedCwd) return true;
|
|
9103
9864
|
if (process.env.NODE9_DEBUG === "1")
|
|
9104
9865
|
console.error(
|
|
9105
9866
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
9106
9867
|
);
|
|
9107
|
-
|
|
9868
|
+
import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
9108
9869
|
} catch {
|
|
9109
9870
|
try {
|
|
9110
|
-
|
|
9871
|
+
import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
9111
9872
|
} catch {
|
|
9112
9873
|
}
|
|
9113
9874
|
return true;
|
|
9114
9875
|
}
|
|
9115
9876
|
}
|
|
9116
9877
|
try {
|
|
9117
|
-
|
|
9878
|
+
import_fs21.default.mkdirSync(shadowDir, { recursive: true });
|
|
9118
9879
|
} catch {
|
|
9119
9880
|
}
|
|
9120
|
-
const init = (0,
|
|
9881
|
+
const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
9121
9882
|
if (init.status !== 0 || init.error) {
|
|
9122
9883
|
const reason = init.error ? init.error.message : init.stderr?.toString();
|
|
9123
9884
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
9124
9885
|
return false;
|
|
9125
9886
|
}
|
|
9126
|
-
const configFile =
|
|
9127
|
-
(0,
|
|
9887
|
+
const configFile = import_path23.default.join(shadowDir, "config");
|
|
9888
|
+
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
9128
9889
|
timeout: 3e3
|
|
9129
9890
|
});
|
|
9130
|
-
(0,
|
|
9891
|
+
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
|
|
9131
9892
|
timeout: 3e3
|
|
9132
9893
|
});
|
|
9133
9894
|
try {
|
|
9134
|
-
|
|
9895
|
+
import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
9135
9896
|
} catch {
|
|
9136
9897
|
}
|
|
9137
9898
|
return true;
|
|
9138
9899
|
}
|
|
9139
9900
|
function buildGitEnv(cwd) {
|
|
9140
9901
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9141
|
-
const check = (0,
|
|
9902
|
+
const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
9142
9903
|
env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
|
|
9143
9904
|
timeout: 2e3
|
|
9144
9905
|
});
|
|
@@ -9151,23 +9912,23 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9151
9912
|
let indexFile = null;
|
|
9152
9913
|
try {
|
|
9153
9914
|
const rawFilePath = extractFilePath(args);
|
|
9154
|
-
const absFilePath = rawFilePath &&
|
|
9915
|
+
const absFilePath = rawFilePath && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
9155
9916
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
9156
9917
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9157
9918
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
9158
9919
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
9159
|
-
indexFile =
|
|
9920
|
+
indexFile = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
9160
9921
|
const shadowEnv = {
|
|
9161
9922
|
...process.env,
|
|
9162
9923
|
GIT_DIR: shadowDir,
|
|
9163
9924
|
GIT_WORK_TREE: cwd,
|
|
9164
9925
|
GIT_INDEX_FILE: indexFile
|
|
9165
9926
|
};
|
|
9166
|
-
(0,
|
|
9167
|
-
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 });
|
|
9168
9929
|
const treeHash = treeRes.stdout?.toString().trim();
|
|
9169
9930
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
9170
|
-
const commitRes = (0,
|
|
9931
|
+
const commitRes = (0, import_child_process9.spawnSync)(
|
|
9171
9932
|
"git",
|
|
9172
9933
|
["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
|
|
9173
9934
|
{ env: shadowEnv, timeout: GIT_TIMEOUT }
|
|
@@ -9179,7 +9940,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9179
9940
|
let capturedFiles = [];
|
|
9180
9941
|
let capturedDiff = null;
|
|
9181
9942
|
if (prevEntry) {
|
|
9182
|
-
const filesRes = (0,
|
|
9943
|
+
const filesRes = (0, import_child_process9.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
|
|
9183
9944
|
env: shadowEnv,
|
|
9184
9945
|
timeout: GIT_TIMEOUT
|
|
9185
9946
|
});
|
|
@@ -9189,7 +9950,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9189
9950
|
if (capturedFiles.length === 0) {
|
|
9190
9951
|
return prevEntry.hash;
|
|
9191
9952
|
}
|
|
9192
|
-
const diffRes = (0,
|
|
9953
|
+
const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
|
|
9193
9954
|
env: shadowEnv,
|
|
9194
9955
|
timeout: GIT_TIMEOUT
|
|
9195
9956
|
});
|
|
@@ -9197,7 +9958,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9197
9958
|
capturedDiff = diffRes.stdout?.toString() || null;
|
|
9198
9959
|
}
|
|
9199
9960
|
} else {
|
|
9200
|
-
const filesRes = (0,
|
|
9961
|
+
const filesRes = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
|
|
9201
9962
|
env: shadowEnv,
|
|
9202
9963
|
timeout: GIT_TIMEOUT
|
|
9203
9964
|
});
|
|
@@ -9228,9 +9989,9 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9228
9989
|
writeStack(stack);
|
|
9229
9990
|
const entry = stack[stack.length - 1];
|
|
9230
9991
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
9231
|
-
|
|
9992
|
+
import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
9232
9993
|
if (shouldGc) {
|
|
9233
|
-
(0,
|
|
9994
|
+
(0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
9234
9995
|
}
|
|
9235
9996
|
return commitHash;
|
|
9236
9997
|
} catch (err2) {
|
|
@@ -9239,7 +10000,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9239
10000
|
} finally {
|
|
9240
10001
|
if (indexFile) {
|
|
9241
10002
|
try {
|
|
9242
|
-
|
|
10003
|
+
import_fs21.default.unlinkSync(indexFile);
|
|
9243
10004
|
} catch {
|
|
9244
10005
|
}
|
|
9245
10006
|
}
|
|
@@ -9251,14 +10012,14 @@ function getSnapshotHistory() {
|
|
|
9251
10012
|
function computeUndoDiff(hash, cwd) {
|
|
9252
10013
|
try {
|
|
9253
10014
|
const env = buildGitEnv(cwd);
|
|
9254
|
-
const statRes = (0,
|
|
10015
|
+
const statRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
|
|
9255
10016
|
cwd,
|
|
9256
10017
|
env,
|
|
9257
10018
|
timeout: GIT_TIMEOUT
|
|
9258
10019
|
});
|
|
9259
10020
|
const stat = statRes.stdout?.toString().trim();
|
|
9260
10021
|
if (!stat || statRes.status !== 0) return null;
|
|
9261
|
-
const diffRes = (0,
|
|
10022
|
+
const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--", "."], {
|
|
9262
10023
|
cwd,
|
|
9263
10024
|
env,
|
|
9264
10025
|
timeout: GIT_TIMEOUT
|
|
@@ -9277,7 +10038,7 @@ function applyUndo(hash, cwd) {
|
|
|
9277
10038
|
try {
|
|
9278
10039
|
const dir = cwd ?? process.cwd();
|
|
9279
10040
|
const env = buildGitEnv(dir);
|
|
9280
|
-
const restore = (0,
|
|
10041
|
+
const restore = (0, import_child_process9.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
9281
10042
|
cwd: dir,
|
|
9282
10043
|
env,
|
|
9283
10044
|
timeout: GIT_TIMEOUT
|
|
@@ -9289,7 +10050,7 @@ function applyUndo(hash, cwd) {
|
|
|
9289
10050
|
}
|
|
9290
10051
|
return false;
|
|
9291
10052
|
}
|
|
9292
|
-
const lsTree = (0,
|
|
10053
|
+
const lsTree = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
9293
10054
|
cwd: dir,
|
|
9294
10055
|
env,
|
|
9295
10056
|
timeout: GIT_TIMEOUT
|
|
@@ -9308,16 +10069,16 @@ function applyUndo(hash, cwd) {
|
|
|
9308
10069
|
`);
|
|
9309
10070
|
return false;
|
|
9310
10071
|
}
|
|
9311
|
-
const tracked = (0,
|
|
9312
|
-
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"], {
|
|
9313
10074
|
cwd: dir,
|
|
9314
10075
|
env,
|
|
9315
10076
|
timeout: GIT_TIMEOUT
|
|
9316
10077
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
9317
10078
|
for (const file of [...tracked, ...untracked]) {
|
|
9318
|
-
const fullPath =
|
|
9319
|
-
if (!snapshotFiles.has(file) &&
|
|
9320
|
-
|
|
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);
|
|
9321
10082
|
}
|
|
9322
10083
|
}
|
|
9323
10084
|
return true;
|
|
@@ -9341,9 +10102,9 @@ function registerCheckCommand(program2) {
|
|
|
9341
10102
|
} catch (err2) {
|
|
9342
10103
|
const tempConfig = getConfig();
|
|
9343
10104
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9344
|
-
const logPath =
|
|
10105
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
9345
10106
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9346
|
-
|
|
10107
|
+
import_fs22.default.appendFileSync(
|
|
9347
10108
|
logPath,
|
|
9348
10109
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9349
10110
|
RAW: ${raw}
|
|
@@ -9356,11 +10117,11 @@ RAW: ${raw}
|
|
|
9356
10117
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9357
10118
|
try {
|
|
9358
10119
|
const scriptPath = process.argv[1];
|
|
9359
|
-
if (typeof scriptPath !== "string" || !
|
|
10120
|
+
if (typeof scriptPath !== "string" || !import_path24.default.isAbsolute(scriptPath))
|
|
9360
10121
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9361
|
-
const resolvedScript =
|
|
9362
|
-
const packageDist =
|
|
9363
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
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)
|
|
9364
10125
|
throw new Error(
|
|
9365
10126
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
9366
10127
|
);
|
|
@@ -9375,17 +10136,17 @@ RAW: ${raw}
|
|
|
9375
10136
|
]) {
|
|
9376
10137
|
delete safeEnv[key];
|
|
9377
10138
|
}
|
|
9378
|
-
const d = (0,
|
|
10139
|
+
const d = (0, import_child_process10.spawn)(process.execPath, [scriptPath, "daemon"], {
|
|
9379
10140
|
detached: true,
|
|
9380
10141
|
stdio: "ignore",
|
|
9381
10142
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
9382
10143
|
});
|
|
9383
10144
|
d.unref();
|
|
9384
10145
|
} catch (spawnErr) {
|
|
9385
|
-
const logPath =
|
|
10146
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
9386
10147
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
9387
10148
|
try {
|
|
9388
|
-
|
|
10149
|
+
import_fs22.default.appendFileSync(
|
|
9389
10150
|
logPath,
|
|
9390
10151
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
9391
10152
|
`
|
|
@@ -9395,10 +10156,10 @@ RAW: ${raw}
|
|
|
9395
10156
|
}
|
|
9396
10157
|
}
|
|
9397
10158
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9398
|
-
const logPath =
|
|
9399
|
-
if (!
|
|
9400
|
-
|
|
9401
|
-
|
|
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}
|
|
9402
10163
|
`);
|
|
9403
10164
|
}
|
|
9404
10165
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9411,8 +10172,8 @@ RAW: ${raw}
|
|
|
9411
10172
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9412
10173
|
let ttyFd = null;
|
|
9413
10174
|
try {
|
|
9414
|
-
ttyFd =
|
|
9415
|
-
const writeTty = (line) =>
|
|
10175
|
+
ttyFd = import_fs22.default.openSync("/dev/tty", "w");
|
|
10176
|
+
const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
|
|
9416
10177
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9417
10178
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
9418
10179
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9431,7 +10192,7 @@ RAW: ${raw}
|
|
|
9431
10192
|
} finally {
|
|
9432
10193
|
if (ttyFd !== null)
|
|
9433
10194
|
try {
|
|
9434
|
-
|
|
10195
|
+
import_fs22.default.closeSync(ttyFd);
|
|
9435
10196
|
} catch {
|
|
9436
10197
|
}
|
|
9437
10198
|
}
|
|
@@ -9463,7 +10224,7 @@ RAW: ${raw}
|
|
|
9463
10224
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9464
10225
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9465
10226
|
}
|
|
9466
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10227
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9467
10228
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9468
10229
|
cwd: safeCwdForAuth
|
|
9469
10230
|
});
|
|
@@ -9475,12 +10236,12 @@ RAW: ${raw}
|
|
|
9475
10236
|
}
|
|
9476
10237
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9477
10238
|
try {
|
|
9478
|
-
const tty =
|
|
9479
|
-
|
|
10239
|
+
const tty = import_fs22.default.openSync("/dev/tty", "w");
|
|
10240
|
+
import_fs22.default.writeSync(
|
|
9480
10241
|
tty,
|
|
9481
10242
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9482
10243
|
);
|
|
9483
|
-
|
|
10244
|
+
import_fs22.default.closeSync(tty);
|
|
9484
10245
|
} catch {
|
|
9485
10246
|
}
|
|
9486
10247
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9507,9 +10268,9 @@ RAW: ${raw}
|
|
|
9507
10268
|
});
|
|
9508
10269
|
} catch (err2) {
|
|
9509
10270
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9510
|
-
const logPath =
|
|
10271
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
|
|
9511
10272
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9512
|
-
|
|
10273
|
+
import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9513
10274
|
`);
|
|
9514
10275
|
}
|
|
9515
10276
|
process.exit(0);
|
|
@@ -9543,9 +10304,9 @@ RAW: ${raw}
|
|
|
9543
10304
|
}
|
|
9544
10305
|
|
|
9545
10306
|
// src/cli/commands/log.ts
|
|
9546
|
-
var
|
|
9547
|
-
var
|
|
9548
|
-
var
|
|
10307
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10308
|
+
var import_path25 = __toESM(require("path"));
|
|
10309
|
+
var import_os19 = __toESM(require("os"));
|
|
9549
10310
|
init_audit();
|
|
9550
10311
|
init_config();
|
|
9551
10312
|
init_policy();
|
|
@@ -9621,10 +10382,10 @@ function registerLogCommand(program2) {
|
|
|
9621
10382
|
decision: "allowed",
|
|
9622
10383
|
source: "post-hook"
|
|
9623
10384
|
};
|
|
9624
|
-
const logPath =
|
|
9625
|
-
if (!
|
|
9626
|
-
|
|
9627
|
-
|
|
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");
|
|
9628
10389
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9629
10390
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9630
10391
|
if (command) {
|
|
@@ -9657,7 +10418,7 @@ function registerLogCommand(program2) {
|
|
|
9657
10418
|
}
|
|
9658
10419
|
}
|
|
9659
10420
|
}
|
|
9660
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
10421
|
+
const safeCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9661
10422
|
const config = getConfig(safeCwd);
|
|
9662
10423
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9663
10424
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9666,9 +10427,9 @@ function registerLogCommand(program2) {
|
|
|
9666
10427
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9667
10428
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9668
10429
|
`);
|
|
9669
|
-
const debugPath =
|
|
10430
|
+
const debugPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
9670
10431
|
try {
|
|
9671
|
-
|
|
10432
|
+
import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9672
10433
|
`);
|
|
9673
10434
|
} catch {
|
|
9674
10435
|
}
|
|
@@ -9698,10 +10459,10 @@ init_audit();
|
|
|
9698
10459
|
init_config();
|
|
9699
10460
|
|
|
9700
10461
|
// src/utils/https-fetch.ts
|
|
9701
|
-
var
|
|
10462
|
+
var import_https2 = __toESM(require("https"));
|
|
9702
10463
|
function httpsFetch(url) {
|
|
9703
10464
|
return new Promise((resolve, reject) => {
|
|
9704
|
-
|
|
10465
|
+
import_https2.default.get(url, (res) => {
|
|
9705
10466
|
if (res.statusCode !== 200) {
|
|
9706
10467
|
reject(new Error(`HTTP ${String(res.statusCode)} for ${url}`));
|
|
9707
10468
|
res.resume();
|
|
@@ -10068,14 +10829,14 @@ function registerConfigShowCommand(program2) {
|
|
|
10068
10829
|
|
|
10069
10830
|
// src/cli/commands/doctor.ts
|
|
10070
10831
|
var import_chalk7 = __toESM(require("chalk"));
|
|
10071
|
-
var
|
|
10072
|
-
var
|
|
10073
|
-
var
|
|
10074
|
-
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");
|
|
10075
10836
|
init_daemon();
|
|
10076
10837
|
function registerDoctorCommand(program2, version2) {
|
|
10077
10838
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
10078
|
-
const homeDir2 =
|
|
10839
|
+
const homeDir2 = import_os20.default.homedir();
|
|
10079
10840
|
let failures = 0;
|
|
10080
10841
|
function pass(msg) {
|
|
10081
10842
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -10097,7 +10858,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10097
10858
|
`));
|
|
10098
10859
|
section("Binary");
|
|
10099
10860
|
try {
|
|
10100
|
-
const which = (0,
|
|
10861
|
+
const which = (0, import_child_process11.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
10101
10862
|
pass(`node9 found at ${which}`);
|
|
10102
10863
|
} catch {
|
|
10103
10864
|
warn(
|
|
@@ -10115,7 +10876,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10115
10876
|
);
|
|
10116
10877
|
}
|
|
10117
10878
|
try {
|
|
10118
|
-
const gitVersion = (0,
|
|
10879
|
+
const gitVersion = (0, import_child_process11.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
10119
10880
|
pass(gitVersion);
|
|
10120
10881
|
} catch {
|
|
10121
10882
|
warn(
|
|
@@ -10124,10 +10885,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10124
10885
|
);
|
|
10125
10886
|
}
|
|
10126
10887
|
section("Configuration");
|
|
10127
|
-
const globalConfigPath =
|
|
10128
|
-
if (
|
|
10888
|
+
const globalConfigPath = import_path26.default.join(homeDir2, ".node9", "config.json");
|
|
10889
|
+
if (import_fs24.default.existsSync(globalConfigPath)) {
|
|
10129
10890
|
try {
|
|
10130
|
-
JSON.parse(
|
|
10891
|
+
JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
|
|
10131
10892
|
pass("~/.node9/config.json found and valid");
|
|
10132
10893
|
} catch {
|
|
10133
10894
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -10135,10 +10896,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10135
10896
|
} else {
|
|
10136
10897
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
10137
10898
|
}
|
|
10138
|
-
const projectConfigPath =
|
|
10139
|
-
if (
|
|
10899
|
+
const projectConfigPath = import_path26.default.join(process.cwd(), "node9.config.json");
|
|
10900
|
+
if (import_fs24.default.existsSync(projectConfigPath)) {
|
|
10140
10901
|
try {
|
|
10141
|
-
JSON.parse(
|
|
10902
|
+
JSON.parse(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
|
|
10142
10903
|
pass("node9.config.json found and valid (project)");
|
|
10143
10904
|
} catch {
|
|
10144
10905
|
fail(
|
|
@@ -10147,8 +10908,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10147
10908
|
);
|
|
10148
10909
|
}
|
|
10149
10910
|
}
|
|
10150
|
-
const credsPath =
|
|
10151
|
-
if (
|
|
10911
|
+
const credsPath = import_path26.default.join(homeDir2, ".node9", "credentials.json");
|
|
10912
|
+
if (import_fs24.default.existsSync(credsPath)) {
|
|
10152
10913
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
10153
10914
|
} else {
|
|
10154
10915
|
warn(
|
|
@@ -10157,10 +10918,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10157
10918
|
);
|
|
10158
10919
|
}
|
|
10159
10920
|
section("Agent Hooks");
|
|
10160
|
-
const claudeSettingsPath =
|
|
10161
|
-
if (
|
|
10921
|
+
const claudeSettingsPath = import_path26.default.join(homeDir2, ".claude", "settings.json");
|
|
10922
|
+
if (import_fs24.default.existsSync(claudeSettingsPath)) {
|
|
10162
10923
|
try {
|
|
10163
|
-
const cs = JSON.parse(
|
|
10924
|
+
const cs = JSON.parse(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
10164
10925
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
10165
10926
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10166
10927
|
);
|
|
@@ -10176,10 +10937,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10176
10937
|
} else {
|
|
10177
10938
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
10178
10939
|
}
|
|
10179
|
-
const geminiSettingsPath =
|
|
10180
|
-
if (
|
|
10940
|
+
const geminiSettingsPath = import_path26.default.join(homeDir2, ".gemini", "settings.json");
|
|
10941
|
+
if (import_fs24.default.existsSync(geminiSettingsPath)) {
|
|
10181
10942
|
try {
|
|
10182
|
-
const gs = JSON.parse(
|
|
10943
|
+
const gs = JSON.parse(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
10183
10944
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
10184
10945
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10185
10946
|
);
|
|
@@ -10195,10 +10956,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10195
10956
|
} else {
|
|
10196
10957
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
10197
10958
|
}
|
|
10198
|
-
const cursorHooksPath =
|
|
10199
|
-
if (
|
|
10959
|
+
const cursorHooksPath = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
|
|
10960
|
+
if (import_fs24.default.existsSync(cursorHooksPath)) {
|
|
10200
10961
|
try {
|
|
10201
|
-
const cur = JSON.parse(
|
|
10962
|
+
const cur = JSON.parse(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
10202
10963
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
10203
10964
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
10204
10965
|
);
|
|
@@ -10236,9 +10997,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10236
10997
|
|
|
10237
10998
|
// src/cli/commands/audit.ts
|
|
10238
10999
|
var import_chalk8 = __toESM(require("chalk"));
|
|
10239
|
-
var
|
|
10240
|
-
var
|
|
10241
|
-
var
|
|
11000
|
+
var import_fs25 = __toESM(require("fs"));
|
|
11001
|
+
var import_path27 = __toESM(require("path"));
|
|
11002
|
+
var import_os21 = __toESM(require("os"));
|
|
10242
11003
|
function formatRelativeTime(timestamp) {
|
|
10243
11004
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
10244
11005
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -10251,14 +11012,14 @@ function formatRelativeTime(timestamp) {
|
|
|
10251
11012
|
}
|
|
10252
11013
|
function registerAuditCommand(program2) {
|
|
10253
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) => {
|
|
10254
|
-
const logPath =
|
|
10255
|
-
if (!
|
|
11015
|
+
const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
|
|
11016
|
+
if (!import_fs25.default.existsSync(logPath)) {
|
|
10256
11017
|
console.log(
|
|
10257
11018
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
10258
11019
|
);
|
|
10259
11020
|
return;
|
|
10260
11021
|
}
|
|
10261
|
-
const raw =
|
|
11022
|
+
const raw = import_fs25.default.readFileSync(logPath, "utf-8");
|
|
10262
11023
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
10263
11024
|
let entries = lines.flatMap((line) => {
|
|
10264
11025
|
try {
|
|
@@ -10312,9 +11073,9 @@ function registerAuditCommand(program2) {
|
|
|
10312
11073
|
|
|
10313
11074
|
// src/cli/commands/report.ts
|
|
10314
11075
|
var import_chalk9 = __toESM(require("chalk"));
|
|
10315
|
-
var
|
|
10316
|
-
var
|
|
10317
|
-
var
|
|
11076
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11077
|
+
var import_path28 = __toESM(require("path"));
|
|
11078
|
+
var import_os22 = __toESM(require("os"));
|
|
10318
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;
|
|
10319
11080
|
function buildTestTimestamps(allEntries) {
|
|
10320
11081
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -10361,8 +11122,8 @@ function getDateRange(period) {
|
|
|
10361
11122
|
}
|
|
10362
11123
|
}
|
|
10363
11124
|
function parseAuditLog(logPath) {
|
|
10364
|
-
if (!
|
|
10365
|
-
const raw =
|
|
11125
|
+
if (!import_fs26.default.existsSync(logPath)) return [];
|
|
11126
|
+
const raw = import_fs26.default.readFileSync(logPath, "utf-8");
|
|
10366
11127
|
return raw.split("\n").flatMap((line) => {
|
|
10367
11128
|
if (!line.trim()) return [];
|
|
10368
11129
|
try {
|
|
@@ -10388,9 +11149,9 @@ function colorBar(value, max, width) {
|
|
|
10388
11149
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
10389
11150
|
return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
|
|
10390
11151
|
}
|
|
10391
|
-
function pct(
|
|
11152
|
+
function pct(num3, total) {
|
|
10392
11153
|
if (total === 0) return "\u2013";
|
|
10393
|
-
return Math.round(
|
|
11154
|
+
return Math.round(num3 / total * 100) + "%";
|
|
10394
11155
|
}
|
|
10395
11156
|
function fmtDate(d) {
|
|
10396
11157
|
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
@@ -10431,11 +11192,11 @@ function loadClaudeCost(start, end) {
|
|
|
10431
11192
|
inputTokens: 0,
|
|
10432
11193
|
cacheReadTokens: 0
|
|
10433
11194
|
};
|
|
10434
|
-
const projectsDir =
|
|
10435
|
-
if (!
|
|
11195
|
+
const projectsDir = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
|
|
11196
|
+
if (!import_fs26.default.existsSync(projectsDir)) return empty;
|
|
10436
11197
|
let dirs;
|
|
10437
11198
|
try {
|
|
10438
|
-
dirs =
|
|
11199
|
+
dirs = import_fs26.default.readdirSync(projectsDir);
|
|
10439
11200
|
} catch {
|
|
10440
11201
|
return empty;
|
|
10441
11202
|
}
|
|
@@ -10445,18 +11206,18 @@ function loadClaudeCost(start, end) {
|
|
|
10445
11206
|
const byDay = /* @__PURE__ */ new Map();
|
|
10446
11207
|
const byModel = /* @__PURE__ */ new Map();
|
|
10447
11208
|
for (const proj of dirs) {
|
|
10448
|
-
const projPath =
|
|
11209
|
+
const projPath = import_path28.default.join(projectsDir, proj);
|
|
10449
11210
|
let files;
|
|
10450
11211
|
try {
|
|
10451
|
-
const stat =
|
|
11212
|
+
const stat = import_fs26.default.statSync(projPath);
|
|
10452
11213
|
if (!stat.isDirectory()) continue;
|
|
10453
|
-
files =
|
|
11214
|
+
files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10454
11215
|
} catch {
|
|
10455
11216
|
continue;
|
|
10456
11217
|
}
|
|
10457
11218
|
for (const file of files) {
|
|
10458
11219
|
try {
|
|
10459
|
-
const raw =
|
|
11220
|
+
const raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
|
|
10460
11221
|
for (const line of raw.split("\n")) {
|
|
10461
11222
|
if (!line.trim()) continue;
|
|
10462
11223
|
let entry;
|
|
@@ -10499,7 +11260,7 @@ function registerReportCommand(program2) {
|
|
|
10499
11260
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
10500
11261
|
options.period
|
|
10501
11262
|
) ? options.period : "7d";
|
|
10502
|
-
const logPath =
|
|
11263
|
+
const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
|
|
10503
11264
|
const allEntries = parseAuditLog(logPath);
|
|
10504
11265
|
if (allEntries.length === 0) {
|
|
10505
11266
|
console.log(
|
|
@@ -10747,26 +11508,67 @@ function registerReportCommand(program2) {
|
|
|
10747
11508
|
|
|
10748
11509
|
// src/cli/commands/daemon-cmd.ts
|
|
10749
11510
|
var import_chalk10 = __toESM(require("chalk"));
|
|
10750
|
-
var
|
|
11511
|
+
var import_child_process12 = require("child_process");
|
|
10751
11512
|
init_daemon2();
|
|
10752
11513
|
init_daemon();
|
|
11514
|
+
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
10753
11515
|
function registerDaemonCommand(program2) {
|
|
10754
|
-
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(
|
|
10755
11517
|
"-w, --watch",
|
|
10756
11518
|
"Start daemon + open browser, stay alive permanently (Flight Recorder mode)"
|
|
10757
11519
|
).action(
|
|
10758
11520
|
async (action, options) => {
|
|
10759
11521
|
const cmd = (action ?? "start").toLowerCase();
|
|
10760
|
-
if (cmd === "
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
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);
|
|
10767
11535
|
}
|
|
10768
|
-
if (
|
|
10769
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
11565
|
+
if (cmd === "status") return daemonStatus();
|
|
11566
|
+
if (cmd !== "start" && action !== void 0) {
|
|
11567
|
+
console.error(import_chalk10.default.red(`Unknown daemon action: "${action}". Use: ${VALID_ACTIONS}`));
|
|
11568
|
+
process.exit(1);
|
|
11569
|
+
}
|
|
11570
|
+
if (options.watch) {
|
|
11571
|
+
process.env.NODE9_WATCH_MODE = "1";
|
|
10770
11572
|
setTimeout(() => {
|
|
10771
11573
|
openBrowserLocal();
|
|
10772
11574
|
console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
@@ -10780,7 +11582,7 @@ function registerDaemonCommand(program2) {
|
|
|
10780
11582
|
console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
10781
11583
|
process.exit(0);
|
|
10782
11584
|
}
|
|
10783
|
-
const child = (0,
|
|
11585
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10784
11586
|
detached: true,
|
|
10785
11587
|
stdio: "ignore"
|
|
10786
11588
|
});
|
|
@@ -10795,7 +11597,7 @@ function registerDaemonCommand(program2) {
|
|
|
10795
11597
|
process.exit(0);
|
|
10796
11598
|
}
|
|
10797
11599
|
if (options.background) {
|
|
10798
|
-
const child = (0,
|
|
11600
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10799
11601
|
detached: true,
|
|
10800
11602
|
stdio: "ignore"
|
|
10801
11603
|
});
|
|
@@ -10811,14 +11613,14 @@ function registerDaemonCommand(program2) {
|
|
|
10811
11613
|
|
|
10812
11614
|
// src/cli/commands/status.ts
|
|
10813
11615
|
var import_chalk11 = __toESM(require("chalk"));
|
|
10814
|
-
var
|
|
10815
|
-
var
|
|
10816
|
-
var
|
|
11616
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11617
|
+
var import_path29 = __toESM(require("path"));
|
|
11618
|
+
var import_os23 = __toESM(require("os"));
|
|
10817
11619
|
init_core();
|
|
10818
11620
|
init_daemon();
|
|
10819
11621
|
function readJson2(filePath) {
|
|
10820
11622
|
try {
|
|
10821
|
-
if (
|
|
11623
|
+
if (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
|
|
10822
11624
|
} catch {
|
|
10823
11625
|
}
|
|
10824
11626
|
return null;
|
|
@@ -10883,28 +11685,28 @@ function registerStatusCommand(program2) {
|
|
|
10883
11685
|
console.log("");
|
|
10884
11686
|
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
10885
11687
|
console.log(` Mode: ${modeLabel}`);
|
|
10886
|
-
const projectConfig =
|
|
10887
|
-
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");
|
|
10888
11690
|
console.log(
|
|
10889
|
-
` Local: ${
|
|
11691
|
+
` Local: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10890
11692
|
);
|
|
10891
11693
|
console.log(
|
|
10892
|
-
` Global: ${
|
|
11694
|
+
` Global: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10893
11695
|
);
|
|
10894
11696
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10895
11697
|
console.log(
|
|
10896
11698
|
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10897
11699
|
);
|
|
10898
11700
|
}
|
|
10899
|
-
const homeDir2 =
|
|
11701
|
+
const homeDir2 = import_os23.default.homedir();
|
|
10900
11702
|
const claudeSettings = readJson2(
|
|
10901
|
-
|
|
11703
|
+
import_path29.default.join(homeDir2, ".claude", "settings.json")
|
|
10902
11704
|
);
|
|
10903
|
-
const claudeConfig = readJson2(
|
|
11705
|
+
const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
|
|
10904
11706
|
const geminiSettings = readJson2(
|
|
10905
|
-
|
|
11707
|
+
import_path29.default.join(homeDir2, ".gemini", "settings.json")
|
|
10906
11708
|
);
|
|
10907
|
-
const cursorConfig = readJson2(
|
|
11709
|
+
const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
10908
11710
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10909
11711
|
if (agentFound) {
|
|
10910
11712
|
console.log("");
|
|
@@ -10963,12 +11765,13 @@ function registerStatusCommand(program2) {
|
|
|
10963
11765
|
|
|
10964
11766
|
// src/cli/commands/init.ts
|
|
10965
11767
|
var import_chalk12 = __toESM(require("chalk"));
|
|
10966
|
-
var
|
|
10967
|
-
var
|
|
10968
|
-
var
|
|
10969
|
-
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"));
|
|
10970
11772
|
init_core();
|
|
10971
11773
|
init_shields();
|
|
11774
|
+
init_service();
|
|
10972
11775
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
|
|
10973
11776
|
function fireTelemetryPing(agents) {
|
|
10974
11777
|
try {
|
|
@@ -10978,7 +11781,7 @@ function fireTelemetryPing(agents) {
|
|
|
10978
11781
|
os: process.platform,
|
|
10979
11782
|
node9_version: process.env.npm_package_version ?? "unknown"
|
|
10980
11783
|
});
|
|
10981
|
-
const req =
|
|
11784
|
+
const req = import_https3.default.request(
|
|
10982
11785
|
{
|
|
10983
11786
|
hostname: "api.node9.ai",
|
|
10984
11787
|
path: "/api/v1/telemetry",
|
|
@@ -11025,15 +11828,15 @@ function registerInitCommand(program2) {
|
|
|
11025
11828
|
}
|
|
11026
11829
|
console.log("");
|
|
11027
11830
|
}
|
|
11028
|
-
const configPath =
|
|
11029
|
-
if (
|
|
11831
|
+
const configPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11832
|
+
if (import_fs28.default.existsSync(configPath) && !options.force) {
|
|
11030
11833
|
try {
|
|
11031
|
-
const existing = JSON.parse(
|
|
11834
|
+
const existing = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
11032
11835
|
const settings = existing.settings ?? {};
|
|
11033
11836
|
if (settings.mode !== chosenMode) {
|
|
11034
11837
|
settings.mode = chosenMode;
|
|
11035
11838
|
existing.settings = settings;
|
|
11036
|
-
|
|
11839
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11037
11840
|
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
11038
11841
|
} else {
|
|
11039
11842
|
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -11046,9 +11849,9 @@ function registerInitCommand(program2) {
|
|
|
11046
11849
|
...DEFAULT_CONFIG,
|
|
11047
11850
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
11048
11851
|
};
|
|
11049
|
-
const dir =
|
|
11050
|
-
if (!
|
|
11051
|
-
|
|
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");
|
|
11052
11855
|
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
11053
11856
|
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
11054
11857
|
}
|
|
@@ -11060,9 +11863,13 @@ function registerInitCommand(program2) {
|
|
|
11060
11863
|
);
|
|
11061
11864
|
if (found.length === 0) {
|
|
11062
11865
|
console.log(
|
|
11063
|
-
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>")
|
|
11064
11872
|
);
|
|
11065
|
-
console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
|
|
11066
11873
|
return;
|
|
11067
11874
|
}
|
|
11068
11875
|
console.log(import_chalk12.default.bold("Detected agents:"));
|
|
@@ -11076,6 +11883,32 @@ function registerInitCommand(program2) {
|
|
|
11076
11883
|
else if (agent === "gemini") await setupGemini();
|
|
11077
11884
|
else if (agent === "cursor") await setupCursor();
|
|
11078
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
|
+
}
|
|
11079
11912
|
console.log("");
|
|
11080
11913
|
}
|
|
11081
11914
|
{
|
|
@@ -11102,7 +11935,7 @@ function registerInitCommand(program2) {
|
|
|
11102
11935
|
}
|
|
11103
11936
|
|
|
11104
11937
|
// src/cli/commands/undo.ts
|
|
11105
|
-
var
|
|
11938
|
+
var import_path31 = __toESM(require("path"));
|
|
11106
11939
|
var import_chalk14 = __toESM(require("chalk"));
|
|
11107
11940
|
|
|
11108
11941
|
// src/tui/undo-navigator.ts
|
|
@@ -11261,7 +12094,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
11261
12094
|
let dir = startDir;
|
|
11262
12095
|
while (true) {
|
|
11263
12096
|
if (cwds.has(dir)) return dir;
|
|
11264
|
-
const parent =
|
|
12097
|
+
const parent = import_path31.default.dirname(dir);
|
|
11265
12098
|
if (parent === dir) return null;
|
|
11266
12099
|
dir = parent;
|
|
11267
12100
|
}
|
|
@@ -11389,7 +12222,7 @@ function registerUndoCommand(program2) {
|
|
|
11389
12222
|
|
|
11390
12223
|
// src/cli/commands/watch.ts
|
|
11391
12224
|
var import_chalk15 = __toESM(require("chalk"));
|
|
11392
|
-
var
|
|
12225
|
+
var import_child_process13 = require("child_process");
|
|
11393
12226
|
init_daemon();
|
|
11394
12227
|
function registerWatchCommand(program2) {
|
|
11395
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) => {
|
|
@@ -11406,7 +12239,7 @@ function registerWatchCommand(program2) {
|
|
|
11406
12239
|
}
|
|
11407
12240
|
} catch {
|
|
11408
12241
|
console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
11409
|
-
const child = (0,
|
|
12242
|
+
const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
11410
12243
|
detached: true,
|
|
11411
12244
|
stdio: "ignore",
|
|
11412
12245
|
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
@@ -11436,7 +12269,7 @@ function registerWatchCommand(program2) {
|
|
|
11436
12269
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
11437
12270
|
)
|
|
11438
12271
|
);
|
|
11439
|
-
const result = (0,
|
|
12272
|
+
const result = (0, import_child_process13.spawnSync)(cmd, args, {
|
|
11440
12273
|
stdio: "inherit",
|
|
11441
12274
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
11442
12275
|
});
|
|
@@ -11451,18 +12284,18 @@ function registerWatchCommand(program2) {
|
|
|
11451
12284
|
// src/mcp-gateway/index.ts
|
|
11452
12285
|
var import_readline3 = __toESM(require("readline"));
|
|
11453
12286
|
var import_chalk16 = __toESM(require("chalk"));
|
|
11454
|
-
var
|
|
12287
|
+
var import_child_process14 = require("child_process");
|
|
11455
12288
|
var import_execa3 = require("execa");
|
|
11456
12289
|
init_orchestrator();
|
|
11457
12290
|
init_provenance();
|
|
11458
12291
|
|
|
11459
12292
|
// src/mcp-pin.ts
|
|
11460
|
-
var
|
|
11461
|
-
var
|
|
11462
|
-
var
|
|
12293
|
+
var import_fs29 = __toESM(require("fs"));
|
|
12294
|
+
var import_path32 = __toESM(require("path"));
|
|
12295
|
+
var import_os25 = __toESM(require("os"));
|
|
11463
12296
|
var import_crypto9 = __toESM(require("crypto"));
|
|
11464
12297
|
function getPinsFilePath() {
|
|
11465
|
-
return
|
|
12298
|
+
return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
|
|
11466
12299
|
}
|
|
11467
12300
|
function hashToolDefinitions(tools) {
|
|
11468
12301
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -11479,7 +12312,7 @@ function getServerKey(upstreamCommand) {
|
|
|
11479
12312
|
function readMcpPinsSafe() {
|
|
11480
12313
|
const filePath = getPinsFilePath();
|
|
11481
12314
|
try {
|
|
11482
|
-
const raw =
|
|
12315
|
+
const raw = import_fs29.default.readFileSync(filePath, "utf-8");
|
|
11483
12316
|
if (!raw.trim()) {
|
|
11484
12317
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
11485
12318
|
}
|
|
@@ -11503,10 +12336,10 @@ function readMcpPins() {
|
|
|
11503
12336
|
}
|
|
11504
12337
|
function writeMcpPins(data) {
|
|
11505
12338
|
const filePath = getPinsFilePath();
|
|
11506
|
-
|
|
12339
|
+
import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
|
|
11507
12340
|
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
11508
|
-
|
|
11509
|
-
|
|
12341
|
+
import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
12342
|
+
import_fs29.default.renameSync(tmp, filePath);
|
|
11510
12343
|
}
|
|
11511
12344
|
function checkPin(serverKey, currentHash) {
|
|
11512
12345
|
const result = readMcpPinsSafe();
|
|
@@ -11623,7 +12456,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11623
12456
|
const safeEnv = Object.fromEntries(
|
|
11624
12457
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
11625
12458
|
);
|
|
11626
|
-
const child = (0,
|
|
12459
|
+
const child = (0, import_child_process14.spawn)(executable, cmdArgs, {
|
|
11627
12460
|
stdio: ["pipe", "pipe", "inherit"],
|
|
11628
12461
|
// control stdin/stdout; inherit stderr
|
|
11629
12462
|
shell: false,
|
|
@@ -11878,9 +12711,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11878
12711
|
|
|
11879
12712
|
// src/mcp-server/index.ts
|
|
11880
12713
|
var import_readline4 = __toESM(require("readline"));
|
|
11881
|
-
var
|
|
11882
|
-
var
|
|
11883
|
-
var
|
|
12714
|
+
var import_fs30 = __toESM(require("fs"));
|
|
12715
|
+
var import_os26 = __toESM(require("os"));
|
|
12716
|
+
var import_path33 = __toESM(require("path"));
|
|
11884
12717
|
init_core();
|
|
11885
12718
|
init_daemon();
|
|
11886
12719
|
init_shields();
|
|
@@ -12055,13 +12888,13 @@ function handleStatus() {
|
|
|
12055
12888
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
12056
12889
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
12057
12890
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
12058
|
-
const projectConfig =
|
|
12059
|
-
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");
|
|
12060
12893
|
lines.push(
|
|
12061
|
-
`Project config (node9.config.json): ${
|
|
12894
|
+
`Project config (node9.config.json): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
12062
12895
|
);
|
|
12063
12896
|
lines.push(
|
|
12064
|
-
`Global config (~/.node9/config.json): ${
|
|
12897
|
+
`Global config (~/.node9/config.json): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
12065
12898
|
);
|
|
12066
12899
|
return lines.join("\n");
|
|
12067
12900
|
}
|
|
@@ -12135,21 +12968,21 @@ function handleShieldDisable(args) {
|
|
|
12135
12968
|
writeActiveShields(active.filter((s) => s !== name));
|
|
12136
12969
|
return `Shield "${name}" disabled.`;
|
|
12137
12970
|
}
|
|
12138
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
12971
|
+
var GLOBAL_CONFIG_PATH2 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
12139
12972
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
12140
12973
|
function readGlobalConfigRaw() {
|
|
12141
12974
|
try {
|
|
12142
|
-
if (
|
|
12143
|
-
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"));
|
|
12144
12977
|
}
|
|
12145
12978
|
} catch {
|
|
12146
12979
|
}
|
|
12147
12980
|
return {};
|
|
12148
12981
|
}
|
|
12149
12982
|
function writeGlobalConfigRaw(data) {
|
|
12150
|
-
const dir =
|
|
12151
|
-
if (!
|
|
12152
|
-
|
|
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");
|
|
12153
12986
|
}
|
|
12154
12987
|
function handleApproverList() {
|
|
12155
12988
|
const config = getConfig();
|
|
@@ -12192,9 +13025,9 @@ function handleApproverSet(args) {
|
|
|
12192
13025
|
}
|
|
12193
13026
|
function handleAuditGet(args) {
|
|
12194
13027
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
12195
|
-
const auditPath =
|
|
12196
|
-
if (!
|
|
12197
|
-
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);
|
|
12198
13031
|
const recent = lines.slice(-limit);
|
|
12199
13032
|
const entries = recent.map((line) => {
|
|
12200
13033
|
try {
|
|
@@ -12512,25 +13345,950 @@ function registerMcpPinCommand(program2) {
|
|
|
12512
13345
|
});
|
|
12513
13346
|
}
|
|
12514
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
|
+
|
|
12515
14273
|
// src/cli.ts
|
|
12516
14274
|
var { version } = JSON.parse(
|
|
12517
|
-
|
|
14275
|
+
import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
|
|
12518
14276
|
);
|
|
12519
14277
|
var program = new import_commander.Command();
|
|
12520
14278
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
12521
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) => {
|
|
12522
|
-
const
|
|
12523
|
-
const credPath =
|
|
12524
|
-
if (!
|
|
12525
|
-
|
|
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 });
|
|
12526
14284
|
const profileName = options.profile || "default";
|
|
12527
14285
|
let existingCreds = {};
|
|
12528
14286
|
try {
|
|
12529
|
-
if (
|
|
12530
|
-
const raw = JSON.parse(
|
|
14287
|
+
if (import_fs35.default.existsSync(credPath)) {
|
|
14288
|
+
const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
|
|
12531
14289
|
if (raw.apiKey) {
|
|
12532
14290
|
existingCreds = {
|
|
12533
|
-
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl ||
|
|
14291
|
+
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
12534
14292
|
};
|
|
12535
14293
|
} else {
|
|
12536
14294
|
existingCreds = raw;
|
|
@@ -12538,14 +14296,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12538
14296
|
}
|
|
12539
14297
|
} catch {
|
|
12540
14298
|
}
|
|
12541
|
-
existingCreds[profileName] = { apiKey, apiUrl:
|
|
12542
|
-
|
|
14299
|
+
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14300
|
+
import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
12543
14301
|
if (profileName === "default") {
|
|
12544
|
-
const configPath =
|
|
14302
|
+
const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
|
|
12545
14303
|
let config = {};
|
|
12546
14304
|
try {
|
|
12547
|
-
if (
|
|
12548
|
-
config = JSON.parse(
|
|
14305
|
+
if (import_fs35.default.existsSync(configPath))
|
|
14306
|
+
config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
|
|
12549
14307
|
} catch {
|
|
12550
14308
|
}
|
|
12551
14309
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -12560,39 +14318,47 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12560
14318
|
approvers.cloud = false;
|
|
12561
14319
|
}
|
|
12562
14320
|
s.approvers = approvers;
|
|
12563
|
-
if (!
|
|
12564
|
-
|
|
12565
|
-
|
|
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 });
|
|
12566
14324
|
}
|
|
12567
14325
|
if (options.profile && profileName !== "default") {
|
|
12568
|
-
console.log(
|
|
12569
|
-
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`));
|
|
12570
14328
|
} else if (options.local) {
|
|
12571
|
-
console.log(
|
|
12572
|
-
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.`));
|
|
12573
14331
|
} else {
|
|
12574
|
-
console.log(
|
|
12575
|
-
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.`));
|
|
12576
14334
|
}
|
|
12577
14335
|
});
|
|
12578
|
-
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) => {
|
|
12579
14337
|
if (target === "gemini") return await setupGemini();
|
|
12580
14338
|
if (target === "claude") return await setupClaude();
|
|
12581
14339
|
if (target === "cursor") return await setupCursor();
|
|
14340
|
+
if (target === "windsurf") return await setupWindsurf();
|
|
14341
|
+
if (target === "vscode") return await setupVSCode();
|
|
12582
14342
|
if (target === "hud") return setupHud();
|
|
12583
|
-
console.error(
|
|
14343
|
+
console.error(
|
|
14344
|
+
import_chalk24.default.red(
|
|
14345
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14346
|
+
)
|
|
14347
|
+
);
|
|
12584
14348
|
process.exit(1);
|
|
12585
14349
|
});
|
|
12586
|
-
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) => {
|
|
12587
14351
|
if (!target) {
|
|
12588
|
-
console.log(
|
|
12589
|
-
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");
|
|
12590
14354
|
console.log(" Targets:");
|
|
12591
|
-
console.log(" " +
|
|
12592
|
-
console.log(" " +
|
|
12593
|
-
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)");
|
|
12594
14360
|
process.stdout.write(
|
|
12595
|
-
" " +
|
|
14361
|
+
" " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
12596
14362
|
);
|
|
12597
14363
|
console.log("");
|
|
12598
14364
|
return;
|
|
@@ -12601,93 +14367,108 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
12601
14367
|
if (t === "gemini") return await setupGemini();
|
|
12602
14368
|
if (t === "claude") return await setupClaude();
|
|
12603
14369
|
if (t === "cursor") return await setupCursor();
|
|
14370
|
+
if (t === "windsurf") return await setupWindsurf();
|
|
14371
|
+
if (t === "vscode") return await setupVSCode();
|
|
12604
14372
|
if (t === "hud") return setupHud();
|
|
12605
|
-
console.error(
|
|
14373
|
+
console.error(
|
|
14374
|
+
import_chalk24.default.red(
|
|
14375
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14376
|
+
)
|
|
14377
|
+
);
|
|
12606
14378
|
process.exit(1);
|
|
12607
14379
|
});
|
|
12608
|
-
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) => {
|
|
12609
14384
|
let fn;
|
|
12610
14385
|
if (target === "claude") fn = teardownClaude;
|
|
12611
14386
|
else if (target === "gemini") fn = teardownGemini;
|
|
12612
14387
|
else if (target === "cursor") fn = teardownCursor;
|
|
14388
|
+
else if (target === "windsurf") fn = teardownWindsurf;
|
|
14389
|
+
else if (target === "vscode") fn = teardownVSCode;
|
|
12613
14390
|
else if (target === "hud") fn = teardownHud;
|
|
12614
14391
|
else {
|
|
12615
14392
|
console.error(
|
|
12616
|
-
|
|
14393
|
+
import_chalk24.default.red(
|
|
14394
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14395
|
+
)
|
|
12617
14396
|
);
|
|
12618
14397
|
process.exit(1);
|
|
12619
14398
|
}
|
|
12620
|
-
console.log(
|
|
14399
|
+
console.log(import_chalk24.default.cyan(`
|
|
12621
14400
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
12622
14401
|
`));
|
|
12623
14402
|
try {
|
|
12624
14403
|
fn();
|
|
12625
14404
|
} catch (err2) {
|
|
12626
|
-
console.error(
|
|
14405
|
+
console.error(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
12627
14406
|
process.exit(1);
|
|
12628
14407
|
}
|
|
12629
|
-
console.log(
|
|
14408
|
+
console.log(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
|
|
12630
14409
|
});
|
|
12631
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) => {
|
|
12632
|
-
console.log(
|
|
12633
|
-
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..."));
|
|
12634
14413
|
try {
|
|
12635
14414
|
stopDaemon();
|
|
12636
|
-
console.log(
|
|
14415
|
+
console.log(import_chalk24.default.green(" \u2705 Daemon stopped"));
|
|
12637
14416
|
} catch {
|
|
12638
|
-
console.log(
|
|
14417
|
+
console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
12639
14418
|
}
|
|
12640
|
-
console.log(
|
|
14419
|
+
console.log(import_chalk24.default.bold("\nRemoving hooks..."));
|
|
12641
14420
|
let teardownFailed = false;
|
|
12642
14421
|
for (const [label, fn] of [
|
|
12643
14422
|
["Claude", teardownClaude],
|
|
12644
14423
|
["Gemini", teardownGemini],
|
|
12645
|
-
["Cursor", teardownCursor]
|
|
14424
|
+
["Cursor", teardownCursor],
|
|
14425
|
+
["Windsurf", teardownWindsurf],
|
|
14426
|
+
["VSCode", teardownVSCode]
|
|
12646
14427
|
]) {
|
|
12647
14428
|
try {
|
|
12648
14429
|
fn();
|
|
12649
14430
|
} catch (err2) {
|
|
12650
14431
|
teardownFailed = true;
|
|
12651
14432
|
console.error(
|
|
12652
|
-
|
|
14433
|
+
import_chalk24.default.red(
|
|
12653
14434
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
12654
14435
|
)
|
|
12655
14436
|
);
|
|
12656
14437
|
}
|
|
12657
14438
|
}
|
|
12658
14439
|
if (options.purge) {
|
|
12659
|
-
const node9Dir =
|
|
12660
|
-
if (
|
|
14440
|
+
const node9Dir = import_path38.default.join(import_os31.default.homedir(), ".node9");
|
|
14441
|
+
if (import_fs35.default.existsSync(node9Dir)) {
|
|
12661
14442
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
12662
14443
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
12663
14444
|
default: false
|
|
12664
14445
|
});
|
|
12665
14446
|
if (confirmed) {
|
|
12666
|
-
|
|
12667
|
-
if (
|
|
14447
|
+
import_fs35.default.rmSync(node9Dir, { recursive: true });
|
|
14448
|
+
if (import_fs35.default.existsSync(node9Dir)) {
|
|
12668
14449
|
console.error(
|
|
12669
|
-
|
|
14450
|
+
import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
12670
14451
|
);
|
|
12671
14452
|
} else {
|
|
12672
|
-
console.log(
|
|
14453
|
+
console.log(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
12673
14454
|
}
|
|
12674
14455
|
} else {
|
|
12675
|
-
console.log(
|
|
14456
|
+
console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
12676
14457
|
}
|
|
12677
14458
|
} else {
|
|
12678
|
-
console.log(
|
|
14459
|
+
console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
12679
14460
|
}
|
|
12680
14461
|
} else {
|
|
12681
14462
|
console.log(
|
|
12682
|
-
|
|
14463
|
+
import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
12683
14464
|
);
|
|
12684
14465
|
}
|
|
12685
14466
|
if (teardownFailed) {
|
|
12686
|
-
console.error(
|
|
14467
|
+
console.error(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
12687
14468
|
process.exit(1);
|
|
12688
14469
|
}
|
|
12689
|
-
console.log(
|
|
12690
|
-
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"));
|
|
12691
14472
|
});
|
|
12692
14473
|
registerDoctorCommand(program, version);
|
|
12693
14474
|
program.command("explain").description(
|
|
@@ -12700,7 +14481,7 @@ program.command("explain").description(
|
|
|
12700
14481
|
try {
|
|
12701
14482
|
args = JSON.parse(trimmed);
|
|
12702
14483
|
} catch {
|
|
12703
|
-
console.error(
|
|
14484
|
+
console.error(import_chalk24.default.red(`
|
|
12704
14485
|
\u274C Invalid JSON: ${trimmed}
|
|
12705
14486
|
`));
|
|
12706
14487
|
process.exit(1);
|
|
@@ -12711,54 +14492,54 @@ program.command("explain").description(
|
|
|
12711
14492
|
}
|
|
12712
14493
|
const result = await explainPolicy(tool, args);
|
|
12713
14494
|
console.log("");
|
|
12714
|
-
console.log(
|
|
14495
|
+
console.log(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
12715
14496
|
console.log("");
|
|
12716
|
-
console.log(` ${
|
|
14497
|
+
console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
|
|
12717
14498
|
if (argsRaw) {
|
|
12718
|
-
const
|
|
12719
|
-
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)}`);
|
|
12720
14501
|
}
|
|
12721
14502
|
console.log("");
|
|
12722
|
-
console.log(
|
|
14503
|
+
console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
|
|
12723
14504
|
for (const tier of result.waterfall) {
|
|
12724
|
-
const
|
|
14505
|
+
const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
|
|
12725
14506
|
const label = tier.label.padEnd(16);
|
|
12726
14507
|
let statusStr;
|
|
12727
14508
|
if (tier.tier === 1) {
|
|
12728
|
-
statusStr =
|
|
14509
|
+
statusStr = import_chalk24.default.gray(tier.note ?? "");
|
|
12729
14510
|
} else if (tier.status === "active") {
|
|
12730
|
-
const loc = tier.path ?
|
|
12731
|
-
const note = tier.note ?
|
|
12732
|
-
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 : "");
|
|
12733
14514
|
} else {
|
|
12734
|
-
statusStr =
|
|
14515
|
+
statusStr = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
12735
14516
|
}
|
|
12736
|
-
console.log(`${
|
|
14517
|
+
console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
|
|
12737
14518
|
}
|
|
12738
14519
|
console.log("");
|
|
12739
|
-
console.log(
|
|
14520
|
+
console.log(import_chalk24.default.bold("Policy Evaluation:"));
|
|
12740
14521
|
for (const step of result.steps) {
|
|
12741
14522
|
const isFinal = step.isFinal;
|
|
12742
14523
|
let icon;
|
|
12743
|
-
if (step.outcome === "allow") icon =
|
|
12744
|
-
else if (step.outcome === "review") icon =
|
|
12745
|
-
else if (step.outcome === "skip") icon =
|
|
12746
|
-
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 ");
|
|
12747
14528
|
const name = step.name.padEnd(18);
|
|
12748
|
-
const nameStr = isFinal ?
|
|
12749
|
-
const detail = isFinal ?
|
|
12750
|
-
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") : "";
|
|
12751
14532
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
12752
14533
|
}
|
|
12753
14534
|
console.log("");
|
|
12754
14535
|
if (result.decision === "allow") {
|
|
12755
|
-
console.log(
|
|
14536
|
+
console.log(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
|
|
12756
14537
|
} else {
|
|
12757
14538
|
console.log(
|
|
12758
|
-
|
|
14539
|
+
import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
|
|
12759
14540
|
);
|
|
12760
14541
|
if (result.blockedByLabel) {
|
|
12761
|
-
console.log(
|
|
14542
|
+
console.log(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
12762
14543
|
}
|
|
12763
14544
|
}
|
|
12764
14545
|
console.log("");
|
|
@@ -12773,7 +14554,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
12773
14554
|
try {
|
|
12774
14555
|
await startTail2(options);
|
|
12775
14556
|
} catch (err2) {
|
|
12776
|
-
console.error(
|
|
14557
|
+
console.error(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
12777
14558
|
process.exit(1);
|
|
12778
14559
|
}
|
|
12779
14560
|
});
|
|
@@ -12805,14 +14586,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12805
14586
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12806
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) => {
|
|
12807
14588
|
if (subcommand === "debug") {
|
|
12808
|
-
const flagFile =
|
|
14589
|
+
const flagFile = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
|
|
12809
14590
|
if (state === "on") {
|
|
12810
|
-
|
|
12811
|
-
|
|
14591
|
+
import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
|
|
14592
|
+
import_fs35.default.writeFileSync(flagFile, "");
|
|
12812
14593
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12813
14594
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12814
14595
|
} else if (state === "off") {
|
|
12815
|
-
if (
|
|
14596
|
+
if (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
|
|
12816
14597
|
console.log("HUD debug logging disabled.");
|
|
12817
14598
|
} else {
|
|
12818
14599
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12827,7 +14608,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12827
14608
|
const ms = parseDuration(options.duration);
|
|
12828
14609
|
if (ms === null) {
|
|
12829
14610
|
console.error(
|
|
12830
|
-
|
|
14611
|
+
import_chalk24.default.red(`
|
|
12831
14612
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
12832
14613
|
`)
|
|
12833
14614
|
);
|
|
@@ -12835,20 +14616,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12835
14616
|
}
|
|
12836
14617
|
pauseNode9(ms, options.duration);
|
|
12837
14618
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
12838
|
-
console.log(
|
|
14619
|
+
console.log(import_chalk24.default.yellow(`
|
|
12839
14620
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
12840
|
-
console.log(
|
|
12841
|
-
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.
|
|
12842
14623
|
`));
|
|
12843
14624
|
});
|
|
12844
14625
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
12845
14626
|
const { paused } = checkPause();
|
|
12846
14627
|
if (!paused) {
|
|
12847
|
-
console.log(
|
|
14628
|
+
console.log(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
12848
14629
|
return;
|
|
12849
14630
|
}
|
|
12850
14631
|
resumeNode9();
|
|
12851
|
-
console.log(
|
|
14632
|
+
console.log(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
12852
14633
|
});
|
|
12853
14634
|
var HOOK_BASED_AGENTS = {
|
|
12854
14635
|
claude: "claude",
|
|
@@ -12861,15 +14642,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12861
14642
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
12862
14643
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
12863
14644
|
console.error(
|
|
12864
|
-
|
|
14645
|
+
import_chalk24.default.yellow(`
|
|
12865
14646
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
12866
14647
|
);
|
|
12867
|
-
console.error(
|
|
14648
|
+
console.error(import_chalk24.default.white(`
|
|
12868
14649
|
"${target}" uses its own hook system. Use:`));
|
|
12869
14650
|
console.error(
|
|
12870
|
-
|
|
14651
|
+
import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
|
|
12871
14652
|
);
|
|
12872
|
-
console.error(
|
|
14653
|
+
console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
|
|
12873
14654
|
process.exit(1);
|
|
12874
14655
|
}
|
|
12875
14656
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -12886,7 +14667,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12886
14667
|
}
|
|
12887
14668
|
);
|
|
12888
14669
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
12889
|
-
console.error(
|
|
14670
|
+
console.error(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
12890
14671
|
const daemonReady = await autoStartDaemonAndWait();
|
|
12891
14672
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
12892
14673
|
}
|
|
@@ -12899,12 +14680,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12899
14680
|
}
|
|
12900
14681
|
if (!result.approved) {
|
|
12901
14682
|
console.error(
|
|
12902
|
-
|
|
14683
|
+
import_chalk24.default.red(`
|
|
12903
14684
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
12904
14685
|
);
|
|
12905
14686
|
process.exit(1);
|
|
12906
14687
|
}
|
|
12907
|
-
console.error(
|
|
14688
|
+
console.error(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
12908
14689
|
await runProxy(fullCommand);
|
|
12909
14690
|
} else {
|
|
12910
14691
|
program.help();
|
|
@@ -12914,14 +14695,18 @@ registerUndoCommand(program);
|
|
|
12914
14695
|
registerShieldCommand(program);
|
|
12915
14696
|
registerConfigShowCommand(program);
|
|
12916
14697
|
registerTrustCommand(program);
|
|
14698
|
+
registerSyncCommand(program);
|
|
14699
|
+
registerAgentsCommand(program);
|
|
14700
|
+
registerScanCommand(program);
|
|
14701
|
+
registerSessionsCommand(program);
|
|
12917
14702
|
if (process.argv[2] !== "daemon") {
|
|
12918
14703
|
process.on("unhandledRejection", (reason) => {
|
|
12919
14704
|
const isCheckHook = process.argv[2] === "check";
|
|
12920
14705
|
if (isCheckHook) {
|
|
12921
14706
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12922
|
-
const logPath =
|
|
14707
|
+
const logPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
|
|
12923
14708
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12924
|
-
|
|
14709
|
+
import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12925
14710
|
`);
|
|
12926
14711
|
}
|
|
12927
14712
|
process.exit(0);
|