@node9/proxy 1.40.0 → 1.41.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/dist/cli.js +631 -451
- package/dist/cli.mjs +629 -449
- package/dist/dashboard.mjs +5 -0
- package/dist/index.js +17 -1
- package/dist/index.mjs +17 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -185,8 +185,8 @@ function sanitizeConfig(raw) {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
const lines = result.error.issues.map((issue) => {
|
|
188
|
-
const
|
|
189
|
-
return ` \u2022 ${
|
|
188
|
+
const path62 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
189
|
+
return ` \u2022 ${path62}: ${issue.message}`;
|
|
190
190
|
});
|
|
191
191
|
return {
|
|
192
192
|
sanitized,
|
|
@@ -271,6 +271,11 @@ var init_config_schema = __esm({
|
|
|
271
271
|
allowGlobalPause: z.boolean().optional(),
|
|
272
272
|
auditHashArgs: z.boolean().optional(),
|
|
273
273
|
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
274
|
+
// Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
|
|
275
|
+
// inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
|
|
276
|
+
// node9's own approver (terminal/native/cloud). Unset → smart default
|
|
277
|
+
// (ask for ask-capable agents unless a cloud approver is configured).
|
|
278
|
+
reviewChannel: z.enum(["ask", "approver"]).optional(),
|
|
274
279
|
cloudSyncIntervalHours: z.number().positive().optional(),
|
|
275
280
|
// Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
|
|
276
281
|
// to true; set false to fall back to local-only auditing.
|
|
@@ -1258,9 +1263,9 @@ function matchesPattern(text, patterns) {
|
|
|
1258
1263
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1259
1264
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1260
1265
|
}
|
|
1261
|
-
function getNestedValue(obj,
|
|
1266
|
+
function getNestedValue(obj, path62) {
|
|
1262
1267
|
if (!obj || typeof obj !== "object") return null;
|
|
1263
|
-
const segments =
|
|
1268
|
+
const segments = path62.split(".");
|
|
1264
1269
|
for (const seg of segments) {
|
|
1265
1270
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1266
1271
|
}
|
|
@@ -4178,6 +4183,7 @@ function getConfig(cwd) {
|
|
|
4178
4183
|
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
4179
4184
|
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
4180
4185
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
4186
|
+
if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
|
|
4181
4187
|
if (s.cloudSyncIntervalHours !== void 0)
|
|
4182
4188
|
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
4183
4189
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
@@ -6330,7 +6336,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
6330
6336
|
tool: toolName,
|
|
6331
6337
|
args,
|
|
6332
6338
|
ts: actTs,
|
|
6333
|
-
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
6339
|
+
status: result.review ? "review" : result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
6334
6340
|
label: result.blockedByLabel,
|
|
6335
6341
|
ruleHit: result.ruleHit,
|
|
6336
6342
|
observeWouldBlock: result.observeWouldBlock,
|
|
@@ -6693,6 +6699,16 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
6693
6699
|
taintWarning
|
|
6694
6700
|
);
|
|
6695
6701
|
}
|
|
6702
|
+
const cloudEnforcedForDefer = approvers.cloud && !!creds?.apiKey;
|
|
6703
|
+
if (options?.deferReview && !taintWarning && !cloudEnforcedForDefer) {
|
|
6704
|
+
return {
|
|
6705
|
+
approved: false,
|
|
6706
|
+
review: true,
|
|
6707
|
+
reason: explainableLabel || "Node9 flagged this action for review.",
|
|
6708
|
+
ruleDescription: policyRuleDescription,
|
|
6709
|
+
blockedByLabel: explainableLabel
|
|
6710
|
+
};
|
|
6711
|
+
}
|
|
6696
6712
|
let cloudRequestId = null;
|
|
6697
6713
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
6698
6714
|
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
@@ -7504,6 +7520,18 @@ function removeNode9McpServer(servers) {
|
|
|
7504
7520
|
function printDaemonTip() {
|
|
7505
7521
|
console.log(chalk.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups."));
|
|
7506
7522
|
}
|
|
7523
|
+
function printInlineAskNotice() {
|
|
7524
|
+
console.log(
|
|
7525
|
+
chalk.cyan(
|
|
7526
|
+
" \u{1F4AC} Review prompts appear inline in your agent (approve/deny in the chat) by default."
|
|
7527
|
+
)
|
|
7528
|
+
);
|
|
7529
|
+
console.log(
|
|
7530
|
+
chalk.gray(
|
|
7531
|
+
' Prefer node9\u2019s own approver? Set "reviewChannel": "approver" in config, or add --no-ask to the hook.\n (Inline prompts are auto-disabled when a cloud approver is configured.)'
|
|
7532
|
+
)
|
|
7533
|
+
);
|
|
7534
|
+
}
|
|
7507
7535
|
function fullPathCommand(subcommand) {
|
|
7508
7536
|
if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
|
|
7509
7537
|
const nodeExec = toForwardSlashes(process.execPath);
|
|
@@ -7828,6 +7856,7 @@ async function setupClaude() {
|
|
|
7828
7856
|
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
|
|
7829
7857
|
console.log(chalk.gray(" Restart Claude Code for changes to take effect."));
|
|
7830
7858
|
printDaemonTip();
|
|
7859
|
+
printInlineAskNotice();
|
|
7831
7860
|
}
|
|
7832
7861
|
}
|
|
7833
7862
|
async function setupGemini() {
|
|
@@ -8224,6 +8253,7 @@ async function setupCopilot() {
|
|
|
8224
8253
|
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting GitHub Copilot CLI!"));
|
|
8225
8254
|
console.log(chalk.gray(" Restart Copilot CLI for changes to take effect."));
|
|
8226
8255
|
printDaemonTip();
|
|
8256
|
+
printInlineAskNotice();
|
|
8227
8257
|
}
|
|
8228
8258
|
function teardownCopilot() {
|
|
8229
8259
|
const homeDir2 = os12.homedir();
|
|
@@ -15510,8 +15540,8 @@ function fileSignature(filePath) {
|
|
|
15510
15540
|
const fd = fs27.openSync(filePath, "r");
|
|
15511
15541
|
try {
|
|
15512
15542
|
const buf = Buffer.alloc(512);
|
|
15513
|
-
const
|
|
15514
|
-
const slice = buf.subarray(0,
|
|
15543
|
+
const read2 = fs27.readSync(fd, buf, 0, 512, 0);
|
|
15544
|
+
const slice = buf.subarray(0, read2);
|
|
15515
15545
|
const nl = slice.indexOf(10);
|
|
15516
15546
|
const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
|
|
15517
15547
|
return crypto5.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
|
|
@@ -15615,13 +15645,13 @@ async function shipOnce(deps = {}) {
|
|
|
15615
15645
|
const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
|
|
15616
15646
|
const buf = Buffer.alloc(toRead);
|
|
15617
15647
|
const fd = fs27.openSync(auditLogPath, "r");
|
|
15618
|
-
let
|
|
15648
|
+
let read2;
|
|
15619
15649
|
try {
|
|
15620
|
-
|
|
15650
|
+
read2 = fs27.readSync(fd, buf, 0, toRead, offset);
|
|
15621
15651
|
} finally {
|
|
15622
15652
|
fs27.closeSync(fd);
|
|
15623
15653
|
}
|
|
15624
|
-
const { rows, consumed } = buildWireRows(buf.subarray(0,
|
|
15654
|
+
const { rows, consumed } = buildWireRows(buf.subarray(0, read2));
|
|
15625
15655
|
if (consumed === 0) break;
|
|
15626
15656
|
for (let i = 0; i < rows.length; i += MAX_BATCH) {
|
|
15627
15657
|
const batch = rows.slice(i, i + MAX_BATCH);
|
|
@@ -17199,9 +17229,9 @@ __export(tail_exports, {
|
|
|
17199
17229
|
});
|
|
17200
17230
|
import http3 from "http";
|
|
17201
17231
|
import chalk34 from "chalk";
|
|
17202
|
-
import
|
|
17203
|
-
import
|
|
17204
|
-
import
|
|
17232
|
+
import fs61 from "fs";
|
|
17233
|
+
import os53 from "os";
|
|
17234
|
+
import path59 from "path";
|
|
17205
17235
|
import readline6 from "readline";
|
|
17206
17236
|
import { spawn as spawn8 } from "child_process";
|
|
17207
17237
|
function shortenPathSummary(s) {
|
|
@@ -17225,20 +17255,20 @@ function getModelContextLimit(model) {
|
|
|
17225
17255
|
return 2e5;
|
|
17226
17256
|
}
|
|
17227
17257
|
function readSessionUsage() {
|
|
17228
|
-
const projectsDir =
|
|
17229
|
-
if (!
|
|
17258
|
+
const projectsDir = path59.join(os53.homedir(), ".claude", "projects");
|
|
17259
|
+
if (!fs61.existsSync(projectsDir)) return null;
|
|
17230
17260
|
let latestFile = null;
|
|
17231
17261
|
let latestMtime = 0;
|
|
17232
17262
|
try {
|
|
17233
|
-
for (const dir of
|
|
17234
|
-
const dirPath =
|
|
17263
|
+
for (const dir of fs61.readdirSync(projectsDir)) {
|
|
17264
|
+
const dirPath = path59.join(projectsDir, dir);
|
|
17235
17265
|
try {
|
|
17236
|
-
if (!
|
|
17237
|
-
for (const file of
|
|
17266
|
+
if (!fs61.statSync(dirPath).isDirectory()) continue;
|
|
17267
|
+
for (const file of fs61.readdirSync(dirPath)) {
|
|
17238
17268
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
17239
|
-
const filePath =
|
|
17269
|
+
const filePath = path59.join(dirPath, file);
|
|
17240
17270
|
try {
|
|
17241
|
-
const mtime =
|
|
17271
|
+
const mtime = fs61.statSync(filePath).mtimeMs;
|
|
17242
17272
|
if (mtime > latestMtime) {
|
|
17243
17273
|
latestMtime = mtime;
|
|
17244
17274
|
latestFile = filePath;
|
|
@@ -17253,7 +17283,7 @@ function readSessionUsage() {
|
|
|
17253
17283
|
}
|
|
17254
17284
|
if (!latestFile) return null;
|
|
17255
17285
|
try {
|
|
17256
|
-
const lines =
|
|
17286
|
+
const lines = fs61.readFileSync(latestFile, "utf-8").split("\n");
|
|
17257
17287
|
let lastModel = "";
|
|
17258
17288
|
let lastInput = 0;
|
|
17259
17289
|
let lastOutput = 0;
|
|
@@ -17314,7 +17344,7 @@ function formatBase(activity) {
|
|
|
17314
17344
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
17315
17345
|
const icon = getIcon(activity.tool);
|
|
17316
17346
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
17317
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
17347
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os53.homedir(), "~");
|
|
17318
17348
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
17319
17349
|
return `${chalk34.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk34.white.bold(toolName)} ${chalk34.dim(argsPreview)}`;
|
|
17320
17350
|
}
|
|
@@ -17353,9 +17383,9 @@ function renderPending(activity) {
|
|
|
17353
17383
|
}
|
|
17354
17384
|
async function ensureDaemon() {
|
|
17355
17385
|
let pidPort = null;
|
|
17356
|
-
if (
|
|
17386
|
+
if (fs61.existsSync(PID_FILE)) {
|
|
17357
17387
|
try {
|
|
17358
|
-
const { port } = JSON.parse(
|
|
17388
|
+
const { port } = JSON.parse(fs61.readFileSync(PID_FILE, "utf-8"));
|
|
17359
17389
|
pidPort = port;
|
|
17360
17390
|
} catch {
|
|
17361
17391
|
console.error(chalk34.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -17511,9 +17541,9 @@ function buildRecoveryCardLines(req) {
|
|
|
17511
17541
|
];
|
|
17512
17542
|
}
|
|
17513
17543
|
function readApproversFromDisk() {
|
|
17514
|
-
const configPath2 =
|
|
17544
|
+
const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
|
|
17515
17545
|
try {
|
|
17516
|
-
const raw = JSON.parse(
|
|
17546
|
+
const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
|
|
17517
17547
|
const settings = raw.settings ?? {};
|
|
17518
17548
|
return settings.approvers ?? {};
|
|
17519
17549
|
} catch {
|
|
@@ -17529,15 +17559,15 @@ function approverStatusLine() {
|
|
|
17529
17559
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
17530
17560
|
}
|
|
17531
17561
|
function toggleApprover(channel) {
|
|
17532
|
-
const configPath2 =
|
|
17562
|
+
const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
|
|
17533
17563
|
try {
|
|
17534
|
-
const raw = JSON.parse(
|
|
17564
|
+
const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
|
|
17535
17565
|
const settings = raw.settings ?? {};
|
|
17536
17566
|
const approvers = settings.approvers ?? {};
|
|
17537
17567
|
approvers[channel] = approvers[channel] === false;
|
|
17538
17568
|
settings.approvers = approvers;
|
|
17539
17569
|
raw.settings = settings;
|
|
17540
|
-
|
|
17570
|
+
fs61.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
|
|
17541
17571
|
} catch (err2) {
|
|
17542
17572
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
17543
17573
|
`);
|
|
@@ -17709,8 +17739,8 @@ async function startTail(options = {}) {
|
|
|
17709
17739
|
}
|
|
17710
17740
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
17711
17741
|
try {
|
|
17712
|
-
|
|
17713
|
-
|
|
17742
|
+
fs61.appendFileSync(
|
|
17743
|
+
path59.join(os53.homedir(), ".node9", "hook-debug.log"),
|
|
17714
17744
|
`[tail] POST /decision failed: ${String(err2)}
|
|
17715
17745
|
`
|
|
17716
17746
|
);
|
|
@@ -17774,9 +17804,9 @@ async function startTail(options = {}) {
|
|
|
17774
17804
|
};
|
|
17775
17805
|
process.stdin.on("keypress", onKeypress);
|
|
17776
17806
|
}
|
|
17777
|
-
const auditLog =
|
|
17807
|
+
const auditLog = path59.join(os53.homedir(), ".node9", "audit.log");
|
|
17778
17808
|
try {
|
|
17779
|
-
const unackedDlp =
|
|
17809
|
+
const unackedDlp = fs61.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
17780
17810
|
if (unackedDlp > 0) {
|
|
17781
17811
|
console.log("");
|
|
17782
17812
|
console.log(
|
|
@@ -17816,7 +17846,7 @@ async function startTail(options = {}) {
|
|
|
17816
17846
|
if (stallWarned) return;
|
|
17817
17847
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
17818
17848
|
try {
|
|
17819
|
-
const auditMtime =
|
|
17849
|
+
const auditMtime = fs61.statSync(auditLog).mtimeMs;
|
|
17820
17850
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
17821
17851
|
console.log("");
|
|
17822
17852
|
console.log(
|
|
@@ -18007,7 +18037,7 @@ var init_tail = __esm({
|
|
|
18007
18037
|
"use strict";
|
|
18008
18038
|
init_daemon2();
|
|
18009
18039
|
init_daemon();
|
|
18010
|
-
PID_FILE =
|
|
18040
|
+
PID_FILE = path59.join(os53.homedir(), ".node9", "daemon.pid");
|
|
18011
18041
|
ICONS = {
|
|
18012
18042
|
bash: "\u{1F4BB}",
|
|
18013
18043
|
shell: "\u{1F4BB}",
|
|
@@ -18055,9 +18085,9 @@ __export(hud_exports, {
|
|
|
18055
18085
|
main: () => main,
|
|
18056
18086
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
18057
18087
|
});
|
|
18058
|
-
import
|
|
18059
|
-
import
|
|
18060
|
-
import
|
|
18088
|
+
import fs62 from "fs";
|
|
18089
|
+
import path60 from "path";
|
|
18090
|
+
import os54 from "os";
|
|
18061
18091
|
import http4 from "http";
|
|
18062
18092
|
async function readStdin() {
|
|
18063
18093
|
const chunks = [];
|
|
@@ -18133,9 +18163,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
18133
18163
|
return ` (${m}m left)`;
|
|
18134
18164
|
}
|
|
18135
18165
|
function safeReadJson(filePath) {
|
|
18136
|
-
if (!
|
|
18166
|
+
if (!fs62.existsSync(filePath)) return null;
|
|
18137
18167
|
try {
|
|
18138
|
-
return JSON.parse(
|
|
18168
|
+
return JSON.parse(fs62.readFileSync(filePath, "utf-8"));
|
|
18139
18169
|
} catch {
|
|
18140
18170
|
return null;
|
|
18141
18171
|
}
|
|
@@ -18156,12 +18186,12 @@ function countHooksInFile(filePath) {
|
|
|
18156
18186
|
return Object.keys(cfg.hooks).length;
|
|
18157
18187
|
}
|
|
18158
18188
|
function countRulesInDir(rulesDir) {
|
|
18159
|
-
if (!
|
|
18189
|
+
if (!fs62.existsSync(rulesDir)) return 0;
|
|
18160
18190
|
let count = 0;
|
|
18161
18191
|
try {
|
|
18162
|
-
for (const entry of
|
|
18192
|
+
for (const entry of fs62.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
18163
18193
|
if (entry.isDirectory()) {
|
|
18164
|
-
count += countRulesInDir(
|
|
18194
|
+
count += countRulesInDir(path60.join(rulesDir, entry.name));
|
|
18165
18195
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
18166
18196
|
count++;
|
|
18167
18197
|
}
|
|
@@ -18172,46 +18202,46 @@ function countRulesInDir(rulesDir) {
|
|
|
18172
18202
|
}
|
|
18173
18203
|
function isSamePath(a, b) {
|
|
18174
18204
|
try {
|
|
18175
|
-
return
|
|
18205
|
+
return path60.resolve(a) === path60.resolve(b);
|
|
18176
18206
|
} catch {
|
|
18177
18207
|
return false;
|
|
18178
18208
|
}
|
|
18179
18209
|
}
|
|
18180
18210
|
function countConfigs(cwd) {
|
|
18181
|
-
const homeDir2 =
|
|
18182
|
-
const claudeDir =
|
|
18211
|
+
const homeDir2 = os54.homedir();
|
|
18212
|
+
const claudeDir = path60.join(homeDir2, ".claude");
|
|
18183
18213
|
let claudeMdCount = 0;
|
|
18184
18214
|
let rulesCount = 0;
|
|
18185
18215
|
let hooksCount = 0;
|
|
18186
18216
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
18187
18217
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
18188
|
-
if (
|
|
18189
|
-
rulesCount += countRulesInDir(
|
|
18190
|
-
const userSettings =
|
|
18218
|
+
if (fs62.existsSync(path60.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
18219
|
+
rulesCount += countRulesInDir(path60.join(claudeDir, "rules"));
|
|
18220
|
+
const userSettings = path60.join(claudeDir, "settings.json");
|
|
18191
18221
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
18192
18222
|
hooksCount += countHooksInFile(userSettings);
|
|
18193
|
-
const userClaudeJson =
|
|
18223
|
+
const userClaudeJson = path60.join(homeDir2, ".claude.json");
|
|
18194
18224
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
18195
18225
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
18196
18226
|
userMcpServers.delete(name);
|
|
18197
18227
|
}
|
|
18198
18228
|
if (cwd) {
|
|
18199
|
-
if (
|
|
18200
|
-
if (
|
|
18201
|
-
const projectClaudeDir =
|
|
18229
|
+
if (fs62.existsSync(path60.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
18230
|
+
if (fs62.existsSync(path60.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
18231
|
+
const projectClaudeDir = path60.join(cwd, ".claude");
|
|
18202
18232
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
18203
18233
|
if (!overlapsUserScope) {
|
|
18204
|
-
if (
|
|
18205
|
-
rulesCount += countRulesInDir(
|
|
18206
|
-
const projSettings =
|
|
18234
|
+
if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
18235
|
+
rulesCount += countRulesInDir(path60.join(projectClaudeDir, "rules"));
|
|
18236
|
+
const projSettings = path60.join(projectClaudeDir, "settings.json");
|
|
18207
18237
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
18208
18238
|
hooksCount += countHooksInFile(projSettings);
|
|
18209
18239
|
}
|
|
18210
|
-
if (
|
|
18211
|
-
const localSettings =
|
|
18240
|
+
if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
18241
|
+
const localSettings = path60.join(projectClaudeDir, "settings.local.json");
|
|
18212
18242
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
18213
18243
|
hooksCount += countHooksInFile(localSettings);
|
|
18214
|
-
const mcpJsonServers = getMcpServerNames(
|
|
18244
|
+
const mcpJsonServers = getMcpServerNames(path60.join(cwd, ".mcp.json"));
|
|
18215
18245
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
18216
18246
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
18217
18247
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -18244,12 +18274,12 @@ function readActiveShieldsHud() {
|
|
|
18244
18274
|
return shieldsCache.value;
|
|
18245
18275
|
}
|
|
18246
18276
|
try {
|
|
18247
|
-
const shieldsPath =
|
|
18248
|
-
if (!
|
|
18277
|
+
const shieldsPath = path60.join(os54.homedir(), ".node9", "shields.json");
|
|
18278
|
+
if (!fs62.existsSync(shieldsPath)) {
|
|
18249
18279
|
shieldsCache = { value: [], ts: now };
|
|
18250
18280
|
return [];
|
|
18251
18281
|
}
|
|
18252
|
-
const parsed = JSON.parse(
|
|
18282
|
+
const parsed = JSON.parse(fs62.readFileSync(shieldsPath, "utf-8"));
|
|
18253
18283
|
if (!Array.isArray(parsed.active)) {
|
|
18254
18284
|
shieldsCache = { value: [], ts: now };
|
|
18255
18285
|
return [];
|
|
@@ -18351,17 +18381,17 @@ function renderContextLine(stdin) {
|
|
|
18351
18381
|
async function main() {
|
|
18352
18382
|
try {
|
|
18353
18383
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
18354
|
-
if (
|
|
18384
|
+
if (fs62.existsSync(path60.join(os54.homedir(), ".node9", "hud-debug"))) {
|
|
18355
18385
|
try {
|
|
18356
|
-
const logPath =
|
|
18386
|
+
const logPath = path60.join(os54.homedir(), ".node9", "hud-debug.log");
|
|
18357
18387
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18358
18388
|
let size = 0;
|
|
18359
18389
|
try {
|
|
18360
|
-
size =
|
|
18390
|
+
size = fs62.statSync(logPath).size;
|
|
18361
18391
|
} catch {
|
|
18362
18392
|
}
|
|
18363
18393
|
if (size < MAX_LOG_SIZE) {
|
|
18364
|
-
|
|
18394
|
+
fs62.appendFileSync(
|
|
18365
18395
|
logPath,
|
|
18366
18396
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
18367
18397
|
);
|
|
@@ -18382,11 +18412,11 @@ async function main() {
|
|
|
18382
18412
|
try {
|
|
18383
18413
|
const cwd = stdin.cwd ?? process.cwd();
|
|
18384
18414
|
for (const configPath2 of [
|
|
18385
|
-
|
|
18386
|
-
|
|
18415
|
+
path60.join(cwd, "node9.config.json"),
|
|
18416
|
+
path60.join(os54.homedir(), ".node9", "config.json")
|
|
18387
18417
|
]) {
|
|
18388
|
-
if (!
|
|
18389
|
-
const cfg = JSON.parse(
|
|
18418
|
+
if (!fs62.existsSync(configPath2)) continue;
|
|
18419
|
+
const cfg = JSON.parse(fs62.readFileSync(configPath2, "utf-8"));
|
|
18390
18420
|
const hud = cfg.settings?.hud;
|
|
18391
18421
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
18392
18422
|
}
|
|
@@ -18433,9 +18463,9 @@ init_setup();
|
|
|
18433
18463
|
init_daemon2();
|
|
18434
18464
|
import { Command } from "commander";
|
|
18435
18465
|
import chalk35 from "chalk";
|
|
18436
|
-
import
|
|
18437
|
-
import
|
|
18438
|
-
import
|
|
18466
|
+
import fs63 from "fs";
|
|
18467
|
+
import path61 from "path";
|
|
18468
|
+
import os55 from "os";
|
|
18439
18469
|
import { spawn as spawn9 } from "child_process";
|
|
18440
18470
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
18441
18471
|
|
|
@@ -18530,6 +18560,10 @@ INSTRUCTIONS:
|
|
|
18530
18560
|
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
18531
18561
|
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
18532
18562
|
}
|
|
18563
|
+
function buildReviewMessage(blockedByLabel, ruleDescription) {
|
|
18564
|
+
const why = ruleDescription || blockedByLabel || "this action needs your review";
|
|
18565
|
+
return `Node9 flagged this for your review: ${why}. Approve to proceed, or deny to cancel.`;
|
|
18566
|
+
}
|
|
18533
18567
|
|
|
18534
18568
|
// src/proxy/index.ts
|
|
18535
18569
|
function sanitize(value) {
|
|
@@ -18660,10 +18694,10 @@ init_daemon();
|
|
|
18660
18694
|
init_config();
|
|
18661
18695
|
init_policy();
|
|
18662
18696
|
import chalk9 from "chalk";
|
|
18663
|
-
import
|
|
18697
|
+
import fs37 from "fs";
|
|
18664
18698
|
import { spawn as spawn5 } from "child_process";
|
|
18665
|
-
import
|
|
18666
|
-
import
|
|
18699
|
+
import path38 from "path";
|
|
18700
|
+
import os33 from "os";
|
|
18667
18701
|
|
|
18668
18702
|
// src/undo.ts
|
|
18669
18703
|
import { spawnSync as spawnSync3, spawn as spawn4 } from "child_process";
|
|
@@ -19201,6 +19235,78 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
19201
19235
|
// src/cli/commands/check.ts
|
|
19202
19236
|
init_dlp();
|
|
19203
19237
|
init_audit();
|
|
19238
|
+
|
|
19239
|
+
// src/review-pending.ts
|
|
19240
|
+
init_hasher();
|
|
19241
|
+
import fs36 from "fs";
|
|
19242
|
+
import os32 from "os";
|
|
19243
|
+
import path37 from "path";
|
|
19244
|
+
function storePath() {
|
|
19245
|
+
return process.env.NODE9_PENDING_STORE || path37.join(os32.homedir(), ".node9", "pending-reviews.json");
|
|
19246
|
+
}
|
|
19247
|
+
var TTL_MS2 = 6 * 60 * 60 * 1e3;
|
|
19248
|
+
var MAX_ENTRIES = 500;
|
|
19249
|
+
function reviewCorrelationKey(payload) {
|
|
19250
|
+
if (typeof payload.tool_use_id === "string" && payload.tool_use_id) {
|
|
19251
|
+
return `tuid:${payload.tool_use_id}`;
|
|
19252
|
+
}
|
|
19253
|
+
const sid = payload.session_id ?? payload.conversationId;
|
|
19254
|
+
const tool = payload.tool_name;
|
|
19255
|
+
if (typeof sid === "string" && sid && typeof tool === "string" && tool) {
|
|
19256
|
+
return `h:${sid}|${tool}|${hashArgs(payload.tool_input)}`;
|
|
19257
|
+
}
|
|
19258
|
+
return null;
|
|
19259
|
+
}
|
|
19260
|
+
function read() {
|
|
19261
|
+
try {
|
|
19262
|
+
const parsed = JSON.parse(fs36.readFileSync(storePath(), "utf-8"));
|
|
19263
|
+
if (parsed && Array.isArray(parsed.entries)) return parsed;
|
|
19264
|
+
} catch {
|
|
19265
|
+
}
|
|
19266
|
+
return { entries: [] };
|
|
19267
|
+
}
|
|
19268
|
+
function write(store) {
|
|
19269
|
+
try {
|
|
19270
|
+
const p = storePath();
|
|
19271
|
+
const dir = path37.dirname(p);
|
|
19272
|
+
if (!fs36.existsSync(dir)) fs36.mkdirSync(dir, { recursive: true });
|
|
19273
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
19274
|
+
fs36.writeFileSync(tmp, JSON.stringify(store));
|
|
19275
|
+
fs36.renameSync(tmp, p);
|
|
19276
|
+
} catch {
|
|
19277
|
+
}
|
|
19278
|
+
}
|
|
19279
|
+
function prune(entries, now) {
|
|
19280
|
+
const fresh = entries.filter((e) => now - e.ts < TTL_MS2);
|
|
19281
|
+
return fresh.length > MAX_ENTRIES ? fresh.slice(fresh.length - MAX_ENTRIES) : fresh;
|
|
19282
|
+
}
|
|
19283
|
+
function recordPendingReview(entry) {
|
|
19284
|
+
try {
|
|
19285
|
+
const store = read();
|
|
19286
|
+
store.entries = prune(store.entries, entry.ts);
|
|
19287
|
+
store.entries.push(entry);
|
|
19288
|
+
write(store);
|
|
19289
|
+
} catch {
|
|
19290
|
+
}
|
|
19291
|
+
}
|
|
19292
|
+
function resolvePendingReview(key, now = Date.now()) {
|
|
19293
|
+
try {
|
|
19294
|
+
const store = read();
|
|
19295
|
+
const idx = store.entries.findIndex((e) => e.key === key);
|
|
19296
|
+
if (idx === -1) {
|
|
19297
|
+
const pruned = prune(store.entries, now);
|
|
19298
|
+
if (pruned.length !== store.entries.length) write({ entries: pruned });
|
|
19299
|
+
return null;
|
|
19300
|
+
}
|
|
19301
|
+
const [match] = store.entries.splice(idx, 1);
|
|
19302
|
+
write({ entries: prune(store.entries, now) });
|
|
19303
|
+
return match;
|
|
19304
|
+
} catch {
|
|
19305
|
+
return null;
|
|
19306
|
+
}
|
|
19307
|
+
}
|
|
19308
|
+
|
|
19309
|
+
// src/cli/commands/check.ts
|
|
19204
19310
|
init_hook_payload();
|
|
19205
19311
|
function sanitize2(value) {
|
|
19206
19312
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
@@ -19249,11 +19355,26 @@ function detectAiAgent(payload) {
|
|
|
19249
19355
|
}
|
|
19250
19356
|
return "Terminal";
|
|
19251
19357
|
}
|
|
19358
|
+
function agentSupportsAsk(agent) {
|
|
19359
|
+
return agent === "Claude Code" || agent === "GitHub Copilot";
|
|
19360
|
+
}
|
|
19361
|
+
function resolveAskMode(agent, opts, config) {
|
|
19362
|
+
if (!agentSupportsAsk(agent)) return false;
|
|
19363
|
+
if (config.settings.approvers.cloud === true) return false;
|
|
19364
|
+
if (opts.ask === true) return true;
|
|
19365
|
+
if (opts.ask === false) return false;
|
|
19366
|
+
if (config.settings.reviewChannel === "ask") return true;
|
|
19367
|
+
if (config.settings.reviewChannel === "approver") return false;
|
|
19368
|
+
return true;
|
|
19369
|
+
}
|
|
19252
19370
|
function registerCheckCommand(program2) {
|
|
19253
19371
|
program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").option(
|
|
19254
19372
|
"--agent <name>",
|
|
19255
19373
|
"Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
|
|
19256
|
-
).
|
|
19374
|
+
).option(
|
|
19375
|
+
"--ask",
|
|
19376
|
+
"Route review verdicts to the agent\u2019s native inline approve/deny prompt (Claude Code / GitHub Copilot only)"
|
|
19377
|
+
).option("--no-ask", "Force node9\u2019s own approver for review verdicts (override default-on)").action(async (data, opts) => {
|
|
19257
19378
|
const agentOverride = agentLabelFromFlag(opts?.agent);
|
|
19258
19379
|
const processPayload = async (raw) => {
|
|
19259
19380
|
try {
|
|
@@ -19264,9 +19385,9 @@ function registerCheckCommand(program2) {
|
|
|
19264
19385
|
} catch (err2) {
|
|
19265
19386
|
const tempConfig = getConfig();
|
|
19266
19387
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
19267
|
-
const logPath =
|
|
19388
|
+
const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19268
19389
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
19269
|
-
|
|
19390
|
+
fs37.appendFileSync(
|
|
19270
19391
|
logPath,
|
|
19271
19392
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
19272
19393
|
RAW: ${raw}
|
|
@@ -19279,14 +19400,14 @@ RAW: ${raw}
|
|
|
19279
19400
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
19280
19401
|
if (process.env.NODE9_DEBUG === "1") {
|
|
19281
19402
|
try {
|
|
19282
|
-
const logPath =
|
|
19283
|
-
if (!
|
|
19284
|
-
|
|
19403
|
+
const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19404
|
+
if (!fs37.existsSync(path38.dirname(logPath)))
|
|
19405
|
+
fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
|
|
19285
19406
|
const sanitized = JSON.stringify({
|
|
19286
19407
|
...payload,
|
|
19287
19408
|
prompt: `<redacted, ${prompt.length} bytes>`
|
|
19288
19409
|
});
|
|
19289
|
-
|
|
19410
|
+
fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
|
|
19290
19411
|
`);
|
|
19291
19412
|
} catch {
|
|
19292
19413
|
}
|
|
@@ -19306,8 +19427,8 @@ RAW: ${raw}
|
|
|
19306
19427
|
);
|
|
19307
19428
|
const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
|
|
19308
19429
|
try {
|
|
19309
|
-
const ttyFd =
|
|
19310
|
-
|
|
19430
|
+
const ttyFd = fs37.openSync("/dev/tty", "w");
|
|
19431
|
+
fs37.writeSync(
|
|
19311
19432
|
ttyFd,
|
|
19312
19433
|
chalk9.bgRed.white.bold(`
|
|
19313
19434
|
\u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
|
|
@@ -19317,7 +19438,7 @@ RAW: ${raw}
|
|
|
19317
19438
|
|
|
19318
19439
|
`)
|
|
19319
19440
|
);
|
|
19320
|
-
|
|
19441
|
+
fs37.closeSync(ttyFd);
|
|
19321
19442
|
} catch {
|
|
19322
19443
|
}
|
|
19323
19444
|
const isCodex = agent2 === "Codex";
|
|
@@ -19336,16 +19457,16 @@ RAW: ${raw}
|
|
|
19336
19457
|
process.exit(2);
|
|
19337
19458
|
}
|
|
19338
19459
|
const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
|
|
19339
|
-
const safeCwdForConfig = typeof payloadCwd === "string" &&
|
|
19460
|
+
const safeCwdForConfig = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
19340
19461
|
const config = getConfig(safeCwdForConfig);
|
|
19341
19462
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
19342
19463
|
try {
|
|
19343
19464
|
const scriptPath = process.argv[1];
|
|
19344
|
-
if (typeof scriptPath !== "string" || !
|
|
19465
|
+
if (typeof scriptPath !== "string" || !path38.isAbsolute(scriptPath))
|
|
19345
19466
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
19346
|
-
const resolvedScript =
|
|
19347
|
-
const packageDist =
|
|
19348
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
19467
|
+
const resolvedScript = fs37.realpathSync(scriptPath);
|
|
19468
|
+
const packageDist = fs37.realpathSync(path38.resolve(__dirname, "../.."));
|
|
19469
|
+
if (!resolvedScript.startsWith(packageDist + path38.sep) && resolvedScript !== packageDist)
|
|
19349
19470
|
throw new Error(
|
|
19350
19471
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
19351
19472
|
);
|
|
@@ -19367,10 +19488,10 @@ RAW: ${raw}
|
|
|
19367
19488
|
});
|
|
19368
19489
|
d.unref();
|
|
19369
19490
|
} catch (spawnErr) {
|
|
19370
|
-
const logPath =
|
|
19491
|
+
const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19371
19492
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
19372
19493
|
try {
|
|
19373
|
-
|
|
19494
|
+
fs37.appendFileSync(
|
|
19374
19495
|
logPath,
|
|
19375
19496
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
19376
19497
|
`
|
|
@@ -19380,10 +19501,10 @@ RAW: ${raw}
|
|
|
19380
19501
|
}
|
|
19381
19502
|
}
|
|
19382
19503
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
19383
|
-
const logPath =
|
|
19384
|
-
if (!
|
|
19385
|
-
|
|
19386
|
-
|
|
19504
|
+
const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19505
|
+
if (!fs37.existsSync(path38.dirname(logPath)))
|
|
19506
|
+
fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
|
|
19507
|
+
fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
19387
19508
|
`);
|
|
19388
19509
|
}
|
|
19389
19510
|
const rawToolName = sanitize2(extractToolName(payload));
|
|
@@ -19397,8 +19518,8 @@ RAW: ${raw}
|
|
|
19397
19518
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
19398
19519
|
let ttyFd = null;
|
|
19399
19520
|
try {
|
|
19400
|
-
ttyFd =
|
|
19401
|
-
const writeTty = (line) =>
|
|
19521
|
+
ttyFd = fs37.openSync("/dev/tty", "w");
|
|
19522
|
+
const writeTty = (line) => fs37.writeSync(ttyFd, line + "\n");
|
|
19402
19523
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
19403
19524
|
writeTty(chalk9.bgRed.white.bold(`
|
|
19404
19525
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -19417,7 +19538,7 @@ RAW: ${raw}
|
|
|
19417
19538
|
} finally {
|
|
19418
19539
|
if (ttyFd !== null)
|
|
19419
19540
|
try {
|
|
19420
|
-
|
|
19541
|
+
fs37.closeSync(ttyFd);
|
|
19421
19542
|
} catch {
|
|
19422
19543
|
}
|
|
19423
19544
|
}
|
|
@@ -19456,6 +19577,53 @@ RAW: ${raw}
|
|
|
19456
19577
|
);
|
|
19457
19578
|
process.exit(2);
|
|
19458
19579
|
};
|
|
19580
|
+
const sendAsk = (result2) => {
|
|
19581
|
+
const msg = buildReviewMessage(result2.blockedByLabel, result2.ruleDescription);
|
|
19582
|
+
try {
|
|
19583
|
+
const key = reviewCorrelationKey(payload);
|
|
19584
|
+
if (key) {
|
|
19585
|
+
const sid = typeof payload.session_id === "string" ? payload.session_id : typeof payload.conversationId === "string" ? payload.conversationId : void 0;
|
|
19586
|
+
recordPendingReview({
|
|
19587
|
+
key,
|
|
19588
|
+
agent,
|
|
19589
|
+
tool: toolName,
|
|
19590
|
+
sessionId: sid,
|
|
19591
|
+
ts: Date.now(),
|
|
19592
|
+
label: result2.blockedByLabel
|
|
19593
|
+
});
|
|
19594
|
+
}
|
|
19595
|
+
} catch {
|
|
19596
|
+
}
|
|
19597
|
+
try {
|
|
19598
|
+
const ttyFd = fs37.openSync("/dev/tty", "w");
|
|
19599
|
+
fs37.writeSync(
|
|
19600
|
+
ttyFd,
|
|
19601
|
+
chalk9.yellow(
|
|
19602
|
+
`
|
|
19603
|
+
\u26A0\uFE0F Node9: review requested for "${toolName}" \u2014 answer in the prompt.
|
|
19604
|
+
`
|
|
19605
|
+
)
|
|
19606
|
+
);
|
|
19607
|
+
fs37.closeSync(ttyFd);
|
|
19608
|
+
} catch {
|
|
19609
|
+
}
|
|
19610
|
+
if (agent === "GitHub Copilot") {
|
|
19611
|
+
process.stdout.write(
|
|
19612
|
+
JSON.stringify({ permissionDecision: "ask", permissionDecisionReason: msg }) + "\n"
|
|
19613
|
+
);
|
|
19614
|
+
} else {
|
|
19615
|
+
process.stdout.write(
|
|
19616
|
+
JSON.stringify({
|
|
19617
|
+
hookSpecificOutput: {
|
|
19618
|
+
hookEventName: "PreToolUse",
|
|
19619
|
+
permissionDecision: "ask",
|
|
19620
|
+
permissionDecisionReason: msg
|
|
19621
|
+
}
|
|
19622
|
+
}) + "\n"
|
|
19623
|
+
);
|
|
19624
|
+
}
|
|
19625
|
+
process.exit(0);
|
|
19626
|
+
};
|
|
19459
19627
|
if (!toolName) {
|
|
19460
19628
|
sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
|
|
19461
19629
|
return;
|
|
@@ -19468,17 +19636,17 @@ RAW: ${raw}
|
|
|
19468
19636
|
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
19469
19637
|
if (skillPinCfg.enabled && safeSessionId) {
|
|
19470
19638
|
try {
|
|
19471
|
-
const sessionsDir =
|
|
19472
|
-
const flagPath =
|
|
19639
|
+
const sessionsDir = path38.join(os33.homedir(), ".node9", "skill-sessions");
|
|
19640
|
+
const flagPath = path38.join(sessionsDir, `${safeSessionId}.json`);
|
|
19473
19641
|
let flag = null;
|
|
19474
19642
|
try {
|
|
19475
|
-
flag = JSON.parse(
|
|
19643
|
+
flag = JSON.parse(fs37.readFileSync(flagPath, "utf-8"));
|
|
19476
19644
|
} catch {
|
|
19477
19645
|
}
|
|
19478
19646
|
const writeFlag = (data2) => {
|
|
19479
19647
|
try {
|
|
19480
|
-
|
|
19481
|
-
|
|
19648
|
+
fs37.mkdirSync(sessionsDir, { recursive: true });
|
|
19649
|
+
fs37.writeFileSync(
|
|
19482
19650
|
flagPath,
|
|
19483
19651
|
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
19484
19652
|
{ mode: 384 }
|
|
@@ -19489,8 +19657,8 @@ RAW: ${raw}
|
|
|
19489
19657
|
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
19490
19658
|
let ttyFd = null;
|
|
19491
19659
|
try {
|
|
19492
|
-
ttyFd =
|
|
19493
|
-
const w = (line) =>
|
|
19660
|
+
ttyFd = fs37.openSync("/dev/tty", "w");
|
|
19661
|
+
const w = (line) => fs37.writeSync(ttyFd, line + "\n");
|
|
19494
19662
|
w(chalk9.yellow(`
|
|
19495
19663
|
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
19496
19664
|
w(chalk9.gray(` ${detail}`));
|
|
@@ -19505,7 +19673,7 @@ RAW: ${raw}
|
|
|
19505
19673
|
} finally {
|
|
19506
19674
|
if (ttyFd !== null)
|
|
19507
19675
|
try {
|
|
19508
|
-
|
|
19676
|
+
fs37.closeSync(ttyFd);
|
|
19509
19677
|
} catch {
|
|
19510
19678
|
}
|
|
19511
19679
|
}
|
|
@@ -19521,7 +19689,7 @@ RAW: ${raw}
|
|
|
19521
19689
|
return;
|
|
19522
19690
|
}
|
|
19523
19691
|
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
19524
|
-
const absoluteCwd = typeof payloadCwd === "string" &&
|
|
19692
|
+
const absoluteCwd = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
19525
19693
|
const extraRoots = skillPinCfg.roots;
|
|
19526
19694
|
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
19527
19695
|
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
@@ -19562,10 +19730,10 @@ RAW: ${raw}
|
|
|
19562
19730
|
}
|
|
19563
19731
|
try {
|
|
19564
19732
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
19565
|
-
for (const name of
|
|
19566
|
-
const p =
|
|
19733
|
+
for (const name of fs37.readdirSync(sessionsDir)) {
|
|
19734
|
+
const p = path38.join(sessionsDir, name);
|
|
19567
19735
|
try {
|
|
19568
|
-
if (
|
|
19736
|
+
if (fs37.statSync(p).mtimeMs < cutoff) fs37.unlinkSync(p);
|
|
19569
19737
|
} catch {
|
|
19570
19738
|
}
|
|
19571
19739
|
}
|
|
@@ -19575,9 +19743,9 @@ RAW: ${raw}
|
|
|
19575
19743
|
} catch (err2) {
|
|
19576
19744
|
if (process.env.NODE9_DEBUG === "1") {
|
|
19577
19745
|
try {
|
|
19578
|
-
const dbg =
|
|
19746
|
+
const dbg = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19579
19747
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
19580
|
-
|
|
19748
|
+
fs37.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
19581
19749
|
`);
|
|
19582
19750
|
} catch {
|
|
19583
19751
|
}
|
|
@@ -19587,9 +19755,11 @@ RAW: ${raw}
|
|
|
19587
19755
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
19588
19756
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
19589
19757
|
}
|
|
19590
|
-
const safeCwdForAuth = typeof payloadCwd === "string" &&
|
|
19758
|
+
const safeCwdForAuth = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
19759
|
+
const askMode = resolveAskMode(agent, opts, config);
|
|
19591
19760
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
19592
|
-
cwd: safeCwdForAuth
|
|
19761
|
+
cwd: safeCwdForAuth,
|
|
19762
|
+
deferReview: askMode
|
|
19593
19763
|
});
|
|
19594
19764
|
if (result.approved) {
|
|
19595
19765
|
if (result.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
@@ -19597,14 +19767,18 @@ RAW: ${raw}
|
|
|
19597
19767
|
`);
|
|
19598
19768
|
process.exit(0);
|
|
19599
19769
|
}
|
|
19770
|
+
if (result.review) {
|
|
19771
|
+
sendAsk(result);
|
|
19772
|
+
return;
|
|
19773
|
+
}
|
|
19600
19774
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
19601
19775
|
try {
|
|
19602
|
-
const tty =
|
|
19603
|
-
|
|
19776
|
+
const tty = fs37.openSync("/dev/tty", "w");
|
|
19777
|
+
fs37.writeSync(
|
|
19604
19778
|
tty,
|
|
19605
19779
|
chalk9.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
19606
19780
|
);
|
|
19607
|
-
|
|
19781
|
+
fs37.closeSync(tty);
|
|
19608
19782
|
} catch {
|
|
19609
19783
|
}
|
|
19610
19784
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -19631,9 +19805,9 @@ RAW: ${raw}
|
|
|
19631
19805
|
});
|
|
19632
19806
|
} catch (err2) {
|
|
19633
19807
|
if (process.env.NODE9_DEBUG === "1") {
|
|
19634
|
-
const logPath =
|
|
19808
|
+
const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
19635
19809
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
19636
|
-
|
|
19810
|
+
fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
19637
19811
|
`);
|
|
19638
19812
|
}
|
|
19639
19813
|
process.exit(0);
|
|
@@ -19669,9 +19843,9 @@ RAW: ${raw}
|
|
|
19669
19843
|
// src/cli/commands/log.ts
|
|
19670
19844
|
init_audit();
|
|
19671
19845
|
init_config();
|
|
19672
|
-
import
|
|
19673
|
-
import
|
|
19674
|
-
import
|
|
19846
|
+
import fs38 from "fs";
|
|
19847
|
+
import path39 from "path";
|
|
19848
|
+
import os34 from "os";
|
|
19675
19849
|
init_daemon();
|
|
19676
19850
|
init_dlp();
|
|
19677
19851
|
|
|
@@ -19762,21 +19936,27 @@ function registerLogCommand(program2) {
|
|
|
19762
19936
|
return void 0;
|
|
19763
19937
|
})();
|
|
19764
19938
|
const agent = agentOverride !== void 0 ? agentOverride : metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.toolCall !== void 0 || payload.conversationId !== void 0 ? "Antigravity" : payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call" ? "Hermes" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE ? "Hermes" : process.env.ANTIGRAVITY_CONVERSATION_ID ? "Antigravity" : void 0;
|
|
19939
|
+
let reviewApproved = false;
|
|
19940
|
+
try {
|
|
19941
|
+
const key = reviewCorrelationKey(payload);
|
|
19942
|
+
if (key && resolvePendingReview(key)) reviewApproved = true;
|
|
19943
|
+
} catch {
|
|
19944
|
+
}
|
|
19765
19945
|
const entry = {
|
|
19766
19946
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19767
19947
|
tool,
|
|
19768
19948
|
args: JSON.parse(redactSecrets(JSON.stringify(rawInput))),
|
|
19769
19949
|
decision: "allowed",
|
|
19770
|
-
source: "post-hook"
|
|
19950
|
+
source: reviewApproved ? "inline-review-approved" : "post-hook"
|
|
19771
19951
|
};
|
|
19772
19952
|
if (agent) entry.agent = agent;
|
|
19773
19953
|
if (rawToolName !== tool) entry.agentToolName = rawToolName;
|
|
19774
19954
|
const payloadSessionId = payload.session_id ?? payload.conversationId;
|
|
19775
19955
|
if (payloadSessionId) entry.sessionId = payloadSessionId;
|
|
19776
|
-
const logPath =
|
|
19777
|
-
if (!
|
|
19778
|
-
|
|
19779
|
-
|
|
19956
|
+
const logPath = path39.join(os34.homedir(), ".node9", "audit.log");
|
|
19957
|
+
if (!fs38.existsSync(path39.dirname(logPath)))
|
|
19958
|
+
fs38.mkdirSync(path39.dirname(logPath), { recursive: true });
|
|
19959
|
+
fs38.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
19780
19960
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
19781
19961
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
19782
19962
|
if (command) {
|
|
@@ -19810,7 +19990,7 @@ function registerLogCommand(program2) {
|
|
|
19810
19990
|
}
|
|
19811
19991
|
}
|
|
19812
19992
|
const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
|
|
19813
|
-
const safeCwd = typeof payloadCwd === "string" &&
|
|
19993
|
+
const safeCwd = typeof payloadCwd === "string" && path39.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
19814
19994
|
const config = getConfig(safeCwd);
|
|
19815
19995
|
{
|
|
19816
19996
|
const toolOutput = payload.tool_response?.output;
|
|
@@ -19887,9 +20067,9 @@ function registerLogCommand(program2) {
|
|
|
19887
20067
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
19888
20068
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
19889
20069
|
`);
|
|
19890
|
-
const debugPath =
|
|
20070
|
+
const debugPath = path39.join(os34.homedir(), ".node9", "hook-debug.log");
|
|
19891
20071
|
try {
|
|
19892
|
-
|
|
20072
|
+
fs38.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
19893
20073
|
`);
|
|
19894
20074
|
} catch {
|
|
19895
20075
|
}
|
|
@@ -20291,22 +20471,22 @@ function registerConfigShowCommand(program2) {
|
|
|
20291
20471
|
init_daemon();
|
|
20292
20472
|
init_config();
|
|
20293
20473
|
import chalk11 from "chalk";
|
|
20294
|
-
import
|
|
20295
|
-
import
|
|
20296
|
-
import
|
|
20474
|
+
import fs40 from "fs";
|
|
20475
|
+
import path41 from "path";
|
|
20476
|
+
import os36 from "os";
|
|
20297
20477
|
import { execSync } from "child_process";
|
|
20298
20478
|
|
|
20299
20479
|
// src/agent-wiring.ts
|
|
20300
20480
|
init_setup();
|
|
20301
|
-
import
|
|
20302
|
-
import
|
|
20303
|
-
import
|
|
20481
|
+
import fs39 from "fs";
|
|
20482
|
+
import path40 from "path";
|
|
20483
|
+
import os35 from "os";
|
|
20304
20484
|
import * as yaml2 from "yaml";
|
|
20305
20485
|
import { parse as parseToml2 } from "smol-toml";
|
|
20306
20486
|
function readJson2(filePath) {
|
|
20307
|
-
if (!
|
|
20487
|
+
if (!fs39.existsSync(filePath)) return null;
|
|
20308
20488
|
try {
|
|
20309
|
-
return JSON.parse(
|
|
20489
|
+
return JSON.parse(fs39.readFileSync(filePath, "utf-8"));
|
|
20310
20490
|
} catch {
|
|
20311
20491
|
return "invalid";
|
|
20312
20492
|
}
|
|
@@ -20318,10 +20498,10 @@ function flatHaveNode9Hook(entries) {
|
|
|
20318
20498
|
return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
|
|
20319
20499
|
}
|
|
20320
20500
|
function readHookRoot(filePath, format) {
|
|
20321
|
-
if (!
|
|
20501
|
+
if (!fs39.existsSync(filePath)) return "absent";
|
|
20322
20502
|
let raw;
|
|
20323
20503
|
try {
|
|
20324
|
-
raw =
|
|
20504
|
+
raw = fs39.readFileSync(filePath, "utf-8");
|
|
20325
20505
|
} catch {
|
|
20326
20506
|
return "absent";
|
|
20327
20507
|
}
|
|
@@ -20344,10 +20524,10 @@ function detectMcp(servers) {
|
|
|
20344
20524
|
return { wrapped, present };
|
|
20345
20525
|
}
|
|
20346
20526
|
function readMcp(filePath, format) {
|
|
20347
|
-
if (!
|
|
20527
|
+
if (!fs39.existsSync(filePath)) return { wrapped: [], present: false };
|
|
20348
20528
|
try {
|
|
20349
20529
|
if (format === "toml") {
|
|
20350
|
-
const parsed2 = parseToml2(
|
|
20530
|
+
const parsed2 = parseToml2(fs39.readFileSync(filePath, "utf-8"));
|
|
20351
20531
|
return detectMcp(parsed2?.mcp_servers);
|
|
20352
20532
|
}
|
|
20353
20533
|
const parsed = readJson2(filePath);
|
|
@@ -20359,7 +20539,7 @@ function readMcp(filePath, format) {
|
|
|
20359
20539
|
}
|
|
20360
20540
|
var exists = (p) => {
|
|
20361
20541
|
try {
|
|
20362
|
-
return
|
|
20542
|
+
return fs39.existsSync(p);
|
|
20363
20543
|
} catch {
|
|
20364
20544
|
return false;
|
|
20365
20545
|
}
|
|
@@ -20373,52 +20553,52 @@ var AGENT_SPECS = [
|
|
|
20373
20553
|
id: "claude",
|
|
20374
20554
|
label: "Claude Code",
|
|
20375
20555
|
setupCommand: "node9 agents add claude",
|
|
20376
|
-
hookFile: (h) =>
|
|
20556
|
+
hookFile: (h) => path40.join(h, ".claude", "settings.json"),
|
|
20377
20557
|
hookFormat: "matcher",
|
|
20378
20558
|
hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
|
|
20379
|
-
mcpFile: (h) =>
|
|
20380
|
-
present: (h) => exists(
|
|
20559
|
+
mcpFile: (h) => path40.join(h, ".claude.json"),
|
|
20560
|
+
present: (h) => exists(path40.join(h, ".claude", "settings.json")) || exists(path40.join(h, ".claude.json"))
|
|
20381
20561
|
},
|
|
20382
20562
|
{
|
|
20383
20563
|
id: "gemini",
|
|
20384
20564
|
label: "Gemini CLI",
|
|
20385
20565
|
setupCommand: "node9 agents add gemini",
|
|
20386
|
-
hookFile: (h) =>
|
|
20566
|
+
hookFile: (h) => path40.join(h, ".gemini", "settings.json"),
|
|
20387
20567
|
hookFormat: "matcher",
|
|
20388
20568
|
hookEvents: [ck("BeforeTool"), lg("AfterTool")],
|
|
20389
|
-
mcpFile: (h) =>
|
|
20390
|
-
present: (h) => exists(
|
|
20569
|
+
mcpFile: (h) => path40.join(h, ".gemini", "settings.json"),
|
|
20570
|
+
present: (h) => exists(path40.join(h, ".gemini", "settings.json"))
|
|
20391
20571
|
},
|
|
20392
20572
|
{
|
|
20393
20573
|
id: "codex",
|
|
20394
20574
|
label: "Codex",
|
|
20395
20575
|
setupCommand: "node9 agents add codex",
|
|
20396
|
-
hookFile: (h) =>
|
|
20576
|
+
hookFile: (h) => path40.join(h, ".codex", "hooks.json"),
|
|
20397
20577
|
hookFormat: "matcher",
|
|
20398
20578
|
hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
|
|
20399
|
-
mcpFile: (h) =>
|
|
20579
|
+
mcpFile: (h) => path40.join(h, ".codex", "config.toml"),
|
|
20400
20580
|
mcpFormat: "toml",
|
|
20401
|
-
present: (h) => exists(
|
|
20581
|
+
present: (h) => exists(path40.join(h, ".codex"))
|
|
20402
20582
|
},
|
|
20403
20583
|
{
|
|
20404
20584
|
id: "antigravity",
|
|
20405
20585
|
label: "Antigravity",
|
|
20406
20586
|
setupCommand: "node9 agents add antigravity",
|
|
20407
|
-
hookFile: (h) =>
|
|
20587
|
+
hookFile: (h) => path40.join(h, ".gemini", "config", "hooks.json"),
|
|
20408
20588
|
hookFormat: "matcher",
|
|
20409
20589
|
hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
|
|
20410
|
-
mcpFile: (h) =>
|
|
20411
|
-
present: (h) => exists(
|
|
20590
|
+
mcpFile: (h) => path40.join(h, ".gemini", "config", "mcp_config.json"),
|
|
20591
|
+
present: (h) => exists(path40.join(h, ".gemini", "config", "hooks.json")) || exists(path40.join(h, ".gemini", "antigravity-cli")) || exists(path40.join(h, ".gemini", "antigravity-ide"))
|
|
20412
20592
|
},
|
|
20413
20593
|
{
|
|
20414
20594
|
id: "copilot",
|
|
20415
20595
|
label: "GitHub Copilot",
|
|
20416
20596
|
setupCommand: "node9 agents add copilot",
|
|
20417
|
-
hookFile: (h) =>
|
|
20597
|
+
hookFile: (h) => path40.join(h, ".copilot", "hooks", "node9.json"),
|
|
20418
20598
|
hookFormat: "flat",
|
|
20419
20599
|
hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
|
|
20420
|
-
mcpFile: (h) =>
|
|
20421
|
-
present: (h) => exists(
|
|
20600
|
+
mcpFile: (h) => path40.join(h, ".copilot", "mcp-config.json"),
|
|
20601
|
+
present: (h) => exists(path40.join(h, ".copilot"))
|
|
20422
20602
|
},
|
|
20423
20603
|
{
|
|
20424
20604
|
id: "cursor",
|
|
@@ -20427,8 +20607,8 @@ var AGENT_SPECS = [
|
|
|
20427
20607
|
// MCP-only — no hook file (see note above).
|
|
20428
20608
|
hookFormat: "flat",
|
|
20429
20609
|
hookEvents: [],
|
|
20430
|
-
mcpFile: (h) =>
|
|
20431
|
-
present: (h) => exists(
|
|
20610
|
+
mcpFile: (h) => path40.join(h, ".cursor", "mcp.json"),
|
|
20611
|
+
present: (h) => exists(path40.join(h, ".cursor", "mcp.json"))
|
|
20432
20612
|
},
|
|
20433
20613
|
{
|
|
20434
20614
|
id: "hermes",
|
|
@@ -20449,8 +20629,8 @@ var AGENT_SPECS = [
|
|
|
20449
20629
|
setupCommand: "node9 agents add opencode",
|
|
20450
20630
|
hookFormat: "flat",
|
|
20451
20631
|
hookEvents: [],
|
|
20452
|
-
shimFile: (h) =>
|
|
20453
|
-
present: (h) => exists(
|
|
20632
|
+
shimFile: (h) => path40.join(h, ".config", "opencode", "plugins", "node9.js"),
|
|
20633
|
+
present: (h) => exists(path40.join(h, ".config", "opencode")) || exists(path40.join(h, ".config", "opencode", "plugins", "node9.js"))
|
|
20454
20634
|
},
|
|
20455
20635
|
{
|
|
20456
20636
|
id: "pi",
|
|
@@ -20458,11 +20638,11 @@ var AGENT_SPECS = [
|
|
|
20458
20638
|
setupCommand: "node9 agents add pi",
|
|
20459
20639
|
hookFormat: "flat",
|
|
20460
20640
|
hookEvents: [],
|
|
20461
|
-
shimFile: (h) =>
|
|
20462
|
-
present: (h) => exists(
|
|
20641
|
+
shimFile: (h) => path40.join(h, ".pi", "agent", "extensions", "node9.js"),
|
|
20642
|
+
present: (h) => exists(path40.join(h, ".pi", "agent")) || exists(path40.join(h, ".pi", "agent", "extensions", "node9.js"))
|
|
20463
20643
|
}
|
|
20464
20644
|
];
|
|
20465
|
-
function getAgentWiring(home =
|
|
20645
|
+
function getAgentWiring(home = os35.homedir()) {
|
|
20466
20646
|
const detected = detectAgents(home);
|
|
20467
20647
|
return AGENT_SPECS.map((spec) => {
|
|
20468
20648
|
const present = spec.present(home);
|
|
@@ -20514,7 +20694,7 @@ function getAgentWiring(home = os34.homedir()) {
|
|
|
20514
20694
|
// src/cli/commands/doctor.ts
|
|
20515
20695
|
function registerDoctorCommand(program2, version2) {
|
|
20516
20696
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
|
|
20517
|
-
const homeDir2 =
|
|
20697
|
+
const homeDir2 = os36.homedir();
|
|
20518
20698
|
let failures = 0;
|
|
20519
20699
|
function pass(msg) {
|
|
20520
20700
|
console.log(chalk11.green(" \u2705 ") + msg);
|
|
@@ -20560,10 +20740,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
20560
20740
|
);
|
|
20561
20741
|
}
|
|
20562
20742
|
section("Configuration");
|
|
20563
|
-
const globalConfigPath =
|
|
20564
|
-
if (
|
|
20743
|
+
const globalConfigPath = path41.join(homeDir2, ".node9", "config.json");
|
|
20744
|
+
if (fs40.existsSync(globalConfigPath)) {
|
|
20565
20745
|
try {
|
|
20566
|
-
JSON.parse(
|
|
20746
|
+
JSON.parse(fs40.readFileSync(globalConfigPath, "utf-8"));
|
|
20567
20747
|
pass("~/.node9/config.json found and valid");
|
|
20568
20748
|
} catch {
|
|
20569
20749
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -20571,10 +20751,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
20571
20751
|
} else {
|
|
20572
20752
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
20573
20753
|
}
|
|
20574
|
-
const projectConfigPath =
|
|
20575
|
-
if (
|
|
20754
|
+
const projectConfigPath = path41.join(process.cwd(), "node9.config.json");
|
|
20755
|
+
if (fs40.existsSync(projectConfigPath)) {
|
|
20576
20756
|
try {
|
|
20577
|
-
JSON.parse(
|
|
20757
|
+
JSON.parse(fs40.readFileSync(projectConfigPath, "utf-8"));
|
|
20578
20758
|
pass("node9.config.json found and valid (project)");
|
|
20579
20759
|
} catch {
|
|
20580
20760
|
fail(
|
|
@@ -20583,8 +20763,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
20583
20763
|
);
|
|
20584
20764
|
}
|
|
20585
20765
|
}
|
|
20586
|
-
const credsPath =
|
|
20587
|
-
if (
|
|
20766
|
+
const credsPath = path41.join(homeDir2, ".node9", "credentials.json");
|
|
20767
|
+
if (fs40.existsSync(credsPath)) {
|
|
20588
20768
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
20589
20769
|
} else {
|
|
20590
20770
|
warn(
|
|
@@ -20628,7 +20808,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
20628
20808
|
try {
|
|
20629
20809
|
const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
|
|
20630
20810
|
const cfg = getConfig();
|
|
20631
|
-
const creds =
|
|
20811
|
+
const creds = fs40.existsSync(path41.join(os36.homedir(), ".node9", "credentials.json"));
|
|
20632
20812
|
if (!creds) {
|
|
20633
20813
|
warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
|
|
20634
20814
|
} else if (!cfg.settings.approvers.cloud) {
|
|
@@ -20678,9 +20858,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
20678
20858
|
|
|
20679
20859
|
// src/cli/commands/audit.ts
|
|
20680
20860
|
import chalk12 from "chalk";
|
|
20681
|
-
import
|
|
20682
|
-
import
|
|
20683
|
-
import
|
|
20861
|
+
import fs41 from "fs";
|
|
20862
|
+
import path42 from "path";
|
|
20863
|
+
import os37 from "os";
|
|
20684
20864
|
function formatRelativeTime(timestamp) {
|
|
20685
20865
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
20686
20866
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -20693,14 +20873,14 @@ function formatRelativeTime(timestamp) {
|
|
|
20693
20873
|
}
|
|
20694
20874
|
function registerAuditCommand(program2) {
|
|
20695
20875
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
20696
|
-
const logPath =
|
|
20697
|
-
if (!
|
|
20876
|
+
const logPath = path42.join(os37.homedir(), ".node9", "audit.log");
|
|
20877
|
+
if (!fs41.existsSync(logPath)) {
|
|
20698
20878
|
console.log(
|
|
20699
20879
|
chalk12.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
20700
20880
|
);
|
|
20701
20881
|
return;
|
|
20702
20882
|
}
|
|
20703
|
-
const raw =
|
|
20883
|
+
const raw = fs41.readFileSync(logPath, "utf-8");
|
|
20704
20884
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
20705
20885
|
let entries = lines.flatMap((line) => {
|
|
20706
20886
|
try {
|
|
@@ -20759,9 +20939,9 @@ import chalk13 from "chalk";
|
|
|
20759
20939
|
init_costSync();
|
|
20760
20940
|
init_litellm();
|
|
20761
20941
|
init_cost_codex();
|
|
20762
|
-
import
|
|
20763
|
-
import
|
|
20764
|
-
import
|
|
20942
|
+
import fs42 from "fs";
|
|
20943
|
+
import os38 from "os";
|
|
20944
|
+
import path43 from "path";
|
|
20765
20945
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
20766
20946
|
function buildTestTimestamps(allEntries) {
|
|
20767
20947
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -20841,8 +21021,8 @@ function getDateRange(period, now) {
|
|
|
20841
21021
|
}
|
|
20842
21022
|
}
|
|
20843
21023
|
function parseAuditLog(logPath) {
|
|
20844
|
-
if (!
|
|
20845
|
-
const raw =
|
|
21024
|
+
if (!fs42.existsSync(logPath)) return [];
|
|
21025
|
+
const raw = fs42.readFileSync(logPath, "utf-8");
|
|
20846
21026
|
return raw.split("\n").flatMap((line) => {
|
|
20847
21027
|
if (!line.trim()) return [];
|
|
20848
21028
|
try {
|
|
@@ -20889,25 +21069,25 @@ function freezeClaudeCost(acc) {
|
|
|
20889
21069
|
};
|
|
20890
21070
|
}
|
|
20891
21071
|
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
20892
|
-
const projPath =
|
|
21072
|
+
const projPath = path43.join(projectsDir, proj);
|
|
20893
21073
|
let files;
|
|
20894
21074
|
try {
|
|
20895
|
-
const stat =
|
|
21075
|
+
const stat = fs42.statSync(projPath);
|
|
20896
21076
|
if (!stat.isDirectory()) return;
|
|
20897
|
-
files =
|
|
21077
|
+
files = fs42.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
20898
21078
|
} catch {
|
|
20899
21079
|
return;
|
|
20900
21080
|
}
|
|
20901
21081
|
const startMs = start.getTime();
|
|
20902
21082
|
for (const file of files) {
|
|
20903
|
-
const filePath =
|
|
21083
|
+
const filePath = path43.join(projPath, file);
|
|
20904
21084
|
try {
|
|
20905
|
-
if (
|
|
21085
|
+
if (fs42.statSync(filePath).mtimeMs < startMs) continue;
|
|
20906
21086
|
} catch {
|
|
20907
21087
|
continue;
|
|
20908
21088
|
}
|
|
20909
21089
|
try {
|
|
20910
|
-
const raw =
|
|
21090
|
+
const raw = fs42.readFileSync(filePath, "utf-8");
|
|
20911
21091
|
for (const line of raw.split("\n")) {
|
|
20912
21092
|
if (!line.trim()) continue;
|
|
20913
21093
|
let entry;
|
|
@@ -20957,10 +21137,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
20957
21137
|
}
|
|
20958
21138
|
function loadClaudeCost(start, end, projectsDir) {
|
|
20959
21139
|
const acc = emptyClaudeCostAccumulator();
|
|
20960
|
-
if (!
|
|
21140
|
+
if (!fs42.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
20961
21141
|
let dirs;
|
|
20962
21142
|
try {
|
|
20963
|
-
dirs =
|
|
21143
|
+
dirs = fs42.readdirSync(projectsDir);
|
|
20964
21144
|
} catch {
|
|
20965
21145
|
return freezeClaudeCost(acc);
|
|
20966
21146
|
}
|
|
@@ -20972,7 +21152,7 @@ function loadClaudeCost(start, end, projectsDir) {
|
|
|
20972
21152
|
function processCodexCostFile(filePath, start, end, acc) {
|
|
20973
21153
|
let lines;
|
|
20974
21154
|
try {
|
|
20975
|
-
lines =
|
|
21155
|
+
lines = fs42.readFileSync(filePath, "utf-8").split("\n");
|
|
20976
21156
|
} catch {
|
|
20977
21157
|
return;
|
|
20978
21158
|
}
|
|
@@ -21027,31 +21207,31 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
21027
21207
|
}
|
|
21028
21208
|
function listCodexSessionFiles2(sessionsBase) {
|
|
21029
21209
|
const jsonlFiles = [];
|
|
21030
|
-
if (!
|
|
21210
|
+
if (!fs42.existsSync(sessionsBase)) return jsonlFiles;
|
|
21031
21211
|
try {
|
|
21032
|
-
for (const year of
|
|
21033
|
-
const yearPath =
|
|
21212
|
+
for (const year of fs42.readdirSync(sessionsBase)) {
|
|
21213
|
+
const yearPath = path43.join(sessionsBase, year);
|
|
21034
21214
|
try {
|
|
21035
|
-
if (!
|
|
21215
|
+
if (!fs42.statSync(yearPath).isDirectory()) continue;
|
|
21036
21216
|
} catch {
|
|
21037
21217
|
continue;
|
|
21038
21218
|
}
|
|
21039
|
-
for (const month of
|
|
21040
|
-
const monthPath =
|
|
21219
|
+
for (const month of fs42.readdirSync(yearPath)) {
|
|
21220
|
+
const monthPath = path43.join(yearPath, month);
|
|
21041
21221
|
try {
|
|
21042
|
-
if (!
|
|
21222
|
+
if (!fs42.statSync(monthPath).isDirectory()) continue;
|
|
21043
21223
|
} catch {
|
|
21044
21224
|
continue;
|
|
21045
21225
|
}
|
|
21046
|
-
for (const day of
|
|
21047
|
-
const dayPath =
|
|
21226
|
+
for (const day of fs42.readdirSync(monthPath)) {
|
|
21227
|
+
const dayPath = path43.join(monthPath, day);
|
|
21048
21228
|
try {
|
|
21049
|
-
if (!
|
|
21229
|
+
if (!fs42.statSync(dayPath).isDirectory()) continue;
|
|
21050
21230
|
} catch {
|
|
21051
21231
|
continue;
|
|
21052
21232
|
}
|
|
21053
|
-
for (const file of
|
|
21054
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
21233
|
+
for (const file of fs42.readdirSync(dayPath)) {
|
|
21234
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path43.join(dayPath, file));
|
|
21055
21235
|
}
|
|
21056
21236
|
}
|
|
21057
21237
|
}
|
|
@@ -21116,13 +21296,13 @@ function freezeGeminiCost(acc) {
|
|
|
21116
21296
|
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
21117
21297
|
const startMs = start.getTime();
|
|
21118
21298
|
try {
|
|
21119
|
-
if (
|
|
21299
|
+
if (fs42.statSync(filePath).mtimeMs < startMs) return;
|
|
21120
21300
|
} catch {
|
|
21121
21301
|
return;
|
|
21122
21302
|
}
|
|
21123
21303
|
let raw;
|
|
21124
21304
|
try {
|
|
21125
|
-
raw =
|
|
21305
|
+
raw = fs42.readFileSync(filePath, "utf-8");
|
|
21126
21306
|
} catch {
|
|
21127
21307
|
return;
|
|
21128
21308
|
}
|
|
@@ -21171,30 +21351,30 @@ function listGeminiSessionFiles2(geminiTmpDir2) {
|
|
|
21171
21351
|
const out = [];
|
|
21172
21352
|
let dirs;
|
|
21173
21353
|
try {
|
|
21174
|
-
if (!
|
|
21175
|
-
dirs =
|
|
21354
|
+
if (!fs42.statSync(geminiTmpDir2).isDirectory()) return out;
|
|
21355
|
+
dirs = fs42.readdirSync(geminiTmpDir2);
|
|
21176
21356
|
} catch {
|
|
21177
21357
|
return out;
|
|
21178
21358
|
}
|
|
21179
21359
|
for (const proj of dirs) {
|
|
21180
|
-
const chatsDir =
|
|
21360
|
+
const chatsDir = path43.join(geminiTmpDir2, proj, "chats");
|
|
21181
21361
|
let files;
|
|
21182
21362
|
try {
|
|
21183
|
-
if (!
|
|
21184
|
-
files =
|
|
21363
|
+
if (!fs42.statSync(chatsDir).isDirectory()) continue;
|
|
21364
|
+
files = fs42.readdirSync(chatsDir);
|
|
21185
21365
|
} catch {
|
|
21186
21366
|
continue;
|
|
21187
21367
|
}
|
|
21188
21368
|
for (const f of files) {
|
|
21189
21369
|
if (!f.endsWith(".jsonl")) continue;
|
|
21190
|
-
out.push({ projectKey: proj, file:
|
|
21370
|
+
out.push({ projectKey: proj, file: path43.join(chatsDir, f) });
|
|
21191
21371
|
}
|
|
21192
21372
|
}
|
|
21193
21373
|
return out;
|
|
21194
21374
|
}
|
|
21195
21375
|
function loadGeminiCost(start, end, geminiTmpDir2) {
|
|
21196
21376
|
const acc = emptyGeminiAccumulator();
|
|
21197
|
-
if (!
|
|
21377
|
+
if (!fs42.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
|
|
21198
21378
|
for (const { projectKey, file } of listGeminiSessionFiles2(geminiTmpDir2)) {
|
|
21199
21379
|
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
21200
21380
|
}
|
|
@@ -21202,11 +21382,11 @@ function loadGeminiCost(start, end, geminiTmpDir2) {
|
|
|
21202
21382
|
}
|
|
21203
21383
|
function aggregateReportFromAudit(period, opts = {}) {
|
|
21204
21384
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
21205
|
-
const auditLogPath = opts.auditLogPath ??
|
|
21206
|
-
const claudeProjectsDir = opts.claudeProjectsDir ??
|
|
21207
|
-
const codexSessionsDir2 = opts.codexSessionsDir ??
|
|
21208
|
-
const geminiTmpDir2 = opts.geminiTmpDir ??
|
|
21209
|
-
const hasAuditFile =
|
|
21385
|
+
const auditLogPath = opts.auditLogPath ?? path43.join(os38.homedir(), ".node9", "audit.log");
|
|
21386
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? path43.join(os38.homedir(), ".claude", "projects");
|
|
21387
|
+
const codexSessionsDir2 = opts.codexSessionsDir ?? path43.join(os38.homedir(), ".codex", "sessions");
|
|
21388
|
+
const geminiTmpDir2 = opts.geminiTmpDir ?? path43.join(os38.homedir(), ".gemini", "tmp");
|
|
21389
|
+
const hasAuditFile = fs42.existsSync(auditLogPath);
|
|
21210
21390
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
21211
21391
|
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
21212
21392
|
const { start, end } = getDateRange(period, now);
|
|
@@ -21904,9 +22084,9 @@ function registerDaemonCommand(program2) {
|
|
|
21904
22084
|
init_core();
|
|
21905
22085
|
init_daemon();
|
|
21906
22086
|
import chalk15 from "chalk";
|
|
21907
|
-
import
|
|
21908
|
-
import
|
|
21909
|
-
import
|
|
22087
|
+
import fs43 from "fs";
|
|
22088
|
+
import path44 from "path";
|
|
22089
|
+
import os39 from "os";
|
|
21910
22090
|
function printAgentSection(label2, hookPairs, wrapped) {
|
|
21911
22091
|
console.log(chalk15.bold(` ${label2}`));
|
|
21912
22092
|
for (const { name, present } of hookPairs) {
|
|
@@ -21960,20 +22140,20 @@ function registerStatusCommand(program2) {
|
|
|
21960
22140
|
console.log("");
|
|
21961
22141
|
const modeLabel = settings.mode === "audit" ? chalk15.blue("audit") : settings.mode === "strict" ? chalk15.red("strict") : chalk15.white("standard");
|
|
21962
22142
|
console.log(` Mode: ${modeLabel}`);
|
|
21963
|
-
const projectConfig =
|
|
21964
|
-
const globalConfig =
|
|
22143
|
+
const projectConfig = path44.join(process.cwd(), "node9.config.json");
|
|
22144
|
+
const globalConfig = path44.join(os39.homedir(), ".node9", "config.json");
|
|
21965
22145
|
console.log(
|
|
21966
|
-
` Local: ${
|
|
22146
|
+
` Local: ${fs43.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
|
|
21967
22147
|
);
|
|
21968
22148
|
console.log(
|
|
21969
|
-
` Global: ${
|
|
22149
|
+
` Global: ${fs43.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
|
|
21970
22150
|
);
|
|
21971
22151
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
21972
22152
|
console.log(
|
|
21973
22153
|
` Sandbox: ${chalk15.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
21974
22154
|
);
|
|
21975
22155
|
}
|
|
21976
|
-
const wiring = getAgentWiring(
|
|
22156
|
+
const wiring = getAgentWiring(os39.homedir()).filter((a) => a.present);
|
|
21977
22157
|
if (wiring.length > 0) {
|
|
21978
22158
|
console.log("");
|
|
21979
22159
|
console.log(chalk15.bold(" Agent Wiring:"));
|
|
@@ -22012,9 +22192,9 @@ init_setup();
|
|
|
22012
22192
|
init_shields();
|
|
22013
22193
|
init_service();
|
|
22014
22194
|
import chalk16 from "chalk";
|
|
22015
|
-
import
|
|
22016
|
-
import
|
|
22017
|
-
import
|
|
22195
|
+
import fs44 from "fs";
|
|
22196
|
+
import path45 from "path";
|
|
22197
|
+
import os40 from "os";
|
|
22018
22198
|
import https4 from "https";
|
|
22019
22199
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
|
|
22020
22200
|
function buildTelemetryPayload(agents, firstInstall) {
|
|
@@ -22100,16 +22280,16 @@ function registerInitCommand(program2) {
|
|
|
22100
22280
|
}
|
|
22101
22281
|
console.log("");
|
|
22102
22282
|
}
|
|
22103
|
-
const configPath2 =
|
|
22104
|
-
const isFirstInstall = !
|
|
22105
|
-
if (
|
|
22283
|
+
const configPath2 = path45.join(os40.homedir(), ".node9", "config.json");
|
|
22284
|
+
const isFirstInstall = !fs44.existsSync(configPath2);
|
|
22285
|
+
if (fs44.existsSync(configPath2) && !options.force) {
|
|
22106
22286
|
try {
|
|
22107
|
-
const existing = JSON.parse(
|
|
22287
|
+
const existing = JSON.parse(fs44.readFileSync(configPath2, "utf-8"));
|
|
22108
22288
|
const settings = existing.settings ?? {};
|
|
22109
22289
|
if (settings.mode !== chosenMode) {
|
|
22110
22290
|
settings.mode = chosenMode;
|
|
22111
22291
|
existing.settings = settings;
|
|
22112
|
-
|
|
22292
|
+
fs44.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
|
|
22113
22293
|
console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
22114
22294
|
} else {
|
|
22115
22295
|
console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
|
|
@@ -22122,9 +22302,9 @@ function registerInitCommand(program2) {
|
|
|
22122
22302
|
...DEFAULT_CONFIG,
|
|
22123
22303
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
22124
22304
|
};
|
|
22125
|
-
const dir =
|
|
22126
|
-
if (!
|
|
22127
|
-
|
|
22305
|
+
const dir = path45.dirname(configPath2);
|
|
22306
|
+
if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
|
|
22307
|
+
fs44.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
|
|
22128
22308
|
console.log(chalk16.green(`\u2705 Config created: ${configPath2}`));
|
|
22129
22309
|
console.log(chalk16.gray(` Mode: ${chosenMode}`));
|
|
22130
22310
|
}
|
|
@@ -22229,7 +22409,7 @@ function registerInitCommand(program2) {
|
|
|
22229
22409
|
}
|
|
22230
22410
|
|
|
22231
22411
|
// src/cli/commands/undo.ts
|
|
22232
|
-
import
|
|
22412
|
+
import path46 from "path";
|
|
22233
22413
|
import chalk18 from "chalk";
|
|
22234
22414
|
|
|
22235
22415
|
// src/tui/undo-navigator.ts
|
|
@@ -22388,7 +22568,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
22388
22568
|
let dir = startDir;
|
|
22389
22569
|
while (true) {
|
|
22390
22570
|
if (cwds.has(dir)) return dir;
|
|
22391
|
-
const parent =
|
|
22571
|
+
const parent = path46.dirname(dir);
|
|
22392
22572
|
if (parent === dir) return null;
|
|
22393
22573
|
dir = parent;
|
|
22394
22574
|
}
|
|
@@ -23023,9 +23203,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
23023
23203
|
|
|
23024
23204
|
// src/mcp-server/index.ts
|
|
23025
23205
|
import readline5 from "readline";
|
|
23026
|
-
import
|
|
23027
|
-
import
|
|
23028
|
-
import
|
|
23206
|
+
import fs45 from "fs";
|
|
23207
|
+
import os41 from "os";
|
|
23208
|
+
import path47 from "path";
|
|
23029
23209
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
23030
23210
|
init_core();
|
|
23031
23211
|
init_daemon();
|
|
@@ -23276,13 +23456,13 @@ function handleStatus() {
|
|
|
23276
23456
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
23277
23457
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
23278
23458
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
23279
|
-
const projectConfig =
|
|
23280
|
-
const globalConfig =
|
|
23459
|
+
const projectConfig = path47.join(process.cwd(), "node9.config.json");
|
|
23460
|
+
const globalConfig = path47.join(os41.homedir(), ".node9", "config.json");
|
|
23281
23461
|
lines.push(
|
|
23282
|
-
`Project config (node9.config.json): ${
|
|
23462
|
+
`Project config (node9.config.json): ${fs45.existsSync(projectConfig) ? "present" : "not found"}`
|
|
23283
23463
|
);
|
|
23284
23464
|
lines.push(
|
|
23285
|
-
`Global config (~/.node9/config.json): ${
|
|
23465
|
+
`Global config (~/.node9/config.json): ${fs45.existsSync(globalConfig) ? "present" : "not found"}`
|
|
23286
23466
|
);
|
|
23287
23467
|
return lines.join("\n");
|
|
23288
23468
|
}
|
|
@@ -23356,21 +23536,21 @@ function handleShieldDisable(args) {
|
|
|
23356
23536
|
writeActiveShields(active.filter((s) => s !== name));
|
|
23357
23537
|
return `Shield "${name}" disabled.`;
|
|
23358
23538
|
}
|
|
23359
|
-
var GLOBAL_CONFIG_PATH =
|
|
23539
|
+
var GLOBAL_CONFIG_PATH = path47.join(os41.homedir(), ".node9", "config.json");
|
|
23360
23540
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
23361
23541
|
function readGlobalConfigRaw() {
|
|
23362
23542
|
try {
|
|
23363
|
-
if (
|
|
23364
|
-
return JSON.parse(
|
|
23543
|
+
if (fs45.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
23544
|
+
return JSON.parse(fs45.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
23365
23545
|
}
|
|
23366
23546
|
} catch {
|
|
23367
23547
|
}
|
|
23368
23548
|
return {};
|
|
23369
23549
|
}
|
|
23370
23550
|
function writeGlobalConfigRaw(data) {
|
|
23371
|
-
const dir =
|
|
23372
|
-
if (!
|
|
23373
|
-
|
|
23551
|
+
const dir = path47.dirname(GLOBAL_CONFIG_PATH);
|
|
23552
|
+
if (!fs45.existsSync(dir)) fs45.mkdirSync(dir, { recursive: true });
|
|
23553
|
+
fs45.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
23374
23554
|
}
|
|
23375
23555
|
function handleApproverList() {
|
|
23376
23556
|
const config = getConfig();
|
|
@@ -23414,9 +23594,9 @@ function handleApproverSet(args) {
|
|
|
23414
23594
|
function handleAuditGet(args) {
|
|
23415
23595
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
23416
23596
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
23417
|
-
const auditPath =
|
|
23418
|
-
if (!
|
|
23419
|
-
const rawLines =
|
|
23597
|
+
const auditPath = path47.join(os41.homedir(), ".node9", "audit.log");
|
|
23598
|
+
if (!fs45.existsSync(auditPath)) return "No audit log found.";
|
|
23599
|
+
const rawLines = fs45.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
23420
23600
|
const parsed = [];
|
|
23421
23601
|
for (const line of rawLines) {
|
|
23422
23602
|
try {
|
|
@@ -23751,7 +23931,7 @@ function registerTrustCommand(program2) {
|
|
|
23751
23931
|
// src/cli/commands/mcp-pin.ts
|
|
23752
23932
|
init_mcp_pin();
|
|
23753
23933
|
import chalk21 from "chalk";
|
|
23754
|
-
import
|
|
23934
|
+
import fs46 from "fs";
|
|
23755
23935
|
function registerMcpPinCommand(program2) {
|
|
23756
23936
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
23757
23937
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -23762,7 +23942,7 @@ function registerMcpPinCommand(program2) {
|
|
|
23762
23942
|
let repoCorrupt = false;
|
|
23763
23943
|
if (found.source === "repo") {
|
|
23764
23944
|
try {
|
|
23765
|
-
const raw =
|
|
23945
|
+
const raw = fs46.readFileSync(found.path, "utf-8");
|
|
23766
23946
|
const parsed = JSON.parse(raw);
|
|
23767
23947
|
repoEntries = parsed.servers ?? {};
|
|
23768
23948
|
} catch {
|
|
@@ -24077,25 +24257,25 @@ init_scan();
|
|
|
24077
24257
|
import chalk25 from "chalk";
|
|
24078
24258
|
|
|
24079
24259
|
// src/posture/index.ts
|
|
24080
|
-
import
|
|
24260
|
+
import os45 from "os";
|
|
24081
24261
|
|
|
24082
24262
|
// src/posture/secrets.ts
|
|
24083
24263
|
init_dist();
|
|
24084
|
-
import
|
|
24085
|
-
import
|
|
24086
|
-
import
|
|
24264
|
+
import fs47 from "fs";
|
|
24265
|
+
import path48 from "path";
|
|
24266
|
+
import os42 from "os";
|
|
24087
24267
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
24088
24268
|
function displayPath(p, home) {
|
|
24089
24269
|
if (p === home) return "~";
|
|
24090
|
-
const prefix = home.endsWith(
|
|
24091
|
-
if (p.startsWith(prefix)) return "~" +
|
|
24270
|
+
const prefix = home.endsWith(path48.sep) ? home : home + path48.sep;
|
|
24271
|
+
if (p.startsWith(prefix)) return "~" + path48.sep + p.slice(prefix.length);
|
|
24092
24272
|
return p;
|
|
24093
24273
|
}
|
|
24094
24274
|
function safeRead(file) {
|
|
24095
24275
|
try {
|
|
24096
|
-
const stat =
|
|
24276
|
+
const stat = fs47.statSync(file);
|
|
24097
24277
|
if (!stat.isFile() || stat.size === 0 || stat.size > MAX_FILE_BYTES) return null;
|
|
24098
|
-
return
|
|
24278
|
+
return fs47.readFileSync(file, "utf8");
|
|
24099
24279
|
} catch {
|
|
24100
24280
|
return null;
|
|
24101
24281
|
}
|
|
@@ -24103,8 +24283,8 @@ function safeRead(file) {
|
|
|
24103
24283
|
function candidateFiles(home, cwd) {
|
|
24104
24284
|
const files = /* @__PURE__ */ new Set();
|
|
24105
24285
|
try {
|
|
24106
|
-
for (const name of
|
|
24107
|
-
if (name === ".env" || name.startsWith(".env.")) files.add(
|
|
24286
|
+
for (const name of fs47.readdirSync(cwd)) {
|
|
24287
|
+
if (name === ".env" || name.startsWith(".env.")) files.add(path48.join(cwd, name));
|
|
24108
24288
|
}
|
|
24109
24289
|
} catch {
|
|
24110
24290
|
}
|
|
@@ -24112,21 +24292,21 @@ function candidateFiles(home, cwd) {
|
|
|
24112
24292
|
if (spec.hookFile) files.add(spec.hookFile(home));
|
|
24113
24293
|
if (spec.mcpFile) files.add(spec.mcpFile(home));
|
|
24114
24294
|
}
|
|
24115
|
-
files.add(
|
|
24295
|
+
files.add(path48.join(home, ".env"));
|
|
24116
24296
|
return [...files];
|
|
24117
24297
|
}
|
|
24118
24298
|
function credentialMaterial(home) {
|
|
24119
24299
|
return [
|
|
24120
|
-
|
|
24121
|
-
|
|
24122
|
-
|
|
24123
|
-
|
|
24124
|
-
|
|
24125
|
-
|
|
24300
|
+
path48.join(home, ".ssh", "id_rsa"),
|
|
24301
|
+
path48.join(home, ".ssh", "id_dsa"),
|
|
24302
|
+
path48.join(home, ".ssh", "id_ecdsa"),
|
|
24303
|
+
path48.join(home, ".ssh", "id_ed25519"),
|
|
24304
|
+
path48.join(home, ".aws", "credentials"),
|
|
24305
|
+
path48.join(home, ".config", "gcloud", "application_default_credentials.json")
|
|
24126
24306
|
];
|
|
24127
24307
|
}
|
|
24128
24308
|
function checkSecrets(ctx) {
|
|
24129
|
-
const home = ctx.home ||
|
|
24309
|
+
const home = ctx.home || os42.homedir();
|
|
24130
24310
|
const findings = [];
|
|
24131
24311
|
const plaintext = [];
|
|
24132
24312
|
const plaintextPaths = [];
|
|
@@ -24159,7 +24339,7 @@ function checkSecrets(ctx) {
|
|
|
24159
24339
|
const credPaths = [];
|
|
24160
24340
|
for (const file of credentialMaterial(home)) {
|
|
24161
24341
|
try {
|
|
24162
|
-
if (
|
|
24342
|
+
if (fs47.statSync(file).isFile()) {
|
|
24163
24343
|
creds.push(displayPath(file, home));
|
|
24164
24344
|
credPaths.push(file);
|
|
24165
24345
|
}
|
|
@@ -24185,7 +24365,7 @@ function checkSecrets(ctx) {
|
|
|
24185
24365
|
|
|
24186
24366
|
// src/posture/egress.ts
|
|
24187
24367
|
init_config();
|
|
24188
|
-
import
|
|
24368
|
+
import fs48 from "fs";
|
|
24189
24369
|
|
|
24190
24370
|
// src/sandbox/templates.ts
|
|
24191
24371
|
var AGENT_NPM_PACKAGE = {
|
|
@@ -24306,7 +24486,7 @@ exec gosu "$RUN_AS_USER" bash -lc '
|
|
|
24306
24486
|
// src/posture/egress.ts
|
|
24307
24487
|
function sandboxEgressWallActive() {
|
|
24308
24488
|
try {
|
|
24309
|
-
return
|
|
24489
|
+
return fs48.existsSync(ALLOWED_DOMAINS_PATH);
|
|
24310
24490
|
} catch {
|
|
24311
24491
|
return false;
|
|
24312
24492
|
}
|
|
@@ -24416,25 +24596,25 @@ async function checkGate(ctx) {
|
|
|
24416
24596
|
|
|
24417
24597
|
// src/posture/supply-chain.ts
|
|
24418
24598
|
init_provenance();
|
|
24419
|
-
import
|
|
24420
|
-
import
|
|
24421
|
-
import
|
|
24599
|
+
import fs49 from "fs";
|
|
24600
|
+
import os43 from "os";
|
|
24601
|
+
import path49 from "path";
|
|
24422
24602
|
import { parse as parseToml3 } from "smol-toml";
|
|
24423
24603
|
var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "pnpx", "bunx", "dlx", "yarn", "pnpm", "bun"]);
|
|
24424
24604
|
function isNode9Managed(command, args = []) {
|
|
24425
24605
|
if (!command) return false;
|
|
24426
|
-
if (
|
|
24427
|
-
if (PACKAGE_RUNNERS.has(
|
|
24428
|
-
return args.some((a) => a === "node9" ||
|
|
24606
|
+
if (path49.basename(command).toLowerCase() === "node9") return true;
|
|
24607
|
+
if (PACKAGE_RUNNERS.has(path49.basename(command).toLowerCase())) {
|
|
24608
|
+
return args.some((a) => a === "node9" || path49.basename(a).toLowerCase() === "node9");
|
|
24429
24609
|
}
|
|
24430
24610
|
return false;
|
|
24431
24611
|
}
|
|
24432
24612
|
var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
|
|
24433
24613
|
function readServers(file, format, agent) {
|
|
24434
24614
|
try {
|
|
24435
|
-
const stat =
|
|
24615
|
+
const stat = fs49.statSync(file);
|
|
24436
24616
|
if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
|
|
24437
|
-
const text =
|
|
24617
|
+
const text = fs49.readFileSync(file, "utf8");
|
|
24438
24618
|
const map = format === "toml" ? parseToml3(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
|
|
24439
24619
|
if (!map || typeof map !== "object") return [];
|
|
24440
24620
|
return Object.entries(map).map(([name, v]) => ({
|
|
@@ -24448,7 +24628,7 @@ function readServers(file, format, agent) {
|
|
|
24448
24628
|
}
|
|
24449
24629
|
}
|
|
24450
24630
|
function checkSupplyChain(ctx) {
|
|
24451
|
-
const home = ctx.home ||
|
|
24631
|
+
const home = ctx.home || os43.homedir();
|
|
24452
24632
|
const servers = [];
|
|
24453
24633
|
for (const spec of AGENT_SPECS) {
|
|
24454
24634
|
if (!spec.mcpFile) continue;
|
|
@@ -24530,12 +24710,12 @@ async function checkPrivilege(ctx) {
|
|
|
24530
24710
|
}
|
|
24531
24711
|
|
|
24532
24712
|
// src/posture/containment.ts
|
|
24533
|
-
import
|
|
24713
|
+
import fs50 from "fs";
|
|
24534
24714
|
var ISOLATION_WEIGHT = 12;
|
|
24535
24715
|
function inContainer() {
|
|
24536
|
-
if (
|
|
24716
|
+
if (fs50.existsSync("/.dockerenv") || fs50.existsSync("/run/.containerenv")) return true;
|
|
24537
24717
|
try {
|
|
24538
|
-
const cgroup =
|
|
24718
|
+
const cgroup = fs50.readFileSync("/proc/1/cgroup", "utf8");
|
|
24539
24719
|
if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
|
|
24540
24720
|
} catch {
|
|
24541
24721
|
}
|
|
@@ -24574,7 +24754,7 @@ Lighter \u2014 harden in place, keep full host access (about +${Math.round(
|
|
|
24574
24754
|
}
|
|
24575
24755
|
|
|
24576
24756
|
// src/posture/inbound.ts
|
|
24577
|
-
import
|
|
24757
|
+
import fs51 from "fs";
|
|
24578
24758
|
var DB_EXPOSURE_WEIGHT = 4;
|
|
24579
24759
|
var KNOWN_SERVICE_PORTS = {
|
|
24580
24760
|
5432: "PostgreSQL",
|
|
@@ -24661,7 +24841,7 @@ function collectListeners() {
|
|
|
24661
24841
|
const byPort = /* @__PURE__ */ new Map();
|
|
24662
24842
|
for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
24663
24843
|
try {
|
|
24664
|
-
for (const l of parseListeners(
|
|
24844
|
+
for (const l of parseListeners(fs51.readFileSync(file, "utf8"))) {
|
|
24665
24845
|
if (!byPort.has(l.port)) byPort.set(l.port, l);
|
|
24666
24846
|
}
|
|
24667
24847
|
} catch {
|
|
@@ -24673,11 +24853,11 @@ function readProc(pid) {
|
|
|
24673
24853
|
let comm = "unknown";
|
|
24674
24854
|
let cmdline = "";
|
|
24675
24855
|
try {
|
|
24676
|
-
comm =
|
|
24856
|
+
comm = fs51.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
|
|
24677
24857
|
} catch {
|
|
24678
24858
|
}
|
|
24679
24859
|
try {
|
|
24680
|
-
cmdline =
|
|
24860
|
+
cmdline = fs51.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
|
|
24681
24861
|
} catch {
|
|
24682
24862
|
}
|
|
24683
24863
|
return { comm, cmdline };
|
|
@@ -24687,21 +24867,21 @@ function resolveProcesses(inodes) {
|
|
|
24687
24867
|
if (inodes.size === 0) return map;
|
|
24688
24868
|
let pids;
|
|
24689
24869
|
try {
|
|
24690
|
-
pids =
|
|
24870
|
+
pids = fs51.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
24691
24871
|
} catch {
|
|
24692
24872
|
return map;
|
|
24693
24873
|
}
|
|
24694
24874
|
for (const pid of pids) {
|
|
24695
24875
|
let fds;
|
|
24696
24876
|
try {
|
|
24697
|
-
fds =
|
|
24877
|
+
fds = fs51.readdirSync(`/proc/${pid}/fd`);
|
|
24698
24878
|
} catch {
|
|
24699
24879
|
continue;
|
|
24700
24880
|
}
|
|
24701
24881
|
for (const fd of fds) {
|
|
24702
24882
|
let link;
|
|
24703
24883
|
try {
|
|
24704
|
-
link =
|
|
24884
|
+
link = fs51.readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
24705
24885
|
} catch {
|
|
24706
24886
|
continue;
|
|
24707
24887
|
}
|
|
@@ -24772,9 +24952,9 @@ function checkInbound(ctx) {
|
|
|
24772
24952
|
|
|
24773
24953
|
// src/posture/coverage.ts
|
|
24774
24954
|
init_config();
|
|
24775
|
-
import
|
|
24955
|
+
import os44 from "os";
|
|
24776
24956
|
function checkCoverage(ctx) {
|
|
24777
|
-
const home = ctx.home ||
|
|
24957
|
+
const home = ctx.home || os44.homedir();
|
|
24778
24958
|
const findings = [];
|
|
24779
24959
|
const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
|
|
24780
24960
|
if (protectedAgents.length === 0) {
|
|
@@ -24982,7 +25162,7 @@ async function runChecks(checks, ctx) {
|
|
|
24982
25162
|
}
|
|
24983
25163
|
async function runPosture(opts = {}) {
|
|
24984
25164
|
const ctx = {
|
|
24985
|
-
home: opts.home ??
|
|
25165
|
+
home: opts.home ?? os45.homedir(),
|
|
24986
25166
|
cwd: opts.cwd ?? process.cwd(),
|
|
24987
25167
|
agent: opts.agent
|
|
24988
25168
|
};
|
|
@@ -25252,9 +25432,9 @@ function registerPostureCommand(program2) {
|
|
|
25252
25432
|
init_config();
|
|
25253
25433
|
init_dist();
|
|
25254
25434
|
import chalk26 from "chalk";
|
|
25255
|
-
import
|
|
25256
|
-
import
|
|
25257
|
-
import
|
|
25435
|
+
import fs52 from "fs";
|
|
25436
|
+
import os46 from "os";
|
|
25437
|
+
import path50 from "path";
|
|
25258
25438
|
var DEFAULT_EGRESS = {
|
|
25259
25439
|
enabled: false,
|
|
25260
25440
|
mode: "review",
|
|
@@ -25263,12 +25443,12 @@ var DEFAULT_EGRESS = {
|
|
|
25263
25443
|
allowPrivate: true
|
|
25264
25444
|
};
|
|
25265
25445
|
function configPath() {
|
|
25266
|
-
return
|
|
25446
|
+
return path50.join(os46.homedir(), ".node9", "config.json");
|
|
25267
25447
|
}
|
|
25268
25448
|
function readRawConfig() {
|
|
25269
25449
|
let text;
|
|
25270
25450
|
try {
|
|
25271
|
-
text =
|
|
25451
|
+
text = fs52.readFileSync(configPath(), "utf8");
|
|
25272
25452
|
} catch (err2) {
|
|
25273
25453
|
if (err2.code === "ENOENT") return {};
|
|
25274
25454
|
throw err2;
|
|
@@ -25283,8 +25463,8 @@ function readRawConfig() {
|
|
|
25283
25463
|
}
|
|
25284
25464
|
function writeRawConfig(config) {
|
|
25285
25465
|
const p = configPath();
|
|
25286
|
-
|
|
25287
|
-
|
|
25466
|
+
fs52.mkdirSync(path50.dirname(p), { recursive: true });
|
|
25467
|
+
fs52.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
25288
25468
|
}
|
|
25289
25469
|
function applyEgress(config, change) {
|
|
25290
25470
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -25378,13 +25558,13 @@ function registerEgressCommand(program2) {
|
|
|
25378
25558
|
// src/cli/commands/sandbox.ts
|
|
25379
25559
|
init_config();
|
|
25380
25560
|
import chalk27 from "chalk";
|
|
25381
|
-
import
|
|
25382
|
-
import
|
|
25561
|
+
import fs55 from "fs";
|
|
25562
|
+
import path53 from "path";
|
|
25383
25563
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
25384
25564
|
|
|
25385
25565
|
// src/sandbox/config.ts
|
|
25386
|
-
import
|
|
25387
|
-
import
|
|
25566
|
+
import fs53 from "fs";
|
|
25567
|
+
import path51 from "path";
|
|
25388
25568
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
25389
25569
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
25390
25570
|
var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
|
|
@@ -25457,16 +25637,16 @@ function scaffoldSandboxYaml(agent) {
|
|
|
25457
25637
|
return header + stringifyYaml(defaultSandboxConfig(agent));
|
|
25458
25638
|
}
|
|
25459
25639
|
function sandboxConfigPath(cwd = process.cwd()) {
|
|
25460
|
-
return
|
|
25640
|
+
return path51.join(cwd, SANDBOX_CONFIG_FILE);
|
|
25461
25641
|
}
|
|
25462
25642
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
25463
25643
|
const p = sandboxConfigPath(cwd);
|
|
25464
|
-
if (!
|
|
25644
|
+
if (!fs53.existsSync(p)) {
|
|
25465
25645
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
25466
25646
|
}
|
|
25467
25647
|
let raw;
|
|
25468
25648
|
try {
|
|
25469
|
-
raw = parseYaml(
|
|
25649
|
+
raw = parseYaml(fs53.readFileSync(p, "utf-8"));
|
|
25470
25650
|
} catch (err2) {
|
|
25471
25651
|
throw new Error(
|
|
25472
25652
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -25521,13 +25701,13 @@ function compileAllowlist(input) {
|
|
|
25521
25701
|
}
|
|
25522
25702
|
|
|
25523
25703
|
// src/sandbox/runtime.ts
|
|
25524
|
-
import
|
|
25525
|
-
import
|
|
25526
|
-
import
|
|
25704
|
+
import fs54 from "fs";
|
|
25705
|
+
import os47 from "os";
|
|
25706
|
+
import path52 from "path";
|
|
25527
25707
|
import crypto8 from "crypto";
|
|
25528
25708
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
25529
25709
|
function sandboxDataDir(cwd = process.cwd()) {
|
|
25530
|
-
return
|
|
25710
|
+
return path52.join(cwd, ".node9", "sandbox", "data");
|
|
25531
25711
|
}
|
|
25532
25712
|
function detectEngine(engine) {
|
|
25533
25713
|
const r = spawnSync5(engine, ["--version"], { encoding: "utf-8" });
|
|
@@ -25538,7 +25718,7 @@ function detectEngine(engine) {
|
|
|
25538
25718
|
}
|
|
25539
25719
|
function agentCredentialsMount(agent) {
|
|
25540
25720
|
const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
|
|
25541
|
-
return { hostPath:
|
|
25721
|
+
return { hostPath: path52.join(os47.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
|
|
25542
25722
|
}
|
|
25543
25723
|
function buildRunArgs(opts) {
|
|
25544
25724
|
const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
|
|
@@ -25548,7 +25728,7 @@ function buildRunArgs(opts) {
|
|
|
25548
25728
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
25549
25729
|
if (config.node9.mountAgentCredentials) {
|
|
25550
25730
|
const creds = agentCredentialsMount(config.agent);
|
|
25551
|
-
if (
|
|
25731
|
+
if (fs54.existsSync(creds.hostPath)) {
|
|
25552
25732
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
25553
25733
|
}
|
|
25554
25734
|
}
|
|
@@ -25566,30 +25746,30 @@ function imageContentHash(dockerfile, entrypoint) {
|
|
|
25566
25746
|
return crypto8.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
|
|
25567
25747
|
}
|
|
25568
25748
|
function sandboxBuildDir(cwd = process.cwd()) {
|
|
25569
|
-
return
|
|
25749
|
+
return path52.join(cwd, ".node9", "sandbox", "build");
|
|
25570
25750
|
}
|
|
25571
25751
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
25572
25752
|
const dir = sandboxBuildDir(cwd);
|
|
25573
|
-
|
|
25574
|
-
|
|
25575
|
-
|
|
25753
|
+
fs54.mkdirSync(dir, { recursive: true });
|
|
25754
|
+
fs54.writeFileSync(path52.join(dir, "Dockerfile"), dockerfile);
|
|
25755
|
+
fs54.writeFileSync(path52.join(dir, "entrypoint.sh"), entrypoint);
|
|
25576
25756
|
return dir;
|
|
25577
25757
|
}
|
|
25578
25758
|
function writeAllowlist(cwd, hosts) {
|
|
25579
|
-
const dir =
|
|
25580
|
-
|
|
25581
|
-
const p =
|
|
25582
|
-
|
|
25759
|
+
const dir = path52.join(cwd, ".node9", "sandbox");
|
|
25760
|
+
fs54.mkdirSync(dir, { recursive: true });
|
|
25761
|
+
const p = path52.join(dir, "allowed-domains.txt");
|
|
25762
|
+
fs54.writeFileSync(p, hosts.join("\n") + "\n");
|
|
25583
25763
|
return p;
|
|
25584
25764
|
}
|
|
25585
25765
|
function resolveHomePath(p) {
|
|
25586
|
-
return p.startsWith("~") ?
|
|
25766
|
+
return p.startsWith("~") ? path52.join(os47.homedir(), p.slice(1)) : path52.resolve(p);
|
|
25587
25767
|
}
|
|
25588
25768
|
|
|
25589
25769
|
// src/cli/commands/sandbox.ts
|
|
25590
25770
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
25591
|
-
|
|
25592
|
-
const configPath2 =
|
|
25771
|
+
fs55.mkdirSync(dataDir, { recursive: true });
|
|
25772
|
+
const configPath2 = path53.join(dataDir, "config.json");
|
|
25593
25773
|
const seed = {
|
|
25594
25774
|
settings: {
|
|
25595
25775
|
approvers: {
|
|
@@ -25600,7 +25780,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
25600
25780
|
}
|
|
25601
25781
|
}
|
|
25602
25782
|
};
|
|
25603
|
-
|
|
25783
|
+
fs55.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
25604
25784
|
}
|
|
25605
25785
|
function registerSandboxCommand(program2, version2) {
|
|
25606
25786
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -25608,13 +25788,13 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25608
25788
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
25609
25789
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
25610
25790
|
const p = sandboxConfigPath();
|
|
25611
|
-
if (
|
|
25791
|
+
if (fs55.existsSync(p)) {
|
|
25612
25792
|
console.log(
|
|
25613
25793
|
chalk27.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
25614
25794
|
);
|
|
25615
25795
|
return;
|
|
25616
25796
|
}
|
|
25617
|
-
|
|
25797
|
+
fs55.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
25618
25798
|
console.log(
|
|
25619
25799
|
chalk27.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk27.dim(` (agent: ${agent})`)
|
|
25620
25800
|
);
|
|
@@ -25654,8 +25834,8 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25654
25834
|
const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
|
|
25655
25835
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
25656
25836
|
const image = sandbox.runtime.image;
|
|
25657
|
-
const hashFile =
|
|
25658
|
-
const lastHash =
|
|
25837
|
+
const hashFile = path53.join(sandboxBuildDir(cwd), ".image-hash");
|
|
25838
|
+
const lastHash = fs55.existsSync(hashFile) ? fs55.readFileSync(hashFile, "utf-8").trim() : "";
|
|
25659
25839
|
const imageExists = spawnSync6(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
25660
25840
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
25661
25841
|
if (needBuild) {
|
|
@@ -25667,7 +25847,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25667
25847
|
console.error(chalk27.red(" build failed."));
|
|
25668
25848
|
process.exit(b.status ?? 1);
|
|
25669
25849
|
}
|
|
25670
|
-
|
|
25850
|
+
fs55.writeFileSync(hashFile, hash);
|
|
25671
25851
|
}
|
|
25672
25852
|
const dataDir = sandboxDataDir(cwd);
|
|
25673
25853
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -25681,7 +25861,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25681
25861
|
});
|
|
25682
25862
|
if (sandbox.node9.mountAgentCredentials) {
|
|
25683
25863
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
25684
|
-
if (
|
|
25864
|
+
if (fs55.existsSync(creds.hostPath)) {
|
|
25685
25865
|
console.log(chalk27.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
25686
25866
|
} else {
|
|
25687
25867
|
console.log(
|
|
@@ -25697,20 +25877,20 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25697
25877
|
process.exit(r.status ?? 0);
|
|
25698
25878
|
});
|
|
25699
25879
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
25700
|
-
const auditPath =
|
|
25701
|
-
if (!
|
|
25880
|
+
const auditPath = path53.join(sandboxDataDir(), "audit.log");
|
|
25881
|
+
if (!fs55.existsSync(auditPath)) {
|
|
25702
25882
|
console.log(chalk27.dim(" no sandbox audit yet."));
|
|
25703
25883
|
return;
|
|
25704
25884
|
}
|
|
25705
25885
|
spawnSync6("tail", ["-f", auditPath], { stdio: "inherit" });
|
|
25706
25886
|
});
|
|
25707
25887
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
25708
|
-
const auditPath =
|
|
25709
|
-
if (!
|
|
25888
|
+
const auditPath = path53.join(sandboxDataDir(), "audit.log");
|
|
25889
|
+
if (!fs55.existsSync(auditPath)) {
|
|
25710
25890
|
console.log(chalk27.dim(" no sandbox audit yet."));
|
|
25711
25891
|
return;
|
|
25712
25892
|
}
|
|
25713
|
-
process.stdout.write(
|
|
25893
|
+
process.stdout.write(fs55.readFileSync(auditPath, "utf-8"));
|
|
25714
25894
|
});
|
|
25715
25895
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
25716
25896
|
const cwd = process.cwd();
|
|
@@ -25724,7 +25904,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
25724
25904
|
stdio: "ignore"
|
|
25725
25905
|
});
|
|
25726
25906
|
}
|
|
25727
|
-
|
|
25907
|
+
fs55.rmSync(path53.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
25728
25908
|
console.log(chalk27.green(" \u2713 sandbox image + build + data removed."));
|
|
25729
25909
|
});
|
|
25730
25910
|
}
|
|
@@ -25735,9 +25915,9 @@ init_litellm();
|
|
|
25735
25915
|
init_cost_gemini();
|
|
25736
25916
|
init_cost_codex();
|
|
25737
25917
|
import chalk28 from "chalk";
|
|
25738
|
-
import
|
|
25739
|
-
import
|
|
25740
|
-
import
|
|
25918
|
+
import fs56 from "fs";
|
|
25919
|
+
import path54 from "path";
|
|
25920
|
+
import os48 from "os";
|
|
25741
25921
|
function modelPrice(model) {
|
|
25742
25922
|
const t = pricingFor(model);
|
|
25743
25923
|
if (!t) return null;
|
|
@@ -25754,10 +25934,10 @@ function encodeProjectPath(projectPath) {
|
|
|
25754
25934
|
}
|
|
25755
25935
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
25756
25936
|
const encoded = encodeProjectPath(projectPath);
|
|
25757
|
-
return
|
|
25937
|
+
return path54.join(os48.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
25758
25938
|
}
|
|
25759
25939
|
function projectLabel(projectPath) {
|
|
25760
|
-
return projectPath.replace(
|
|
25940
|
+
return projectPath.replace(os48.homedir(), "~");
|
|
25761
25941
|
}
|
|
25762
25942
|
function parseHistoryLines(lines) {
|
|
25763
25943
|
const entries = [];
|
|
@@ -25826,10 +26006,10 @@ function parseSessionLines(lines) {
|
|
|
25826
26006
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
25827
26007
|
}
|
|
25828
26008
|
function loadAuditEntries(auditPath) {
|
|
25829
|
-
const aPath = auditPath ??
|
|
26009
|
+
const aPath = auditPath ?? path54.join(os48.homedir(), ".node9", "audit.log");
|
|
25830
26010
|
let raw;
|
|
25831
26011
|
try {
|
|
25832
|
-
raw =
|
|
26012
|
+
raw = fs56.readFileSync(aPath, "utf-8");
|
|
25833
26013
|
} catch {
|
|
25834
26014
|
return [];
|
|
25835
26015
|
}
|
|
@@ -25865,8 +26045,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
25865
26045
|
return result;
|
|
25866
26046
|
}
|
|
25867
26047
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
25868
|
-
const tmpDir =
|
|
25869
|
-
if (!
|
|
26048
|
+
const tmpDir = path54.join(os48.homedir(), ".gemini", "tmp");
|
|
26049
|
+
if (!fs56.existsSync(tmpDir)) return [];
|
|
25870
26050
|
const cutoff = days !== null ? (() => {
|
|
25871
26051
|
const d = /* @__PURE__ */ new Date();
|
|
25872
26052
|
d.setDate(d.getDate() - days);
|
|
@@ -25875,35 +26055,35 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
25875
26055
|
})() : null;
|
|
25876
26056
|
let slugDirs;
|
|
25877
26057
|
try {
|
|
25878
|
-
slugDirs =
|
|
26058
|
+
slugDirs = fs56.readdirSync(tmpDir);
|
|
25879
26059
|
} catch {
|
|
25880
26060
|
return [];
|
|
25881
26061
|
}
|
|
25882
26062
|
const summaries = [];
|
|
25883
26063
|
for (const slug of slugDirs) {
|
|
25884
|
-
const slugPath =
|
|
26064
|
+
const slugPath = path54.join(tmpDir, slug);
|
|
25885
26065
|
try {
|
|
25886
|
-
if (!
|
|
26066
|
+
if (!fs56.statSync(slugPath).isDirectory()) continue;
|
|
25887
26067
|
} catch {
|
|
25888
26068
|
continue;
|
|
25889
26069
|
}
|
|
25890
|
-
let projectRoot =
|
|
26070
|
+
let projectRoot = path54.join(os48.homedir(), slug);
|
|
25891
26071
|
try {
|
|
25892
|
-
projectRoot =
|
|
26072
|
+
projectRoot = fs56.readFileSync(path54.join(slugPath, ".project_root"), "utf-8").trim();
|
|
25893
26073
|
} catch {
|
|
25894
26074
|
}
|
|
25895
|
-
const chatsDir =
|
|
25896
|
-
if (!
|
|
26075
|
+
const chatsDir = path54.join(slugPath, "chats");
|
|
26076
|
+
if (!fs56.existsSync(chatsDir)) continue;
|
|
25897
26077
|
let chatFiles;
|
|
25898
26078
|
try {
|
|
25899
|
-
chatFiles =
|
|
26079
|
+
chatFiles = fs56.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
25900
26080
|
} catch {
|
|
25901
26081
|
continue;
|
|
25902
26082
|
}
|
|
25903
26083
|
for (const chatFile of chatFiles) {
|
|
25904
26084
|
let raw;
|
|
25905
26085
|
try {
|
|
25906
|
-
raw =
|
|
26086
|
+
raw = fs56.readFileSync(path54.join(chatsDir, chatFile), "utf-8");
|
|
25907
26087
|
} catch {
|
|
25908
26088
|
continue;
|
|
25909
26089
|
}
|
|
@@ -25983,8 +26163,8 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
25983
26163
|
return summaries;
|
|
25984
26164
|
}
|
|
25985
26165
|
function buildCodexSessions(days, allAuditEntries) {
|
|
25986
|
-
const sessionsBase =
|
|
25987
|
-
if (!
|
|
26166
|
+
const sessionsBase = path54.join(os48.homedir(), ".codex", "sessions");
|
|
26167
|
+
if (!fs56.existsSync(sessionsBase)) return [];
|
|
25988
26168
|
const cutoff = days !== null ? (() => {
|
|
25989
26169
|
const d = /* @__PURE__ */ new Date();
|
|
25990
26170
|
d.setDate(d.getDate() - days);
|
|
@@ -25993,29 +26173,29 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
25993
26173
|
})() : null;
|
|
25994
26174
|
const jsonlFiles = [];
|
|
25995
26175
|
try {
|
|
25996
|
-
for (const year of
|
|
25997
|
-
const yearPath =
|
|
26176
|
+
for (const year of fs56.readdirSync(sessionsBase)) {
|
|
26177
|
+
const yearPath = path54.join(sessionsBase, year);
|
|
25998
26178
|
try {
|
|
25999
|
-
if (!
|
|
26179
|
+
if (!fs56.statSync(yearPath).isDirectory()) continue;
|
|
26000
26180
|
} catch {
|
|
26001
26181
|
continue;
|
|
26002
26182
|
}
|
|
26003
|
-
for (const month of
|
|
26004
|
-
const monthPath =
|
|
26183
|
+
for (const month of fs56.readdirSync(yearPath)) {
|
|
26184
|
+
const monthPath = path54.join(yearPath, month);
|
|
26005
26185
|
try {
|
|
26006
|
-
if (!
|
|
26186
|
+
if (!fs56.statSync(monthPath).isDirectory()) continue;
|
|
26007
26187
|
} catch {
|
|
26008
26188
|
continue;
|
|
26009
26189
|
}
|
|
26010
|
-
for (const day of
|
|
26011
|
-
const dayPath =
|
|
26190
|
+
for (const day of fs56.readdirSync(monthPath)) {
|
|
26191
|
+
const dayPath = path54.join(monthPath, day);
|
|
26012
26192
|
try {
|
|
26013
|
-
if (!
|
|
26193
|
+
if (!fs56.statSync(dayPath).isDirectory()) continue;
|
|
26014
26194
|
} catch {
|
|
26015
26195
|
continue;
|
|
26016
26196
|
}
|
|
26017
|
-
for (const file of
|
|
26018
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
26197
|
+
for (const file of fs56.readdirSync(dayPath)) {
|
|
26198
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path54.join(dayPath, file));
|
|
26019
26199
|
}
|
|
26020
26200
|
}
|
|
26021
26201
|
}
|
|
@@ -26027,7 +26207,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
26027
26207
|
for (const filePath of jsonlFiles) {
|
|
26028
26208
|
let lines;
|
|
26029
26209
|
try {
|
|
26030
|
-
lines =
|
|
26210
|
+
lines = fs56.readFileSync(filePath, "utf-8").split("\n");
|
|
26031
26211
|
} catch {
|
|
26032
26212
|
continue;
|
|
26033
26213
|
}
|
|
@@ -26113,10 +26293,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
26113
26293
|
return summaries;
|
|
26114
26294
|
}
|
|
26115
26295
|
function buildSessions(days, historyPath) {
|
|
26116
|
-
const hPath = historyPath ??
|
|
26296
|
+
const hPath = historyPath ?? path54.join(os48.homedir(), ".claude", "history.jsonl");
|
|
26117
26297
|
let historyRaw = "";
|
|
26118
26298
|
try {
|
|
26119
|
-
historyRaw =
|
|
26299
|
+
historyRaw = fs56.readFileSync(hPath, "utf-8");
|
|
26120
26300
|
} catch {
|
|
26121
26301
|
}
|
|
26122
26302
|
const cutoff = days !== null ? (() => {
|
|
@@ -26140,7 +26320,7 @@ function buildSessions(days, historyPath) {
|
|
|
26140
26320
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
26141
26321
|
let sessionLines = [];
|
|
26142
26322
|
try {
|
|
26143
|
-
sessionLines =
|
|
26323
|
+
sessionLines = fs56.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
26144
26324
|
} catch {
|
|
26145
26325
|
}
|
|
26146
26326
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -26534,12 +26714,12 @@ function registerSessionTaintCommand(program2) {
|
|
|
26534
26714
|
|
|
26535
26715
|
// src/cli/commands/skill-pin.ts
|
|
26536
26716
|
import chalk30 from "chalk";
|
|
26537
|
-
import
|
|
26538
|
-
import
|
|
26539
|
-
import
|
|
26717
|
+
import fs57 from "fs";
|
|
26718
|
+
import os49 from "os";
|
|
26719
|
+
import path55 from "path";
|
|
26540
26720
|
function wipeSkillSessions() {
|
|
26541
26721
|
try {
|
|
26542
|
-
|
|
26722
|
+
fs57.rmSync(path55.join(os49.homedir(), ".node9", "skill-sessions"), {
|
|
26543
26723
|
recursive: true,
|
|
26544
26724
|
force: true
|
|
26545
26725
|
});
|
|
@@ -26621,15 +26801,15 @@ function registerSkillPinCommand(program2) {
|
|
|
26621
26801
|
}
|
|
26622
26802
|
|
|
26623
26803
|
// src/cli/commands/decisions.ts
|
|
26624
|
-
import
|
|
26625
|
-
import
|
|
26626
|
-
import
|
|
26804
|
+
import fs58 from "fs";
|
|
26805
|
+
import os50 from "os";
|
|
26806
|
+
import path56 from "path";
|
|
26627
26807
|
import chalk31 from "chalk";
|
|
26628
|
-
var DECISIONS_FILE2 =
|
|
26808
|
+
var DECISIONS_FILE2 = path56.join(os50.homedir(), ".node9", "decisions.json");
|
|
26629
26809
|
function readDecisions() {
|
|
26630
26810
|
try {
|
|
26631
|
-
if (!
|
|
26632
|
-
const raw =
|
|
26811
|
+
if (!fs58.existsSync(DECISIONS_FILE2)) return {};
|
|
26812
|
+
const raw = fs58.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
26633
26813
|
const parsed = JSON.parse(raw);
|
|
26634
26814
|
const out = {};
|
|
26635
26815
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -26641,11 +26821,11 @@ function readDecisions() {
|
|
|
26641
26821
|
}
|
|
26642
26822
|
}
|
|
26643
26823
|
function writeDecisions(d) {
|
|
26644
|
-
const dir =
|
|
26645
|
-
if (!
|
|
26824
|
+
const dir = path56.dirname(DECISIONS_FILE2);
|
|
26825
|
+
if (!fs58.existsSync(dir)) fs58.mkdirSync(dir, { recursive: true });
|
|
26646
26826
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
26647
|
-
|
|
26648
|
-
|
|
26827
|
+
fs58.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
26828
|
+
fs58.renameSync(tmp, DECISIONS_FILE2);
|
|
26649
26829
|
}
|
|
26650
26830
|
function registerDecisionsCommand(program2) {
|
|
26651
26831
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -26702,18 +26882,18 @@ Persistent decisions (${entries.length})
|
|
|
26702
26882
|
|
|
26703
26883
|
// src/cli/commands/dlp.ts
|
|
26704
26884
|
import chalk32 from "chalk";
|
|
26705
|
-
import
|
|
26706
|
-
import
|
|
26707
|
-
import
|
|
26708
|
-
var AUDIT_LOG =
|
|
26709
|
-
var RESOLVED_FILE =
|
|
26885
|
+
import fs59 from "fs";
|
|
26886
|
+
import path57 from "path";
|
|
26887
|
+
import os51 from "os";
|
|
26888
|
+
var AUDIT_LOG = path57.join(os51.homedir(), ".node9", "audit.log");
|
|
26889
|
+
var RESOLVED_FILE = path57.join(os51.homedir(), ".node9", "dlp-resolved.json");
|
|
26710
26890
|
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
26711
26891
|
function stripAnsi(s) {
|
|
26712
26892
|
return s.replace(ANSI_RE, "");
|
|
26713
26893
|
}
|
|
26714
26894
|
function loadResolved() {
|
|
26715
26895
|
try {
|
|
26716
|
-
const raw = JSON.parse(
|
|
26896
|
+
const raw = JSON.parse(fs59.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
26717
26897
|
return new Set(raw);
|
|
26718
26898
|
} catch {
|
|
26719
26899
|
return /* @__PURE__ */ new Set();
|
|
@@ -26721,13 +26901,13 @@ function loadResolved() {
|
|
|
26721
26901
|
}
|
|
26722
26902
|
function saveResolved(resolved) {
|
|
26723
26903
|
try {
|
|
26724
|
-
|
|
26904
|
+
fs59.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
26725
26905
|
} catch {
|
|
26726
26906
|
}
|
|
26727
26907
|
}
|
|
26728
26908
|
function loadDlpFindings() {
|
|
26729
|
-
if (!
|
|
26730
|
-
return
|
|
26909
|
+
if (!fs59.existsSync(AUDIT_LOG)) return [];
|
|
26910
|
+
return fs59.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
26731
26911
|
if (!line.trim()) return [];
|
|
26732
26912
|
try {
|
|
26733
26913
|
const e = JSON.parse(line);
|
|
@@ -26826,14 +27006,14 @@ function registerDlpCommand(program2) {
|
|
|
26826
27006
|
// src/cli/commands/mask.ts
|
|
26827
27007
|
init_dlp();
|
|
26828
27008
|
import chalk33 from "chalk";
|
|
26829
|
-
import
|
|
26830
|
-
import
|
|
26831
|
-
import
|
|
27009
|
+
import fs60 from "fs";
|
|
27010
|
+
import path58 from "path";
|
|
27011
|
+
import os52 from "os";
|
|
26832
27012
|
function findJsonlFiles(dir) {
|
|
26833
27013
|
const results = [];
|
|
26834
|
-
if (!
|
|
26835
|
-
for (const entry of
|
|
26836
|
-
const full =
|
|
27014
|
+
if (!fs60.existsSync(dir)) return results;
|
|
27015
|
+
for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
|
|
27016
|
+
const full = path58.join(dir, entry.name);
|
|
26837
27017
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
26838
27018
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
26839
27019
|
}
|
|
@@ -26876,7 +27056,7 @@ function redactJson(obj) {
|
|
|
26876
27056
|
function processFile(filePath, dryRun) {
|
|
26877
27057
|
let raw;
|
|
26878
27058
|
try {
|
|
26879
|
-
raw =
|
|
27059
|
+
raw = fs60.readFileSync(filePath, "utf-8");
|
|
26880
27060
|
} catch {
|
|
26881
27061
|
return { redactedLines: 0, patterns: [] };
|
|
26882
27062
|
}
|
|
@@ -26908,14 +27088,14 @@ function processFile(filePath, dryRun) {
|
|
|
26908
27088
|
}
|
|
26909
27089
|
}
|
|
26910
27090
|
if (!dryRun && redactedLines > 0) {
|
|
26911
|
-
|
|
27091
|
+
fs60.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
26912
27092
|
}
|
|
26913
27093
|
return { redactedLines, patterns };
|
|
26914
27094
|
}
|
|
26915
27095
|
function processJsonFile(filePath, dryRun) {
|
|
26916
27096
|
let raw;
|
|
26917
27097
|
try {
|
|
26918
|
-
raw =
|
|
27098
|
+
raw = fs60.readFileSync(filePath, "utf-8");
|
|
26919
27099
|
} catch {
|
|
26920
27100
|
return { redactedLines: 0, patterns: [] };
|
|
26921
27101
|
}
|
|
@@ -26928,15 +27108,15 @@ function processJsonFile(filePath, dryRun) {
|
|
|
26928
27108
|
const { value, modified, found } = redactJson(parsed);
|
|
26929
27109
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
26930
27110
|
if (!dryRun) {
|
|
26931
|
-
|
|
27111
|
+
fs60.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
26932
27112
|
}
|
|
26933
27113
|
return { redactedLines: 1, patterns: found };
|
|
26934
27114
|
}
|
|
26935
27115
|
function findJsonFiles(dir) {
|
|
26936
27116
|
const results = [];
|
|
26937
|
-
if (!
|
|
26938
|
-
for (const entry of
|
|
26939
|
-
const full =
|
|
27117
|
+
if (!fs60.existsSync(dir)) return results;
|
|
27118
|
+
for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
|
|
27119
|
+
const full = path58.join(dir, entry.name);
|
|
26940
27120
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
26941
27121
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
26942
27122
|
}
|
|
@@ -26945,9 +27125,9 @@ function findJsonFiles(dir) {
|
|
|
26945
27125
|
function registerMaskCommand(program2) {
|
|
26946
27126
|
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) => {
|
|
26947
27127
|
const dryRun = !!options.dryRun;
|
|
26948
|
-
const home =
|
|
26949
|
-
const claudeDir =
|
|
26950
|
-
const geminiDir =
|
|
27128
|
+
const home = os52.homedir();
|
|
27129
|
+
const claudeDir = path58.join(home, ".claude", "projects");
|
|
27130
|
+
const geminiDir = path58.join(home, ".gemini", "tmp");
|
|
26951
27131
|
const allFiles = [
|
|
26952
27132
|
...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
|
|
26953
27133
|
...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
|
|
@@ -26955,7 +27135,7 @@ function registerMaskCommand(program2) {
|
|
|
26955
27135
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
26956
27136
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
26957
27137
|
try {
|
|
26958
|
-
return
|
|
27138
|
+
return fs60.statSync(f.path).mtime >= cutoff;
|
|
26959
27139
|
} catch {
|
|
26960
27140
|
return false;
|
|
26961
27141
|
}
|
|
@@ -27011,20 +27191,20 @@ function registerMaskCommand(program2) {
|
|
|
27011
27191
|
// src/cli.ts
|
|
27012
27192
|
init_blast();
|
|
27013
27193
|
var { version } = JSON.parse(
|
|
27014
|
-
|
|
27194
|
+
fs63.readFileSync(path61.join(__dirname, "../package.json"), "utf-8")
|
|
27015
27195
|
);
|
|
27016
27196
|
var program = new Command();
|
|
27017
27197
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
27018
27198
|
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) => {
|
|
27019
27199
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
27020
|
-
const credPath =
|
|
27021
|
-
if (!
|
|
27022
|
-
|
|
27200
|
+
const credPath = path61.join(os55.homedir(), ".node9", "credentials.json");
|
|
27201
|
+
if (!fs63.existsSync(path61.dirname(credPath)))
|
|
27202
|
+
fs63.mkdirSync(path61.dirname(credPath), { recursive: true });
|
|
27023
27203
|
const profileName = options.profile || "default";
|
|
27024
27204
|
let existingCreds = {};
|
|
27025
27205
|
try {
|
|
27026
|
-
if (
|
|
27027
|
-
const raw = JSON.parse(
|
|
27206
|
+
if (fs63.existsSync(credPath)) {
|
|
27207
|
+
const raw = JSON.parse(fs63.readFileSync(credPath, "utf-8"));
|
|
27028
27208
|
if (raw.apiKey) {
|
|
27029
27209
|
existingCreds = {
|
|
27030
27210
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -27036,14 +27216,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
27036
27216
|
} catch {
|
|
27037
27217
|
}
|
|
27038
27218
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
27039
|
-
|
|
27219
|
+
fs63.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
27040
27220
|
let effectiveCloud = null;
|
|
27041
27221
|
if (profileName === "default") {
|
|
27042
|
-
const configPath2 =
|
|
27222
|
+
const configPath2 = path61.join(os55.homedir(), ".node9", "config.json");
|
|
27043
27223
|
let config = {};
|
|
27044
27224
|
try {
|
|
27045
|
-
if (
|
|
27046
|
-
config = JSON.parse(
|
|
27225
|
+
if (fs63.existsSync(configPath2))
|
|
27226
|
+
config = JSON.parse(fs63.readFileSync(configPath2, "utf-8"));
|
|
27047
27227
|
} catch {
|
|
27048
27228
|
}
|
|
27049
27229
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -27058,9 +27238,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
27058
27238
|
approvers.cloud = false;
|
|
27059
27239
|
}
|
|
27060
27240
|
s.approvers = approvers;
|
|
27061
|
-
if (!
|
|
27062
|
-
|
|
27063
|
-
|
|
27241
|
+
if (!fs63.existsSync(path61.dirname(configPath2)))
|
|
27242
|
+
fs63.mkdirSync(path61.dirname(configPath2), { recursive: true });
|
|
27243
|
+
fs63.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
|
|
27064
27244
|
effectiveCloud = approvers.cloud === true;
|
|
27065
27245
|
}
|
|
27066
27246
|
if (options.profile && profileName !== "default") {
|
|
@@ -27238,15 +27418,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
27238
27418
|
}
|
|
27239
27419
|
}
|
|
27240
27420
|
if (options.purge) {
|
|
27241
|
-
const node9Dir =
|
|
27242
|
-
if (
|
|
27421
|
+
const node9Dir = path61.join(os55.homedir(), ".node9");
|
|
27422
|
+
if (fs63.existsSync(node9Dir)) {
|
|
27243
27423
|
const confirmed = await confirm2({
|
|
27244
27424
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
27245
27425
|
default: false
|
|
27246
27426
|
});
|
|
27247
27427
|
if (confirmed) {
|
|
27248
|
-
|
|
27249
|
-
if (
|
|
27428
|
+
fs63.rmSync(node9Dir, { recursive: true });
|
|
27429
|
+
if (fs63.existsSync(node9Dir)) {
|
|
27250
27430
|
console.error(
|
|
27251
27431
|
chalk35.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
27252
27432
|
);
|
|
@@ -27361,7 +27541,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
27361
27541
|
});
|
|
27362
27542
|
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
27363
27543
|
try {
|
|
27364
|
-
const dashboardPath =
|
|
27544
|
+
const dashboardPath = path61.join(__dirname, "dashboard.mjs");
|
|
27365
27545
|
const dynamicImport = new Function("id", "return import(id)");
|
|
27366
27546
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
27367
27547
|
await mod.startMonitor();
|
|
@@ -27399,14 +27579,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
27399
27579
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
27400
27580
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
27401
27581
|
if (subcommand === "debug") {
|
|
27402
|
-
const flagFile =
|
|
27582
|
+
const flagFile = path61.join(os55.homedir(), ".node9", "hud-debug");
|
|
27403
27583
|
if (state === "on") {
|
|
27404
|
-
|
|
27405
|
-
|
|
27584
|
+
fs63.mkdirSync(path61.dirname(flagFile), { recursive: true });
|
|
27585
|
+
fs63.writeFileSync(flagFile, "");
|
|
27406
27586
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
27407
27587
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
27408
27588
|
} else if (state === "off") {
|
|
27409
|
-
if (
|
|
27589
|
+
if (fs63.existsSync(flagFile)) fs63.unlinkSync(flagFile);
|
|
27410
27590
|
console.log("HUD debug logging disabled.");
|
|
27411
27591
|
} else {
|
|
27412
27592
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -27527,9 +27707,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
27527
27707
|
const isCheckHook = process.argv[2] === "check";
|
|
27528
27708
|
if (isCheckHook) {
|
|
27529
27709
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
27530
|
-
const logPath =
|
|
27710
|
+
const logPath = path61.join(os55.homedir(), ".node9", "hook-debug.log");
|
|
27531
27711
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
27532
|
-
|
|
27712
|
+
fs63.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
27533
27713
|
`);
|
|
27534
27714
|
}
|
|
27535
27715
|
process.exit(0);
|