@node9/proxy 1.38.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/cli.js +1024 -460
- package/dist/cli.mjs +1022 -458
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -206,8 +206,8 @@ function sanitizeConfig(raw) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
const lines = result.error.issues.map((issue) => {
|
|
209
|
-
const
|
|
210
|
-
return ` \u2022 ${
|
|
209
|
+
const path61 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
210
|
+
return ` \u2022 ${path61}: ${issue.message}`;
|
|
211
211
|
});
|
|
212
212
|
return {
|
|
213
213
|
sanitized,
|
|
@@ -1274,9 +1274,9 @@ function matchesPattern(text, patterns) {
|
|
|
1274
1274
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1275
1275
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1276
1276
|
}
|
|
1277
|
-
function getNestedValue(obj,
|
|
1277
|
+
function getNestedValue(obj, path61) {
|
|
1278
1278
|
if (!obj || typeof obj !== "object") return null;
|
|
1279
|
-
const segments =
|
|
1279
|
+
const segments = path61.split(".");
|
|
1280
1280
|
for (const seg of segments) {
|
|
1281
1281
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1282
1282
|
}
|
|
@@ -14035,6 +14035,9 @@ function registerScanCommand(program2) {
|
|
|
14035
14035
|
console.log(
|
|
14036
14036
|
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 scan --drill-down") + import_chalk5.default.dim(" full commands + session IDs")
|
|
14037
14037
|
);
|
|
14038
|
+
console.log(
|
|
14039
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan.underline("https://node9.ai/auth/signup?ref=cli_scan") + import_chalk5.default.dim(" track your fleet")
|
|
14040
|
+
);
|
|
14038
14041
|
console.log("");
|
|
14039
14042
|
return;
|
|
14040
14043
|
}
|
|
@@ -14220,7 +14223,11 @@ function registerScanCommand(program2) {
|
|
|
14220
14223
|
" Hooks into Claude Code automatically. Every tool call checked before it runs."
|
|
14221
14224
|
)
|
|
14222
14225
|
);
|
|
14223
|
-
console.log("
|
|
14226
|
+
console.log("");
|
|
14227
|
+
console.log(" " + import_chalk5.default.bold("See the full report & track your fleet:"));
|
|
14228
|
+
console.log(
|
|
14229
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan.underline("https://node9.ai/auth/signup?ref=cli_scan")
|
|
14230
|
+
);
|
|
14224
14231
|
}
|
|
14225
14232
|
console.log("");
|
|
14226
14233
|
}
|
|
@@ -17239,20 +17246,20 @@ function getModelContextLimit(model) {
|
|
|
17239
17246
|
return 2e5;
|
|
17240
17247
|
}
|
|
17241
17248
|
function readSessionUsage() {
|
|
17242
|
-
const projectsDir =
|
|
17243
|
-
if (!
|
|
17249
|
+
const projectsDir = import_path58.default.join(import_os52.default.homedir(), ".claude", "projects");
|
|
17250
|
+
if (!import_fs60.default.existsSync(projectsDir)) return null;
|
|
17244
17251
|
let latestFile = null;
|
|
17245
17252
|
let latestMtime = 0;
|
|
17246
17253
|
try {
|
|
17247
|
-
for (const dir of
|
|
17248
|
-
const dirPath =
|
|
17254
|
+
for (const dir of import_fs60.default.readdirSync(projectsDir)) {
|
|
17255
|
+
const dirPath = import_path58.default.join(projectsDir, dir);
|
|
17249
17256
|
try {
|
|
17250
|
-
if (!
|
|
17251
|
-
for (const file of
|
|
17257
|
+
if (!import_fs60.default.statSync(dirPath).isDirectory()) continue;
|
|
17258
|
+
for (const file of import_fs60.default.readdirSync(dirPath)) {
|
|
17252
17259
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
17253
|
-
const filePath =
|
|
17260
|
+
const filePath = import_path58.default.join(dirPath, file);
|
|
17254
17261
|
try {
|
|
17255
|
-
const mtime =
|
|
17262
|
+
const mtime = import_fs60.default.statSync(filePath).mtimeMs;
|
|
17256
17263
|
if (mtime > latestMtime) {
|
|
17257
17264
|
latestMtime = mtime;
|
|
17258
17265
|
latestFile = filePath;
|
|
@@ -17267,7 +17274,7 @@ function readSessionUsage() {
|
|
|
17267
17274
|
}
|
|
17268
17275
|
if (!latestFile) return null;
|
|
17269
17276
|
try {
|
|
17270
|
-
const lines =
|
|
17277
|
+
const lines = import_fs60.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
17271
17278
|
let lastModel = "";
|
|
17272
17279
|
let lastInput = 0;
|
|
17273
17280
|
let lastOutput = 0;
|
|
@@ -17292,10 +17299,10 @@ function readSessionUsage() {
|
|
|
17292
17299
|
}
|
|
17293
17300
|
}
|
|
17294
17301
|
function formatContextStat(stat) {
|
|
17295
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
17302
|
+
const pctColor = stat.fillPct >= 80 ? import_chalk34.default.red : stat.fillPct >= 50 ? import_chalk34.default.yellow : import_chalk34.default.cyan;
|
|
17296
17303
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
17297
17304
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
17298
|
-
return
|
|
17305
|
+
return import_chalk34.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk34.default.dim(
|
|
17299
17306
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
17300
17307
|
);
|
|
17301
17308
|
}
|
|
@@ -17318,32 +17325,32 @@ function agentLabel(agent, mcpServer, sessionId) {
|
|
|
17318
17325
|
const tag = sessionTag(sessionId);
|
|
17319
17326
|
const tagSuffix = tag ? `\xB7${tag}` : "";
|
|
17320
17327
|
if (!agent || agent === "Terminal") {
|
|
17321
|
-
return mcpServer ?
|
|
17328
|
+
return mcpServer ? import_chalk34.default.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
17322
17329
|
}
|
|
17323
17330
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
17324
|
-
if (!short) return mcpServer ?
|
|
17325
|
-
return mcpServer ?
|
|
17331
|
+
if (!short) return mcpServer ? import_chalk34.default.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
17332
|
+
return mcpServer ? import_chalk34.default.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : import_chalk34.default.dim(`[${short}${tagSuffix}] `);
|
|
17326
17333
|
}
|
|
17327
17334
|
function formatBase(activity) {
|
|
17328
17335
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
17329
17336
|
const icon = getIcon(activity.tool);
|
|
17330
17337
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
17331
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
17338
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os52.default.homedir(), "~");
|
|
17332
17339
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
17333
|
-
return `${
|
|
17340
|
+
return `${import_chalk34.default.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${import_chalk34.default.white.bold(toolName)} ${import_chalk34.default.dim(argsPreview)}`;
|
|
17334
17341
|
}
|
|
17335
17342
|
function renderResult(activity, result) {
|
|
17336
17343
|
const base = formatBase(activity);
|
|
17337
17344
|
let status;
|
|
17338
17345
|
if (result.status === "allow") {
|
|
17339
|
-
status =
|
|
17346
|
+
status = import_chalk34.default.green("\u2713 ALLOW");
|
|
17340
17347
|
} else if (result.status === "dlp") {
|
|
17341
|
-
status =
|
|
17348
|
+
status = import_chalk34.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
17342
17349
|
} else {
|
|
17343
|
-
status =
|
|
17350
|
+
status = import_chalk34.default.red("\u2717 BLOCK");
|
|
17344
17351
|
}
|
|
17345
17352
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
17346
|
-
const costSuffix = cost == null ? "" :
|
|
17353
|
+
const costSuffix = cost == null ? "" : import_chalk34.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
17347
17354
|
if (process.stdout.isTTY) {
|
|
17348
17355
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
17349
17356
|
import_readline6.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -17360,19 +17367,19 @@ function renderResult(activity, result) {
|
|
|
17360
17367
|
}
|
|
17361
17368
|
function renderPending(activity) {
|
|
17362
17369
|
if (!process.stdout.isTTY) return;
|
|
17363
|
-
const line = `${formatBase(activity)} ${
|
|
17370
|
+
const line = `${formatBase(activity)} ${import_chalk34.default.yellow("\u25CF \u2026")}`;
|
|
17364
17371
|
pendingShownForId = activity.id;
|
|
17365
17372
|
pendingWrappedLines = wrappedLineCount(line);
|
|
17366
17373
|
process.stdout.write(`${line}\r`);
|
|
17367
17374
|
}
|
|
17368
17375
|
async function ensureDaemon() {
|
|
17369
17376
|
let pidPort = null;
|
|
17370
|
-
if (
|
|
17377
|
+
if (import_fs60.default.existsSync(PID_FILE)) {
|
|
17371
17378
|
try {
|
|
17372
|
-
const { port } = JSON.parse(
|
|
17379
|
+
const { port } = JSON.parse(import_fs60.default.readFileSync(PID_FILE, "utf-8"));
|
|
17373
17380
|
pidPort = port;
|
|
17374
17381
|
} catch {
|
|
17375
|
-
console.error(
|
|
17382
|
+
console.error(import_chalk34.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
17376
17383
|
}
|
|
17377
17384
|
}
|
|
17378
17385
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -17383,8 +17390,8 @@ async function ensureDaemon() {
|
|
|
17383
17390
|
if (res.ok) return checkPort;
|
|
17384
17391
|
} catch {
|
|
17385
17392
|
}
|
|
17386
|
-
console.log(
|
|
17387
|
-
const child = (0,
|
|
17393
|
+
console.log(import_chalk34.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
17394
|
+
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
17388
17395
|
detached: true,
|
|
17389
17396
|
stdio: "ignore",
|
|
17390
17397
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -17400,7 +17407,7 @@ async function ensureDaemon() {
|
|
|
17400
17407
|
} catch {
|
|
17401
17408
|
}
|
|
17402
17409
|
}
|
|
17403
|
-
console.error(
|
|
17410
|
+
console.error(import_chalk34.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
17404
17411
|
process.exit(1);
|
|
17405
17412
|
}
|
|
17406
17413
|
function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
@@ -17469,7 +17476,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
17469
17476
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
17470
17477
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
17471
17478
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
17472
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
17479
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk34.default.dim(`(${req.agent})`)}` : "";
|
|
17473
17480
|
const lines = [
|
|
17474
17481
|
``,
|
|
17475
17482
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -17525,9 +17532,9 @@ function buildRecoveryCardLines(req) {
|
|
|
17525
17532
|
];
|
|
17526
17533
|
}
|
|
17527
17534
|
function readApproversFromDisk() {
|
|
17528
|
-
const configPath2 =
|
|
17535
|
+
const configPath2 = import_path58.default.join(import_os52.default.homedir(), ".node9", "config.json");
|
|
17529
17536
|
try {
|
|
17530
|
-
const raw = JSON.parse(
|
|
17537
|
+
const raw = JSON.parse(import_fs60.default.readFileSync(configPath2, "utf-8"));
|
|
17531
17538
|
const settings = raw.settings ?? {};
|
|
17532
17539
|
return settings.approvers ?? {};
|
|
17533
17540
|
} catch {
|
|
@@ -17538,20 +17545,20 @@ function approverStatusLine() {
|
|
|
17538
17545
|
const a = readApproversFromDisk();
|
|
17539
17546
|
const fmt = (label2, key) => {
|
|
17540
17547
|
const on = a[key] !== false;
|
|
17541
|
-
return `[${key[0]}]${label2.slice(1)} ${on ?
|
|
17548
|
+
return `[${key[0]}]${label2.slice(1)} ${on ? import_chalk34.default.green("\u2713") : import_chalk34.default.dim("\u2717")}`;
|
|
17542
17549
|
};
|
|
17543
17550
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
17544
17551
|
}
|
|
17545
17552
|
function toggleApprover(channel) {
|
|
17546
|
-
const configPath2 =
|
|
17553
|
+
const configPath2 = import_path58.default.join(import_os52.default.homedir(), ".node9", "config.json");
|
|
17547
17554
|
try {
|
|
17548
|
-
const raw = JSON.parse(
|
|
17555
|
+
const raw = JSON.parse(import_fs60.default.readFileSync(configPath2, "utf-8"));
|
|
17549
17556
|
const settings = raw.settings ?? {};
|
|
17550
17557
|
const approvers = settings.approvers ?? {};
|
|
17551
17558
|
approvers[channel] = approvers[channel] === false;
|
|
17552
17559
|
settings.approvers = approvers;
|
|
17553
17560
|
raw.settings = settings;
|
|
17554
|
-
|
|
17561
|
+
import_fs60.default.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
|
|
17555
17562
|
} catch (err2) {
|
|
17556
17563
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
17557
17564
|
`);
|
|
@@ -17583,7 +17590,7 @@ async function startTail(options = {}) {
|
|
|
17583
17590
|
req2.end();
|
|
17584
17591
|
});
|
|
17585
17592
|
if (result.ok) {
|
|
17586
|
-
console.log(
|
|
17593
|
+
console.log(import_chalk34.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
17587
17594
|
} else if (result.code === "ECONNREFUSED") {
|
|
17588
17595
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
17589
17596
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -17629,7 +17636,7 @@ async function startTail(options = {}) {
|
|
|
17629
17636
|
const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
17630
17637
|
if (channel) {
|
|
17631
17638
|
toggleApprover(channel);
|
|
17632
|
-
console.log(
|
|
17639
|
+
console.log(import_chalk34.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
17633
17640
|
}
|
|
17634
17641
|
};
|
|
17635
17642
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -17695,7 +17702,7 @@ async function startTail(options = {}) {
|
|
|
17695
17702
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
17696
17703
|
)
|
|
17697
17704
|
);
|
|
17698
|
-
const decisionStamp = action === "always-allow" ?
|
|
17705
|
+
const decisionStamp = action === "always-allow" ? import_chalk34.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk34.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk34.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk34.default.yellow("\u21A9 REDIRECT AI") : import_chalk34.default.red("\u2717 DENIED");
|
|
17699
17706
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
17700
17707
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
17701
17708
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -17723,8 +17730,8 @@ async function startTail(options = {}) {
|
|
|
17723
17730
|
}
|
|
17724
17731
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
17725
17732
|
try {
|
|
17726
|
-
|
|
17727
|
-
|
|
17733
|
+
import_fs60.default.appendFileSync(
|
|
17734
|
+
import_path58.default.join(import_os52.default.homedir(), ".node9", "hook-debug.log"),
|
|
17728
17735
|
`[tail] POST /decision failed: ${String(err2)}
|
|
17729
17736
|
`
|
|
17730
17737
|
);
|
|
@@ -17746,7 +17753,7 @@ async function startTail(options = {}) {
|
|
|
17746
17753
|
);
|
|
17747
17754
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
17748
17755
|
if (externalDecision) {
|
|
17749
|
-
const source = externalDecision === "allow" ?
|
|
17756
|
+
const source = externalDecision === "allow" ? import_chalk34.default.green("\u2713 ALLOWED") : import_chalk34.default.red("\u2717 DENIED");
|
|
17750
17757
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
17751
17758
|
}
|
|
17752
17759
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -17788,31 +17795,31 @@ async function startTail(options = {}) {
|
|
|
17788
17795
|
};
|
|
17789
17796
|
process.stdin.on("keypress", onKeypress);
|
|
17790
17797
|
}
|
|
17791
|
-
const auditLog =
|
|
17798
|
+
const auditLog = import_path58.default.join(import_os52.default.homedir(), ".node9", "audit.log");
|
|
17792
17799
|
try {
|
|
17793
|
-
const unackedDlp =
|
|
17800
|
+
const unackedDlp = import_fs60.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
17794
17801
|
if (unackedDlp > 0) {
|
|
17795
17802
|
console.log("");
|
|
17796
17803
|
console.log(
|
|
17797
|
-
|
|
17804
|
+
import_chalk34.default.bgRed.white.bold(
|
|
17798
17805
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
17799
17806
|
)
|
|
17800
17807
|
);
|
|
17801
17808
|
}
|
|
17802
17809
|
} catch {
|
|
17803
17810
|
}
|
|
17804
|
-
console.log(
|
|
17811
|
+
console.log(import_chalk34.default.cyan.bold(`
|
|
17805
17812
|
\u{1F6F0}\uFE0F Node9 tail`));
|
|
17806
17813
|
if (canApprove) {
|
|
17807
|
-
console.log(
|
|
17808
|
-
console.log(
|
|
17814
|
+
console.log(import_chalk34.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
17815
|
+
console.log(import_chalk34.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
17809
17816
|
}
|
|
17810
17817
|
const ctxStat = readSessionUsage();
|
|
17811
17818
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
17812
17819
|
if (options.history) {
|
|
17813
|
-
console.log(
|
|
17820
|
+
console.log(import_chalk34.default.dim("Showing history + live events.\n"));
|
|
17814
17821
|
} else {
|
|
17815
|
-
console.log(
|
|
17822
|
+
console.log(import_chalk34.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
17816
17823
|
}
|
|
17817
17824
|
process.on("SIGINT", () => {
|
|
17818
17825
|
exitIdleMode();
|
|
@@ -17822,7 +17829,7 @@ async function startTail(options = {}) {
|
|
|
17822
17829
|
import_readline6.default.clearLine(process.stdout, 0);
|
|
17823
17830
|
import_readline6.default.cursorTo(process.stdout, 0);
|
|
17824
17831
|
}
|
|
17825
|
-
console.log(
|
|
17832
|
+
console.log(import_chalk34.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
17826
17833
|
process.exit(0);
|
|
17827
17834
|
});
|
|
17828
17835
|
const STALL_THRESHOLD_MS = 6e4;
|
|
@@ -17830,11 +17837,11 @@ async function startTail(options = {}) {
|
|
|
17830
17837
|
if (stallWarned) return;
|
|
17831
17838
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
17832
17839
|
try {
|
|
17833
|
-
const auditMtime =
|
|
17840
|
+
const auditMtime = import_fs60.default.statSync(auditLog).mtimeMs;
|
|
17834
17841
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
17835
17842
|
console.log("");
|
|
17836
17843
|
console.log(
|
|
17837
|
-
|
|
17844
|
+
import_chalk34.default.yellow(
|
|
17838
17845
|
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
17839
17846
|
)
|
|
17840
17847
|
);
|
|
@@ -17851,7 +17858,7 @@ async function startTail(options = {}) {
|
|
|
17851
17858
|
},
|
|
17852
17859
|
(res) => {
|
|
17853
17860
|
if (res.statusCode !== 200) {
|
|
17854
|
-
console.error(
|
|
17861
|
+
console.error(import_chalk34.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
17855
17862
|
process.exit(1);
|
|
17856
17863
|
}
|
|
17857
17864
|
if (canApprove) enterIdleMode();
|
|
@@ -17882,7 +17889,7 @@ async function startTail(options = {}) {
|
|
|
17882
17889
|
import_readline6.default.clearLine(process.stdout, 0);
|
|
17883
17890
|
import_readline6.default.cursorTo(process.stdout, 0);
|
|
17884
17891
|
}
|
|
17885
|
-
console.log(
|
|
17892
|
+
console.log(import_chalk34.default.red("\n\u274C Daemon disconnected."));
|
|
17886
17893
|
process.exit(1);
|
|
17887
17894
|
});
|
|
17888
17895
|
}
|
|
@@ -17895,7 +17902,7 @@ async function startTail(options = {}) {
|
|
|
17895
17902
|
const parsed = JSON.parse(rawData);
|
|
17896
17903
|
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
17897
17904
|
console.log("");
|
|
17898
|
-
console.log(
|
|
17905
|
+
console.log(import_chalk34.default.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
17899
17906
|
} catch {
|
|
17900
17907
|
}
|
|
17901
17908
|
return;
|
|
@@ -17980,9 +17987,9 @@ async function startTail(options = {}) {
|
|
|
17980
17987
|
const rawSummary = data.argsSummary ?? data.tool;
|
|
17981
17988
|
const summary = shortenPathSummary(rawSummary);
|
|
17982
17989
|
const fileCount = data.fileCount ?? 0;
|
|
17983
|
-
const files = fileCount > 0 ?
|
|
17990
|
+
const files = fileCount > 0 ? import_chalk34.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
17984
17991
|
process.stdout.write(
|
|
17985
|
-
`${
|
|
17992
|
+
`${import_chalk34.default.dim(time)} ${import_chalk34.default.cyan("\u{1F4F8} snapshot")} ${import_chalk34.default.dim(hash)} ${summary}${files}
|
|
17986
17993
|
`
|
|
17987
17994
|
);
|
|
17988
17995
|
return;
|
|
@@ -17999,36 +18006,36 @@ async function startTail(options = {}) {
|
|
|
17999
18006
|
if (event === "execution-result") {
|
|
18000
18007
|
const exec = data;
|
|
18001
18008
|
const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
|
|
18002
|
-
const arrow = exec.isError ?
|
|
18009
|
+
const arrow = exec.isError ? import_chalk34.default.red(" \u21B3 \u2717") : import_chalk34.default.green(" \u21B3 \u2713");
|
|
18003
18010
|
const label2 = agentLabel(exec.agent, exec.mcpServer);
|
|
18004
18011
|
const tool = (exec.tool ?? "").slice(0, 16);
|
|
18005
|
-
const duration = typeof exec.durationMs === "number" ?
|
|
18012
|
+
const duration = typeof exec.durationMs === "number" ? import_chalk34.default.dim(` (${exec.durationMs}ms)`) : "";
|
|
18006
18013
|
console.log(
|
|
18007
|
-
`${
|
|
18014
|
+
`${import_chalk34.default.gray(time)} ${arrow} ${label2}${import_chalk34.default.dim(tool)}${import_chalk34.default.dim(" completed")}${duration}`
|
|
18008
18015
|
);
|
|
18009
18016
|
}
|
|
18010
18017
|
}
|
|
18011
18018
|
req.on("error", (err2) => {
|
|
18012
18019
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
18013
|
-
console.error(
|
|
18020
|
+
console.error(import_chalk34.default.red(`
|
|
18014
18021
|
\u274C ${msg}`));
|
|
18015
18022
|
process.exit(1);
|
|
18016
18023
|
});
|
|
18017
18024
|
}
|
|
18018
|
-
var import_http3,
|
|
18025
|
+
var import_http3, import_chalk34, import_fs60, import_os52, import_path58, import_readline6, import_child_process14, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
18019
18026
|
var init_tail = __esm({
|
|
18020
18027
|
"src/tui/tail.ts"() {
|
|
18021
18028
|
"use strict";
|
|
18022
18029
|
import_http3 = __toESM(require("http"));
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18030
|
+
import_chalk34 = __toESM(require("chalk"));
|
|
18031
|
+
import_fs60 = __toESM(require("fs"));
|
|
18032
|
+
import_os52 = __toESM(require("os"));
|
|
18033
|
+
import_path58 = __toESM(require("path"));
|
|
18027
18034
|
import_readline6 = __toESM(require("readline"));
|
|
18028
|
-
|
|
18035
|
+
import_child_process14 = require("child_process");
|
|
18029
18036
|
init_daemon2();
|
|
18030
18037
|
init_daemon();
|
|
18031
|
-
PID_FILE =
|
|
18038
|
+
PID_FILE = import_path58.default.join(import_os52.default.homedir(), ".node9", "daemon.pid");
|
|
18032
18039
|
ICONS = {
|
|
18033
18040
|
bash: "\u{1F4BB}",
|
|
18034
18041
|
shell: "\u{1F4BB}",
|
|
@@ -18150,9 +18157,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
18150
18157
|
return ` (${m}m left)`;
|
|
18151
18158
|
}
|
|
18152
18159
|
function safeReadJson(filePath) {
|
|
18153
|
-
if (!
|
|
18160
|
+
if (!import_fs61.default.existsSync(filePath)) return null;
|
|
18154
18161
|
try {
|
|
18155
|
-
return JSON.parse(
|
|
18162
|
+
return JSON.parse(import_fs61.default.readFileSync(filePath, "utf-8"));
|
|
18156
18163
|
} catch {
|
|
18157
18164
|
return null;
|
|
18158
18165
|
}
|
|
@@ -18173,12 +18180,12 @@ function countHooksInFile(filePath) {
|
|
|
18173
18180
|
return Object.keys(cfg.hooks).length;
|
|
18174
18181
|
}
|
|
18175
18182
|
function countRulesInDir(rulesDir) {
|
|
18176
|
-
if (!
|
|
18183
|
+
if (!import_fs61.default.existsSync(rulesDir)) return 0;
|
|
18177
18184
|
let count = 0;
|
|
18178
18185
|
try {
|
|
18179
|
-
for (const entry of
|
|
18186
|
+
for (const entry of import_fs61.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
18180
18187
|
if (entry.isDirectory()) {
|
|
18181
|
-
count += countRulesInDir(
|
|
18188
|
+
count += countRulesInDir(import_path59.default.join(rulesDir, entry.name));
|
|
18182
18189
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
18183
18190
|
count++;
|
|
18184
18191
|
}
|
|
@@ -18189,46 +18196,46 @@ function countRulesInDir(rulesDir) {
|
|
|
18189
18196
|
}
|
|
18190
18197
|
function isSamePath(a, b) {
|
|
18191
18198
|
try {
|
|
18192
|
-
return
|
|
18199
|
+
return import_path59.default.resolve(a) === import_path59.default.resolve(b);
|
|
18193
18200
|
} catch {
|
|
18194
18201
|
return false;
|
|
18195
18202
|
}
|
|
18196
18203
|
}
|
|
18197
18204
|
function countConfigs(cwd) {
|
|
18198
|
-
const homeDir2 =
|
|
18199
|
-
const claudeDir =
|
|
18205
|
+
const homeDir2 = import_os53.default.homedir();
|
|
18206
|
+
const claudeDir = import_path59.default.join(homeDir2, ".claude");
|
|
18200
18207
|
let claudeMdCount = 0;
|
|
18201
18208
|
let rulesCount = 0;
|
|
18202
18209
|
let hooksCount = 0;
|
|
18203
18210
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
18204
18211
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
18205
|
-
if (
|
|
18206
|
-
rulesCount += countRulesInDir(
|
|
18207
|
-
const userSettings =
|
|
18212
|
+
if (import_fs61.default.existsSync(import_path59.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
18213
|
+
rulesCount += countRulesInDir(import_path59.default.join(claudeDir, "rules"));
|
|
18214
|
+
const userSettings = import_path59.default.join(claudeDir, "settings.json");
|
|
18208
18215
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
18209
18216
|
hooksCount += countHooksInFile(userSettings);
|
|
18210
|
-
const userClaudeJson =
|
|
18217
|
+
const userClaudeJson = import_path59.default.join(homeDir2, ".claude.json");
|
|
18211
18218
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
18212
18219
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
18213
18220
|
userMcpServers.delete(name);
|
|
18214
18221
|
}
|
|
18215
18222
|
if (cwd) {
|
|
18216
|
-
if (
|
|
18217
|
-
if (
|
|
18218
|
-
const projectClaudeDir =
|
|
18223
|
+
if (import_fs61.default.existsSync(import_path59.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
18224
|
+
if (import_fs61.default.existsSync(import_path59.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
18225
|
+
const projectClaudeDir = import_path59.default.join(cwd, ".claude");
|
|
18219
18226
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
18220
18227
|
if (!overlapsUserScope) {
|
|
18221
|
-
if (
|
|
18222
|
-
rulesCount += countRulesInDir(
|
|
18223
|
-
const projSettings =
|
|
18228
|
+
if (import_fs61.default.existsSync(import_path59.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
18229
|
+
rulesCount += countRulesInDir(import_path59.default.join(projectClaudeDir, "rules"));
|
|
18230
|
+
const projSettings = import_path59.default.join(projectClaudeDir, "settings.json");
|
|
18224
18231
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
18225
18232
|
hooksCount += countHooksInFile(projSettings);
|
|
18226
18233
|
}
|
|
18227
|
-
if (
|
|
18228
|
-
const localSettings =
|
|
18234
|
+
if (import_fs61.default.existsSync(import_path59.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
18235
|
+
const localSettings = import_path59.default.join(projectClaudeDir, "settings.local.json");
|
|
18229
18236
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
18230
18237
|
hooksCount += countHooksInFile(localSettings);
|
|
18231
|
-
const mcpJsonServers = getMcpServerNames(
|
|
18238
|
+
const mcpJsonServers = getMcpServerNames(import_path59.default.join(cwd, ".mcp.json"));
|
|
18232
18239
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
18233
18240
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
18234
18241
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -18261,12 +18268,12 @@ function readActiveShieldsHud() {
|
|
|
18261
18268
|
return shieldsCache.value;
|
|
18262
18269
|
}
|
|
18263
18270
|
try {
|
|
18264
|
-
const shieldsPath =
|
|
18265
|
-
if (!
|
|
18271
|
+
const shieldsPath = import_path59.default.join(import_os53.default.homedir(), ".node9", "shields.json");
|
|
18272
|
+
if (!import_fs61.default.existsSync(shieldsPath)) {
|
|
18266
18273
|
shieldsCache = { value: [], ts: now };
|
|
18267
18274
|
return [];
|
|
18268
18275
|
}
|
|
18269
|
-
const parsed = JSON.parse(
|
|
18276
|
+
const parsed = JSON.parse(import_fs61.default.readFileSync(shieldsPath, "utf-8"));
|
|
18270
18277
|
if (!Array.isArray(parsed.active)) {
|
|
18271
18278
|
shieldsCache = { value: [], ts: now };
|
|
18272
18279
|
return [];
|
|
@@ -18368,17 +18375,17 @@ function renderContextLine(stdin) {
|
|
|
18368
18375
|
async function main() {
|
|
18369
18376
|
try {
|
|
18370
18377
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
18371
|
-
if (
|
|
18378
|
+
if (import_fs61.default.existsSync(import_path59.default.join(import_os53.default.homedir(), ".node9", "hud-debug"))) {
|
|
18372
18379
|
try {
|
|
18373
|
-
const logPath =
|
|
18380
|
+
const logPath = import_path59.default.join(import_os53.default.homedir(), ".node9", "hud-debug.log");
|
|
18374
18381
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18375
18382
|
let size = 0;
|
|
18376
18383
|
try {
|
|
18377
|
-
size =
|
|
18384
|
+
size = import_fs61.default.statSync(logPath).size;
|
|
18378
18385
|
} catch {
|
|
18379
18386
|
}
|
|
18380
18387
|
if (size < MAX_LOG_SIZE) {
|
|
18381
|
-
|
|
18388
|
+
import_fs61.default.appendFileSync(
|
|
18382
18389
|
logPath,
|
|
18383
18390
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
18384
18391
|
);
|
|
@@ -18399,11 +18406,11 @@ async function main() {
|
|
|
18399
18406
|
try {
|
|
18400
18407
|
const cwd = stdin.cwd ?? process.cwd();
|
|
18401
18408
|
for (const configPath2 of [
|
|
18402
|
-
|
|
18403
|
-
|
|
18409
|
+
import_path59.default.join(cwd, "node9.config.json"),
|
|
18410
|
+
import_path59.default.join(import_os53.default.homedir(), ".node9", "config.json")
|
|
18404
18411
|
]) {
|
|
18405
|
-
if (!
|
|
18406
|
-
const cfg = JSON.parse(
|
|
18412
|
+
if (!import_fs61.default.existsSync(configPath2)) continue;
|
|
18413
|
+
const cfg = JSON.parse(import_fs61.default.readFileSync(configPath2, "utf-8"));
|
|
18407
18414
|
const hud = cfg.settings?.hud;
|
|
18408
18415
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
18409
18416
|
}
|
|
@@ -18421,13 +18428,13 @@ async function main() {
|
|
|
18421
18428
|
renderOffline();
|
|
18422
18429
|
}
|
|
18423
18430
|
}
|
|
18424
|
-
var
|
|
18431
|
+
var import_fs61, import_path59, import_os53, import_http4, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
18425
18432
|
var init_hud = __esm({
|
|
18426
18433
|
"src/cli/hud.ts"() {
|
|
18427
18434
|
"use strict";
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18435
|
+
import_fs61 = __toESM(require("fs"));
|
|
18436
|
+
import_path59 = __toESM(require("path"));
|
|
18437
|
+
import_os53 = __toESM(require("os"));
|
|
18431
18438
|
import_http4 = __toESM(require("http"));
|
|
18432
18439
|
init_daemon();
|
|
18433
18440
|
RESET3 = "\x1B[0m";
|
|
@@ -18453,10 +18460,11 @@ var import_commander = require("commander");
|
|
|
18453
18460
|
init_core();
|
|
18454
18461
|
init_setup();
|
|
18455
18462
|
init_daemon2();
|
|
18456
|
-
var
|
|
18457
|
-
var
|
|
18458
|
-
var
|
|
18459
|
-
var
|
|
18463
|
+
var import_chalk35 = __toESM(require("chalk"));
|
|
18464
|
+
var import_fs62 = __toESM(require("fs"));
|
|
18465
|
+
var import_path60 = __toESM(require("path"));
|
|
18466
|
+
var import_os54 = __toESM(require("os"));
|
|
18467
|
+
var import_child_process15 = require("child_process");
|
|
18460
18468
|
var import_prompts2 = require("@inquirer/prompts");
|
|
18461
18469
|
|
|
18462
18470
|
// src/utils/duration.ts
|
|
@@ -24204,7 +24212,133 @@ function checkSecrets(ctx) {
|
|
|
24204
24212
|
}
|
|
24205
24213
|
|
|
24206
24214
|
// src/posture/egress.ts
|
|
24215
|
+
var import_fs47 = __toESM(require("fs"));
|
|
24207
24216
|
init_config();
|
|
24217
|
+
|
|
24218
|
+
// src/sandbox/templates.ts
|
|
24219
|
+
var AGENT_NPM_PACKAGE = {
|
|
24220
|
+
claude: "@anthropic-ai/claude-code",
|
|
24221
|
+
codex: "@openai/codex"
|
|
24222
|
+
};
|
|
24223
|
+
function pinnedNode9Version(hostVersion) {
|
|
24224
|
+
return hostVersion && /^\d+\.\d+\.\d+$/.test(hostVersion) ? hostVersion : "latest";
|
|
24225
|
+
}
|
|
24226
|
+
var AGENT_BIN = {
|
|
24227
|
+
claude: "claude",
|
|
24228
|
+
codex: "codex"
|
|
24229
|
+
};
|
|
24230
|
+
var RUN_AS_USER = "agent";
|
|
24231
|
+
var ALLOWED_DOMAINS_PATH = "/etc/node9-sandbox/allowed-domains.txt";
|
|
24232
|
+
function renderDockerfile(config, node9Version2) {
|
|
24233
|
+
const agentPkg = AGENT_NPM_PACKAGE[config.agent];
|
|
24234
|
+
return `# Auto-generated by node9 sandbox. Do not edit by hand.
|
|
24235
|
+
FROM node:22-bookworm
|
|
24236
|
+
|
|
24237
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
24238
|
+
|
|
24239
|
+
# Wall + base tooling (iptables/ipset/dig/gosu) \u2014 ported from Isag.
|
|
24240
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
24241
|
+
ca-certificates curl git gosu iproute2 ipset iptables dnsutils jq \\
|
|
24242
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
24243
|
+
|
|
24244
|
+
# The worker (agent CLI).
|
|
24245
|
+
RUN npm install -g ${agentPkg}
|
|
24246
|
+
|
|
24247
|
+
# The guard (node9), pinned to the host version.
|
|
24248
|
+
RUN npm install -g node9-ai@${node9Version2}
|
|
24249
|
+
|
|
24250
|
+
# Non-root runtime user at uid 1000 (matches the typical single-user host so the
|
|
24251
|
+
# mounted ~/.claude / ~/.codex / project are read/writable). The node base image
|
|
24252
|
+
# already claims uid 1000 for the 'node' user \u2014 free it first (cf. Isag/ubuntu).
|
|
24253
|
+
RUN userdel -r node 2>/dev/null || true; \\
|
|
24254
|
+
userdel -r ubuntu 2>/dev/null || true; \\
|
|
24255
|
+
useradd --create-home --uid 1000 --shell /bin/bash ${RUN_AS_USER}
|
|
24256
|
+
|
|
24257
|
+
# Wire the agent's node9 hooks into the runtime user's home (build-time, static).
|
|
24258
|
+
RUN gosu ${RUN_AS_USER} node9 agents add ${config.agent} || true
|
|
24259
|
+
|
|
24260
|
+
RUN mkdir -p /workspace /etc/node9-sandbox \\
|
|
24261
|
+
&& chown ${RUN_AS_USER}:${RUN_AS_USER} /workspace
|
|
24262
|
+
|
|
24263
|
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
24264
|
+
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
24265
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
24266
|
+
`;
|
|
24267
|
+
}
|
|
24268
|
+
function renderEntrypoint(config) {
|
|
24269
|
+
const agentBin = AGENT_BIN[config.agent];
|
|
24270
|
+
return `#!/usr/bin/env bash
|
|
24271
|
+
# Auto-generated by node9 sandbox. Seals the egress wall (root), then drops to the
|
|
24272
|
+
# non-root agent which starts the node9 daemon + execs the agent.
|
|
24273
|
+
set -Eeuo pipefail
|
|
24274
|
+
|
|
24275
|
+
DOMAINS_FILE="${ALLOWED_DOMAINS_PATH}"
|
|
24276
|
+
RUN_AS_USER="${RUN_AS_USER}"
|
|
24277
|
+
|
|
24278
|
+
[[ -s "$DOMAINS_FILE" ]] || { echo "entrypoint: missing/empty $DOMAINS_FILE" >&2; exit 1; }
|
|
24279
|
+
|
|
24280
|
+
# Own the mounted node9 data dir so the agent user can write audit there.
|
|
24281
|
+
mkdir -p "/home/$RUN_AS_USER/.node9"
|
|
24282
|
+
chown -R "$RUN_AS_USER:$RUN_AS_USER" "/home/$RUN_AS_USER/.node9" || true
|
|
24283
|
+
|
|
24284
|
+
# \u2500\u2500 Resolve the allowlist \u2192 ipset (union of every resolver, like Isag) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
24285
|
+
mapfile -t LOCAL_DNS < <(awk '/^nameserver / {print $2}' /etc/resolv.conf)
|
|
24286
|
+
[[ \${#LOCAL_DNS[@]} -gt 0 ]] || { echo "entrypoint: no resolvers in /etc/resolv.conf" >&2; exit 1; }
|
|
24287
|
+
|
|
24288
|
+
ipset create node9_allowed hash:ip family inet -exist
|
|
24289
|
+
ipset flush node9_allowed
|
|
24290
|
+
|
|
24291
|
+
while IFS= read -r domain; do
|
|
24292
|
+
[[ -n "$domain" ]] || continue
|
|
24293
|
+
found=0
|
|
24294
|
+
# union the local resolver + each upstream so CDN/anycast IP rotation is covered
|
|
24295
|
+
for ip in $(getent ahostsv4 "$domain" 2>/dev/null | awk '{print $1}' | sort -u); do
|
|
24296
|
+
ipset add node9_allowed "$ip" -exist; found=1
|
|
24297
|
+
done
|
|
24298
|
+
for r in "\${LOCAL_DNS[@]}"; do
|
|
24299
|
+
for ip in $(dig +short +time=2 +tries=1 @"$r" A "$domain" 2>/dev/null | awk '/^[0-9.]+$/'); do
|
|
24300
|
+
ipset add node9_allowed "$ip" -exist; found=1
|
|
24301
|
+
done
|
|
24302
|
+
done
|
|
24303
|
+
[[ $found -eq 1 ]] || { echo "entrypoint: failed to resolve $domain" >&2; exit 1; }
|
|
24304
|
+
echo "entrypoint: allowed $domain"
|
|
24305
|
+
done < "$DOMAINS_FILE"
|
|
24306
|
+
|
|
24307
|
+
# \u2500\u2500 Seal iptables: deny-by-default except lo, established, DNS, the allowlist \u2500\u2500\u2500\u2500
|
|
24308
|
+
echo "entrypoint: sealing firewall..."
|
|
24309
|
+
iptables -F; iptables -X
|
|
24310
|
+
iptables -P INPUT DROP
|
|
24311
|
+
iptables -P FORWARD DROP
|
|
24312
|
+
iptables -P OUTPUT DROP
|
|
24313
|
+
iptables -A INPUT -i lo -j ACCEPT
|
|
24314
|
+
iptables -A OUTPUT -o lo -j ACCEPT
|
|
24315
|
+
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
24316
|
+
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
24317
|
+
for r in "\${LOCAL_DNS[@]}"; do
|
|
24318
|
+
iptables -A OUTPUT -p udp -d "$r" --dport 53 -j ACCEPT
|
|
24319
|
+
iptables -A OUTPUT -p tcp -d "$r" --dport 53 -j ACCEPT
|
|
24320
|
+
done
|
|
24321
|
+
iptables -A OUTPUT -m set --match-set node9_allowed dst -j ACCEPT
|
|
24322
|
+
|
|
24323
|
+
# \u2500\u2500 Drop to the agent: start the node9 daemon (as the user), then exec the agent \u2500
|
|
24324
|
+
echo "entrypoint: starting node9 + ${agentBin} as $RUN_AS_USER"
|
|
24325
|
+
exec gosu "$RUN_AS_USER" bash -lc '
|
|
24326
|
+
set -e
|
|
24327
|
+
node9 daemon --background >/dev/null 2>&1 || true
|
|
24328
|
+
cd /workspace
|
|
24329
|
+
exec ${agentBin} "$@"
|
|
24330
|
+
' -- "$@"
|
|
24331
|
+
`;
|
|
24332
|
+
}
|
|
24333
|
+
|
|
24334
|
+
// src/posture/egress.ts
|
|
24335
|
+
function sandboxEgressWallActive() {
|
|
24336
|
+
try {
|
|
24337
|
+
return import_fs47.default.existsSync(ALLOWED_DOMAINS_PATH);
|
|
24338
|
+
} catch {
|
|
24339
|
+
return false;
|
|
24340
|
+
}
|
|
24341
|
+
}
|
|
24208
24342
|
function evaluateEgressConfig(egress) {
|
|
24209
24343
|
if (egress.enabled && egress.mode === "block") {
|
|
24210
24344
|
return {
|
|
@@ -24254,6 +24388,21 @@ function evaluateEgressConfig(egress) {
|
|
|
24254
24388
|
};
|
|
24255
24389
|
}
|
|
24256
24390
|
function checkEgress(ctx) {
|
|
24391
|
+
if (sandboxEgressWallActive()) {
|
|
24392
|
+
return [
|
|
24393
|
+
{
|
|
24394
|
+
category: "Egress",
|
|
24395
|
+
severity: "advisory",
|
|
24396
|
+
title: "Egress is hard-blocked by the sandbox kernel wall",
|
|
24397
|
+
what: "Outbound is deny-by-default at the kernel; only the allowlist is reachable.",
|
|
24398
|
+
why: "The sandbox seals egress with an ipset/iptables wall before the agent starts.",
|
|
24399
|
+
who: "Even a compromised agent can only reach the allowlisted hosts.",
|
|
24400
|
+
owner: "node9",
|
|
24401
|
+
detail: [],
|
|
24402
|
+
coverage: { state: "covered", level: "block", via: "sandbox egress wall" }
|
|
24403
|
+
}
|
|
24404
|
+
];
|
|
24405
|
+
}
|
|
24257
24406
|
const config = getConfig(ctx.cwd);
|
|
24258
24407
|
const egress = config.policy.egress;
|
|
24259
24408
|
return [evaluateEgressConfig({ enabled: egress.enabled, mode: egress.mode })];
|
|
@@ -24294,7 +24443,7 @@ async function checkGate(ctx) {
|
|
|
24294
24443
|
}
|
|
24295
24444
|
|
|
24296
24445
|
// src/posture/supply-chain.ts
|
|
24297
|
-
var
|
|
24446
|
+
var import_fs48 = __toESM(require("fs"));
|
|
24298
24447
|
var import_os42 = __toESM(require("os"));
|
|
24299
24448
|
var import_path48 = __toESM(require("path"));
|
|
24300
24449
|
var import_smol_toml3 = require("smol-toml");
|
|
@@ -24311,9 +24460,9 @@ function isNode9Managed(command, args = []) {
|
|
|
24311
24460
|
var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
|
|
24312
24461
|
function readServers(file, format, agent) {
|
|
24313
24462
|
try {
|
|
24314
|
-
const stat =
|
|
24463
|
+
const stat = import_fs48.default.statSync(file);
|
|
24315
24464
|
if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
|
|
24316
|
-
const text =
|
|
24465
|
+
const text = import_fs48.default.readFileSync(file, "utf8");
|
|
24317
24466
|
const map = format === "toml" ? (0, import_smol_toml3.parse)(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
|
|
24318
24467
|
if (!map || typeof map !== "object") return [];
|
|
24319
24468
|
return Object.entries(map).map(([name, v]) => ({
|
|
@@ -24409,11 +24558,12 @@ async function checkPrivilege(ctx) {
|
|
|
24409
24558
|
}
|
|
24410
24559
|
|
|
24411
24560
|
// src/posture/containment.ts
|
|
24412
|
-
var
|
|
24561
|
+
var import_fs49 = __toESM(require("fs"));
|
|
24562
|
+
var ISOLATION_WEIGHT = 12;
|
|
24413
24563
|
function inContainer() {
|
|
24414
|
-
if (
|
|
24564
|
+
if (import_fs49.default.existsSync("/.dockerenv") || import_fs49.default.existsSync("/run/.containerenv")) return true;
|
|
24415
24565
|
try {
|
|
24416
|
-
const cgroup =
|
|
24566
|
+
const cgroup = import_fs49.default.readFileSync("/proc/1/cgroup", "utf8");
|
|
24417
24567
|
if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
|
|
24418
24568
|
} catch {
|
|
24419
24569
|
}
|
|
@@ -24432,14 +24582,28 @@ function checkContainment(_ctx) {
|
|
|
24432
24582
|
detail: [],
|
|
24433
24583
|
owner: "os",
|
|
24434
24584
|
node9Reduces: true,
|
|
24435
|
-
|
|
24436
|
-
|
|
24585
|
+
// The single biggest hardening gap, and node9 now fully remedies it
|
|
24586
|
+
// (`node9 sandbox run`). Deducts while open; closing it is the headline
|
|
24587
|
+
// payoff. No coverageProbe → stays OPEN (scored) until adopted; live
|
|
24588
|
+
// partial-credit for the lighter shield path is a fast-follow.
|
|
24589
|
+
scoreWeight: ISOLATION_WEIGHT,
|
|
24590
|
+
gain: "jailed container \xB7 kernel egress wall \xB7 scoped mounts \xB7 governed inside",
|
|
24591
|
+
cost: "the agent works inside /workspace, not your live host",
|
|
24592
|
+
fix: `Two ways to shrink the blast radius \u2014 pick by how much flexibility you need:
|
|
24593
|
+
Strongest \u2014 jail it (closes this gap, +${ISOLATION_WEIGHT}):
|
|
24594
|
+
\u2022 node9 sandbox run <agent>
|
|
24595
|
+
Lighter \u2014 harden in place, keep full host access (about +${Math.round(
|
|
24596
|
+
ISOLATION_WEIGHT / 2
|
|
24597
|
+
)}):
|
|
24598
|
+
\u2022 node9 shield enable project-jail \u2014 block stray credential reads
|
|
24599
|
+
\u2022 node9 egress lock \u2014 block data exfil`
|
|
24437
24600
|
}
|
|
24438
24601
|
];
|
|
24439
24602
|
}
|
|
24440
24603
|
|
|
24441
24604
|
// src/posture/inbound.ts
|
|
24442
|
-
var
|
|
24605
|
+
var import_fs50 = __toESM(require("fs"));
|
|
24606
|
+
var DB_EXPOSURE_WEIGHT = 4;
|
|
24443
24607
|
var KNOWN_SERVICE_PORTS = {
|
|
24444
24608
|
5432: "PostgreSQL",
|
|
24445
24609
|
6379: "Redis",
|
|
@@ -24525,7 +24689,7 @@ function collectListeners() {
|
|
|
24525
24689
|
const byPort = /* @__PURE__ */ new Map();
|
|
24526
24690
|
for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
24527
24691
|
try {
|
|
24528
|
-
for (const l of parseListeners(
|
|
24692
|
+
for (const l of parseListeners(import_fs50.default.readFileSync(file, "utf8"))) {
|
|
24529
24693
|
if (!byPort.has(l.port)) byPort.set(l.port, l);
|
|
24530
24694
|
}
|
|
24531
24695
|
} catch {
|
|
@@ -24537,11 +24701,11 @@ function readProc(pid) {
|
|
|
24537
24701
|
let comm = "unknown";
|
|
24538
24702
|
let cmdline = "";
|
|
24539
24703
|
try {
|
|
24540
|
-
comm =
|
|
24704
|
+
comm = import_fs50.default.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
|
|
24541
24705
|
} catch {
|
|
24542
24706
|
}
|
|
24543
24707
|
try {
|
|
24544
|
-
cmdline =
|
|
24708
|
+
cmdline = import_fs50.default.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
|
|
24545
24709
|
} catch {
|
|
24546
24710
|
}
|
|
24547
24711
|
return { comm, cmdline };
|
|
@@ -24551,21 +24715,21 @@ function resolveProcesses(inodes) {
|
|
|
24551
24715
|
if (inodes.size === 0) return map;
|
|
24552
24716
|
let pids;
|
|
24553
24717
|
try {
|
|
24554
|
-
pids =
|
|
24718
|
+
pids = import_fs50.default.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
24555
24719
|
} catch {
|
|
24556
24720
|
return map;
|
|
24557
24721
|
}
|
|
24558
24722
|
for (const pid of pids) {
|
|
24559
24723
|
let fds;
|
|
24560
24724
|
try {
|
|
24561
|
-
fds =
|
|
24725
|
+
fds = import_fs50.default.readdirSync(`/proc/${pid}/fd`);
|
|
24562
24726
|
} catch {
|
|
24563
24727
|
continue;
|
|
24564
24728
|
}
|
|
24565
24729
|
for (const fd of fds) {
|
|
24566
24730
|
let link;
|
|
24567
24731
|
try {
|
|
24568
|
-
link =
|
|
24732
|
+
link = import_fs50.default.readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
24569
24733
|
} catch {
|
|
24570
24734
|
continue;
|
|
24571
24735
|
}
|
|
@@ -24620,8 +24784,15 @@ function checkInbound(ctx) {
|
|
|
24620
24784
|
// Only when node9 actually has a shield for an exposed service — otherwise
|
|
24621
24785
|
// (bare dev servers) it stays purely the user's to rebind.
|
|
24622
24786
|
node9Reduces: reduces,
|
|
24623
|
-
|
|
24624
|
-
|
|
24787
|
+
// When a db-shield applies this is real, node9-addressable hardening → it
|
|
24788
|
+
// scores (and stays OPEN, no cantFix probe). Bare dev servers node9 can't
|
|
24789
|
+
// touch stay can't-fix / your-part / unscored.
|
|
24790
|
+
...reduces ? {
|
|
24791
|
+
scoreWeight: DB_EXPOSURE_WEIGHT,
|
|
24792
|
+
gain: "blocks DROP TABLE / TRUNCATE / FLUSHALL on the exposed DB",
|
|
24793
|
+
cost: "you confirm legit destructive migrations"
|
|
24794
|
+
} : { coverageProbe: { kind: "cantFix" } },
|
|
24795
|
+
fix
|
|
24625
24796
|
});
|
|
24626
24797
|
}
|
|
24627
24798
|
return findings;
|
|
@@ -24671,16 +24842,20 @@ function scorePosture(findings, checksRun) {
|
|
|
24671
24842
|
const open = findings.filter(
|
|
24672
24843
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
|
|
24673
24844
|
);
|
|
24674
|
-
const count = (sev) => open.filter((f) => f.severity === sev).length;
|
|
24675
|
-
|
|
24845
|
+
const count = (sev) => open.filter((f) => f.severity === sev && !f.scoreWeight).length;
|
|
24846
|
+
const base = computeSecurityScore({
|
|
24676
24847
|
critical: count("critical"),
|
|
24677
24848
|
high: count("high"),
|
|
24678
24849
|
medium: count("medium"),
|
|
24679
|
-
// Denominator = number of checks evaluated. With computeSecurityScore's
|
|
24680
|
-
// caps this makes any critical → critical tier, any high → at-risk, and a
|
|
24681
|
-
// fully clean run (0 findings, checksRun > 0) → 100/good.
|
|
24682
24850
|
total: Math.max(checksRun, 1)
|
|
24683
24851
|
});
|
|
24852
|
+
const headroom = open.reduce((sum, f) => sum + (f.scoreWeight ?? 0), 0);
|
|
24853
|
+
const score = Math.max(0, base.score - headroom);
|
|
24854
|
+
const tier = score >= 80 ? "good" : score >= 50 ? "at-risk" : "critical";
|
|
24855
|
+
return { score, tier };
|
|
24856
|
+
}
|
|
24857
|
+
function openHeadroom(findings) {
|
|
24858
|
+
return findings.filter((f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix").reduce((sum, f) => sum + (f.scoreWeight ?? 0), 0);
|
|
24684
24859
|
}
|
|
24685
24860
|
|
|
24686
24861
|
// src/posture/headline.ts
|
|
@@ -24890,9 +25065,10 @@ var LABEL_WIDTH = 14;
|
|
|
24890
25065
|
function label(category) {
|
|
24891
25066
|
return import_chalk24.default.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
24892
25067
|
}
|
|
24893
|
-
function renderFinding(f) {
|
|
25068
|
+
function renderFinding(f, showWeight = false) {
|
|
24894
25069
|
const lines = [];
|
|
24895
|
-
|
|
25070
|
+
const wt = showWeight && f.scoreWeight ? import_chalk24.default.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
25071
|
+
lines.push(` ${ICON[f.severity]} ${label(f.category)}${wt}${f.title}`);
|
|
24896
25072
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
24897
25073
|
const width = 80 - indent.length;
|
|
24898
25074
|
for (const s of [f.what, f.why, f.who]) {
|
|
@@ -24908,6 +25084,16 @@ function renderFinding(f) {
|
|
|
24908
25084
|
}
|
|
24909
25085
|
}
|
|
24910
25086
|
}
|
|
25087
|
+
const tradeoff = [
|
|
25088
|
+
[f.gain, "gain: ", import_chalk24.default.green],
|
|
25089
|
+
[f.cost, "cost: ", import_chalk24.default.yellow]
|
|
25090
|
+
];
|
|
25091
|
+
for (const [text, lbl, color2] of tradeoff) {
|
|
25092
|
+
if (!text) continue;
|
|
25093
|
+
wrap(text, width - 6).forEach((l, i) => {
|
|
25094
|
+
lines.push(indent + (i === 0 ? color2(lbl) : " ") + import_chalk24.default.gray(l));
|
|
25095
|
+
});
|
|
25096
|
+
}
|
|
24911
25097
|
return lines;
|
|
24912
25098
|
}
|
|
24913
25099
|
function renderPosture(result) {
|
|
@@ -24917,15 +25103,11 @@ function renderPosture(result) {
|
|
|
24917
25103
|
lines.push(
|
|
24918
25104
|
import_chalk24.default.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + import_chalk24.default.gray(` \u2014 ${result.agent}`) + ` ${import_chalk24.default.bold(`Score: ${result.score}/100`)} (${tier})`
|
|
24919
25105
|
);
|
|
24920
|
-
const
|
|
24921
|
-
|
|
24922
|
-
).length;
|
|
24923
|
-
if (advisories > 0) {
|
|
24924
|
-
const word = advisories === 1 ? "advisory" : "advisories";
|
|
24925
|
-
const verb = advisories === 1 ? "doesn't" : "don't";
|
|
25106
|
+
const headroom = openHeadroom(result.findings);
|
|
25107
|
+
if (headroom > 0) {
|
|
24926
25108
|
lines.push(
|
|
24927
25109
|
" " + import_chalk24.default.gray(
|
|
24928
|
-
`${
|
|
25110
|
+
`${headroom} pts of headroom below \u2014 each layer adds security at some cost to flexibility. You choose which to close.`
|
|
24929
25111
|
)
|
|
24930
25112
|
);
|
|
24931
25113
|
}
|
|
@@ -24941,7 +25123,7 @@ function renderPosture(result) {
|
|
|
24941
25123
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
24942
25124
|
const open = result.findings.filter((f) => f.coverage?.state !== "covered");
|
|
24943
25125
|
if (covered.length > 0) {
|
|
24944
|
-
lines.push(" " + import_chalk24.default.green("\u{1F7E2} node9 is
|
|
25126
|
+
lines.push(" " + import_chalk24.default.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
24945
25127
|
for (const f of covered) {
|
|
24946
25128
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
24947
25129
|
const via = f.coverage?.via ?? "node9";
|
|
@@ -24956,18 +25138,16 @@ function renderPosture(result) {
|
|
|
24956
25138
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
24957
25139
|
if (node9Open.length > 0) {
|
|
24958
25140
|
lines.push(" " + import_chalk24.default.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
24959
|
-
for (const f of node9Open) lines.push(...renderFinding(f));
|
|
25141
|
+
for (const f of node9Open) lines.push(...renderFinding(f, true));
|
|
24960
25142
|
}
|
|
24961
25143
|
if (reduceOpen.length > 0) {
|
|
24962
25144
|
if (node9Open.length > 0) lines.push("");
|
|
24963
|
-
lines.push(
|
|
24964
|
-
|
|
24965
|
-
);
|
|
24966
|
-
for (const f of reduceOpen) lines.push(...renderFinding(f));
|
|
25145
|
+
lines.push(" " + import_chalk24.default.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
25146
|
+
for (const f of reduceOpen) lines.push(...renderFinding(f, true));
|
|
24967
25147
|
}
|
|
24968
25148
|
if (osOpen.length > 0) {
|
|
24969
25149
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
24970
|
-
lines.push(" " + import_chalk24.default.bold("\u{1F9F1}
|
|
25150
|
+
lines.push(" " + import_chalk24.default.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
24971
25151
|
for (const f of osOpen) lines.push(...renderFinding(f));
|
|
24972
25152
|
}
|
|
24973
25153
|
for (const cat of result.passedCategories) {
|
|
@@ -24987,7 +25167,12 @@ function renderPosture(result) {
|
|
|
24987
25167
|
if (med) parts.push(import_chalk24.default.yellow(`${med} medium`));
|
|
24988
25168
|
if (adv) parts.push(import_chalk24.default.gray(`${adv} advisory`));
|
|
24989
25169
|
const summary = parts.length ? parts.join(" \xB7 ") : import_chalk24.default.green("no findings");
|
|
24990
|
-
lines.push(` ${summary}
|
|
25170
|
+
lines.push(` ${summary}`);
|
|
25171
|
+
lines.push("");
|
|
25172
|
+
lines.push(" " + import_chalk24.default.bold("Track this across your fleet & keep it green:"));
|
|
25173
|
+
lines.push(
|
|
25174
|
+
" " + import_chalk24.default.dim("\u2192 ") + import_chalk24.default.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
|
|
25175
|
+
);
|
|
24991
25176
|
lines.push("");
|
|
24992
25177
|
return lines.join("\n");
|
|
24993
25178
|
}
|
|
@@ -25017,7 +25202,12 @@ function buildShipBody(result) {
|
|
|
25017
25202
|
// The runnable fix / OS action — commands + advice, never a path.
|
|
25018
25203
|
fix: f.fix,
|
|
25019
25204
|
// Whose job it is. Default 'os' so the SaaS never falsely claims node9 can fix it.
|
|
25020
|
-
owner: f.owner ?? "os"
|
|
25205
|
+
owner: f.owner ?? "os",
|
|
25206
|
+
// Hardening weight + the flexibility tradeoff (generic prose / a number —
|
|
25207
|
+
// no values or paths), so the fleet view can show the same headroom story.
|
|
25208
|
+
scoreWeight: f.scoreWeight,
|
|
25209
|
+
gain: f.gain,
|
|
25210
|
+
cost: f.cost
|
|
25021
25211
|
}))
|
|
25022
25212
|
};
|
|
25023
25213
|
}
|
|
@@ -25088,7 +25278,7 @@ function registerPostureCommand(program2) {
|
|
|
25088
25278
|
|
|
25089
25279
|
// src/cli/commands/egress.ts
|
|
25090
25280
|
var import_chalk26 = __toESM(require("chalk"));
|
|
25091
|
-
var
|
|
25281
|
+
var import_fs51 = __toESM(require("fs"));
|
|
25092
25282
|
var import_os45 = __toESM(require("os"));
|
|
25093
25283
|
var import_path49 = __toESM(require("path"));
|
|
25094
25284
|
init_config();
|
|
@@ -25106,7 +25296,7 @@ function configPath() {
|
|
|
25106
25296
|
function readRawConfig() {
|
|
25107
25297
|
let text;
|
|
25108
25298
|
try {
|
|
25109
|
-
text =
|
|
25299
|
+
text = import_fs51.default.readFileSync(configPath(), "utf8");
|
|
25110
25300
|
} catch (err2) {
|
|
25111
25301
|
if (err2.code === "ENOENT") return {};
|
|
25112
25302
|
throw err2;
|
|
@@ -25121,8 +25311,8 @@ function readRawConfig() {
|
|
|
25121
25311
|
}
|
|
25122
25312
|
function writeRawConfig(config) {
|
|
25123
25313
|
const p = configPath();
|
|
25124
|
-
|
|
25125
|
-
|
|
25314
|
+
import_fs51.default.mkdirSync(import_path49.default.dirname(p), { recursive: true });
|
|
25315
|
+
import_fs51.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
25126
25316
|
}
|
|
25127
25317
|
function applyEgress(config, change) {
|
|
25128
25318
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -25213,11 +25403,365 @@ function registerEgressCommand(program2) {
|
|
|
25213
25403
|
egress.action(showStatus);
|
|
25214
25404
|
}
|
|
25215
25405
|
|
|
25216
|
-
// src/cli/commands/
|
|
25406
|
+
// src/cli/commands/sandbox.ts
|
|
25217
25407
|
var import_chalk27 = __toESM(require("chalk"));
|
|
25218
|
-
var
|
|
25408
|
+
var import_fs54 = __toESM(require("fs"));
|
|
25409
|
+
var import_path52 = __toESM(require("path"));
|
|
25410
|
+
var import_child_process13 = require("child_process");
|
|
25411
|
+
init_config();
|
|
25412
|
+
|
|
25413
|
+
// src/sandbox/config.ts
|
|
25414
|
+
var import_fs52 = __toESM(require("fs"));
|
|
25219
25415
|
var import_path50 = __toESM(require("path"));
|
|
25416
|
+
var import_yaml = require("yaml");
|
|
25417
|
+
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
25418
|
+
var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
|
|
25419
|
+
function defaultSandboxConfig(agent) {
|
|
25420
|
+
return {
|
|
25421
|
+
agent,
|
|
25422
|
+
workspace: { mount: ".", target: "/workspace", mode: "rw" },
|
|
25423
|
+
runtime: { engine: "docker", image: "node9-sandbox:local", rebuild: "auto" },
|
|
25424
|
+
outbound: {
|
|
25425
|
+
mode: "block",
|
|
25426
|
+
allow: agent === "codex" ? ["api.openai.com", "api.github.com", "github.com", "registry.npmjs.org"] : ["api.anthropic.com", "api.github.com", "github.com", "registry.npmjs.org"]
|
|
25427
|
+
},
|
|
25428
|
+
inbound: { expose: [] },
|
|
25429
|
+
// Provider key only — NODE9_API_KEY intentionally absent (fix #1).
|
|
25430
|
+
env: { pass: [agent === "codex" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY"] },
|
|
25431
|
+
// Terminal-only approval in the MVP; cloud/native/browser off (fix #1).
|
|
25432
|
+
node9: {
|
|
25433
|
+
approvals: { terminal: true, native: false, browser: false, cloud: false },
|
|
25434
|
+
// Mount the agent's OAuth/creds dir so it can authenticate in the box.
|
|
25435
|
+
mountAgentCredentials: true
|
|
25436
|
+
}
|
|
25437
|
+
};
|
|
25438
|
+
}
|
|
25439
|
+
function asStringArray(v) {
|
|
25440
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
25441
|
+
}
|
|
25442
|
+
function mergeSandboxConfig(raw, fallbackAgent) {
|
|
25443
|
+
const r = raw && typeof raw === "object" ? raw : {};
|
|
25444
|
+
const agent = typeof r.agent === "string" ? r.agent : fallbackAgent;
|
|
25445
|
+
if (agent !== "claude" && agent !== "codex") {
|
|
25446
|
+
throw new Error(`sandbox: unsupported agent "${String(agent)}" (use claude or codex)`);
|
|
25447
|
+
}
|
|
25448
|
+
const d = defaultSandboxConfig(agent);
|
|
25449
|
+
const ws = r.workspace ?? {};
|
|
25450
|
+
const rt = r.runtime ?? {};
|
|
25451
|
+
const out = r.outbound ?? {};
|
|
25452
|
+
const inb = r.inbound ?? {};
|
|
25453
|
+
const env = r.env ?? {};
|
|
25454
|
+
const n9 = r.node9 ?? {};
|
|
25455
|
+
const appr = n9.approvals ?? {};
|
|
25456
|
+
const pass = asStringArray(env.pass).filter((k) => !FORBIDDEN_ENV.has(k));
|
|
25457
|
+
return {
|
|
25458
|
+
agent,
|
|
25459
|
+
workspace: {
|
|
25460
|
+
mount: typeof ws.mount === "string" ? ws.mount : d.workspace.mount,
|
|
25461
|
+
target: typeof ws.target === "string" ? ws.target : d.workspace.target,
|
|
25462
|
+
mode: ws.mode === "ro" ? "ro" : "rw"
|
|
25463
|
+
},
|
|
25464
|
+
runtime: {
|
|
25465
|
+
engine: rt.engine === "podman" ? "podman" : "docker",
|
|
25466
|
+
image: typeof rt.image === "string" ? rt.image : d.runtime.image,
|
|
25467
|
+
rebuild: rt.rebuild === "never" || rt.rebuild === "always" ? rt.rebuild : d.runtime.rebuild
|
|
25468
|
+
},
|
|
25469
|
+
outbound: { mode: "block", allow: out.allow ? asStringArray(out.allow) : d.outbound.allow },
|
|
25470
|
+
inbound: { expose: inb.expose ? asStringArray(inb.expose) : d.inbound.expose },
|
|
25471
|
+
env: { pass: env.pass ? pass : d.env.pass },
|
|
25472
|
+
node9: {
|
|
25473
|
+
approvals: {
|
|
25474
|
+
terminal: appr.terminal !== false,
|
|
25475
|
+
native: appr.native === true,
|
|
25476
|
+
browser: appr.browser === true,
|
|
25477
|
+
cloud: appr.cloud === true
|
|
25478
|
+
},
|
|
25479
|
+
mountAgentCredentials: n9.mountAgentCredentials !== false
|
|
25480
|
+
}
|
|
25481
|
+
};
|
|
25482
|
+
}
|
|
25483
|
+
function scaffoldSandboxYaml(agent) {
|
|
25484
|
+
const header = "# node9.sandbox.yaml \u2014 sandbox TOPOLOGY (what the agent may touch).\n# Security policy (shields / egress rules / approvers) lives in ~/.node9/config.json\n# and applies to both native and sandbox. NODE9_API_KEY is never passed into the box.\n\n";
|
|
25485
|
+
return header + (0, import_yaml.stringify)(defaultSandboxConfig(agent));
|
|
25486
|
+
}
|
|
25487
|
+
function sandboxConfigPath(cwd = process.cwd()) {
|
|
25488
|
+
return import_path50.default.join(cwd, SANDBOX_CONFIG_FILE);
|
|
25489
|
+
}
|
|
25490
|
+
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
25491
|
+
const p = sandboxConfigPath(cwd);
|
|
25492
|
+
if (!import_fs52.default.existsSync(p)) {
|
|
25493
|
+
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
25494
|
+
}
|
|
25495
|
+
let raw;
|
|
25496
|
+
try {
|
|
25497
|
+
raw = (0, import_yaml.parse)(import_fs52.default.readFileSync(p, "utf-8"));
|
|
25498
|
+
} catch (err2) {
|
|
25499
|
+
throw new Error(
|
|
25500
|
+
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
25501
|
+
);
|
|
25502
|
+
}
|
|
25503
|
+
return mergeSandboxConfig(raw, fallbackAgent);
|
|
25504
|
+
}
|
|
25505
|
+
|
|
25506
|
+
// src/sandbox/firewall.ts
|
|
25507
|
+
var AGENT_PROVIDER_HOST = {
|
|
25508
|
+
claude: ["api.anthropic.com"],
|
|
25509
|
+
codex: ["api.openai.com"]
|
|
25510
|
+
};
|
|
25511
|
+
var NODE9_SAAS_HOSTS = ["api.node9.ai", "app.node9.ai", "node9.ai"];
|
|
25512
|
+
function isValidHost2(host) {
|
|
25513
|
+
if (typeof host !== "string") return false;
|
|
25514
|
+
const h = host.trim().toLowerCase();
|
|
25515
|
+
if (!h || h.length > 253) return false;
|
|
25516
|
+
if (/[\s/:@?#\\]/.test(h)) return false;
|
|
25517
|
+
return /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(
|
|
25518
|
+
h
|
|
25519
|
+
);
|
|
25520
|
+
}
|
|
25521
|
+
function compileAllowlist(input) {
|
|
25522
|
+
const norm = (h) => h.trim().toLowerCase();
|
|
25523
|
+
const denySet = /* @__PURE__ */ new Set([...input.configDeny.map(norm), ...NODE9_SAAS_HOSTS.map(norm)]);
|
|
25524
|
+
const candidates = [
|
|
25525
|
+
...AGENT_PROVIDER_HOST[input.agent],
|
|
25526
|
+
...input.sandboxAllow,
|
|
25527
|
+
...input.configAllow
|
|
25528
|
+
].map(norm);
|
|
25529
|
+
const allow = /* @__PURE__ */ new Set();
|
|
25530
|
+
const rejected = [];
|
|
25531
|
+
const denied = [];
|
|
25532
|
+
for (const host of candidates) {
|
|
25533
|
+
if (!host) continue;
|
|
25534
|
+
if (!isValidHost2(host)) {
|
|
25535
|
+
if (!rejected.includes(host)) rejected.push(host);
|
|
25536
|
+
continue;
|
|
25537
|
+
}
|
|
25538
|
+
if (denySet.has(host)) {
|
|
25539
|
+
if (!denied.includes(host)) denied.push(host);
|
|
25540
|
+
continue;
|
|
25541
|
+
}
|
|
25542
|
+
allow.add(host);
|
|
25543
|
+
}
|
|
25544
|
+
return {
|
|
25545
|
+
allow: [...allow].sort(),
|
|
25546
|
+
rejected: rejected.sort(),
|
|
25547
|
+
denied: denied.sort()
|
|
25548
|
+
};
|
|
25549
|
+
}
|
|
25550
|
+
|
|
25551
|
+
// src/sandbox/runtime.ts
|
|
25552
|
+
var import_fs53 = __toESM(require("fs"));
|
|
25220
25553
|
var import_os46 = __toESM(require("os"));
|
|
25554
|
+
var import_path51 = __toESM(require("path"));
|
|
25555
|
+
var import_crypto13 = __toESM(require("crypto"));
|
|
25556
|
+
var import_child_process12 = require("child_process");
|
|
25557
|
+
function sandboxDataDir(cwd = process.cwd()) {
|
|
25558
|
+
return import_path51.default.join(cwd, ".node9", "sandbox", "data");
|
|
25559
|
+
}
|
|
25560
|
+
function detectEngine(engine) {
|
|
25561
|
+
const r = (0, import_child_process12.spawnSync)(engine, ["--version"], { encoding: "utf-8" });
|
|
25562
|
+
if (r.status === 0 && typeof r.stdout === "string") {
|
|
25563
|
+
return { available: true, version: r.stdout.trim() };
|
|
25564
|
+
}
|
|
25565
|
+
return { available: false };
|
|
25566
|
+
}
|
|
25567
|
+
function agentCredentialsMount(agent) {
|
|
25568
|
+
const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
|
|
25569
|
+
return { hostPath: import_path51.default.join(import_os46.default.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
|
|
25570
|
+
}
|
|
25571
|
+
function buildRunArgs(opts) {
|
|
25572
|
+
const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
|
|
25573
|
+
const args = ["run", "--rm", "-it", "--cap-add=NET_ADMIN"];
|
|
25574
|
+
args.push("-v", `${workspaceHostPath}:${config.workspace.target}:${config.workspace.mode}`);
|
|
25575
|
+
args.push("-v", `${dataHostPath}:/home/${RUN_AS_USER}/.node9`);
|
|
25576
|
+
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
25577
|
+
if (config.node9.mountAgentCredentials) {
|
|
25578
|
+
const creds = agentCredentialsMount(config.agent);
|
|
25579
|
+
if (import_fs53.default.existsSync(creds.hostPath)) {
|
|
25580
|
+
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
25581
|
+
}
|
|
25582
|
+
}
|
|
25583
|
+
for (const key of config.env.pass) {
|
|
25584
|
+
if (process.env[key] !== void 0) args.push("-e", key);
|
|
25585
|
+
}
|
|
25586
|
+
for (const port of config.inbound.expose) {
|
|
25587
|
+
args.push("-p", port);
|
|
25588
|
+
}
|
|
25589
|
+
args.push(config.runtime.image);
|
|
25590
|
+
if (agentArgs.length) args.push(...agentArgs);
|
|
25591
|
+
return args;
|
|
25592
|
+
}
|
|
25593
|
+
function imageContentHash(dockerfile, entrypoint) {
|
|
25594
|
+
return import_crypto13.default.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
|
|
25595
|
+
}
|
|
25596
|
+
function sandboxBuildDir(cwd = process.cwd()) {
|
|
25597
|
+
return import_path51.default.join(cwd, ".node9", "sandbox", "build");
|
|
25598
|
+
}
|
|
25599
|
+
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
25600
|
+
const dir = sandboxBuildDir(cwd);
|
|
25601
|
+
import_fs53.default.mkdirSync(dir, { recursive: true });
|
|
25602
|
+
import_fs53.default.writeFileSync(import_path51.default.join(dir, "Dockerfile"), dockerfile);
|
|
25603
|
+
import_fs53.default.writeFileSync(import_path51.default.join(dir, "entrypoint.sh"), entrypoint);
|
|
25604
|
+
return dir;
|
|
25605
|
+
}
|
|
25606
|
+
function writeAllowlist(cwd, hosts) {
|
|
25607
|
+
const dir = import_path51.default.join(cwd, ".node9", "sandbox");
|
|
25608
|
+
import_fs53.default.mkdirSync(dir, { recursive: true });
|
|
25609
|
+
const p = import_path51.default.join(dir, "allowed-domains.txt");
|
|
25610
|
+
import_fs53.default.writeFileSync(p, hosts.join("\n") + "\n");
|
|
25611
|
+
return p;
|
|
25612
|
+
}
|
|
25613
|
+
function resolveHomePath(p) {
|
|
25614
|
+
return p.startsWith("~") ? import_path51.default.join(import_os46.default.homedir(), p.slice(1)) : import_path51.default.resolve(p);
|
|
25615
|
+
}
|
|
25616
|
+
|
|
25617
|
+
// src/cli/commands/sandbox.ts
|
|
25618
|
+
function seedDataDirConfig(dataDir, sandbox) {
|
|
25619
|
+
import_fs54.default.mkdirSync(dataDir, { recursive: true });
|
|
25620
|
+
const configPath2 = import_path52.default.join(dataDir, "config.json");
|
|
25621
|
+
const seed = {
|
|
25622
|
+
settings: {
|
|
25623
|
+
approvers: {
|
|
25624
|
+
terminal: sandbox.node9.approvals.terminal,
|
|
25625
|
+
native: false,
|
|
25626
|
+
browser: false,
|
|
25627
|
+
cloud: false
|
|
25628
|
+
}
|
|
25629
|
+
}
|
|
25630
|
+
};
|
|
25631
|
+
import_fs54.default.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
25632
|
+
}
|
|
25633
|
+
function registerSandboxCommand(program2, version2) {
|
|
25634
|
+
const node9Version2 = pinnedNode9Version(version2);
|
|
25635
|
+
const cmd = program2.command("sandbox").description("Run an agent in a disposable, jailed container \u2014 governed + audited inside");
|
|
25636
|
+
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
25637
|
+
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
25638
|
+
const p = sandboxConfigPath();
|
|
25639
|
+
if (import_fs54.default.existsSync(p)) {
|
|
25640
|
+
console.log(
|
|
25641
|
+
import_chalk27.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
25642
|
+
);
|
|
25643
|
+
return;
|
|
25644
|
+
}
|
|
25645
|
+
import_fs54.default.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
25646
|
+
console.log(
|
|
25647
|
+
import_chalk27.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk27.default.dim(` (agent: ${agent})`)
|
|
25648
|
+
);
|
|
25649
|
+
console.log(
|
|
25650
|
+
import_chalk27.default.dim(" Edit it (mounts / allow / expose), then: ") + import_chalk27.default.cyan("node9 sandbox run")
|
|
25651
|
+
);
|
|
25652
|
+
});
|
|
25653
|
+
cmd.command("run [agent]").description("Build (if needed) + run the agent jailed. Extra args after -- go to the agent.").allowUnknownOption(true).allowExcessArguments(true).action((agentArg, _opts, command) => {
|
|
25654
|
+
const cwd = process.cwd();
|
|
25655
|
+
const sandbox = loadSandboxConfig(cwd, agentArg || "claude");
|
|
25656
|
+
if (agentArg === "claude" || agentArg === "codex") sandbox.agent = agentArg;
|
|
25657
|
+
const engine = detectEngine(sandbox.runtime.engine);
|
|
25658
|
+
if (!engine.available) {
|
|
25659
|
+
console.error(
|
|
25660
|
+
import_chalk27.default.red(` ${sandbox.runtime.engine} not found.`) + import_chalk27.default.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
|
|
25661
|
+
);
|
|
25662
|
+
process.exit(1);
|
|
25663
|
+
}
|
|
25664
|
+
const node9Config = getConfig(cwd);
|
|
25665
|
+
const compiled = compileAllowlist({
|
|
25666
|
+
agent: sandbox.agent,
|
|
25667
|
+
sandboxAllow: sandbox.outbound.allow,
|
|
25668
|
+
configAllow: node9Config.policy.egress.allow,
|
|
25669
|
+
configDeny: node9Config.policy.egress.deny
|
|
25670
|
+
});
|
|
25671
|
+
if (compiled.rejected.length) {
|
|
25672
|
+
console.log(
|
|
25673
|
+
import_chalk27.default.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
|
|
25674
|
+
);
|
|
25675
|
+
}
|
|
25676
|
+
if (compiled.denied.length) {
|
|
25677
|
+
console.log(import_chalk27.default.dim(` (denied: ${compiled.denied.join(", ")})`));
|
|
25678
|
+
}
|
|
25679
|
+
const allowlistPath = writeAllowlist(cwd, compiled.allow);
|
|
25680
|
+
const dockerfile = renderDockerfile(sandbox, node9Version2);
|
|
25681
|
+
const entrypoint = renderEntrypoint(sandbox);
|
|
25682
|
+
const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
|
|
25683
|
+
const hash = imageContentHash(dockerfile, entrypoint);
|
|
25684
|
+
const image = sandbox.runtime.image;
|
|
25685
|
+
const hashFile = import_path52.default.join(sandboxBuildDir(cwd), ".image-hash");
|
|
25686
|
+
const lastHash = import_fs54.default.existsSync(hashFile) ? import_fs54.default.readFileSync(hashFile, "utf-8").trim() : "";
|
|
25687
|
+
const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
25688
|
+
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
25689
|
+
if (needBuild) {
|
|
25690
|
+
console.log(import_chalk27.default.dim(` building ${image} \u2026`));
|
|
25691
|
+
const b = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["build", "-t", image, buildDir], {
|
|
25692
|
+
stdio: "inherit"
|
|
25693
|
+
});
|
|
25694
|
+
if (b.status !== 0) {
|
|
25695
|
+
console.error(import_chalk27.default.red(" build failed."));
|
|
25696
|
+
process.exit(b.status ?? 1);
|
|
25697
|
+
}
|
|
25698
|
+
import_fs54.default.writeFileSync(hashFile, hash);
|
|
25699
|
+
}
|
|
25700
|
+
const dataDir = sandboxDataDir(cwd);
|
|
25701
|
+
seedDataDirConfig(dataDir, sandbox);
|
|
25702
|
+
const passthru = command.args.slice(agentArg ? 1 : 0);
|
|
25703
|
+
const runArgs = buildRunArgs({
|
|
25704
|
+
config: sandbox,
|
|
25705
|
+
workspaceHostPath: resolveHomePath(sandbox.workspace.mount),
|
|
25706
|
+
dataHostPath: dataDir,
|
|
25707
|
+
allowlistHostPath: allowlistPath,
|
|
25708
|
+
agentArgs: passthru
|
|
25709
|
+
});
|
|
25710
|
+
if (sandbox.node9.mountAgentCredentials) {
|
|
25711
|
+
const creds = agentCredentialsMount(sandbox.agent);
|
|
25712
|
+
if (import_fs54.default.existsSync(creds.hostPath)) {
|
|
25713
|
+
console.log(import_chalk27.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
25714
|
+
} else {
|
|
25715
|
+
console.log(
|
|
25716
|
+
import_chalk27.default.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + import_chalk27.default.dim(`the agent must auth via an env key in env.pass.`)
|
|
25717
|
+
);
|
|
25718
|
+
}
|
|
25719
|
+
}
|
|
25720
|
+
console.log(
|
|
25721
|
+
import_chalk27.default.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
|
|
25722
|
+
`)
|
|
25723
|
+
);
|
|
25724
|
+
const r = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, runArgs, { stdio: "inherit" });
|
|
25725
|
+
process.exit(r.status ?? 0);
|
|
25726
|
+
});
|
|
25727
|
+
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
25728
|
+
const auditPath = import_path52.default.join(sandboxDataDir(), "audit.log");
|
|
25729
|
+
if (!import_fs54.default.existsSync(auditPath)) {
|
|
25730
|
+
console.log(import_chalk27.default.dim(" no sandbox audit yet."));
|
|
25731
|
+
return;
|
|
25732
|
+
}
|
|
25733
|
+
(0, import_child_process13.spawnSync)("tail", ["-f", auditPath], { stdio: "inherit" });
|
|
25734
|
+
});
|
|
25735
|
+
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
25736
|
+
const auditPath = import_path52.default.join(sandboxDataDir(), "audit.log");
|
|
25737
|
+
if (!import_fs54.default.existsSync(auditPath)) {
|
|
25738
|
+
console.log(import_chalk27.default.dim(" no sandbox audit yet."));
|
|
25739
|
+
return;
|
|
25740
|
+
}
|
|
25741
|
+
process.stdout.write(import_fs54.default.readFileSync(auditPath, "utf-8"));
|
|
25742
|
+
});
|
|
25743
|
+
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
25744
|
+
const cwd = process.cwd();
|
|
25745
|
+
let sandbox = null;
|
|
25746
|
+
try {
|
|
25747
|
+
sandbox = loadSandboxConfig(cwd);
|
|
25748
|
+
} catch {
|
|
25749
|
+
}
|
|
25750
|
+
if (sandbox) {
|
|
25751
|
+
(0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "rm", "-f", sandbox.runtime.image], {
|
|
25752
|
+
stdio: "ignore"
|
|
25753
|
+
});
|
|
25754
|
+
}
|
|
25755
|
+
import_fs54.default.rmSync(import_path52.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
25756
|
+
console.log(import_chalk27.default.green(" \u2713 sandbox image + build + data removed."));
|
|
25757
|
+
});
|
|
25758
|
+
}
|
|
25759
|
+
|
|
25760
|
+
// src/cli/commands/sessions.ts
|
|
25761
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
25762
|
+
var import_fs55 = __toESM(require("fs"));
|
|
25763
|
+
var import_path53 = __toESM(require("path"));
|
|
25764
|
+
var import_os47 = __toESM(require("os"));
|
|
25221
25765
|
init_scan_summary();
|
|
25222
25766
|
init_litellm();
|
|
25223
25767
|
init_cost_gemini();
|
|
@@ -25238,10 +25782,10 @@ function encodeProjectPath(projectPath) {
|
|
|
25238
25782
|
}
|
|
25239
25783
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
25240
25784
|
const encoded = encodeProjectPath(projectPath);
|
|
25241
|
-
return
|
|
25785
|
+
return import_path53.default.join(import_os47.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
25242
25786
|
}
|
|
25243
25787
|
function projectLabel(projectPath) {
|
|
25244
|
-
return projectPath.replace(
|
|
25788
|
+
return projectPath.replace(import_os47.default.homedir(), "~");
|
|
25245
25789
|
}
|
|
25246
25790
|
function parseHistoryLines(lines) {
|
|
25247
25791
|
const entries = [];
|
|
@@ -25310,10 +25854,10 @@ function parseSessionLines(lines) {
|
|
|
25310
25854
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
25311
25855
|
}
|
|
25312
25856
|
function loadAuditEntries(auditPath) {
|
|
25313
|
-
const aPath = auditPath ??
|
|
25857
|
+
const aPath = auditPath ?? import_path53.default.join(import_os47.default.homedir(), ".node9", "audit.log");
|
|
25314
25858
|
let raw;
|
|
25315
25859
|
try {
|
|
25316
|
-
raw =
|
|
25860
|
+
raw = import_fs55.default.readFileSync(aPath, "utf-8");
|
|
25317
25861
|
} catch {
|
|
25318
25862
|
return [];
|
|
25319
25863
|
}
|
|
@@ -25349,8 +25893,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
25349
25893
|
return result;
|
|
25350
25894
|
}
|
|
25351
25895
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
25352
|
-
const tmpDir =
|
|
25353
|
-
if (!
|
|
25896
|
+
const tmpDir = import_path53.default.join(import_os47.default.homedir(), ".gemini", "tmp");
|
|
25897
|
+
if (!import_fs55.default.existsSync(tmpDir)) return [];
|
|
25354
25898
|
const cutoff = days !== null ? (() => {
|
|
25355
25899
|
const d = /* @__PURE__ */ new Date();
|
|
25356
25900
|
d.setDate(d.getDate() - days);
|
|
@@ -25359,35 +25903,35 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
25359
25903
|
})() : null;
|
|
25360
25904
|
let slugDirs;
|
|
25361
25905
|
try {
|
|
25362
|
-
slugDirs =
|
|
25906
|
+
slugDirs = import_fs55.default.readdirSync(tmpDir);
|
|
25363
25907
|
} catch {
|
|
25364
25908
|
return [];
|
|
25365
25909
|
}
|
|
25366
25910
|
const summaries = [];
|
|
25367
25911
|
for (const slug of slugDirs) {
|
|
25368
|
-
const slugPath =
|
|
25912
|
+
const slugPath = import_path53.default.join(tmpDir, slug);
|
|
25369
25913
|
try {
|
|
25370
|
-
if (!
|
|
25914
|
+
if (!import_fs55.default.statSync(slugPath).isDirectory()) continue;
|
|
25371
25915
|
} catch {
|
|
25372
25916
|
continue;
|
|
25373
25917
|
}
|
|
25374
|
-
let projectRoot =
|
|
25918
|
+
let projectRoot = import_path53.default.join(import_os47.default.homedir(), slug);
|
|
25375
25919
|
try {
|
|
25376
|
-
projectRoot =
|
|
25920
|
+
projectRoot = import_fs55.default.readFileSync(import_path53.default.join(slugPath, ".project_root"), "utf-8").trim();
|
|
25377
25921
|
} catch {
|
|
25378
25922
|
}
|
|
25379
|
-
const chatsDir =
|
|
25380
|
-
if (!
|
|
25923
|
+
const chatsDir = import_path53.default.join(slugPath, "chats");
|
|
25924
|
+
if (!import_fs55.default.existsSync(chatsDir)) continue;
|
|
25381
25925
|
let chatFiles;
|
|
25382
25926
|
try {
|
|
25383
|
-
chatFiles =
|
|
25927
|
+
chatFiles = import_fs55.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
25384
25928
|
} catch {
|
|
25385
25929
|
continue;
|
|
25386
25930
|
}
|
|
25387
25931
|
for (const chatFile of chatFiles) {
|
|
25388
25932
|
let raw;
|
|
25389
25933
|
try {
|
|
25390
|
-
raw =
|
|
25934
|
+
raw = import_fs55.default.readFileSync(import_path53.default.join(chatsDir, chatFile), "utf-8");
|
|
25391
25935
|
} catch {
|
|
25392
25936
|
continue;
|
|
25393
25937
|
}
|
|
@@ -25467,8 +26011,8 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
25467
26011
|
return summaries;
|
|
25468
26012
|
}
|
|
25469
26013
|
function buildCodexSessions(days, allAuditEntries) {
|
|
25470
|
-
const sessionsBase =
|
|
25471
|
-
if (!
|
|
26014
|
+
const sessionsBase = import_path53.default.join(import_os47.default.homedir(), ".codex", "sessions");
|
|
26015
|
+
if (!import_fs55.default.existsSync(sessionsBase)) return [];
|
|
25472
26016
|
const cutoff = days !== null ? (() => {
|
|
25473
26017
|
const d = /* @__PURE__ */ new Date();
|
|
25474
26018
|
d.setDate(d.getDate() - days);
|
|
@@ -25477,29 +26021,29 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
25477
26021
|
})() : null;
|
|
25478
26022
|
const jsonlFiles = [];
|
|
25479
26023
|
try {
|
|
25480
|
-
for (const year of
|
|
25481
|
-
const yearPath =
|
|
26024
|
+
for (const year of import_fs55.default.readdirSync(sessionsBase)) {
|
|
26025
|
+
const yearPath = import_path53.default.join(sessionsBase, year);
|
|
25482
26026
|
try {
|
|
25483
|
-
if (!
|
|
26027
|
+
if (!import_fs55.default.statSync(yearPath).isDirectory()) continue;
|
|
25484
26028
|
} catch {
|
|
25485
26029
|
continue;
|
|
25486
26030
|
}
|
|
25487
|
-
for (const month of
|
|
25488
|
-
const monthPath =
|
|
26031
|
+
for (const month of import_fs55.default.readdirSync(yearPath)) {
|
|
26032
|
+
const monthPath = import_path53.default.join(yearPath, month);
|
|
25489
26033
|
try {
|
|
25490
|
-
if (!
|
|
26034
|
+
if (!import_fs55.default.statSync(monthPath).isDirectory()) continue;
|
|
25491
26035
|
} catch {
|
|
25492
26036
|
continue;
|
|
25493
26037
|
}
|
|
25494
|
-
for (const day of
|
|
25495
|
-
const dayPath =
|
|
26038
|
+
for (const day of import_fs55.default.readdirSync(monthPath)) {
|
|
26039
|
+
const dayPath = import_path53.default.join(monthPath, day);
|
|
25496
26040
|
try {
|
|
25497
|
-
if (!
|
|
26041
|
+
if (!import_fs55.default.statSync(dayPath).isDirectory()) continue;
|
|
25498
26042
|
} catch {
|
|
25499
26043
|
continue;
|
|
25500
26044
|
}
|
|
25501
|
-
for (const file of
|
|
25502
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
26045
|
+
for (const file of import_fs55.default.readdirSync(dayPath)) {
|
|
26046
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path53.default.join(dayPath, file));
|
|
25503
26047
|
}
|
|
25504
26048
|
}
|
|
25505
26049
|
}
|
|
@@ -25511,7 +26055,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
25511
26055
|
for (const filePath of jsonlFiles) {
|
|
25512
26056
|
let lines;
|
|
25513
26057
|
try {
|
|
25514
|
-
lines =
|
|
26058
|
+
lines = import_fs55.default.readFileSync(filePath, "utf-8").split("\n");
|
|
25515
26059
|
} catch {
|
|
25516
26060
|
continue;
|
|
25517
26061
|
}
|
|
@@ -25597,10 +26141,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
25597
26141
|
return summaries;
|
|
25598
26142
|
}
|
|
25599
26143
|
function buildSessions(days, historyPath) {
|
|
25600
|
-
const hPath = historyPath ??
|
|
26144
|
+
const hPath = historyPath ?? import_path53.default.join(import_os47.default.homedir(), ".claude", "history.jsonl");
|
|
25601
26145
|
let historyRaw = "";
|
|
25602
26146
|
try {
|
|
25603
|
-
historyRaw =
|
|
26147
|
+
historyRaw = import_fs55.default.readFileSync(hPath, "utf-8");
|
|
25604
26148
|
} catch {
|
|
25605
26149
|
}
|
|
25606
26150
|
const cutoff = days !== null ? (() => {
|
|
@@ -25624,7 +26168,7 @@ function buildSessions(days, historyPath) {
|
|
|
25624
26168
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
25625
26169
|
let sessionLines = [];
|
|
25626
26170
|
try {
|
|
25627
|
-
sessionLines =
|
|
26171
|
+
sessionLines = import_fs55.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
25628
26172
|
} catch {
|
|
25629
26173
|
}
|
|
25630
26174
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -25710,11 +26254,11 @@ function toolInputSummary(tool, input) {
|
|
|
25710
26254
|
}
|
|
25711
26255
|
function toolColor(tool) {
|
|
25712
26256
|
const t = tool.toLowerCase();
|
|
25713
|
-
if (t === "bash" || t === "execute_bash") return
|
|
25714
|
-
if (t === "write") return
|
|
25715
|
-
if (t === "edit" || t === "notebookedit") return
|
|
25716
|
-
if (t === "read") return
|
|
25717
|
-
return
|
|
26257
|
+
if (t === "bash" || t === "execute_bash") return import_chalk28.default.red;
|
|
26258
|
+
if (t === "write") return import_chalk28.default.green;
|
|
26259
|
+
if (t === "edit" || t === "notebookedit") return import_chalk28.default.yellow;
|
|
26260
|
+
if (t === "read") return import_chalk28.default.cyan;
|
|
26261
|
+
return import_chalk28.default.gray;
|
|
25718
26262
|
}
|
|
25719
26263
|
function barStr2(value, max, width) {
|
|
25720
26264
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -25724,7 +26268,7 @@ function barStr2(value, max, width) {
|
|
|
25724
26268
|
function colorBar2(value, max, width) {
|
|
25725
26269
|
const s = barStr2(value, max, width);
|
|
25726
26270
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
25727
|
-
return
|
|
26271
|
+
return import_chalk28.default.cyan(s.slice(0, filled)) + import_chalk28.default.dim(s.slice(filled));
|
|
25728
26272
|
}
|
|
25729
26273
|
function renderSummary(summaries) {
|
|
25730
26274
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -25754,45 +26298,45 @@ function renderSummary(summaries) {
|
|
|
25754
26298
|
}
|
|
25755
26299
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
25756
26300
|
const W = 20;
|
|
25757
|
-
console.log(
|
|
26301
|
+
console.log(import_chalk28.default.dim(" " + "\u2500".repeat(70)));
|
|
25758
26302
|
console.log(
|
|
25759
|
-
" " +
|
|
26303
|
+
" " + import_chalk28.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk28.default.dim("sessions ") + import_chalk28.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk28.default.dim("total ") + import_chalk28.default.bold.white(String(totalTools).padEnd(6)) + import_chalk28.default.dim("tool calls ") + import_chalk28.default.bold.white(String(totalFiles)) + import_chalk28.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk28.default.dim(" ") + import_chalk28.default.red.bold(String(totalBlocked)) + import_chalk28.default.dim(" blocked by node9") : "")
|
|
25760
26304
|
);
|
|
25761
26305
|
console.log(
|
|
25762
|
-
" " +
|
|
26306
|
+
" " + import_chalk28.default.dim("avg ") + import_chalk28.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk28.default.dim("/session ") + import_chalk28.default.green(String(snapshots)) + import_chalk28.default.dim(` of ${summaries.length} sessions had snapshots`)
|
|
25763
26307
|
);
|
|
25764
26308
|
console.log("");
|
|
25765
|
-
console.log(" " +
|
|
26309
|
+
console.log(" " + import_chalk28.default.dim("Tool breakdown:"));
|
|
25766
26310
|
const maxGroup = Math.max(...Object.values(groups));
|
|
25767
26311
|
for (const [label2, count] of Object.entries(groups)) {
|
|
25768
26312
|
if (count === 0) continue;
|
|
25769
26313
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
25770
26314
|
console.log(
|
|
25771
|
-
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " +
|
|
26315
|
+
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk28.default.white(String(count).padStart(4)) + import_chalk28.default.dim(` (${String(pct)}%)`)
|
|
25772
26316
|
);
|
|
25773
26317
|
}
|
|
25774
26318
|
console.log("");
|
|
25775
26319
|
if (topProjects.length > 1) {
|
|
25776
|
-
console.log(" " +
|
|
26320
|
+
console.log(" " + import_chalk28.default.dim("Cost by project:"));
|
|
25777
26321
|
const maxProjCost = topProjects[0][1];
|
|
25778
26322
|
for (const [proj, cost] of topProjects) {
|
|
25779
26323
|
console.log(
|
|
25780
|
-
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " +
|
|
26324
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk28.default.yellow(fmtCost3(cost))
|
|
25781
26325
|
);
|
|
25782
26326
|
}
|
|
25783
26327
|
console.log("");
|
|
25784
26328
|
}
|
|
25785
|
-
console.log(
|
|
26329
|
+
console.log(import_chalk28.default.dim(" " + "\u2500".repeat(70)));
|
|
25786
26330
|
console.log("");
|
|
25787
26331
|
}
|
|
25788
26332
|
function renderList(summaries, totalCost) {
|
|
25789
26333
|
if (summaries.length === 0) {
|
|
25790
|
-
console.log(
|
|
26334
|
+
console.log(import_chalk28.default.yellow(" No sessions found in the requested range.\n"));
|
|
25791
26335
|
return;
|
|
25792
26336
|
}
|
|
25793
|
-
const totalLabel = totalCost > 0 ?
|
|
26337
|
+
const totalLabel = totalCost > 0 ? import_chalk28.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
25794
26338
|
console.log(
|
|
25795
|
-
" " +
|
|
26339
|
+
" " + import_chalk28.default.white(String(summaries.length)) + import_chalk28.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
25796
26340
|
);
|
|
25797
26341
|
console.log("");
|
|
25798
26342
|
let lastGroup = "";
|
|
@@ -25800,51 +26344,51 @@ function renderList(summaries, totalCost) {
|
|
|
25800
26344
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
25801
26345
|
const group = activeDate + " " + s.projectLabel;
|
|
25802
26346
|
if (group !== lastGroup) {
|
|
25803
|
-
console.log(
|
|
26347
|
+
console.log(import_chalk28.default.dim(" \u2500\u2500\u2500 ") + import_chalk28.default.bold(activeDate) + import_chalk28.default.dim(" " + s.projectLabel));
|
|
25804
26348
|
lastGroup = group;
|
|
25805
26349
|
}
|
|
25806
26350
|
const startDate = fmtDate2(s.startTime);
|
|
25807
|
-
const dateRange = startDate !== activeDate ?
|
|
25808
|
-
const timeStr =
|
|
25809
|
-
const prompt =
|
|
25810
|
-
const tools = s.toolCalls.length > 0 ?
|
|
25811
|
-
const cost = s.costUSD > 0 ?
|
|
25812
|
-
const blocked = s.blockedCalls.length > 0 ?
|
|
25813
|
-
const snap = s.hasSnapshot ?
|
|
25814
|
-
const agentBadge =
|
|
26351
|
+
const dateRange = startDate !== activeDate ? import_chalk28.default.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
|
|
26352
|
+
const timeStr = import_chalk28.default.dim(fmtTime(s.startTime));
|
|
26353
|
+
const prompt = import_chalk28.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
26354
|
+
const tools = s.toolCalls.length > 0 ? import_chalk28.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk28.default.dim(" 0 tools");
|
|
26355
|
+
const cost = s.costUSD > 0 ? import_chalk28.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
26356
|
+
const blocked = s.blockedCalls.length > 0 ? import_chalk28.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
26357
|
+
const snap = s.hasSnapshot ? import_chalk28.default.green(" \u{1F4F8}") : "";
|
|
26358
|
+
const agentBadge = import_chalk28.default[agentColorName(s.agent ?? "claude")](
|
|
25815
26359
|
" " + agentBadgeText(s.agent ?? "claude", 0)
|
|
25816
26360
|
);
|
|
25817
|
-
const sid =
|
|
26361
|
+
const sid = import_chalk28.default.dim(" " + s.sessionId.slice(0, 8));
|
|
25818
26362
|
console.log(
|
|
25819
26363
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
25820
26364
|
);
|
|
25821
26365
|
}
|
|
25822
26366
|
console.log("");
|
|
25823
26367
|
console.log(
|
|
25824
|
-
|
|
26368
|
+
import_chalk28.default.dim(" Run") + " " + import_chalk28.default.cyan("node9 sessions --detail <session-id>") + import_chalk28.default.dim(" for full tool trace.")
|
|
25825
26369
|
);
|
|
25826
26370
|
console.log("");
|
|
25827
26371
|
}
|
|
25828
26372
|
function renderDetail(s) {
|
|
25829
26373
|
console.log("");
|
|
25830
|
-
console.log(
|
|
26374
|
+
console.log(import_chalk28.default.bold(" Session ") + import_chalk28.default.dim(s.sessionId));
|
|
25831
26375
|
console.log(
|
|
25832
|
-
|
|
26376
|
+
import_chalk28.default.bold(" Prompt ") + import_chalk28.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
25833
26377
|
);
|
|
25834
|
-
console.log(
|
|
26378
|
+
console.log(import_chalk28.default.bold(" Project ") + import_chalk28.default.white(s.projectLabel));
|
|
25835
26379
|
if (s.agent) {
|
|
25836
|
-
const agentLabel2 =
|
|
25837
|
-
console.log(
|
|
26380
|
+
const agentLabel2 = import_chalk28.default[agentColorName(s.agent)](agentDisplayName(s.agent));
|
|
26381
|
+
console.log(import_chalk28.default.bold(" Agent ") + agentLabel2);
|
|
25838
26382
|
}
|
|
25839
|
-
console.log(
|
|
26383
|
+
console.log(import_chalk28.default.bold(" When ") + import_chalk28.default.white(fmtDateTime(s.startTime)));
|
|
25840
26384
|
if (s.costUSD > 0)
|
|
25841
|
-
console.log(
|
|
26385
|
+
console.log(import_chalk28.default.bold(" Cost ") + import_chalk28.default.yellow("~" + fmtCost3(s.costUSD)));
|
|
25842
26386
|
console.log(
|
|
25843
|
-
|
|
26387
|
+
import_chalk28.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk28.default.green("\u2713 taken") : import_chalk28.default.dim("none"))
|
|
25844
26388
|
);
|
|
25845
26389
|
console.log("");
|
|
25846
26390
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
25847
|
-
console.log(
|
|
26391
|
+
console.log(import_chalk28.default.dim(" No tool calls recorded.\n"));
|
|
25848
26392
|
return;
|
|
25849
26393
|
}
|
|
25850
26394
|
const timeline = [
|
|
@@ -25857,32 +26401,32 @@ function renderDetail(s) {
|
|
|
25857
26401
|
});
|
|
25858
26402
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
25859
26403
|
if (s.blockedCalls.length > 0)
|
|
25860
|
-
headerParts.push(
|
|
25861
|
-
console.log(
|
|
26404
|
+
headerParts.push(import_chalk28.default.red(`${s.blockedCalls.length} blocked by node9`));
|
|
26405
|
+
console.log(import_chalk28.default.bold(" " + headerParts.join(" \xB7 ")));
|
|
25862
26406
|
console.log("");
|
|
25863
26407
|
for (const entry of timeline) {
|
|
25864
26408
|
if (entry.kind === "tool") {
|
|
25865
26409
|
const tc = entry.tc;
|
|
25866
26410
|
const colorFn = toolColor(tc.tool);
|
|
25867
26411
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
25868
|
-
const detail =
|
|
25869
|
-
const ts = tc.timestamp ?
|
|
26412
|
+
const detail = import_chalk28.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
26413
|
+
const ts = tc.timestamp ? import_chalk28.default.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
25870
26414
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
25871
26415
|
} else {
|
|
25872
26416
|
const bc = entry.bc;
|
|
25873
|
-
const ts = bc.timestamp ?
|
|
25874
|
-
const label2 =
|
|
25875
|
-
const toolName =
|
|
25876
|
-
const argsSummary = bc.args ?
|
|
25877
|
-
const reason = bc.checkedBy ?
|
|
26417
|
+
const ts = bc.timestamp ? import_chalk28.default.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
26418
|
+
const label2 = import_chalk28.default.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
26419
|
+
const toolName = import_chalk28.default.red(bc.tool.padEnd(10));
|
|
26420
|
+
const argsSummary = bc.args ? import_chalk28.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk28.default.dim("[args not logged]");
|
|
26421
|
+
const reason = bc.checkedBy ? import_chalk28.default.dim(" \u2190 " + bc.checkedBy) : "";
|
|
25878
26422
|
console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
|
|
25879
26423
|
}
|
|
25880
26424
|
}
|
|
25881
26425
|
console.log("");
|
|
25882
26426
|
if (s.modifiedFiles.length > 0) {
|
|
25883
|
-
console.log(
|
|
26427
|
+
console.log(import_chalk28.default.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
25884
26428
|
for (const f of s.modifiedFiles) {
|
|
25885
|
-
console.log(" " +
|
|
26429
|
+
console.log(" " + import_chalk28.default.yellow(f));
|
|
25886
26430
|
}
|
|
25887
26431
|
console.log("");
|
|
25888
26432
|
}
|
|
@@ -25890,13 +26434,13 @@ function renderDetail(s) {
|
|
|
25890
26434
|
function registerSessionsCommand(program2) {
|
|
25891
26435
|
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) => {
|
|
25892
26436
|
console.log("");
|
|
25893
|
-
console.log(
|
|
26437
|
+
console.log(import_chalk28.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk28.default.dim(" \u2014 what your AI agent did"));
|
|
25894
26438
|
console.log("");
|
|
25895
26439
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
25896
26440
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
25897
|
-
console.log(
|
|
26441
|
+
console.log(import_chalk28.default.dim(" " + rangeLabel));
|
|
25898
26442
|
console.log("");
|
|
25899
|
-
process.stdout.write(
|
|
26443
|
+
process.stdout.write(import_chalk28.default.dim(" Loading\u2026"));
|
|
25900
26444
|
const summaries = buildSessions(days);
|
|
25901
26445
|
if (process.stdout.isTTY) {
|
|
25902
26446
|
process.stdout.clearLine(0);
|
|
@@ -25909,8 +26453,8 @@ function registerSessionsCommand(program2) {
|
|
|
25909
26453
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
25910
26454
|
);
|
|
25911
26455
|
if (!target) {
|
|
25912
|
-
console.log(
|
|
25913
|
-
console.log(
|
|
26456
|
+
console.log(import_chalk28.default.red(` Session not found: ${options.detail}`));
|
|
26457
|
+
console.log(import_chalk28.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
25914
26458
|
return;
|
|
25915
26459
|
}
|
|
25916
26460
|
renderDetail(target);
|
|
@@ -25923,7 +26467,7 @@ function registerSessionsCommand(program2) {
|
|
|
25923
26467
|
}
|
|
25924
26468
|
|
|
25925
26469
|
// src/cli/commands/session-taint.ts
|
|
25926
|
-
var
|
|
26470
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
25927
26471
|
init_daemon();
|
|
25928
26472
|
function resolveSessionId(records, query) {
|
|
25929
26473
|
const exact = records.find((r) => r.sessionId === query);
|
|
@@ -25950,22 +26494,22 @@ function registerSessionTaintCommand(program2) {
|
|
|
25950
26494
|
const records = await listSessionTaints();
|
|
25951
26495
|
console.log("");
|
|
25952
26496
|
if (records.length === 0) {
|
|
25953
|
-
console.log(
|
|
25954
|
-
console.log(
|
|
26497
|
+
console.log(import_chalk29.default.dim(" No tainted sessions."));
|
|
26498
|
+
console.log(import_chalk29.default.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
|
|
25955
26499
|
return;
|
|
25956
26500
|
}
|
|
25957
26501
|
console.log(
|
|
25958
|
-
" " +
|
|
26502
|
+
" " + import_chalk29.default.bold(String(records.length)) + import_chalk29.default.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
|
|
25959
26503
|
);
|
|
25960
26504
|
console.log("");
|
|
25961
26505
|
for (const r of records) {
|
|
25962
26506
|
console.log(
|
|
25963
|
-
" " +
|
|
26507
|
+
" " + import_chalk29.default.yellow(r.sessionId.slice(0, 8).padEnd(10)) + import_chalk29.default.red(r.source) + sourceGap(r.source) + import_chalk29.default.dim("clears in " + fmtRemaining(r.expiresAt))
|
|
25964
26508
|
);
|
|
25965
26509
|
}
|
|
25966
26510
|
console.log("");
|
|
25967
26511
|
console.log(
|
|
25968
|
-
|
|
26512
|
+
import_chalk29.default.dim(" Run ") + import_chalk29.default.cyan("node9 session-taint clear <id>") + import_chalk29.default.dim(" to release one, or ") + import_chalk29.default.cyan("--all") + import_chalk29.default.dim(" for every session.") + "\n"
|
|
25969
26513
|
);
|
|
25970
26514
|
});
|
|
25971
26515
|
cmd.command("clear").description("Clear a session's taint so its next network/write action isn't held for review").argument("[sessionId]", "Session id to clear (the 8-char prefix from `list` is accepted)").option("--all", "Clear every session taint").action(async (sessionId, opts) => {
|
|
@@ -25973,32 +26517,32 @@ function registerSessionTaintCommand(program2) {
|
|
|
25973
26517
|
if (opts.all) {
|
|
25974
26518
|
const res2 = await clearSessionTaint({ all: true });
|
|
25975
26519
|
if (res2.daemonUnavailable) {
|
|
25976
|
-
console.log(
|
|
26520
|
+
console.log(import_chalk29.default.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
|
|
25977
26521
|
return;
|
|
25978
26522
|
}
|
|
25979
26523
|
console.log(
|
|
25980
|
-
|
|
26524
|
+
import_chalk29.default.green(" \u2713 ") + `Cleared ${import_chalk29.default.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
|
|
25981
26525
|
`
|
|
25982
26526
|
);
|
|
25983
26527
|
return;
|
|
25984
26528
|
}
|
|
25985
26529
|
if (!sessionId) {
|
|
25986
|
-
console.log(
|
|
25987
|
-
console.log(
|
|
26530
|
+
console.log(import_chalk29.default.red(" Provide a session id or --all."));
|
|
26531
|
+
console.log(import_chalk29.default.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
|
|
25988
26532
|
return;
|
|
25989
26533
|
}
|
|
25990
26534
|
const records = await listSessionTaints();
|
|
25991
26535
|
if (records.length === 0) {
|
|
25992
|
-
console.log(
|
|
26536
|
+
console.log(import_chalk29.default.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
|
|
25993
26537
|
return;
|
|
25994
26538
|
}
|
|
25995
26539
|
const resolved = resolveSessionId(records, sessionId);
|
|
25996
26540
|
if ("error" in resolved) {
|
|
25997
26541
|
if (resolved.error === "not-found") {
|
|
25998
|
-
console.log(
|
|
26542
|
+
console.log(import_chalk29.default.red(` No tainted session matches "${sessionId}".`));
|
|
25999
26543
|
} else {
|
|
26000
|
-
console.log(
|
|
26001
|
-
for (const m of resolved.matches) console.log(
|
|
26544
|
+
console.log(import_chalk29.default.red(` "${sessionId}" is ambiguous \u2014 matches:`));
|
|
26545
|
+
for (const m of resolved.matches) console.log(import_chalk29.default.dim(" " + m));
|
|
26002
26546
|
}
|
|
26003
26547
|
console.log("");
|
|
26004
26548
|
return;
|
|
@@ -26006,24 +26550,24 @@ function registerSessionTaintCommand(program2) {
|
|
|
26006
26550
|
const res = await clearSessionTaint({ sessionId: resolved.record.sessionId });
|
|
26007
26551
|
if (res.cleared > 0) {
|
|
26008
26552
|
console.log(
|
|
26009
|
-
|
|
26553
|
+
import_chalk29.default.green(" \u2713 ") + `Cleared taint for ${import_chalk29.default.yellow(resolved.record.sessionId.slice(0, 8))} ` + import_chalk29.default.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
|
|
26010
26554
|
);
|
|
26011
26555
|
} else {
|
|
26012
26556
|
console.log(
|
|
26013
|
-
|
|
26557
|
+
import_chalk29.default.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
|
|
26014
26558
|
);
|
|
26015
26559
|
}
|
|
26016
26560
|
});
|
|
26017
26561
|
}
|
|
26018
26562
|
|
|
26019
26563
|
// src/cli/commands/skill-pin.ts
|
|
26020
|
-
var
|
|
26021
|
-
var
|
|
26022
|
-
var
|
|
26023
|
-
var
|
|
26564
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
26565
|
+
var import_fs56 = __toESM(require("fs"));
|
|
26566
|
+
var import_os48 = __toESM(require("os"));
|
|
26567
|
+
var import_path54 = __toESM(require("path"));
|
|
26024
26568
|
function wipeSkillSessions() {
|
|
26025
26569
|
try {
|
|
26026
|
-
|
|
26570
|
+
import_fs56.default.rmSync(import_path54.default.join(import_os48.default.homedir(), ".node9", "skill-sessions"), {
|
|
26027
26571
|
recursive: true,
|
|
26028
26572
|
force: true
|
|
26029
26573
|
});
|
|
@@ -26037,29 +26581,29 @@ function registerSkillPinCommand(program2) {
|
|
|
26037
26581
|
const result = readSkillPinsSafe();
|
|
26038
26582
|
if (!result.ok) {
|
|
26039
26583
|
if (result.reason === "missing") {
|
|
26040
|
-
console.log(
|
|
26584
|
+
console.log(import_chalk30.default.gray("\nNo skill roots are pinned yet."));
|
|
26041
26585
|
console.log(
|
|
26042
|
-
|
|
26586
|
+
import_chalk30.default.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
26043
26587
|
);
|
|
26044
26588
|
return;
|
|
26045
26589
|
}
|
|
26046
|
-
console.error(
|
|
26590
|
+
console.error(import_chalk30.default.red(`
|
|
26047
26591
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
26048
|
-
console.error(
|
|
26592
|
+
console.error(import_chalk30.default.yellow(" Run: node9 skill pin reset\n"));
|
|
26049
26593
|
process.exit(1);
|
|
26050
26594
|
}
|
|
26051
26595
|
const entries = Object.entries(result.pins.roots);
|
|
26052
26596
|
if (entries.length === 0) {
|
|
26053
|
-
console.log(
|
|
26597
|
+
console.log(import_chalk30.default.gray("\nNo skill roots are pinned yet.\n"));
|
|
26054
26598
|
return;
|
|
26055
26599
|
}
|
|
26056
|
-
console.log(
|
|
26600
|
+
console.log(import_chalk30.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
26057
26601
|
for (const [key, entry] of entries) {
|
|
26058
|
-
const missing = entry.exists ? "" :
|
|
26059
|
-
console.log(` ${
|
|
26602
|
+
const missing = entry.exists ? "" : import_chalk30.default.yellow(" (not present at pin time)");
|
|
26603
|
+
console.log(` ${import_chalk30.default.cyan(key)} ${import_chalk30.default.gray(entry.rootPath)}${missing}`);
|
|
26060
26604
|
console.log(` Files (${entry.fileCount})`);
|
|
26061
|
-
console.log(` Hash: ${
|
|
26062
|
-
console.log(` Pinned: ${
|
|
26605
|
+
console.log(` Hash: ${import_chalk30.default.gray(entry.contentHash.slice(0, 16))}...`);
|
|
26606
|
+
console.log(` Pinned: ${import_chalk30.default.gray(entry.pinnedAt)}
|
|
26063
26607
|
`);
|
|
26064
26608
|
}
|
|
26065
26609
|
});
|
|
@@ -26068,52 +26612,52 @@ function registerSkillPinCommand(program2) {
|
|
|
26068
26612
|
try {
|
|
26069
26613
|
pins = readSkillPins();
|
|
26070
26614
|
} catch {
|
|
26071
|
-
console.error(
|
|
26072
|
-
console.error(
|
|
26615
|
+
console.error(import_chalk30.default.red("\n\u274C Pin file is corrupt."));
|
|
26616
|
+
console.error(import_chalk30.default.yellow(" Run: node9 skill pin reset\n"));
|
|
26073
26617
|
process.exit(1);
|
|
26074
26618
|
}
|
|
26075
26619
|
if (!pins.roots[rootKey]) {
|
|
26076
|
-
console.error(
|
|
26620
|
+
console.error(import_chalk30.default.red(`
|
|
26077
26621
|
\u274C No pin found for root key "${rootKey}"
|
|
26078
26622
|
`));
|
|
26079
|
-
console.error(`Run ${
|
|
26623
|
+
console.error(`Run ${import_chalk30.default.cyan("node9 skill pin list")} to see pinned roots.
|
|
26080
26624
|
`);
|
|
26081
26625
|
process.exit(1);
|
|
26082
26626
|
}
|
|
26083
26627
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
26084
26628
|
removePin2(rootKey);
|
|
26085
26629
|
wipeSkillSessions();
|
|
26086
|
-
console.log(
|
|
26087
|
-
\u{1F513} Pin removed for ${
|
|
26088
|
-
console.log(
|
|
26089
|
-
console.log(
|
|
26630
|
+
console.log(import_chalk30.default.green(`
|
|
26631
|
+
\u{1F513} Pin removed for ${import_chalk30.default.cyan(rootKey)}`));
|
|
26632
|
+
console.log(import_chalk30.default.gray(` ${rootPath}`));
|
|
26633
|
+
console.log(import_chalk30.default.gray(" Next session will re-pin with current state.\n"));
|
|
26090
26634
|
});
|
|
26091
26635
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
26092
26636
|
const result = readSkillPinsSafe();
|
|
26093
26637
|
if (!result.ok && result.reason === "missing") {
|
|
26094
26638
|
wipeSkillSessions();
|
|
26095
|
-
console.log(
|
|
26639
|
+
console.log(import_chalk30.default.gray("\nNo pins to clear.\n"));
|
|
26096
26640
|
return;
|
|
26097
26641
|
}
|
|
26098
26642
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
26099
26643
|
clearAllPins2();
|
|
26100
26644
|
wipeSkillSessions();
|
|
26101
|
-
console.log(
|
|
26645
|
+
console.log(import_chalk30.default.green(`
|
|
26102
26646
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
26103
|
-
console.log(
|
|
26647
|
+
console.log(import_chalk30.default.gray(" Next session will re-pin with current state.\n"));
|
|
26104
26648
|
});
|
|
26105
26649
|
}
|
|
26106
26650
|
|
|
26107
26651
|
// src/cli/commands/decisions.ts
|
|
26108
|
-
var
|
|
26109
|
-
var
|
|
26110
|
-
var
|
|
26111
|
-
var
|
|
26112
|
-
var DECISIONS_FILE2 =
|
|
26652
|
+
var import_fs57 = __toESM(require("fs"));
|
|
26653
|
+
var import_os49 = __toESM(require("os"));
|
|
26654
|
+
var import_path55 = __toESM(require("path"));
|
|
26655
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
26656
|
+
var DECISIONS_FILE2 = import_path55.default.join(import_os49.default.homedir(), ".node9", "decisions.json");
|
|
26113
26657
|
function readDecisions() {
|
|
26114
26658
|
try {
|
|
26115
|
-
if (!
|
|
26116
|
-
const raw =
|
|
26659
|
+
if (!import_fs57.default.existsSync(DECISIONS_FILE2)) return {};
|
|
26660
|
+
const raw = import_fs57.default.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
26117
26661
|
const parsed = JSON.parse(raw);
|
|
26118
26662
|
const out = {};
|
|
26119
26663
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -26125,11 +26669,11 @@ function readDecisions() {
|
|
|
26125
26669
|
}
|
|
26126
26670
|
}
|
|
26127
26671
|
function writeDecisions(d) {
|
|
26128
|
-
const dir =
|
|
26129
|
-
if (!
|
|
26672
|
+
const dir = import_path55.default.dirname(DECISIONS_FILE2);
|
|
26673
|
+
if (!import_fs57.default.existsSync(dir)) import_fs57.default.mkdirSync(dir, { recursive: true });
|
|
26130
26674
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
26131
|
-
|
|
26132
|
-
|
|
26675
|
+
import_fs57.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
26676
|
+
import_fs57.default.renameSync(tmp, DECISIONS_FILE2);
|
|
26133
26677
|
}
|
|
26134
26678
|
function registerDecisionsCommand(program2) {
|
|
26135
26679
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -26137,67 +26681,67 @@ function registerDecisionsCommand(program2) {
|
|
|
26137
26681
|
const decisions = readDecisions();
|
|
26138
26682
|
const entries = Object.entries(decisions);
|
|
26139
26683
|
if (entries.length === 0) {
|
|
26140
|
-
console.log(
|
|
26684
|
+
console.log(import_chalk31.default.gray(" No persistent decisions stored."));
|
|
26141
26685
|
console.log(
|
|
26142
|
-
|
|
26143
|
-
`) +
|
|
26686
|
+
import_chalk31.default.gray(` File: ${DECISIONS_FILE2}
|
|
26687
|
+
`) + import_chalk31.default.gray(' Decisions are written when you click "Always Allow" or')
|
|
26144
26688
|
);
|
|
26145
|
-
console.log(
|
|
26689
|
+
console.log(import_chalk31.default.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
26146
26690
|
return;
|
|
26147
26691
|
}
|
|
26148
|
-
console.log(
|
|
26692
|
+
console.log(import_chalk31.default.bold(`
|
|
26149
26693
|
Persistent decisions (${entries.length})
|
|
26150
26694
|
`));
|
|
26151
26695
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
26152
26696
|
for (const [tool, verdict] of entries.sort()) {
|
|
26153
|
-
const colored = verdict === "allow" ?
|
|
26697
|
+
const colored = verdict === "allow" ? import_chalk31.default.green(verdict) : import_chalk31.default.red(verdict);
|
|
26154
26698
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
26155
26699
|
}
|
|
26156
26700
|
console.log(
|
|
26157
|
-
|
|
26701
|
+
import_chalk31.default.gray(`
|
|
26158
26702
|
Stored in ${DECISIONS_FILE2}
|
|
26159
|
-
`) +
|
|
26703
|
+
`) + import_chalk31.default.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
26160
26704
|
);
|
|
26161
26705
|
});
|
|
26162
26706
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
26163
26707
|
const decisions = readDecisions();
|
|
26164
26708
|
if (!(toolName in decisions)) {
|
|
26165
|
-
console.log(
|
|
26709
|
+
console.log(import_chalk31.default.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
26166
26710
|
process.exitCode = 1;
|
|
26167
26711
|
return;
|
|
26168
26712
|
}
|
|
26169
26713
|
delete decisions[toolName];
|
|
26170
26714
|
writeDecisions(decisions);
|
|
26171
|
-
console.log(
|
|
26715
|
+
console.log(import_chalk31.default.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
26172
26716
|
});
|
|
26173
26717
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
26174
26718
|
const decisions = readDecisions();
|
|
26175
26719
|
const count = Object.keys(decisions).length;
|
|
26176
26720
|
if (count === 0) {
|
|
26177
|
-
console.log(
|
|
26721
|
+
console.log(import_chalk31.default.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
26178
26722
|
return;
|
|
26179
26723
|
}
|
|
26180
26724
|
writeDecisions({});
|
|
26181
26725
|
console.log(
|
|
26182
|
-
|
|
26726
|
+
import_chalk31.default.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
26183
26727
|
);
|
|
26184
26728
|
});
|
|
26185
26729
|
}
|
|
26186
26730
|
|
|
26187
26731
|
// src/cli/commands/dlp.ts
|
|
26188
|
-
var
|
|
26189
|
-
var
|
|
26190
|
-
var
|
|
26191
|
-
var
|
|
26192
|
-
var AUDIT_LOG =
|
|
26193
|
-
var RESOLVED_FILE =
|
|
26732
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
26733
|
+
var import_fs58 = __toESM(require("fs"));
|
|
26734
|
+
var import_path56 = __toESM(require("path"));
|
|
26735
|
+
var import_os50 = __toESM(require("os"));
|
|
26736
|
+
var AUDIT_LOG = import_path56.default.join(import_os50.default.homedir(), ".node9", "audit.log");
|
|
26737
|
+
var RESOLVED_FILE = import_path56.default.join(import_os50.default.homedir(), ".node9", "dlp-resolved.json");
|
|
26194
26738
|
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
26195
26739
|
function stripAnsi(s) {
|
|
26196
26740
|
return s.replace(ANSI_RE, "");
|
|
26197
26741
|
}
|
|
26198
26742
|
function loadResolved() {
|
|
26199
26743
|
try {
|
|
26200
|
-
const raw = JSON.parse(
|
|
26744
|
+
const raw = JSON.parse(import_fs58.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
26201
26745
|
return new Set(raw);
|
|
26202
26746
|
} catch {
|
|
26203
26747
|
return /* @__PURE__ */ new Set();
|
|
@@ -26205,13 +26749,13 @@ function loadResolved() {
|
|
|
26205
26749
|
}
|
|
26206
26750
|
function saveResolved(resolved) {
|
|
26207
26751
|
try {
|
|
26208
|
-
|
|
26752
|
+
import_fs58.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
26209
26753
|
} catch {
|
|
26210
26754
|
}
|
|
26211
26755
|
}
|
|
26212
26756
|
function loadDlpFindings() {
|
|
26213
|
-
if (!
|
|
26214
|
-
return
|
|
26757
|
+
if (!import_fs58.default.existsSync(AUDIT_LOG)) return [];
|
|
26758
|
+
return import_fs58.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
26215
26759
|
if (!line.trim()) return [];
|
|
26216
26760
|
try {
|
|
26217
26761
|
const e = JSON.parse(line);
|
|
@@ -26240,14 +26784,14 @@ function registerDlpCommand(program2) {
|
|
|
26240
26784
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
26241
26785
|
const findings = loadDlpFindings();
|
|
26242
26786
|
if (findings.length === 0) {
|
|
26243
|
-
console.log(
|
|
26787
|
+
console.log(import_chalk32.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
26244
26788
|
return;
|
|
26245
26789
|
}
|
|
26246
26790
|
const resolved = loadResolved();
|
|
26247
26791
|
for (const e of findings) resolved.add(entryKey2(e));
|
|
26248
26792
|
saveResolved(resolved);
|
|
26249
26793
|
console.log(
|
|
26250
|
-
|
|
26794
|
+
import_chalk32.default.green(
|
|
26251
26795
|
`
|
|
26252
26796
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
26253
26797
|
`
|
|
@@ -26261,63 +26805,63 @@ function registerDlpCommand(program2) {
|
|
|
26261
26805
|
const resolvedCount = findings.length - open.length;
|
|
26262
26806
|
console.log("");
|
|
26263
26807
|
console.log(
|
|
26264
|
-
|
|
26808
|
+
import_chalk32.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk32.default.dim(" \u2014 secrets found in Claude response text")
|
|
26265
26809
|
);
|
|
26266
26810
|
console.log("");
|
|
26267
26811
|
if (open.length === 0) {
|
|
26268
26812
|
if (resolvedCount > 0) {
|
|
26269
|
-
console.log(
|
|
26813
|
+
console.log(import_chalk32.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
26270
26814
|
} else {
|
|
26271
26815
|
console.log(
|
|
26272
|
-
|
|
26816
|
+
import_chalk32.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
26273
26817
|
);
|
|
26274
26818
|
}
|
|
26275
26819
|
console.log("");
|
|
26276
26820
|
return;
|
|
26277
26821
|
}
|
|
26278
26822
|
console.log(
|
|
26279
|
-
|
|
26823
|
+
import_chalk32.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk32.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
26280
26824
|
);
|
|
26281
26825
|
console.log("");
|
|
26282
26826
|
console.log(
|
|
26283
|
-
|
|
26827
|
+
import_chalk32.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
26284
26828
|
);
|
|
26285
|
-
console.log(
|
|
26829
|
+
console.log(import_chalk32.default.dim(" Rotate each affected key immediately.\n"));
|
|
26286
26830
|
for (const e of open) {
|
|
26287
26831
|
console.log(
|
|
26288
|
-
" " +
|
|
26832
|
+
" " + import_chalk32.default.red("\u25CF") + " " + import_chalk32.default.white(e.dlpPattern ?? "Secret") + import_chalk32.default.dim(" " + fmtDate3(e.ts))
|
|
26289
26833
|
);
|
|
26290
26834
|
if (e.dlpSample) {
|
|
26291
|
-
console.log(" " +
|
|
26835
|
+
console.log(" " + import_chalk32.default.dim("Sample: ") + import_chalk32.default.yellow(stripAnsi(e.dlpSample)));
|
|
26292
26836
|
}
|
|
26293
26837
|
if (e.project) {
|
|
26294
|
-
console.log(" " +
|
|
26838
|
+
console.log(" " + import_chalk32.default.dim("Project: ") + import_chalk32.default.dim(stripAnsi(e.project)));
|
|
26295
26839
|
}
|
|
26296
26840
|
console.log("");
|
|
26297
26841
|
}
|
|
26298
|
-
console.log(" " +
|
|
26299
|
-
console.log(" " +
|
|
26842
|
+
console.log(" " + import_chalk32.default.bold("Next steps:"));
|
|
26843
|
+
console.log(" " + import_chalk32.default.cyan("1.") + " Rotate any exposed keys shown above");
|
|
26300
26844
|
console.log(
|
|
26301
|
-
" " +
|
|
26845
|
+
" " + import_chalk32.default.cyan("2.") + " Run " + import_chalk32.default.white("node9 dlp resolve") + " to acknowledge"
|
|
26302
26846
|
);
|
|
26303
26847
|
console.log(
|
|
26304
|
-
" " +
|
|
26848
|
+
" " + import_chalk32.default.cyan("3.") + " Run " + import_chalk32.default.white("node9 report") + " for full audit history"
|
|
26305
26849
|
);
|
|
26306
26850
|
console.log("");
|
|
26307
26851
|
});
|
|
26308
26852
|
}
|
|
26309
26853
|
|
|
26310
26854
|
// src/cli/commands/mask.ts
|
|
26311
|
-
var
|
|
26312
|
-
var
|
|
26313
|
-
var
|
|
26314
|
-
var
|
|
26855
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
26856
|
+
var import_fs59 = __toESM(require("fs"));
|
|
26857
|
+
var import_path57 = __toESM(require("path"));
|
|
26858
|
+
var import_os51 = __toESM(require("os"));
|
|
26315
26859
|
init_dlp();
|
|
26316
26860
|
function findJsonlFiles(dir) {
|
|
26317
26861
|
const results = [];
|
|
26318
|
-
if (!
|
|
26319
|
-
for (const entry of
|
|
26320
|
-
const full =
|
|
26862
|
+
if (!import_fs59.default.existsSync(dir)) return results;
|
|
26863
|
+
for (const entry of import_fs59.default.readdirSync(dir, { withFileTypes: true })) {
|
|
26864
|
+
const full = import_path57.default.join(dir, entry.name);
|
|
26321
26865
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
26322
26866
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
26323
26867
|
}
|
|
@@ -26360,7 +26904,7 @@ function redactJson(obj) {
|
|
|
26360
26904
|
function processFile(filePath, dryRun) {
|
|
26361
26905
|
let raw;
|
|
26362
26906
|
try {
|
|
26363
|
-
raw =
|
|
26907
|
+
raw = import_fs59.default.readFileSync(filePath, "utf-8");
|
|
26364
26908
|
} catch {
|
|
26365
26909
|
return { redactedLines: 0, patterns: [] };
|
|
26366
26910
|
}
|
|
@@ -26392,14 +26936,14 @@ function processFile(filePath, dryRun) {
|
|
|
26392
26936
|
}
|
|
26393
26937
|
}
|
|
26394
26938
|
if (!dryRun && redactedLines > 0) {
|
|
26395
|
-
|
|
26939
|
+
import_fs59.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
26396
26940
|
}
|
|
26397
26941
|
return { redactedLines, patterns };
|
|
26398
26942
|
}
|
|
26399
26943
|
function processJsonFile(filePath, dryRun) {
|
|
26400
26944
|
let raw;
|
|
26401
26945
|
try {
|
|
26402
|
-
raw =
|
|
26946
|
+
raw = import_fs59.default.readFileSync(filePath, "utf-8");
|
|
26403
26947
|
} catch {
|
|
26404
26948
|
return { redactedLines: 0, patterns: [] };
|
|
26405
26949
|
}
|
|
@@ -26412,15 +26956,15 @@ function processJsonFile(filePath, dryRun) {
|
|
|
26412
26956
|
const { value, modified, found } = redactJson(parsed);
|
|
26413
26957
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
26414
26958
|
if (!dryRun) {
|
|
26415
|
-
|
|
26959
|
+
import_fs59.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
26416
26960
|
}
|
|
26417
26961
|
return { redactedLines: 1, patterns: found };
|
|
26418
26962
|
}
|
|
26419
26963
|
function findJsonFiles(dir) {
|
|
26420
26964
|
const results = [];
|
|
26421
|
-
if (!
|
|
26422
|
-
for (const entry of
|
|
26423
|
-
const full =
|
|
26965
|
+
if (!import_fs59.default.existsSync(dir)) return results;
|
|
26966
|
+
for (const entry of import_fs59.default.readdirSync(dir, { withFileTypes: true })) {
|
|
26967
|
+
const full = import_path57.default.join(dir, entry.name);
|
|
26424
26968
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
26425
26969
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
26426
26970
|
}
|
|
@@ -26429,9 +26973,9 @@ function findJsonFiles(dir) {
|
|
|
26429
26973
|
function registerMaskCommand(program2) {
|
|
26430
26974
|
program2.command("mask").description("Redact plaintext secrets from local AI session history files").option("--dry-run", "show what would be redacted without making changes").option("--all", "scan all history (default: last 30 days)").action(async (options) => {
|
|
26431
26975
|
const dryRun = !!options.dryRun;
|
|
26432
|
-
const home =
|
|
26433
|
-
const claudeDir =
|
|
26434
|
-
const geminiDir =
|
|
26976
|
+
const home = import_os51.default.homedir();
|
|
26977
|
+
const claudeDir = import_path57.default.join(home, ".claude", "projects");
|
|
26978
|
+
const geminiDir = import_path57.default.join(home, ".gemini", "tmp");
|
|
26435
26979
|
const allFiles = [
|
|
26436
26980
|
...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
|
|
26437
26981
|
...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
|
|
@@ -26439,18 +26983,18 @@ function registerMaskCommand(program2) {
|
|
|
26439
26983
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
26440
26984
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
26441
26985
|
try {
|
|
26442
|
-
return
|
|
26986
|
+
return import_fs59.default.statSync(f.path).mtime >= cutoff;
|
|
26443
26987
|
} catch {
|
|
26444
26988
|
return false;
|
|
26445
26989
|
}
|
|
26446
26990
|
}) : allFiles;
|
|
26447
26991
|
if (filtered.length === 0) {
|
|
26448
|
-
console.log(
|
|
26992
|
+
console.log(import_chalk33.default.yellow(" No session files found."));
|
|
26449
26993
|
return;
|
|
26450
26994
|
}
|
|
26451
26995
|
console.log("");
|
|
26452
26996
|
if (dryRun) {
|
|
26453
|
-
console.log(
|
|
26997
|
+
console.log(import_chalk33.default.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
26454
26998
|
}
|
|
26455
26999
|
let totalFiles = 0;
|
|
26456
27000
|
let totalLines = 0;
|
|
@@ -26466,23 +27010,23 @@ function registerMaskCommand(program2) {
|
|
|
26466
27010
|
});
|
|
26467
27011
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
26468
27012
|
console.log(
|
|
26469
|
-
" " +
|
|
27013
|
+
" " + import_chalk33.default.dim(shortPath.slice(0, 60).padEnd(62)) + import_chalk33.default.red(`${verb}: `) + import_chalk33.default.yellow(patterns.join(", ")) + import_chalk33.default.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
26470
27014
|
);
|
|
26471
27015
|
}
|
|
26472
27016
|
}
|
|
26473
27017
|
console.log("");
|
|
26474
27018
|
if (totalFiles === 0) {
|
|
26475
|
-
console.log(
|
|
27019
|
+
console.log(import_chalk33.default.green(" No secrets found in session history."));
|
|
26476
27020
|
} else {
|
|
26477
27021
|
const verb = dryRun ? "would be modified" : "modified";
|
|
26478
27022
|
console.log(
|
|
26479
|
-
|
|
27023
|
+
import_chalk33.default.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + import_chalk33.default.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
26480
27024
|
);
|
|
26481
|
-
console.log(" Patterns: " +
|
|
27025
|
+
console.log(" Patterns: " + import_chalk33.default.yellow(totalPatterns.join(", ")));
|
|
26482
27026
|
if (!dryRun) {
|
|
26483
27027
|
console.log("");
|
|
26484
27028
|
console.log(
|
|
26485
|
-
|
|
27029
|
+
import_chalk33.default.dim(
|
|
26486
27030
|
" Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
|
|
26487
27031
|
)
|
|
26488
27032
|
);
|
|
@@ -26495,20 +27039,20 @@ function registerMaskCommand(program2) {
|
|
|
26495
27039
|
// src/cli.ts
|
|
26496
27040
|
init_blast();
|
|
26497
27041
|
var { version } = JSON.parse(
|
|
26498
|
-
|
|
27042
|
+
import_fs62.default.readFileSync(import_path60.default.join(__dirname, "../package.json"), "utf-8")
|
|
26499
27043
|
);
|
|
26500
27044
|
var program = new import_commander.Command();
|
|
26501
27045
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
26502
27046
|
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) => {
|
|
26503
27047
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
26504
|
-
const credPath =
|
|
26505
|
-
if (!
|
|
26506
|
-
|
|
27048
|
+
const credPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "credentials.json");
|
|
27049
|
+
if (!import_fs62.default.existsSync(import_path60.default.dirname(credPath)))
|
|
27050
|
+
import_fs62.default.mkdirSync(import_path60.default.dirname(credPath), { recursive: true });
|
|
26507
27051
|
const profileName = options.profile || "default";
|
|
26508
27052
|
let existingCreds = {};
|
|
26509
27053
|
try {
|
|
26510
|
-
if (
|
|
26511
|
-
const raw = JSON.parse(
|
|
27054
|
+
if (import_fs62.default.existsSync(credPath)) {
|
|
27055
|
+
const raw = JSON.parse(import_fs62.default.readFileSync(credPath, "utf-8"));
|
|
26512
27056
|
if (raw.apiKey) {
|
|
26513
27057
|
existingCreds = {
|
|
26514
27058
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -26520,14 +27064,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
26520
27064
|
} catch {
|
|
26521
27065
|
}
|
|
26522
27066
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
26523
|
-
|
|
27067
|
+
import_fs62.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
26524
27068
|
let effectiveCloud = null;
|
|
26525
27069
|
if (profileName === "default") {
|
|
26526
|
-
const configPath2 =
|
|
27070
|
+
const configPath2 = import_path60.default.join(import_os54.default.homedir(), ".node9", "config.json");
|
|
26527
27071
|
let config = {};
|
|
26528
27072
|
try {
|
|
26529
|
-
if (
|
|
26530
|
-
config = JSON.parse(
|
|
27073
|
+
if (import_fs62.default.existsSync(configPath2))
|
|
27074
|
+
config = JSON.parse(import_fs62.default.readFileSync(configPath2, "utf-8"));
|
|
26531
27075
|
} catch {
|
|
26532
27076
|
}
|
|
26533
27077
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -26542,30 +27086,49 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
26542
27086
|
approvers.cloud = false;
|
|
26543
27087
|
}
|
|
26544
27088
|
s.approvers = approvers;
|
|
26545
|
-
if (!
|
|
26546
|
-
|
|
26547
|
-
|
|
27089
|
+
if (!import_fs62.default.existsSync(import_path60.default.dirname(configPath2)))
|
|
27090
|
+
import_fs62.default.mkdirSync(import_path60.default.dirname(configPath2), { recursive: true });
|
|
27091
|
+
import_fs62.default.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
|
|
26548
27092
|
effectiveCloud = approvers.cloud === true;
|
|
26549
27093
|
}
|
|
26550
27094
|
if (options.profile && profileName !== "default") {
|
|
26551
|
-
console.log(
|
|
26552
|
-
console.log(
|
|
27095
|
+
console.log(import_chalk35.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
27096
|
+
console.log(import_chalk35.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
26553
27097
|
} else if (options.local || effectiveCloud === false) {
|
|
26554
|
-
console.log(
|
|
26555
|
-
console.log(
|
|
27098
|
+
console.log(import_chalk35.default.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
|
|
27099
|
+
console.log(import_chalk35.default.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
|
|
26556
27100
|
if (!options.local) {
|
|
26557
27101
|
console.log(
|
|
26558
|
-
|
|
27102
|
+
import_chalk35.default.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
|
|
26559
27103
|
);
|
|
26560
27104
|
console.log(
|
|
26561
|
-
|
|
27105
|
+
import_chalk35.default.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
|
|
26562
27106
|
);
|
|
26563
27107
|
}
|
|
26564
27108
|
} else {
|
|
26565
|
-
console.log(
|
|
26566
|
-
console.log(
|
|
27109
|
+
console.log(import_chalk35.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
27110
|
+
console.log(import_chalk35.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
26567
27111
|
}
|
|
26568
27112
|
});
|
|
27113
|
+
program.command("signup").description("Create your node9 account / open the dashboard in your browser").option("--login", "Open the login page instead of signup").action((options) => {
|
|
27114
|
+
const route = options.login ? "auth/login" : "auth/signup";
|
|
27115
|
+
const url = `https://node9.ai/${route}?ref=cli_cmd`;
|
|
27116
|
+
console.log("");
|
|
27117
|
+
console.log(" " + import_chalk35.default.dim("Opening ") + import_chalk35.default.cyan.underline(url));
|
|
27118
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
27119
|
+
try {
|
|
27120
|
+
const child = (0, import_child_process15.spawn)(opener, [url], {
|
|
27121
|
+
stdio: "ignore",
|
|
27122
|
+
detached: true,
|
|
27123
|
+
shell: process.platform === "win32"
|
|
27124
|
+
});
|
|
27125
|
+
child.on("error", () => {
|
|
27126
|
+
});
|
|
27127
|
+
child.unref();
|
|
27128
|
+
} catch {
|
|
27129
|
+
}
|
|
27130
|
+
console.log("");
|
|
27131
|
+
});
|
|
26569
27132
|
program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
|
|
26570
27133
|
"after",
|
|
26571
27134
|
"\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
|
|
@@ -26584,7 +27147,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
|
|
|
26584
27147
|
if (target === "hermes") return setupHermes();
|
|
26585
27148
|
if (target === "hud") return setupHud();
|
|
26586
27149
|
console.error(
|
|
26587
|
-
|
|
27150
|
+
import_chalk35.default.red(
|
|
26588
27151
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
26589
27152
|
)
|
|
26590
27153
|
);
|
|
@@ -26598,20 +27161,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
26598
27161
|
"The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
26599
27162
|
).action(async (target) => {
|
|
26600
27163
|
if (!target) {
|
|
26601
|
-
console.log(
|
|
26602
|
-
console.log(" Usage: " +
|
|
27164
|
+
console.log(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
27165
|
+
console.log(" Usage: " + import_chalk35.default.white("node9 setup <target>") + "\n");
|
|
26603
27166
|
console.log(" Targets:");
|
|
26604
|
-
console.log(" " +
|
|
26605
|
-
console.log(" " +
|
|
26606
|
-
console.log(" " +
|
|
26607
|
-
console.log(" " +
|
|
26608
|
-
console.log(" " +
|
|
26609
|
-
console.log(" " +
|
|
26610
|
-
console.log(" " +
|
|
26611
|
-
console.log(" " +
|
|
26612
|
-
console.log(" " +
|
|
27167
|
+
console.log(" " + import_chalk35.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
27168
|
+
console.log(" " + import_chalk35.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
27169
|
+
console.log(" " + import_chalk35.default.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
|
|
27170
|
+
console.log(" " + import_chalk35.default.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
|
|
27171
|
+
console.log(" " + import_chalk35.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
27172
|
+
console.log(" " + import_chalk35.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
27173
|
+
console.log(" " + import_chalk35.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
27174
|
+
console.log(" " + import_chalk35.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
27175
|
+
console.log(" " + import_chalk35.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
26613
27176
|
process.stdout.write(
|
|
26614
|
-
" " +
|
|
27177
|
+
" " + import_chalk35.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
26615
27178
|
);
|
|
26616
27179
|
console.log("");
|
|
26617
27180
|
return;
|
|
@@ -26628,7 +27191,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
26628
27191
|
if (t === "hermes") return setupHermes();
|
|
26629
27192
|
if (t === "hud") return setupHud();
|
|
26630
27193
|
console.error(
|
|
26631
|
-
|
|
27194
|
+
import_chalk35.default.red(
|
|
26632
27195
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
26633
27196
|
)
|
|
26634
27197
|
);
|
|
@@ -26654,33 +27217,33 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
|
|
|
26654
27217
|
else if (target === "hud") fn = teardownHud;
|
|
26655
27218
|
else {
|
|
26656
27219
|
console.error(
|
|
26657
|
-
|
|
27220
|
+
import_chalk35.default.red(
|
|
26658
27221
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
26659
27222
|
)
|
|
26660
27223
|
);
|
|
26661
27224
|
process.exit(1);
|
|
26662
27225
|
}
|
|
26663
|
-
console.log(
|
|
27226
|
+
console.log(import_chalk35.default.cyan(`
|
|
26664
27227
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
26665
27228
|
`));
|
|
26666
27229
|
try {
|
|
26667
27230
|
fn();
|
|
26668
27231
|
} catch (err2) {
|
|
26669
|
-
console.error(
|
|
27232
|
+
console.error(import_chalk35.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
26670
27233
|
process.exit(1);
|
|
26671
27234
|
}
|
|
26672
|
-
console.log(
|
|
27235
|
+
console.log(import_chalk35.default.gray("\n Restart the agent for changes to take effect."));
|
|
26673
27236
|
});
|
|
26674
27237
|
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) => {
|
|
26675
|
-
console.log(
|
|
26676
|
-
console.log(
|
|
27238
|
+
console.log(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
27239
|
+
console.log(import_chalk35.default.bold("Stopping daemon..."));
|
|
26677
27240
|
try {
|
|
26678
27241
|
stopDaemon();
|
|
26679
|
-
console.log(
|
|
27242
|
+
console.log(import_chalk35.default.green(" \u2705 Daemon stopped"));
|
|
26680
27243
|
} catch {
|
|
26681
|
-
console.log(
|
|
27244
|
+
console.log(import_chalk35.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
26682
27245
|
}
|
|
26683
|
-
console.log(
|
|
27246
|
+
console.log(import_chalk35.default.bold("\nRemoving hooks..."));
|
|
26684
27247
|
let teardownFailed = false;
|
|
26685
27248
|
for (const [label2, fn] of [
|
|
26686
27249
|
["Claude", teardownClaude],
|
|
@@ -26696,45 +27259,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
26696
27259
|
} catch (err2) {
|
|
26697
27260
|
teardownFailed = true;
|
|
26698
27261
|
console.error(
|
|
26699
|
-
|
|
27262
|
+
import_chalk35.default.red(
|
|
26700
27263
|
` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
26701
27264
|
)
|
|
26702
27265
|
);
|
|
26703
27266
|
}
|
|
26704
27267
|
}
|
|
26705
27268
|
if (options.purge) {
|
|
26706
|
-
const node9Dir =
|
|
26707
|
-
if (
|
|
27269
|
+
const node9Dir = import_path60.default.join(import_os54.default.homedir(), ".node9");
|
|
27270
|
+
if (import_fs62.default.existsSync(node9Dir)) {
|
|
26708
27271
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
26709
27272
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
26710
27273
|
default: false
|
|
26711
27274
|
});
|
|
26712
27275
|
if (confirmed) {
|
|
26713
|
-
|
|
26714
|
-
if (
|
|
27276
|
+
import_fs62.default.rmSync(node9Dir, { recursive: true });
|
|
27277
|
+
if (import_fs62.default.existsSync(node9Dir)) {
|
|
26715
27278
|
console.error(
|
|
26716
|
-
|
|
27279
|
+
import_chalk35.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
26717
27280
|
);
|
|
26718
27281
|
} else {
|
|
26719
|
-
console.log(
|
|
27282
|
+
console.log(import_chalk35.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
26720
27283
|
}
|
|
26721
27284
|
} else {
|
|
26722
|
-
console.log(
|
|
27285
|
+
console.log(import_chalk35.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
26723
27286
|
}
|
|
26724
27287
|
} else {
|
|
26725
|
-
console.log(
|
|
27288
|
+
console.log(import_chalk35.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
26726
27289
|
}
|
|
26727
27290
|
} else {
|
|
26728
27291
|
console.log(
|
|
26729
|
-
|
|
27292
|
+
import_chalk35.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
26730
27293
|
);
|
|
26731
27294
|
}
|
|
26732
27295
|
if (teardownFailed) {
|
|
26733
|
-
console.error(
|
|
27296
|
+
console.error(import_chalk35.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
26734
27297
|
process.exit(1);
|
|
26735
27298
|
}
|
|
26736
|
-
console.log(
|
|
26737
|
-
console.log(
|
|
27299
|
+
console.log(import_chalk35.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
|
|
27300
|
+
console.log(import_chalk35.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
26738
27301
|
});
|
|
26739
27302
|
registerDoctorCommand(program, version);
|
|
26740
27303
|
program.command("explain").description(
|
|
@@ -26747,7 +27310,7 @@ program.command("explain").description(
|
|
|
26747
27310
|
try {
|
|
26748
27311
|
args = JSON.parse(trimmed);
|
|
26749
27312
|
} catch {
|
|
26750
|
-
console.error(
|
|
27313
|
+
console.error(import_chalk35.default.red(`
|
|
26751
27314
|
\u274C Invalid JSON: ${trimmed}
|
|
26752
27315
|
`));
|
|
26753
27316
|
process.exit(1);
|
|
@@ -26758,54 +27321,54 @@ program.command("explain").description(
|
|
|
26758
27321
|
}
|
|
26759
27322
|
const result = await explainPolicy(tool, args);
|
|
26760
27323
|
console.log("");
|
|
26761
|
-
console.log(
|
|
27324
|
+
console.log(import_chalk35.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
26762
27325
|
console.log("");
|
|
26763
|
-
console.log(` ${
|
|
27326
|
+
console.log(` ${import_chalk35.default.bold("Tool:")} ${import_chalk35.default.white(result.tool)}`);
|
|
26764
27327
|
if (argsRaw) {
|
|
26765
27328
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
26766
|
-
console.log(` ${
|
|
27329
|
+
console.log(` ${import_chalk35.default.bold("Input:")} ${import_chalk35.default.gray(preview2)}`);
|
|
26767
27330
|
}
|
|
26768
27331
|
console.log("");
|
|
26769
|
-
console.log(
|
|
27332
|
+
console.log(import_chalk35.default.bold("Config Sources (Waterfall):"));
|
|
26770
27333
|
for (const tier of result.waterfall) {
|
|
26771
|
-
const num3 =
|
|
27334
|
+
const num3 = import_chalk35.default.gray(` ${tier.tier}.`);
|
|
26772
27335
|
const label2 = tier.label.padEnd(16);
|
|
26773
27336
|
let statusStr;
|
|
26774
27337
|
if (tier.tier === 1) {
|
|
26775
|
-
statusStr =
|
|
27338
|
+
statusStr = import_chalk35.default.gray(tier.note ?? "");
|
|
26776
27339
|
} else if (tier.status === "active") {
|
|
26777
|
-
const loc = tier.path ?
|
|
26778
|
-
const note = tier.note ?
|
|
26779
|
-
statusStr =
|
|
27340
|
+
const loc = tier.path ? import_chalk35.default.gray(tier.path) : "";
|
|
27341
|
+
const note = tier.note ? import_chalk35.default.gray(`(${tier.note})`) : "";
|
|
27342
|
+
statusStr = import_chalk35.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
26780
27343
|
} else {
|
|
26781
|
-
statusStr =
|
|
27344
|
+
statusStr = import_chalk35.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
26782
27345
|
}
|
|
26783
|
-
console.log(`${num3} ${
|
|
27346
|
+
console.log(`${num3} ${import_chalk35.default.white(label2)} ${statusStr}`);
|
|
26784
27347
|
}
|
|
26785
27348
|
console.log("");
|
|
26786
|
-
console.log(
|
|
27349
|
+
console.log(import_chalk35.default.bold("Policy Evaluation:"));
|
|
26787
27350
|
for (const step of result.steps) {
|
|
26788
27351
|
const isFinal = step.isFinal;
|
|
26789
27352
|
let icon;
|
|
26790
|
-
if (step.outcome === "allow") icon =
|
|
26791
|
-
else if (step.outcome === "review") icon =
|
|
26792
|
-
else if (step.outcome === "skip") icon =
|
|
26793
|
-
else icon =
|
|
27353
|
+
if (step.outcome === "allow") icon = import_chalk35.default.green(" \u2705");
|
|
27354
|
+
else if (step.outcome === "review") icon = import_chalk35.default.red(" \u{1F534}");
|
|
27355
|
+
else if (step.outcome === "skip") icon = import_chalk35.default.gray(" \u2500 ");
|
|
27356
|
+
else icon = import_chalk35.default.gray(" \u25CB ");
|
|
26794
27357
|
const name = step.name.padEnd(18);
|
|
26795
|
-
const nameStr = isFinal ?
|
|
26796
|
-
const detail = isFinal ?
|
|
26797
|
-
const arrow = isFinal ?
|
|
27358
|
+
const nameStr = isFinal ? import_chalk35.default.white.bold(name) : import_chalk35.default.white(name);
|
|
27359
|
+
const detail = isFinal ? import_chalk35.default.white(step.detail) : import_chalk35.default.gray(step.detail);
|
|
27360
|
+
const arrow = isFinal ? import_chalk35.default.yellow(" \u2190 STOP") : "";
|
|
26798
27361
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
26799
27362
|
}
|
|
26800
27363
|
console.log("");
|
|
26801
27364
|
if (result.decision === "allow") {
|
|
26802
|
-
console.log(
|
|
27365
|
+
console.log(import_chalk35.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk35.default.gray(" \u2014 no approval needed"));
|
|
26803
27366
|
} else {
|
|
26804
27367
|
console.log(
|
|
26805
|
-
|
|
27368
|
+
import_chalk35.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk35.default.gray(" \u2014 human approval required")
|
|
26806
27369
|
);
|
|
26807
27370
|
if (result.blockedByLabel) {
|
|
26808
|
-
console.log(
|
|
27371
|
+
console.log(import_chalk35.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
26809
27372
|
}
|
|
26810
27373
|
}
|
|
26811
27374
|
console.log("");
|
|
@@ -26820,18 +27383,18 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
26820
27383
|
try {
|
|
26821
27384
|
await startTail2(options);
|
|
26822
27385
|
} catch (err2) {
|
|
26823
|
-
console.error(
|
|
27386
|
+
console.error(import_chalk35.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
26824
27387
|
process.exit(1);
|
|
26825
27388
|
}
|
|
26826
27389
|
});
|
|
26827
27390
|
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
26828
27391
|
try {
|
|
26829
|
-
const dashboardPath =
|
|
27392
|
+
const dashboardPath = import_path60.default.join(__dirname, "dashboard.mjs");
|
|
26830
27393
|
const dynamicImport = new Function("id", "return import(id)");
|
|
26831
27394
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
26832
27395
|
await mod.startMonitor();
|
|
26833
27396
|
} catch (err2) {
|
|
26834
|
-
console.error(
|
|
27397
|
+
console.error(import_chalk35.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
26835
27398
|
process.exit(1);
|
|
26836
27399
|
}
|
|
26837
27400
|
});
|
|
@@ -26864,14 +27427,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
26864
27427
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
26865
27428
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
26866
27429
|
if (subcommand === "debug") {
|
|
26867
|
-
const flagFile =
|
|
27430
|
+
const flagFile = import_path60.default.join(import_os54.default.homedir(), ".node9", "hud-debug");
|
|
26868
27431
|
if (state === "on") {
|
|
26869
|
-
|
|
26870
|
-
|
|
27432
|
+
import_fs62.default.mkdirSync(import_path60.default.dirname(flagFile), { recursive: true });
|
|
27433
|
+
import_fs62.default.writeFileSync(flagFile, "");
|
|
26871
27434
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
26872
27435
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
26873
27436
|
} else if (state === "off") {
|
|
26874
|
-
if (
|
|
27437
|
+
if (import_fs62.default.existsSync(flagFile)) import_fs62.default.unlinkSync(flagFile);
|
|
26875
27438
|
console.log("HUD debug logging disabled.");
|
|
26876
27439
|
} else {
|
|
26877
27440
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -26886,7 +27449,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
26886
27449
|
const ms = parseDuration(options.duration);
|
|
26887
27450
|
if (ms === null) {
|
|
26888
27451
|
console.error(
|
|
26889
|
-
|
|
27452
|
+
import_chalk35.default.red(`
|
|
26890
27453
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
26891
27454
|
`)
|
|
26892
27455
|
);
|
|
@@ -26894,20 +27457,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
26894
27457
|
}
|
|
26895
27458
|
pauseNode9(ms, options.duration);
|
|
26896
27459
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
26897
|
-
console.log(
|
|
27460
|
+
console.log(import_chalk35.default.yellow(`
|
|
26898
27461
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
26899
|
-
console.log(
|
|
26900
|
-
console.log(
|
|
27462
|
+
console.log(import_chalk35.default.gray(` All tool calls will be allowed without review.`));
|
|
27463
|
+
console.log(import_chalk35.default.gray(` Run "node9 resume" to re-enable early.
|
|
26901
27464
|
`));
|
|
26902
27465
|
});
|
|
26903
27466
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
26904
27467
|
const { paused } = checkPause();
|
|
26905
27468
|
if (!paused) {
|
|
26906
|
-
console.log(
|
|
27469
|
+
console.log(import_chalk35.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
26907
27470
|
return;
|
|
26908
27471
|
}
|
|
26909
27472
|
resumeNode9();
|
|
26910
|
-
console.log(
|
|
27473
|
+
console.log(import_chalk35.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
26911
27474
|
});
|
|
26912
27475
|
var HOOK_BASED_AGENTS = {
|
|
26913
27476
|
claude: "claude",
|
|
@@ -26923,15 +27486,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
26923
27486
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
26924
27487
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
26925
27488
|
console.error(
|
|
26926
|
-
|
|
27489
|
+
import_chalk35.default.yellow(`
|
|
26927
27490
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
26928
27491
|
);
|
|
26929
|
-
console.error(
|
|
27492
|
+
console.error(import_chalk35.default.white(`
|
|
26930
27493
|
"${target}" uses its own hook system. Use:`));
|
|
26931
27494
|
console.error(
|
|
26932
|
-
|
|
27495
|
+
import_chalk35.default.green(` node9 addto ${target} `) + import_chalk35.default.gray("# one-time setup")
|
|
26933
27496
|
);
|
|
26934
|
-
console.error(
|
|
27497
|
+
console.error(import_chalk35.default.green(` ${target} `) + import_chalk35.default.gray("# run normally"));
|
|
26935
27498
|
process.exit(1);
|
|
26936
27499
|
}
|
|
26937
27500
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -26948,7 +27511,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
26948
27511
|
}
|
|
26949
27512
|
);
|
|
26950
27513
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
26951
|
-
console.error(
|
|
27514
|
+
console.error(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
26952
27515
|
const daemonReady = await autoStartDaemonAndWait();
|
|
26953
27516
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
26954
27517
|
}
|
|
@@ -26961,12 +27524,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
26961
27524
|
}
|
|
26962
27525
|
if (!result.approved) {
|
|
26963
27526
|
console.error(
|
|
26964
|
-
|
|
27527
|
+
import_chalk35.default.red(`
|
|
26965
27528
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
26966
27529
|
);
|
|
26967
27530
|
process.exit(1);
|
|
26968
27531
|
}
|
|
26969
|
-
console.error(
|
|
27532
|
+
console.error(import_chalk35.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
26970
27533
|
await runProxy(fullCommand);
|
|
26971
27534
|
} else {
|
|
26972
27535
|
program.help();
|
|
@@ -26981,6 +27544,7 @@ registerAgentsCommand(program);
|
|
|
26981
27544
|
registerScanCommand(program);
|
|
26982
27545
|
registerPostureCommand(program);
|
|
26983
27546
|
registerEgressCommand(program);
|
|
27547
|
+
registerSandboxCommand(program, version);
|
|
26984
27548
|
registerSessionsCommand(program);
|
|
26985
27549
|
registerSessionTaintCommand(program);
|
|
26986
27550
|
registerDlpCommand(program);
|
|
@@ -26991,9 +27555,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
26991
27555
|
const isCheckHook = process.argv[2] === "check";
|
|
26992
27556
|
if (isCheckHook) {
|
|
26993
27557
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
26994
|
-
const logPath =
|
|
27558
|
+
const logPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "hook-debug.log");
|
|
26995
27559
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
26996
|
-
|
|
27560
|
+
import_fs62.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
26997
27561
|
`);
|
|
26998
27562
|
}
|
|
26999
27563
|
process.exit(0);
|