@node9/proxy 1.12.11 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -19
- package/dist/cli.js +543 -208
- package/dist/cli.mjs +541 -206
- package/dist/index.js +15 -3
- package/dist/index.mjs +15 -3
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
const lines = result.error.issues.map((issue) => {
|
|
150
|
-
const
|
|
151
|
-
return ` \u2022 ${
|
|
150
|
+
const path46 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
151
|
+
return ` \u2022 ${path46}: ${issue.message}`;
|
|
152
152
|
});
|
|
153
153
|
return {
|
|
154
154
|
sanitized,
|
|
@@ -1200,7 +1200,26 @@ function scanText(text) {
|
|
|
1200
1200
|
}
|
|
1201
1201
|
return null;
|
|
1202
1202
|
}
|
|
1203
|
-
|
|
1203
|
+
function redactText(text) {
|
|
1204
|
+
const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
|
|
1205
|
+
let result = t;
|
|
1206
|
+
const found = [];
|
|
1207
|
+
const lower = t.toLowerCase();
|
|
1208
|
+
for (const { pattern, globalRegex } of DLP_PATTERNS_GLOBAL) {
|
|
1209
|
+
if (pattern.keywords && !pattern.keywords.some((kw) => lower.includes(kw.toLowerCase()))) {
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
result = result.replace(globalRegex, (match) => {
|
|
1213
|
+
if (DLP_STOPWORDS.some((sw) => match.toLowerCase().includes(sw))) return match;
|
|
1214
|
+
if (pattern.minEntropy !== void 0 && shannonEntropy(match) < pattern.minEntropy)
|
|
1215
|
+
return match;
|
|
1216
|
+
if (!found.includes(pattern.name)) found.push(pattern.name);
|
|
1217
|
+
return `[node9-redacted:${pattern.name}]`;
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
return { result, found };
|
|
1221
|
+
}
|
|
1222
|
+
var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
1204
1223
|
var init_dlp = __esm({
|
|
1205
1224
|
"src/dlp.ts"() {
|
|
1206
1225
|
"use strict";
|
|
@@ -1244,7 +1263,8 @@ var init_dlp = __esm({
|
|
|
1244
1263
|
name: "GitHub Token",
|
|
1245
1264
|
regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/,
|
|
1246
1265
|
severity: "block",
|
|
1247
|
-
keywords: ["ghp_", "gho_", "ghu_", "ghs_"]
|
|
1266
|
+
keywords: ["ghp_", "gho_", "ghu_", "ghs_"],
|
|
1267
|
+
minEntropy: 3
|
|
1248
1268
|
},
|
|
1249
1269
|
{
|
|
1250
1270
|
name: "GitHub Fine-Grained PAT",
|
|
@@ -1295,7 +1315,8 @@ var init_dlp = __esm({
|
|
|
1295
1315
|
name: "GCP API Key",
|
|
1296
1316
|
regex: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
1297
1317
|
severity: "block",
|
|
1298
|
-
keywords: ["aiza"]
|
|
1318
|
+
keywords: ["aiza"],
|
|
1319
|
+
minEntropy: 3
|
|
1299
1320
|
},
|
|
1300
1321
|
{
|
|
1301
1322
|
name: "GCP Service Account",
|
|
@@ -1542,7 +1563,8 @@ var init_dlp = __esm({
|
|
|
1542
1563
|
name: "Mapbox Access Token",
|
|
1543
1564
|
regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
|
|
1544
1565
|
severity: "block",
|
|
1545
|
-
keywords: ["pk.eyj1"]
|
|
1566
|
+
keywords: ["pk.eyj1"],
|
|
1567
|
+
minEntropy: 3
|
|
1546
1568
|
},
|
|
1547
1569
|
// ── Notion ────────────────────────────────────────────────────────────────
|
|
1548
1570
|
{
|
|
@@ -1615,6 +1637,15 @@ var init_dlp = __esm({
|
|
|
1615
1637
|
keywords: ["age-secret-key-"]
|
|
1616
1638
|
}
|
|
1617
1639
|
];
|
|
1640
|
+
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
1641
|
+
(p) => ({
|
|
1642
|
+
pattern: p,
|
|
1643
|
+
globalRegex: new RegExp(
|
|
1644
|
+
p.regex.source,
|
|
1645
|
+
p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g"
|
|
1646
|
+
)
|
|
1647
|
+
})
|
|
1648
|
+
);
|
|
1618
1649
|
SENSITIVE_PATH_PATTERNS = [
|
|
1619
1650
|
/[/\\]\.ssh[/\\]/i,
|
|
1620
1651
|
/[/\\]\.aws[/\\]/i,
|
|
@@ -2181,9 +2212,9 @@ function matchesPattern(text, patterns) {
|
|
|
2181
2212
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
2182
2213
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
2183
2214
|
}
|
|
2184
|
-
function getNestedValue(obj,
|
|
2215
|
+
function getNestedValue(obj, path46) {
|
|
2185
2216
|
if (!obj || typeof obj !== "object") return null;
|
|
2186
|
-
return
|
|
2217
|
+
return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
2187
2218
|
}
|
|
2188
2219
|
function normalizeCommandForPolicy(command) {
|
|
2189
2220
|
try {
|
|
@@ -9150,14 +9181,30 @@ var init_ui = __esm({
|
|
|
9150
9181
|
// \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
|
|
9151
9182
|
if (leaksByPattern.length) {
|
|
9152
9183
|
html +=
|
|
9153
|
-
'<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in
|
|
9184
|
+
'<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in history or shell config</div>';
|
|
9154
9185
|
html += leaksByPattern
|
|
9155
9186
|
.map(([pattern, group]) => {
|
|
9156
9187
|
const count = group.length;
|
|
9157
9188
|
const barPct = Math.round((count / maxBar) * 100);
|
|
9158
9189
|
const detailId = 'detail-' + Math.random().toString(36).slice(2);
|
|
9159
9190
|
const rows = group
|
|
9160
|
-
.map((l) =>
|
|
9191
|
+
.map((l) => {
|
|
9192
|
+
const agentBadge =
|
|
9193
|
+
l.agent === 'gemini'
|
|
9194
|
+
? '[Gemini]'
|
|
9195
|
+
: l.agent === 'codex'
|
|
9196
|
+
? '[Codex]'
|
|
9197
|
+
: l.agent === 'shell'
|
|
9198
|
+
? '[Shell]'
|
|
9199
|
+
: '[Claude]';
|
|
9200
|
+
return findingRow(
|
|
9201
|
+
l.timestamp,
|
|
9202
|
+
'<span style="opacity:0.6;margin-right:6px">' +
|
|
9203
|
+
esc(agentBadge) +
|
|
9204
|
+
'</span>' +
|
|
9205
|
+
esc(l.redactedSample || '')
|
|
9206
|
+
);
|
|
9207
|
+
})
|
|
9161
9208
|
.join('');
|
|
9162
9209
|
return (
|
|
9163
9210
|
'<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
|
|
@@ -9909,6 +9956,7 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9909
9956
|
continue;
|
|
9910
9957
|
}
|
|
9911
9958
|
const sessionCalls = [];
|
|
9959
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9912
9960
|
for (const line of raw.split("\n")) {
|
|
9913
9961
|
if (!line.trim()) continue;
|
|
9914
9962
|
onLine?.();
|
|
@@ -9951,6 +9999,33 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9951
9999
|
}
|
|
9952
10000
|
}
|
|
9953
10001
|
}
|
|
10002
|
+
for (const block of content2) {
|
|
10003
|
+
if (block.type !== "tool_result") continue;
|
|
10004
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
10005
|
+
if (filePath) {
|
|
10006
|
+
const ext = path17.extname(filePath).toLowerCase();
|
|
10007
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
10008
|
+
}
|
|
10009
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
10010
|
+
if (!resultText) continue;
|
|
10011
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
10012
|
+
if (dlpMatch) {
|
|
10013
|
+
const isDupe = result.dlpFindings.some(
|
|
10014
|
+
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
10015
|
+
);
|
|
10016
|
+
if (!isDupe) {
|
|
10017
|
+
result.dlpFindings.push({
|
|
10018
|
+
patternName: dlpMatch.patternName,
|
|
10019
|
+
redactedSample: dlpMatch.redactedSample,
|
|
10020
|
+
toolName: "tool-result",
|
|
10021
|
+
timestamp: entry.timestamp ?? "",
|
|
10022
|
+
project: projLabel,
|
|
10023
|
+
sessionId,
|
|
10024
|
+
agent: "claude"
|
|
10025
|
+
});
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
}
|
|
9954
10029
|
}
|
|
9955
10030
|
continue;
|
|
9956
10031
|
}
|
|
@@ -9970,6 +10045,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9970
10045
|
const toolName = block.name ?? "";
|
|
9971
10046
|
const toolNameLower = toolName.toLowerCase();
|
|
9972
10047
|
const input = block.input ?? {};
|
|
10048
|
+
if (block.id && typeof input.file_path === "string") {
|
|
10049
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
10050
|
+
}
|
|
9973
10051
|
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9974
10052
|
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9975
10053
|
result.bashCalls++;
|
|
@@ -9977,6 +10055,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9977
10055
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
9978
10056
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
9979
10057
|
continue;
|
|
10058
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
10059
|
+
const inputFileExt = inputFilePath ? path17.extname(inputFilePath).toLowerCase() : "";
|
|
10060
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9980
10061
|
const dlpMatch = scanArgs(input);
|
|
9981
10062
|
if (dlpMatch) {
|
|
9982
10063
|
const isDupe = result.dlpFindings.some(
|
|
@@ -10473,6 +10554,44 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
10473
10554
|
}
|
|
10474
10555
|
return result;
|
|
10475
10556
|
}
|
|
10557
|
+
function scanShellConfig() {
|
|
10558
|
+
const home = os12.homedir();
|
|
10559
|
+
const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
|
|
10560
|
+
(f) => path17.join(home, f)
|
|
10561
|
+
);
|
|
10562
|
+
const findings = [];
|
|
10563
|
+
for (const filePath of configFiles) {
|
|
10564
|
+
if (!fs14.existsSync(filePath)) continue;
|
|
10565
|
+
let lines;
|
|
10566
|
+
try {
|
|
10567
|
+
lines = fs14.readFileSync(filePath, "utf-8").split("\n");
|
|
10568
|
+
} catch {
|
|
10569
|
+
continue;
|
|
10570
|
+
}
|
|
10571
|
+
const shortPath = filePath.replace(home, "~");
|
|
10572
|
+
for (const line of lines) {
|
|
10573
|
+
const trimmed = line.trim();
|
|
10574
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
10575
|
+
const dlpMatch = scanArgs({ text: trimmed });
|
|
10576
|
+
if (!dlpMatch) continue;
|
|
10577
|
+
const isDupe = findings.some(
|
|
10578
|
+
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === shortPath
|
|
10579
|
+
);
|
|
10580
|
+
if (!isDupe) {
|
|
10581
|
+
findings.push({
|
|
10582
|
+
patternName: dlpMatch.patternName,
|
|
10583
|
+
redactedSample: dlpMatch.redactedSample,
|
|
10584
|
+
toolName: "shell-config",
|
|
10585
|
+
timestamp: "",
|
|
10586
|
+
project: shortPath,
|
|
10587
|
+
sessionId: "",
|
|
10588
|
+
agent: "shell"
|
|
10589
|
+
});
|
|
10590
|
+
}
|
|
10591
|
+
}
|
|
10592
|
+
}
|
|
10593
|
+
return findings;
|
|
10594
|
+
}
|
|
10476
10595
|
function mergeScans(a, b) {
|
|
10477
10596
|
const dates = [a.firstDate, b.firstDate].filter(Boolean);
|
|
10478
10597
|
const lastDates = [a.lastDate, b.lastDate].filter(Boolean);
|
|
@@ -10587,6 +10706,7 @@ function registerScanCommand(program2) {
|
|
|
10587
10706
|
onLine
|
|
10588
10707
|
);
|
|
10589
10708
|
const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
|
|
10709
|
+
scan.dlpFindings.push(...scanShellConfig());
|
|
10590
10710
|
const summary = buildScanSummary([
|
|
10591
10711
|
{ id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
|
|
10592
10712
|
{ id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
|
|
@@ -10640,7 +10760,7 @@ function registerScanCommand(program2) {
|
|
|
10640
10760
|
}
|
|
10641
10761
|
if (scan.dlpFindings.length > 0) {
|
|
10642
10762
|
console.log(
|
|
10643
|
-
" " + chalk2.red("\u{1F511} Credential leak") + " " + chalk2.red.bold(String(scan.dlpFindings.length).padStart(5)) + chalk2.dim(" secret detected in
|
|
10763
|
+
" " + chalk2.red("\u{1F511} Credential leak") + " " + chalk2.red.bold(String(scan.dlpFindings.length).padStart(5)) + chalk2.dim(" secret detected in history or shell config")
|
|
10644
10764
|
);
|
|
10645
10765
|
}
|
|
10646
10766
|
if (blockedCount > 0) {
|
|
@@ -10649,8 +10769,9 @@ function registerScanCommand(program2) {
|
|
|
10649
10769
|
);
|
|
10650
10770
|
}
|
|
10651
10771
|
if (scan.loopFindings.length > 0) {
|
|
10772
|
+
const loopCost = summary.loopWastedUSD > 0 ? chalk2.dim(" \xB7 ") + chalk2.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
|
|
10652
10773
|
console.log(
|
|
10653
|
-
" " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found")
|
|
10774
|
+
" " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found") + loopCost
|
|
10654
10775
|
);
|
|
10655
10776
|
}
|
|
10656
10777
|
if (reviewCount > 0) {
|
|
@@ -10670,7 +10791,7 @@ function registerScanCommand(program2) {
|
|
|
10670
10791
|
for (const f of shownDlp) {
|
|
10671
10792
|
const ts = f.timestamp ? chalk2.dim(fmtTs(f.timestamp) + " ") : "";
|
|
10672
10793
|
const proj = chalk2.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
10673
|
-
const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : chalk2.cyan("[Claude] ");
|
|
10794
|
+
const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : f.agent === "shell" ? chalk2.yellow("[Shell] ") : chalk2.cyan("[Claude] ");
|
|
10674
10795
|
const sessionSuffix = f.sessionId ? chalk2.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
|
|
10675
10796
|
console.log(
|
|
10676
10797
|
` \u{1F6A8} ${ts}${proj}${agentBadge}` + chalk2.yellow(f.patternName) + chalk2.dim(" ") + chalk2.gray(f.redactedSample) + sessionSuffix
|
|
@@ -10702,10 +10823,11 @@ function registerScanCommand(program2) {
|
|
|
10702
10823
|
}
|
|
10703
10824
|
if (scan.loopFindings.length > 0) {
|
|
10704
10825
|
console.log(" " + chalk2.dim("\u2500".repeat(70)));
|
|
10826
|
+
const loopCostLabel = summary.loopWastedUSD > 0 ? chalk2.dim(" \xB7 ") + chalk2.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
|
|
10705
10827
|
console.log(
|
|
10706
10828
|
" " + chalk2.yellow.bold("\u{1F501} Agent Loops") + chalk2.dim(" \xB7 ") + chalk2.yellow(
|
|
10707
10829
|
`${num(scan.loopFindings.length)} repeated pattern${scan.loopFindings.length !== 1 ? "s" : ""} found`
|
|
10708
|
-
)
|
|
10830
|
+
) + loopCostLabel
|
|
10709
10831
|
);
|
|
10710
10832
|
const shownLoops = drillDown ? scan.loopFindings : scan.loopFindings.slice(0, topN);
|
|
10711
10833
|
for (const f of shownLoops) {
|
|
@@ -10828,7 +10950,7 @@ function registerScanCommand(program2) {
|
|
|
10828
10950
|
}
|
|
10829
10951
|
});
|
|
10830
10952
|
}
|
|
10831
|
-
var CLAUDE_PRICING, GEMINI_PRICING, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
|
|
10953
|
+
var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
|
|
10832
10954
|
var init_scan = __esm({
|
|
10833
10955
|
"src/cli/commands/scan.ts"() {
|
|
10834
10956
|
"use strict";
|
|
@@ -10860,6 +10982,33 @@ var init_scan = __esm({
|
|
|
10860
10982
|
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
10861
10983
|
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
10862
10984
|
};
|
|
10985
|
+
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10986
|
+
".ts",
|
|
10987
|
+
".tsx",
|
|
10988
|
+
".js",
|
|
10989
|
+
".jsx",
|
|
10990
|
+
".mjs",
|
|
10991
|
+
".cjs",
|
|
10992
|
+
".py",
|
|
10993
|
+
".rb",
|
|
10994
|
+
".go",
|
|
10995
|
+
".rs",
|
|
10996
|
+
".java",
|
|
10997
|
+
".kt",
|
|
10998
|
+
".swift",
|
|
10999
|
+
".c",
|
|
11000
|
+
".cpp",
|
|
11001
|
+
".h",
|
|
11002
|
+
".cs",
|
|
11003
|
+
".php",
|
|
11004
|
+
".sh",
|
|
11005
|
+
".bash",
|
|
11006
|
+
".html",
|
|
11007
|
+
".css",
|
|
11008
|
+
".scss",
|
|
11009
|
+
".vue",
|
|
11010
|
+
".svelte"
|
|
11011
|
+
]);
|
|
10863
11012
|
LOOP_TOOLS = /* @__PURE__ */ new Set([
|
|
10864
11013
|
"bash",
|
|
10865
11014
|
"execute_bash",
|
|
@@ -13550,10 +13699,10 @@ __export(tail_exports, {
|
|
|
13550
13699
|
startTail: () => startTail
|
|
13551
13700
|
});
|
|
13552
13701
|
import http2 from "http";
|
|
13553
|
-
import
|
|
13554
|
-
import
|
|
13555
|
-
import
|
|
13556
|
-
import
|
|
13702
|
+
import chalk26 from "chalk";
|
|
13703
|
+
import fs40 from "fs";
|
|
13704
|
+
import os36 from "os";
|
|
13705
|
+
import path43 from "path";
|
|
13557
13706
|
import readline5 from "readline";
|
|
13558
13707
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
13559
13708
|
function getIcon(tool) {
|
|
@@ -13571,20 +13720,20 @@ function getModelContextLimit(model) {
|
|
|
13571
13720
|
return 2e5;
|
|
13572
13721
|
}
|
|
13573
13722
|
function readSessionUsage() {
|
|
13574
|
-
const projectsDir =
|
|
13575
|
-
if (!
|
|
13723
|
+
const projectsDir = path43.join(os36.homedir(), ".claude", "projects");
|
|
13724
|
+
if (!fs40.existsSync(projectsDir)) return null;
|
|
13576
13725
|
let latestFile = null;
|
|
13577
13726
|
let latestMtime = 0;
|
|
13578
13727
|
try {
|
|
13579
|
-
for (const dir of
|
|
13580
|
-
const dirPath =
|
|
13728
|
+
for (const dir of fs40.readdirSync(projectsDir)) {
|
|
13729
|
+
const dirPath = path43.join(projectsDir, dir);
|
|
13581
13730
|
try {
|
|
13582
|
-
if (!
|
|
13583
|
-
for (const file of
|
|
13731
|
+
if (!fs40.statSync(dirPath).isDirectory()) continue;
|
|
13732
|
+
for (const file of fs40.readdirSync(dirPath)) {
|
|
13584
13733
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
13585
|
-
const filePath =
|
|
13734
|
+
const filePath = path43.join(dirPath, file);
|
|
13586
13735
|
try {
|
|
13587
|
-
const mtime =
|
|
13736
|
+
const mtime = fs40.statSync(filePath).mtimeMs;
|
|
13588
13737
|
if (mtime > latestMtime) {
|
|
13589
13738
|
latestMtime = mtime;
|
|
13590
13739
|
latestFile = filePath;
|
|
@@ -13599,7 +13748,7 @@ function readSessionUsage() {
|
|
|
13599
13748
|
}
|
|
13600
13749
|
if (!latestFile) return null;
|
|
13601
13750
|
try {
|
|
13602
|
-
const lines =
|
|
13751
|
+
const lines = fs40.readFileSync(latestFile, "utf-8").split("\n");
|
|
13603
13752
|
let lastModel = "";
|
|
13604
13753
|
let lastInput = 0;
|
|
13605
13754
|
let lastOutput = 0;
|
|
@@ -13624,10 +13773,10 @@ function readSessionUsage() {
|
|
|
13624
13773
|
}
|
|
13625
13774
|
}
|
|
13626
13775
|
function formatContextStat(stat) {
|
|
13627
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
13776
|
+
const pctColor = stat.fillPct >= 80 ? chalk26.red : stat.fillPct >= 50 ? chalk26.yellow : chalk26.cyan;
|
|
13628
13777
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
13629
13778
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
13630
|
-
return
|
|
13779
|
+
return chalk26.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk26.dim(
|
|
13631
13780
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
13632
13781
|
);
|
|
13633
13782
|
}
|
|
@@ -13643,28 +13792,28 @@ function wrappedLineCount(text) {
|
|
|
13643
13792
|
function agentLabel(agent) {
|
|
13644
13793
|
if (!agent || agent === "Terminal") return "";
|
|
13645
13794
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
13646
|
-
return short ?
|
|
13795
|
+
return short ? chalk26.dim(`[${short}] `) : "";
|
|
13647
13796
|
}
|
|
13648
13797
|
function formatBase(activity) {
|
|
13649
13798
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
13650
13799
|
const icon = getIcon(activity.tool);
|
|
13651
13800
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
13652
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
13801
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os36.homedir(), "~");
|
|
13653
13802
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
13654
|
-
return `${
|
|
13803
|
+
return `${chalk26.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk26.white.bold(toolName)} ${chalk26.dim(argsPreview)}`;
|
|
13655
13804
|
}
|
|
13656
13805
|
function renderResult(activity, result) {
|
|
13657
13806
|
const base = formatBase(activity);
|
|
13658
13807
|
let status;
|
|
13659
13808
|
if (result.status === "allow") {
|
|
13660
|
-
status =
|
|
13809
|
+
status = chalk26.green("\u2713 ALLOW");
|
|
13661
13810
|
} else if (result.status === "dlp") {
|
|
13662
|
-
status =
|
|
13811
|
+
status = chalk26.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
13663
13812
|
} else {
|
|
13664
|
-
status =
|
|
13813
|
+
status = chalk26.red("\u2717 BLOCK");
|
|
13665
13814
|
}
|
|
13666
13815
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
13667
|
-
const costSuffix = cost == null ? "" :
|
|
13816
|
+
const costSuffix = cost == null ? "" : chalk26.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
13668
13817
|
if (process.stdout.isTTY) {
|
|
13669
13818
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
13670
13819
|
readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -13681,19 +13830,19 @@ function renderResult(activity, result) {
|
|
|
13681
13830
|
}
|
|
13682
13831
|
function renderPending(activity) {
|
|
13683
13832
|
if (!process.stdout.isTTY) return;
|
|
13684
|
-
const line = `${formatBase(activity)} ${
|
|
13833
|
+
const line = `${formatBase(activity)} ${chalk26.yellow("\u25CF \u2026")}`;
|
|
13685
13834
|
pendingShownForId = activity.id;
|
|
13686
13835
|
pendingWrappedLines = wrappedLineCount(line);
|
|
13687
13836
|
process.stdout.write(`${line}\r`);
|
|
13688
13837
|
}
|
|
13689
13838
|
async function ensureDaemon() {
|
|
13690
13839
|
let pidPort = null;
|
|
13691
|
-
if (
|
|
13840
|
+
if (fs40.existsSync(PID_FILE)) {
|
|
13692
13841
|
try {
|
|
13693
|
-
const { port } = JSON.parse(
|
|
13842
|
+
const { port } = JSON.parse(fs40.readFileSync(PID_FILE, "utf-8"));
|
|
13694
13843
|
pidPort = port;
|
|
13695
13844
|
} catch {
|
|
13696
|
-
console.error(
|
|
13845
|
+
console.error(chalk26.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
13697
13846
|
}
|
|
13698
13847
|
}
|
|
13699
13848
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -13704,7 +13853,7 @@ async function ensureDaemon() {
|
|
|
13704
13853
|
if (res.ok) return checkPort;
|
|
13705
13854
|
} catch {
|
|
13706
13855
|
}
|
|
13707
|
-
console.log(
|
|
13856
|
+
console.log(chalk26.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
13708
13857
|
const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
|
|
13709
13858
|
detached: true,
|
|
13710
13859
|
stdio: "ignore",
|
|
@@ -13721,7 +13870,7 @@ async function ensureDaemon() {
|
|
|
13721
13870
|
} catch {
|
|
13722
13871
|
}
|
|
13723
13872
|
}
|
|
13724
|
-
console.error(
|
|
13873
|
+
console.error(chalk26.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
13725
13874
|
process.exit(1);
|
|
13726
13875
|
}
|
|
13727
13876
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -13787,7 +13936,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
13787
13936
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
13788
13937
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
13789
13938
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
13790
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
13939
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk26.dim(`(${req.agent})`)}` : "";
|
|
13791
13940
|
const lines = [
|
|
13792
13941
|
``,
|
|
13793
13942
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -13843,9 +13992,9 @@ function buildRecoveryCardLines(req) {
|
|
|
13843
13992
|
];
|
|
13844
13993
|
}
|
|
13845
13994
|
function readApproversFromDisk() {
|
|
13846
|
-
const configPath =
|
|
13995
|
+
const configPath = path43.join(os36.homedir(), ".node9", "config.json");
|
|
13847
13996
|
try {
|
|
13848
|
-
const raw = JSON.parse(
|
|
13997
|
+
const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
|
|
13849
13998
|
const settings = raw.settings ?? {};
|
|
13850
13999
|
return settings.approvers ?? {};
|
|
13851
14000
|
} catch {
|
|
@@ -13856,20 +14005,20 @@ function approverStatusLine() {
|
|
|
13856
14005
|
const a = readApproversFromDisk();
|
|
13857
14006
|
const fmt = (label, key) => {
|
|
13858
14007
|
const on = a[key] !== false;
|
|
13859
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
14008
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk26.green("\u2713") : chalk26.dim("\u2717")}`;
|
|
13860
14009
|
};
|
|
13861
14010
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
13862
14011
|
}
|
|
13863
14012
|
function toggleApprover(channel) {
|
|
13864
|
-
const configPath =
|
|
14013
|
+
const configPath = path43.join(os36.homedir(), ".node9", "config.json");
|
|
13865
14014
|
try {
|
|
13866
|
-
const raw = JSON.parse(
|
|
14015
|
+
const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
|
|
13867
14016
|
const settings = raw.settings ?? {};
|
|
13868
14017
|
const approvers = settings.approvers ?? {};
|
|
13869
14018
|
approvers[channel] = approvers[channel] === false;
|
|
13870
14019
|
settings.approvers = approvers;
|
|
13871
14020
|
raw.settings = settings;
|
|
13872
|
-
|
|
14021
|
+
fs40.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
13873
14022
|
} catch (err2) {
|
|
13874
14023
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
13875
14024
|
`);
|
|
@@ -13901,7 +14050,7 @@ async function startTail(options = {}) {
|
|
|
13901
14050
|
req2.end();
|
|
13902
14051
|
});
|
|
13903
14052
|
if (result.ok) {
|
|
13904
|
-
console.log(
|
|
14053
|
+
console.log(chalk26.green("\u2713 Flight Recorder buffer cleared."));
|
|
13905
14054
|
} else if (result.code === "ECONNREFUSED") {
|
|
13906
14055
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
13907
14056
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -13945,7 +14094,7 @@ async function startTail(options = {}) {
|
|
|
13945
14094
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
13946
14095
|
if (channel) {
|
|
13947
14096
|
toggleApprover(channel);
|
|
13948
|
-
console.log(
|
|
14097
|
+
console.log(chalk26.dim(` Approvers: ${approverStatusLine()}`));
|
|
13949
14098
|
}
|
|
13950
14099
|
};
|
|
13951
14100
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -14011,7 +14160,7 @@ async function startTail(options = {}) {
|
|
|
14011
14160
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
14012
14161
|
)
|
|
14013
14162
|
);
|
|
14014
|
-
const decisionStamp = action === "always-allow" ?
|
|
14163
|
+
const decisionStamp = action === "always-allow" ? chalk26.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk26.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk26.green("\u2713 ALLOWED") : action === "redirect" ? chalk26.yellow("\u21A9 REDIRECT AI") : chalk26.red("\u2717 DENIED");
|
|
14015
14164
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
14016
14165
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
14017
14166
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -14039,8 +14188,8 @@ async function startTail(options = {}) {
|
|
|
14039
14188
|
}
|
|
14040
14189
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
14041
14190
|
try {
|
|
14042
|
-
|
|
14043
|
-
|
|
14191
|
+
fs40.appendFileSync(
|
|
14192
|
+
path43.join(os36.homedir(), ".node9", "hook-debug.log"),
|
|
14044
14193
|
`[tail] POST /decision failed: ${String(err2)}
|
|
14045
14194
|
`
|
|
14046
14195
|
);
|
|
@@ -14062,7 +14211,7 @@ async function startTail(options = {}) {
|
|
|
14062
14211
|
);
|
|
14063
14212
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
14064
14213
|
if (externalDecision) {
|
|
14065
|
-
const source = externalDecision === "allow" ?
|
|
14214
|
+
const source = externalDecision === "allow" ? chalk26.green("\u2713 ALLOWED") : chalk26.red("\u2717 DENIED");
|
|
14066
14215
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
14067
14216
|
}
|
|
14068
14217
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -14121,31 +14270,31 @@ async function startTail(options = {}) {
|
|
|
14121
14270
|
}
|
|
14122
14271
|
} catch {
|
|
14123
14272
|
}
|
|
14124
|
-
const auditLog =
|
|
14273
|
+
const auditLog = path43.join(os36.homedir(), ".node9", "audit.log");
|
|
14125
14274
|
try {
|
|
14126
|
-
const unackedDlp =
|
|
14275
|
+
const unackedDlp = fs40.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
14127
14276
|
if (unackedDlp > 0) {
|
|
14128
14277
|
console.log("");
|
|
14129
14278
|
console.log(
|
|
14130
|
-
|
|
14279
|
+
chalk26.bgRed.white.bold(
|
|
14131
14280
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
14132
14281
|
)
|
|
14133
14282
|
);
|
|
14134
14283
|
}
|
|
14135
14284
|
} catch {
|
|
14136
14285
|
}
|
|
14137
|
-
console.log(
|
|
14138
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
14286
|
+
console.log(chalk26.cyan.bold(`
|
|
14287
|
+
\u{1F6F0}\uFE0F Node9 tail `) + chalk26.dim(`\u2192 ${dashboardUrl}`));
|
|
14139
14288
|
if (canApprove) {
|
|
14140
|
-
console.log(
|
|
14141
|
-
console.log(
|
|
14289
|
+
console.log(chalk26.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
14290
|
+
console.log(chalk26.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
14142
14291
|
}
|
|
14143
14292
|
const ctxStat = readSessionUsage();
|
|
14144
14293
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
14145
14294
|
if (options.history) {
|
|
14146
|
-
console.log(
|
|
14295
|
+
console.log(chalk26.dim("Showing history + live events.\n"));
|
|
14147
14296
|
} else {
|
|
14148
|
-
console.log(
|
|
14297
|
+
console.log(chalk26.dim("Showing live events only. Use --history to include past.\n"));
|
|
14149
14298
|
}
|
|
14150
14299
|
process.on("SIGINT", () => {
|
|
14151
14300
|
exitIdleMode();
|
|
@@ -14155,13 +14304,13 @@ async function startTail(options = {}) {
|
|
|
14155
14304
|
readline5.clearLine(process.stdout, 0);
|
|
14156
14305
|
readline5.cursorTo(process.stdout, 0);
|
|
14157
14306
|
}
|
|
14158
|
-
console.log(
|
|
14307
|
+
console.log(chalk26.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
14159
14308
|
process.exit(0);
|
|
14160
14309
|
});
|
|
14161
14310
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
14162
14311
|
const req = http2.get(sseUrl, (res) => {
|
|
14163
14312
|
if (res.statusCode !== 200) {
|
|
14164
|
-
console.error(
|
|
14313
|
+
console.error(chalk26.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
14165
14314
|
process.exit(1);
|
|
14166
14315
|
}
|
|
14167
14316
|
if (canApprove) enterIdleMode();
|
|
@@ -14192,7 +14341,7 @@ async function startTail(options = {}) {
|
|
|
14192
14341
|
readline5.clearLine(process.stdout, 0);
|
|
14193
14342
|
readline5.cursorTo(process.stdout, 0);
|
|
14194
14343
|
}
|
|
14195
|
-
console.log(
|
|
14344
|
+
console.log(chalk26.red("\n\u274C Daemon disconnected."));
|
|
14196
14345
|
process.exit(1);
|
|
14197
14346
|
});
|
|
14198
14347
|
});
|
|
@@ -14284,9 +14433,9 @@ async function startTail(options = {}) {
|
|
|
14284
14433
|
const hash = data.hash ?? "";
|
|
14285
14434
|
const summary = data.argsSummary ?? data.tool;
|
|
14286
14435
|
const fileCount = data.fileCount ?? 0;
|
|
14287
|
-
const files = fileCount > 0 ?
|
|
14436
|
+
const files = fileCount > 0 ? chalk26.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
14288
14437
|
process.stdout.write(
|
|
14289
|
-
`${
|
|
14438
|
+
`${chalk26.dim(time)} ${chalk26.cyan("\u{1F4F8} snapshot")} ${chalk26.dim(hash)} ${summary}${files}
|
|
14290
14439
|
`
|
|
14291
14440
|
);
|
|
14292
14441
|
return;
|
|
@@ -14303,7 +14452,7 @@ async function startTail(options = {}) {
|
|
|
14303
14452
|
}
|
|
14304
14453
|
req.on("error", (err2) => {
|
|
14305
14454
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
14306
|
-
console.error(
|
|
14455
|
+
console.error(chalk26.red(`
|
|
14307
14456
|
\u274C ${msg}`));
|
|
14308
14457
|
process.exit(1);
|
|
14309
14458
|
});
|
|
@@ -14315,7 +14464,7 @@ var init_tail = __esm({
|
|
|
14315
14464
|
init_daemon2();
|
|
14316
14465
|
init_daemon();
|
|
14317
14466
|
init_core();
|
|
14318
|
-
PID_FILE =
|
|
14467
|
+
PID_FILE = path43.join(os36.homedir(), ".node9", "daemon.pid");
|
|
14319
14468
|
ICONS = {
|
|
14320
14469
|
bash: "\u{1F4BB}",
|
|
14321
14470
|
shell: "\u{1F4BB}",
|
|
@@ -14363,9 +14512,9 @@ __export(hud_exports, {
|
|
|
14363
14512
|
main: () => main,
|
|
14364
14513
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
14365
14514
|
});
|
|
14366
|
-
import
|
|
14367
|
-
import
|
|
14368
|
-
import
|
|
14515
|
+
import fs41 from "fs";
|
|
14516
|
+
import path44 from "path";
|
|
14517
|
+
import os37 from "os";
|
|
14369
14518
|
import http3 from "http";
|
|
14370
14519
|
async function readStdin() {
|
|
14371
14520
|
const chunks = [];
|
|
@@ -14441,9 +14590,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
14441
14590
|
return ` (${m}m left)`;
|
|
14442
14591
|
}
|
|
14443
14592
|
function safeReadJson(filePath) {
|
|
14444
|
-
if (!
|
|
14593
|
+
if (!fs41.existsSync(filePath)) return null;
|
|
14445
14594
|
try {
|
|
14446
|
-
return JSON.parse(
|
|
14595
|
+
return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
|
|
14447
14596
|
} catch {
|
|
14448
14597
|
return null;
|
|
14449
14598
|
}
|
|
@@ -14464,12 +14613,12 @@ function countHooksInFile(filePath) {
|
|
|
14464
14613
|
return Object.keys(cfg.hooks).length;
|
|
14465
14614
|
}
|
|
14466
14615
|
function countRulesInDir(rulesDir) {
|
|
14467
|
-
if (!
|
|
14616
|
+
if (!fs41.existsSync(rulesDir)) return 0;
|
|
14468
14617
|
let count = 0;
|
|
14469
14618
|
try {
|
|
14470
|
-
for (const entry of
|
|
14619
|
+
for (const entry of fs41.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
14471
14620
|
if (entry.isDirectory()) {
|
|
14472
|
-
count += countRulesInDir(
|
|
14621
|
+
count += countRulesInDir(path44.join(rulesDir, entry.name));
|
|
14473
14622
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
14474
14623
|
count++;
|
|
14475
14624
|
}
|
|
@@ -14480,46 +14629,46 @@ function countRulesInDir(rulesDir) {
|
|
|
14480
14629
|
}
|
|
14481
14630
|
function isSamePath(a, b) {
|
|
14482
14631
|
try {
|
|
14483
|
-
return
|
|
14632
|
+
return path44.resolve(a) === path44.resolve(b);
|
|
14484
14633
|
} catch {
|
|
14485
14634
|
return false;
|
|
14486
14635
|
}
|
|
14487
14636
|
}
|
|
14488
14637
|
function countConfigs(cwd) {
|
|
14489
|
-
const homeDir2 =
|
|
14490
|
-
const claudeDir =
|
|
14638
|
+
const homeDir2 = os37.homedir();
|
|
14639
|
+
const claudeDir = path44.join(homeDir2, ".claude");
|
|
14491
14640
|
let claudeMdCount = 0;
|
|
14492
14641
|
let rulesCount = 0;
|
|
14493
14642
|
let hooksCount = 0;
|
|
14494
14643
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
14495
14644
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
14496
|
-
if (
|
|
14497
|
-
rulesCount += countRulesInDir(
|
|
14498
|
-
const userSettings =
|
|
14645
|
+
if (fs41.existsSync(path44.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
14646
|
+
rulesCount += countRulesInDir(path44.join(claudeDir, "rules"));
|
|
14647
|
+
const userSettings = path44.join(claudeDir, "settings.json");
|
|
14499
14648
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
14500
14649
|
hooksCount += countHooksInFile(userSettings);
|
|
14501
|
-
const userClaudeJson =
|
|
14650
|
+
const userClaudeJson = path44.join(homeDir2, ".claude.json");
|
|
14502
14651
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
14503
14652
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
14504
14653
|
userMcpServers.delete(name);
|
|
14505
14654
|
}
|
|
14506
14655
|
if (cwd) {
|
|
14507
|
-
if (
|
|
14508
|
-
if (
|
|
14509
|
-
const projectClaudeDir =
|
|
14656
|
+
if (fs41.existsSync(path44.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
14657
|
+
if (fs41.existsSync(path44.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
14658
|
+
const projectClaudeDir = path44.join(cwd, ".claude");
|
|
14510
14659
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
14511
14660
|
if (!overlapsUserScope) {
|
|
14512
|
-
if (
|
|
14513
|
-
rulesCount += countRulesInDir(
|
|
14514
|
-
const projSettings =
|
|
14661
|
+
if (fs41.existsSync(path44.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
14662
|
+
rulesCount += countRulesInDir(path44.join(projectClaudeDir, "rules"));
|
|
14663
|
+
const projSettings = path44.join(projectClaudeDir, "settings.json");
|
|
14515
14664
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
14516
14665
|
hooksCount += countHooksInFile(projSettings);
|
|
14517
14666
|
}
|
|
14518
|
-
if (
|
|
14519
|
-
const localSettings =
|
|
14667
|
+
if (fs41.existsSync(path44.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
14668
|
+
const localSettings = path44.join(projectClaudeDir, "settings.local.json");
|
|
14520
14669
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
14521
14670
|
hooksCount += countHooksInFile(localSettings);
|
|
14522
|
-
const mcpJsonServers = getMcpServerNames(
|
|
14671
|
+
const mcpJsonServers = getMcpServerNames(path44.join(cwd, ".mcp.json"));
|
|
14523
14672
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
14524
14673
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
14525
14674
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -14552,12 +14701,12 @@ function readActiveShieldsHud() {
|
|
|
14552
14701
|
return shieldsCache.value;
|
|
14553
14702
|
}
|
|
14554
14703
|
try {
|
|
14555
|
-
const shieldsPath =
|
|
14556
|
-
if (!
|
|
14704
|
+
const shieldsPath = path44.join(os37.homedir(), ".node9", "shields.json");
|
|
14705
|
+
if (!fs41.existsSync(shieldsPath)) {
|
|
14557
14706
|
shieldsCache = { value: [], ts: now };
|
|
14558
14707
|
return [];
|
|
14559
14708
|
}
|
|
14560
|
-
const parsed = JSON.parse(
|
|
14709
|
+
const parsed = JSON.parse(fs41.readFileSync(shieldsPath, "utf-8"));
|
|
14561
14710
|
if (!Array.isArray(parsed.active)) {
|
|
14562
14711
|
shieldsCache = { value: [], ts: now };
|
|
14563
14712
|
return [];
|
|
@@ -14659,17 +14808,17 @@ function renderContextLine(stdin) {
|
|
|
14659
14808
|
async function main() {
|
|
14660
14809
|
try {
|
|
14661
14810
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
14662
|
-
if (
|
|
14811
|
+
if (fs41.existsSync(path44.join(os37.homedir(), ".node9", "hud-debug"))) {
|
|
14663
14812
|
try {
|
|
14664
|
-
const logPath =
|
|
14813
|
+
const logPath = path44.join(os37.homedir(), ".node9", "hud-debug.log");
|
|
14665
14814
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
14666
14815
|
let size = 0;
|
|
14667
14816
|
try {
|
|
14668
|
-
size =
|
|
14817
|
+
size = fs41.statSync(logPath).size;
|
|
14669
14818
|
} catch {
|
|
14670
14819
|
}
|
|
14671
14820
|
if (size < MAX_LOG_SIZE) {
|
|
14672
|
-
|
|
14821
|
+
fs41.appendFileSync(
|
|
14673
14822
|
logPath,
|
|
14674
14823
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
14675
14824
|
);
|
|
@@ -14690,11 +14839,11 @@ async function main() {
|
|
|
14690
14839
|
try {
|
|
14691
14840
|
const cwd = stdin.cwd ?? process.cwd();
|
|
14692
14841
|
for (const configPath of [
|
|
14693
|
-
|
|
14694
|
-
|
|
14842
|
+
path44.join(cwd, "node9.config.json"),
|
|
14843
|
+
path44.join(os37.homedir(), ".node9", "config.json")
|
|
14695
14844
|
]) {
|
|
14696
|
-
if (!
|
|
14697
|
-
const cfg = JSON.parse(
|
|
14845
|
+
if (!fs41.existsSync(configPath)) continue;
|
|
14846
|
+
const cfg = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
|
|
14698
14847
|
const hud = cfg.settings?.hud;
|
|
14699
14848
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
14700
14849
|
}
|
|
@@ -14740,10 +14889,10 @@ init_core();
|
|
|
14740
14889
|
init_setup();
|
|
14741
14890
|
init_daemon2();
|
|
14742
14891
|
import { Command } from "commander";
|
|
14743
|
-
import
|
|
14744
|
-
import
|
|
14745
|
-
import
|
|
14746
|
-
import
|
|
14892
|
+
import chalk27 from "chalk";
|
|
14893
|
+
import fs42 from "fs";
|
|
14894
|
+
import path45 from "path";
|
|
14895
|
+
import os38 from "os";
|
|
14747
14896
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
14748
14897
|
|
|
14749
14898
|
// src/utils/duration.ts
|
|
@@ -20453,22 +20602,207 @@ function registerDlpCommand(program2) {
|
|
|
20453
20602
|
});
|
|
20454
20603
|
}
|
|
20455
20604
|
|
|
20605
|
+
// src/cli/commands/mask.ts
|
|
20606
|
+
init_dlp();
|
|
20607
|
+
import chalk25 from "chalk";
|
|
20608
|
+
import fs39 from "fs";
|
|
20609
|
+
import path42 from "path";
|
|
20610
|
+
import os35 from "os";
|
|
20611
|
+
function findJsonlFiles(dir) {
|
|
20612
|
+
const results = [];
|
|
20613
|
+
if (!fs39.existsSync(dir)) return results;
|
|
20614
|
+
for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
|
|
20615
|
+
const full = path42.join(dir, entry.name);
|
|
20616
|
+
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
20617
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
20618
|
+
}
|
|
20619
|
+
return results;
|
|
20620
|
+
}
|
|
20621
|
+
function redactJson(obj) {
|
|
20622
|
+
if (typeof obj === "string") {
|
|
20623
|
+
const { result, found } = redactText(obj);
|
|
20624
|
+
return { value: result, modified: result !== obj, found };
|
|
20625
|
+
}
|
|
20626
|
+
if (Array.isArray(obj)) {
|
|
20627
|
+
let modified = false;
|
|
20628
|
+
const found = [];
|
|
20629
|
+
const value = obj.map((item) => {
|
|
20630
|
+
const r = redactJson(item);
|
|
20631
|
+
if (r.modified) modified = true;
|
|
20632
|
+
r.found.forEach((f) => {
|
|
20633
|
+
if (!found.includes(f)) found.push(f);
|
|
20634
|
+
});
|
|
20635
|
+
return r.value;
|
|
20636
|
+
});
|
|
20637
|
+
return { value, modified, found };
|
|
20638
|
+
}
|
|
20639
|
+
if (obj !== null && typeof obj === "object") {
|
|
20640
|
+
let modified = false;
|
|
20641
|
+
const found = [];
|
|
20642
|
+
const value = {};
|
|
20643
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
20644
|
+
const r = redactJson(v);
|
|
20645
|
+
value[k] = r.value;
|
|
20646
|
+
if (r.modified) modified = true;
|
|
20647
|
+
r.found.forEach((f) => {
|
|
20648
|
+
if (!found.includes(f)) found.push(f);
|
|
20649
|
+
});
|
|
20650
|
+
}
|
|
20651
|
+
return { value, modified, found };
|
|
20652
|
+
}
|
|
20653
|
+
return { value: obj, modified: false, found: [] };
|
|
20654
|
+
}
|
|
20655
|
+
function processFile(filePath, dryRun) {
|
|
20656
|
+
let raw;
|
|
20657
|
+
try {
|
|
20658
|
+
raw = fs39.readFileSync(filePath, "utf-8");
|
|
20659
|
+
} catch {
|
|
20660
|
+
return { redactedLines: 0, patterns: [] };
|
|
20661
|
+
}
|
|
20662
|
+
const lines = raw.split("\n");
|
|
20663
|
+
let redactedLines = 0;
|
|
20664
|
+
const patterns = [];
|
|
20665
|
+
const newLines = [];
|
|
20666
|
+
for (const line of lines) {
|
|
20667
|
+
if (!line.trim()) {
|
|
20668
|
+
newLines.push(line);
|
|
20669
|
+
continue;
|
|
20670
|
+
}
|
|
20671
|
+
let parsed;
|
|
20672
|
+
try {
|
|
20673
|
+
parsed = JSON.parse(line);
|
|
20674
|
+
} catch {
|
|
20675
|
+
newLines.push(line);
|
|
20676
|
+
continue;
|
|
20677
|
+
}
|
|
20678
|
+
const { value, modified, found } = redactJson(parsed);
|
|
20679
|
+
if (modified) {
|
|
20680
|
+
redactedLines++;
|
|
20681
|
+
found.forEach((f) => {
|
|
20682
|
+
if (!patterns.includes(f)) patterns.push(f);
|
|
20683
|
+
});
|
|
20684
|
+
newLines.push(JSON.stringify(value));
|
|
20685
|
+
} else {
|
|
20686
|
+
newLines.push(line);
|
|
20687
|
+
}
|
|
20688
|
+
}
|
|
20689
|
+
if (!dryRun && redactedLines > 0) {
|
|
20690
|
+
fs39.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
20691
|
+
}
|
|
20692
|
+
return { redactedLines, patterns };
|
|
20693
|
+
}
|
|
20694
|
+
function processJsonFile(filePath, dryRun) {
|
|
20695
|
+
let raw;
|
|
20696
|
+
try {
|
|
20697
|
+
raw = fs39.readFileSync(filePath, "utf-8");
|
|
20698
|
+
} catch {
|
|
20699
|
+
return { redactedLines: 0, patterns: [] };
|
|
20700
|
+
}
|
|
20701
|
+
let parsed;
|
|
20702
|
+
try {
|
|
20703
|
+
parsed = JSON.parse(raw);
|
|
20704
|
+
} catch {
|
|
20705
|
+
return { redactedLines: 0, patterns: [] };
|
|
20706
|
+
}
|
|
20707
|
+
const { value, modified, found } = redactJson(parsed);
|
|
20708
|
+
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
20709
|
+
if (!dryRun) {
|
|
20710
|
+
fs39.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
20711
|
+
}
|
|
20712
|
+
return { redactedLines: 1, patterns: found };
|
|
20713
|
+
}
|
|
20714
|
+
function findJsonFiles(dir) {
|
|
20715
|
+
const results = [];
|
|
20716
|
+
if (!fs39.existsSync(dir)) return results;
|
|
20717
|
+
for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
|
|
20718
|
+
const full = path42.join(dir, entry.name);
|
|
20719
|
+
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
20720
|
+
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
20721
|
+
}
|
|
20722
|
+
return results;
|
|
20723
|
+
}
|
|
20724
|
+
function registerMaskCommand(program2) {
|
|
20725
|
+
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) => {
|
|
20726
|
+
const dryRun = !!options.dryRun;
|
|
20727
|
+
const home = os35.homedir();
|
|
20728
|
+
const claudeDir = path42.join(home, ".claude", "projects");
|
|
20729
|
+
const geminiDir = path42.join(home, ".gemini", "tmp");
|
|
20730
|
+
const allFiles = [
|
|
20731
|
+
...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
|
|
20732
|
+
...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
|
|
20733
|
+
];
|
|
20734
|
+
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
20735
|
+
const filtered = cutoff ? allFiles.filter((f) => {
|
|
20736
|
+
try {
|
|
20737
|
+
return fs39.statSync(f.path).mtime >= cutoff;
|
|
20738
|
+
} catch {
|
|
20739
|
+
return false;
|
|
20740
|
+
}
|
|
20741
|
+
}) : allFiles;
|
|
20742
|
+
if (filtered.length === 0) {
|
|
20743
|
+
console.log(chalk25.yellow(" No session files found."));
|
|
20744
|
+
return;
|
|
20745
|
+
}
|
|
20746
|
+
console.log("");
|
|
20747
|
+
if (dryRun) {
|
|
20748
|
+
console.log(chalk25.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
20749
|
+
}
|
|
20750
|
+
let totalFiles = 0;
|
|
20751
|
+
let totalLines = 0;
|
|
20752
|
+
const totalPatterns = [];
|
|
20753
|
+
for (const file of filtered) {
|
|
20754
|
+
const shortPath = file.path.replace(home, "~");
|
|
20755
|
+
const { redactedLines, patterns } = file.type === "jsonl" ? processFile(file.path, dryRun) : processJsonFile(file.path, dryRun);
|
|
20756
|
+
if (redactedLines > 0) {
|
|
20757
|
+
totalFiles++;
|
|
20758
|
+
totalLines += redactedLines;
|
|
20759
|
+
patterns.forEach((p) => {
|
|
20760
|
+
if (!totalPatterns.includes(p)) totalPatterns.push(p);
|
|
20761
|
+
});
|
|
20762
|
+
const verb = dryRun ? "Would redact" : "Redacted";
|
|
20763
|
+
console.log(
|
|
20764
|
+
" " + chalk25.dim(shortPath.slice(0, 60).padEnd(62)) + chalk25.red(`${verb}: `) + chalk25.yellow(patterns.join(", ")) + chalk25.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
20765
|
+
);
|
|
20766
|
+
}
|
|
20767
|
+
}
|
|
20768
|
+
console.log("");
|
|
20769
|
+
if (totalFiles === 0) {
|
|
20770
|
+
console.log(chalk25.green(" No secrets found in session history."));
|
|
20771
|
+
} else {
|
|
20772
|
+
const verb = dryRun ? "would be modified" : "modified";
|
|
20773
|
+
console.log(
|
|
20774
|
+
chalk25.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk25.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
20775
|
+
);
|
|
20776
|
+
console.log(" Patterns: " + chalk25.yellow(totalPatterns.join(", ")));
|
|
20777
|
+
if (!dryRun) {
|
|
20778
|
+
console.log("");
|
|
20779
|
+
console.log(
|
|
20780
|
+
chalk25.dim(
|
|
20781
|
+
" Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
|
|
20782
|
+
)
|
|
20783
|
+
);
|
|
20784
|
+
}
|
|
20785
|
+
}
|
|
20786
|
+
console.log("");
|
|
20787
|
+
});
|
|
20788
|
+
}
|
|
20789
|
+
|
|
20456
20790
|
// src/cli.ts
|
|
20457
20791
|
var { version } = JSON.parse(
|
|
20458
|
-
|
|
20792
|
+
fs42.readFileSync(path45.join(__dirname, "../package.json"), "utf-8")
|
|
20459
20793
|
);
|
|
20460
20794
|
var program = new Command();
|
|
20461
20795
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
20462
20796
|
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) => {
|
|
20463
20797
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
20464
|
-
const credPath =
|
|
20465
|
-
if (!
|
|
20466
|
-
|
|
20798
|
+
const credPath = path45.join(os38.homedir(), ".node9", "credentials.json");
|
|
20799
|
+
if (!fs42.existsSync(path45.dirname(credPath)))
|
|
20800
|
+
fs42.mkdirSync(path45.dirname(credPath), { recursive: true });
|
|
20467
20801
|
const profileName = options.profile || "default";
|
|
20468
20802
|
let existingCreds = {};
|
|
20469
20803
|
try {
|
|
20470
|
-
if (
|
|
20471
|
-
const raw = JSON.parse(
|
|
20804
|
+
if (fs42.existsSync(credPath)) {
|
|
20805
|
+
const raw = JSON.parse(fs42.readFileSync(credPath, "utf-8"));
|
|
20472
20806
|
if (raw.apiKey) {
|
|
20473
20807
|
existingCreds = {
|
|
20474
20808
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -20480,13 +20814,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20480
20814
|
} catch {
|
|
20481
20815
|
}
|
|
20482
20816
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
20483
|
-
|
|
20817
|
+
fs42.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
20484
20818
|
if (profileName === "default") {
|
|
20485
|
-
const configPath =
|
|
20819
|
+
const configPath = path45.join(os38.homedir(), ".node9", "config.json");
|
|
20486
20820
|
let config = {};
|
|
20487
20821
|
try {
|
|
20488
|
-
if (
|
|
20489
|
-
config = JSON.parse(
|
|
20822
|
+
if (fs42.existsSync(configPath))
|
|
20823
|
+
config = JSON.parse(fs42.readFileSync(configPath, "utf-8"));
|
|
20490
20824
|
} catch {
|
|
20491
20825
|
}
|
|
20492
20826
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -20501,19 +20835,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20501
20835
|
approvers.cloud = false;
|
|
20502
20836
|
}
|
|
20503
20837
|
s.approvers = approvers;
|
|
20504
|
-
if (!
|
|
20505
|
-
|
|
20506
|
-
|
|
20838
|
+
if (!fs42.existsSync(path45.dirname(configPath)))
|
|
20839
|
+
fs42.mkdirSync(path45.dirname(configPath), { recursive: true });
|
|
20840
|
+
fs42.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
20507
20841
|
}
|
|
20508
20842
|
if (options.profile && profileName !== "default") {
|
|
20509
|
-
console.log(
|
|
20510
|
-
console.log(
|
|
20843
|
+
console.log(chalk27.green(`\u2705 Profile "${profileName}" saved`));
|
|
20844
|
+
console.log(chalk27.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
20511
20845
|
} else if (options.local) {
|
|
20512
|
-
console.log(
|
|
20513
|
-
console.log(
|
|
20846
|
+
console.log(chalk27.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
20847
|
+
console.log(chalk27.gray(` All decisions stay on this machine.`));
|
|
20514
20848
|
} else {
|
|
20515
|
-
console.log(
|
|
20516
|
-
console.log(
|
|
20849
|
+
console.log(chalk27.green(`\u2705 Logged in \u2014 agent mode`));
|
|
20850
|
+
console.log(chalk27.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
20517
20851
|
}
|
|
20518
20852
|
});
|
|
20519
20853
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
@@ -20531,7 +20865,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
20531
20865
|
if (target === "vscode") return await setupVSCode();
|
|
20532
20866
|
if (target === "hud") return setupHud();
|
|
20533
20867
|
console.error(
|
|
20534
|
-
|
|
20868
|
+
chalk27.red(
|
|
20535
20869
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20536
20870
|
)
|
|
20537
20871
|
);
|
|
@@ -20545,17 +20879,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
20545
20879
|
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
20546
20880
|
).action(async (target) => {
|
|
20547
20881
|
if (!target) {
|
|
20548
|
-
console.log(
|
|
20549
|
-
console.log(" Usage: " +
|
|
20882
|
+
console.log(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
20883
|
+
console.log(" Usage: " + chalk27.white("node9 setup <target>") + "\n");
|
|
20550
20884
|
console.log(" Targets:");
|
|
20551
|
-
console.log(" " +
|
|
20552
|
-
console.log(" " +
|
|
20553
|
-
console.log(" " +
|
|
20554
|
-
console.log(" " +
|
|
20555
|
-
console.log(" " +
|
|
20556
|
-
console.log(" " +
|
|
20885
|
+
console.log(" " + chalk27.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
20886
|
+
console.log(" " + chalk27.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
20887
|
+
console.log(" " + chalk27.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
20888
|
+
console.log(" " + chalk27.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
20889
|
+
console.log(" " + chalk27.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
20890
|
+
console.log(" " + chalk27.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
20557
20891
|
process.stdout.write(
|
|
20558
|
-
" " +
|
|
20892
|
+
" " + chalk27.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
20559
20893
|
);
|
|
20560
20894
|
console.log("");
|
|
20561
20895
|
return;
|
|
@@ -20569,7 +20903,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
20569
20903
|
if (t === "vscode") return await setupVSCode();
|
|
20570
20904
|
if (t === "hud") return setupHud();
|
|
20571
20905
|
console.error(
|
|
20572
|
-
|
|
20906
|
+
chalk27.red(
|
|
20573
20907
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20574
20908
|
)
|
|
20575
20909
|
);
|
|
@@ -20592,33 +20926,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
20592
20926
|
else if (target === "hud") fn = teardownHud;
|
|
20593
20927
|
else {
|
|
20594
20928
|
console.error(
|
|
20595
|
-
|
|
20929
|
+
chalk27.red(
|
|
20596
20930
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20597
20931
|
)
|
|
20598
20932
|
);
|
|
20599
20933
|
process.exit(1);
|
|
20600
20934
|
}
|
|
20601
|
-
console.log(
|
|
20935
|
+
console.log(chalk27.cyan(`
|
|
20602
20936
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
20603
20937
|
`));
|
|
20604
20938
|
try {
|
|
20605
20939
|
fn();
|
|
20606
20940
|
} catch (err2) {
|
|
20607
|
-
console.error(
|
|
20941
|
+
console.error(chalk27.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20608
20942
|
process.exit(1);
|
|
20609
20943
|
}
|
|
20610
|
-
console.log(
|
|
20944
|
+
console.log(chalk27.gray("\n Restart the agent for changes to take effect."));
|
|
20611
20945
|
});
|
|
20612
20946
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
20613
|
-
console.log(
|
|
20614
|
-
console.log(
|
|
20947
|
+
console.log(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
20948
|
+
console.log(chalk27.bold("Stopping daemon..."));
|
|
20615
20949
|
try {
|
|
20616
20950
|
stopDaemon();
|
|
20617
|
-
console.log(
|
|
20951
|
+
console.log(chalk27.green(" \u2705 Daemon stopped"));
|
|
20618
20952
|
} catch {
|
|
20619
|
-
console.log(
|
|
20953
|
+
console.log(chalk27.blue(" \u2139\uFE0F Daemon was not running"));
|
|
20620
20954
|
}
|
|
20621
|
-
console.log(
|
|
20955
|
+
console.log(chalk27.bold("\nRemoving hooks..."));
|
|
20622
20956
|
let teardownFailed = false;
|
|
20623
20957
|
for (const [label, fn] of [
|
|
20624
20958
|
["Claude", teardownClaude],
|
|
@@ -20633,45 +20967,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
20633
20967
|
} catch (err2) {
|
|
20634
20968
|
teardownFailed = true;
|
|
20635
20969
|
console.error(
|
|
20636
|
-
|
|
20970
|
+
chalk27.red(
|
|
20637
20971
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
20638
20972
|
)
|
|
20639
20973
|
);
|
|
20640
20974
|
}
|
|
20641
20975
|
}
|
|
20642
20976
|
if (options.purge) {
|
|
20643
|
-
const node9Dir =
|
|
20644
|
-
if (
|
|
20977
|
+
const node9Dir = path45.join(os38.homedir(), ".node9");
|
|
20978
|
+
if (fs42.existsSync(node9Dir)) {
|
|
20645
20979
|
const confirmed = await confirm2({
|
|
20646
20980
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
20647
20981
|
default: false
|
|
20648
20982
|
});
|
|
20649
20983
|
if (confirmed) {
|
|
20650
|
-
|
|
20651
|
-
if (
|
|
20984
|
+
fs42.rmSync(node9Dir, { recursive: true });
|
|
20985
|
+
if (fs42.existsSync(node9Dir)) {
|
|
20652
20986
|
console.error(
|
|
20653
|
-
|
|
20987
|
+
chalk27.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
20654
20988
|
);
|
|
20655
20989
|
} else {
|
|
20656
|
-
console.log(
|
|
20990
|
+
console.log(chalk27.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
20657
20991
|
}
|
|
20658
20992
|
} else {
|
|
20659
|
-
console.log(
|
|
20993
|
+
console.log(chalk27.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
20660
20994
|
}
|
|
20661
20995
|
} else {
|
|
20662
|
-
console.log(
|
|
20996
|
+
console.log(chalk27.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
20663
20997
|
}
|
|
20664
20998
|
} else {
|
|
20665
20999
|
console.log(
|
|
20666
|
-
|
|
21000
|
+
chalk27.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
20667
21001
|
);
|
|
20668
21002
|
}
|
|
20669
21003
|
if (teardownFailed) {
|
|
20670
|
-
console.error(
|
|
21004
|
+
console.error(chalk27.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
20671
21005
|
process.exit(1);
|
|
20672
21006
|
}
|
|
20673
|
-
console.log(
|
|
20674
|
-
console.log(
|
|
21007
|
+
console.log(chalk27.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
21008
|
+
console.log(chalk27.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
20675
21009
|
});
|
|
20676
21010
|
registerDoctorCommand(program, version);
|
|
20677
21011
|
program.command("explain").description(
|
|
@@ -20684,7 +21018,7 @@ program.command("explain").description(
|
|
|
20684
21018
|
try {
|
|
20685
21019
|
args = JSON.parse(trimmed);
|
|
20686
21020
|
} catch {
|
|
20687
|
-
console.error(
|
|
21021
|
+
console.error(chalk27.red(`
|
|
20688
21022
|
\u274C Invalid JSON: ${trimmed}
|
|
20689
21023
|
`));
|
|
20690
21024
|
process.exit(1);
|
|
@@ -20695,54 +21029,54 @@ program.command("explain").description(
|
|
|
20695
21029
|
}
|
|
20696
21030
|
const result = await explainPolicy(tool, args);
|
|
20697
21031
|
console.log("");
|
|
20698
|
-
console.log(
|
|
21032
|
+
console.log(chalk27.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
20699
21033
|
console.log("");
|
|
20700
|
-
console.log(` ${
|
|
21034
|
+
console.log(` ${chalk27.bold("Tool:")} ${chalk27.white(result.tool)}`);
|
|
20701
21035
|
if (argsRaw) {
|
|
20702
21036
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
20703
|
-
console.log(` ${
|
|
21037
|
+
console.log(` ${chalk27.bold("Input:")} ${chalk27.gray(preview2)}`);
|
|
20704
21038
|
}
|
|
20705
21039
|
console.log("");
|
|
20706
|
-
console.log(
|
|
21040
|
+
console.log(chalk27.bold("Config Sources (Waterfall):"));
|
|
20707
21041
|
for (const tier of result.waterfall) {
|
|
20708
|
-
const num3 =
|
|
21042
|
+
const num3 = chalk27.gray(` ${tier.tier}.`);
|
|
20709
21043
|
const label = tier.label.padEnd(16);
|
|
20710
21044
|
let statusStr;
|
|
20711
21045
|
if (tier.tier === 1) {
|
|
20712
|
-
statusStr =
|
|
21046
|
+
statusStr = chalk27.gray(tier.note ?? "");
|
|
20713
21047
|
} else if (tier.status === "active") {
|
|
20714
|
-
const loc = tier.path ?
|
|
20715
|
-
const note = tier.note ?
|
|
20716
|
-
statusStr =
|
|
21048
|
+
const loc = tier.path ? chalk27.gray(tier.path) : "";
|
|
21049
|
+
const note = tier.note ? chalk27.gray(`(${tier.note})`) : "";
|
|
21050
|
+
statusStr = chalk27.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
20717
21051
|
} else {
|
|
20718
|
-
statusStr =
|
|
21052
|
+
statusStr = chalk27.gray("\u25CB " + (tier.note ?? "not found"));
|
|
20719
21053
|
}
|
|
20720
|
-
console.log(`${num3} ${
|
|
21054
|
+
console.log(`${num3} ${chalk27.white(label)} ${statusStr}`);
|
|
20721
21055
|
}
|
|
20722
21056
|
console.log("");
|
|
20723
|
-
console.log(
|
|
21057
|
+
console.log(chalk27.bold("Policy Evaluation:"));
|
|
20724
21058
|
for (const step of result.steps) {
|
|
20725
21059
|
const isFinal = step.isFinal;
|
|
20726
21060
|
let icon;
|
|
20727
|
-
if (step.outcome === "allow") icon =
|
|
20728
|
-
else if (step.outcome === "review") icon =
|
|
20729
|
-
else if (step.outcome === "skip") icon =
|
|
20730
|
-
else icon =
|
|
21061
|
+
if (step.outcome === "allow") icon = chalk27.green(" \u2705");
|
|
21062
|
+
else if (step.outcome === "review") icon = chalk27.red(" \u{1F534}");
|
|
21063
|
+
else if (step.outcome === "skip") icon = chalk27.gray(" \u2500 ");
|
|
21064
|
+
else icon = chalk27.gray(" \u25CB ");
|
|
20731
21065
|
const name = step.name.padEnd(18);
|
|
20732
|
-
const nameStr = isFinal ?
|
|
20733
|
-
const detail = isFinal ?
|
|
20734
|
-
const arrow = isFinal ?
|
|
21066
|
+
const nameStr = isFinal ? chalk27.white.bold(name) : chalk27.white(name);
|
|
21067
|
+
const detail = isFinal ? chalk27.white(step.detail) : chalk27.gray(step.detail);
|
|
21068
|
+
const arrow = isFinal ? chalk27.yellow(" \u2190 STOP") : "";
|
|
20735
21069
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
20736
21070
|
}
|
|
20737
21071
|
console.log("");
|
|
20738
21072
|
if (result.decision === "allow") {
|
|
20739
|
-
console.log(
|
|
21073
|
+
console.log(chalk27.green.bold(" Decision: \u2705 ALLOW") + chalk27.gray(" \u2014 no approval needed"));
|
|
20740
21074
|
} else {
|
|
20741
21075
|
console.log(
|
|
20742
|
-
|
|
21076
|
+
chalk27.red.bold(" Decision: \u{1F534} REVIEW") + chalk27.gray(" \u2014 human approval required")
|
|
20743
21077
|
);
|
|
20744
21078
|
if (result.blockedByLabel) {
|
|
20745
|
-
console.log(
|
|
21079
|
+
console.log(chalk27.gray(` Reason: ${result.blockedByLabel}`));
|
|
20746
21080
|
}
|
|
20747
21081
|
}
|
|
20748
21082
|
console.log("");
|
|
@@ -20757,7 +21091,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20757
21091
|
try {
|
|
20758
21092
|
await startTail2(options);
|
|
20759
21093
|
} catch (err2) {
|
|
20760
|
-
console.error(
|
|
21094
|
+
console.error(chalk27.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20761
21095
|
process.exit(1);
|
|
20762
21096
|
}
|
|
20763
21097
|
});
|
|
@@ -20790,14 +21124,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
20790
21124
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
20791
21125
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
20792
21126
|
if (subcommand === "debug") {
|
|
20793
|
-
const flagFile =
|
|
21127
|
+
const flagFile = path45.join(os38.homedir(), ".node9", "hud-debug");
|
|
20794
21128
|
if (state === "on") {
|
|
20795
|
-
|
|
20796
|
-
|
|
21129
|
+
fs42.mkdirSync(path45.dirname(flagFile), { recursive: true });
|
|
21130
|
+
fs42.writeFileSync(flagFile, "");
|
|
20797
21131
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
20798
21132
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
20799
21133
|
} else if (state === "off") {
|
|
20800
|
-
if (
|
|
21134
|
+
if (fs42.existsSync(flagFile)) fs42.unlinkSync(flagFile);
|
|
20801
21135
|
console.log("HUD debug logging disabled.");
|
|
20802
21136
|
} else {
|
|
20803
21137
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -20812,7 +21146,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
20812
21146
|
const ms = parseDuration(options.duration);
|
|
20813
21147
|
if (ms === null) {
|
|
20814
21148
|
console.error(
|
|
20815
|
-
|
|
21149
|
+
chalk27.red(`
|
|
20816
21150
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
20817
21151
|
`)
|
|
20818
21152
|
);
|
|
@@ -20820,20 +21154,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
20820
21154
|
}
|
|
20821
21155
|
pauseNode9(ms, options.duration);
|
|
20822
21156
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
20823
|
-
console.log(
|
|
21157
|
+
console.log(chalk27.yellow(`
|
|
20824
21158
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
20825
|
-
console.log(
|
|
20826
|
-
console.log(
|
|
21159
|
+
console.log(chalk27.gray(` All tool calls will be allowed without review.`));
|
|
21160
|
+
console.log(chalk27.gray(` Run "node9 resume" to re-enable early.
|
|
20827
21161
|
`));
|
|
20828
21162
|
});
|
|
20829
21163
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
20830
21164
|
const { paused } = checkPause();
|
|
20831
21165
|
if (!paused) {
|
|
20832
|
-
console.log(
|
|
21166
|
+
console.log(chalk27.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
20833
21167
|
return;
|
|
20834
21168
|
}
|
|
20835
21169
|
resumeNode9();
|
|
20836
|
-
console.log(
|
|
21170
|
+
console.log(chalk27.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
20837
21171
|
});
|
|
20838
21172
|
var HOOK_BASED_AGENTS = {
|
|
20839
21173
|
claude: "claude",
|
|
@@ -20846,15 +21180,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20846
21180
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
20847
21181
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
20848
21182
|
console.error(
|
|
20849
|
-
|
|
21183
|
+
chalk27.yellow(`
|
|
20850
21184
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
20851
21185
|
);
|
|
20852
|
-
console.error(
|
|
21186
|
+
console.error(chalk27.white(`
|
|
20853
21187
|
"${target}" uses its own hook system. Use:`));
|
|
20854
21188
|
console.error(
|
|
20855
|
-
|
|
21189
|
+
chalk27.green(` node9 addto ${target} `) + chalk27.gray("# one-time setup")
|
|
20856
21190
|
);
|
|
20857
|
-
console.error(
|
|
21191
|
+
console.error(chalk27.green(` ${target} `) + chalk27.gray("# run normally"));
|
|
20858
21192
|
process.exit(1);
|
|
20859
21193
|
}
|
|
20860
21194
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -20871,7 +21205,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20871
21205
|
}
|
|
20872
21206
|
);
|
|
20873
21207
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
20874
|
-
console.error(
|
|
21208
|
+
console.error(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
20875
21209
|
const daemonReady = await autoStartDaemonAndWait();
|
|
20876
21210
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
20877
21211
|
}
|
|
@@ -20884,12 +21218,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20884
21218
|
}
|
|
20885
21219
|
if (!result.approved) {
|
|
20886
21220
|
console.error(
|
|
20887
|
-
|
|
21221
|
+
chalk27.red(`
|
|
20888
21222
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
20889
21223
|
);
|
|
20890
21224
|
process.exit(1);
|
|
20891
21225
|
}
|
|
20892
|
-
console.error(
|
|
21226
|
+
console.error(chalk27.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
20893
21227
|
await runProxy(fullCommand);
|
|
20894
21228
|
} else {
|
|
20895
21229
|
program.help();
|
|
@@ -20904,14 +21238,15 @@ registerAgentsCommand(program);
|
|
|
20904
21238
|
registerScanCommand(program);
|
|
20905
21239
|
registerSessionsCommand(program);
|
|
20906
21240
|
registerDlpCommand(program);
|
|
21241
|
+
registerMaskCommand(program);
|
|
20907
21242
|
if (process.argv[2] !== "daemon") {
|
|
20908
21243
|
process.on("unhandledRejection", (reason) => {
|
|
20909
21244
|
const isCheckHook = process.argv[2] === "check";
|
|
20910
21245
|
if (isCheckHook) {
|
|
20911
21246
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
20912
|
-
const logPath =
|
|
21247
|
+
const logPath = path45.join(os38.homedir(), ".node9", "hook-debug.log");
|
|
20913
21248
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
20914
|
-
|
|
21249
|
+
fs42.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
20915
21250
|
`);
|
|
20916
21251
|
}
|
|
20917
21252
|
process.exit(0);
|