@node9/proxy 1.13.0 → 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 +430 -200
- package/dist/cli.mjs +428 -198
- package/dist/index.js +9 -0
- package/dist/index.mjs +9 -0
- 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";
|
|
@@ -1618,6 +1637,15 @@ var init_dlp = __esm({
|
|
|
1618
1637
|
keywords: ["age-secret-key-"]
|
|
1619
1638
|
}
|
|
1620
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
|
+
);
|
|
1621
1649
|
SENSITIVE_PATH_PATTERNS = [
|
|
1622
1650
|
/[/\\]\.ssh[/\\]/i,
|
|
1623
1651
|
/[/\\]\.aws[/\\]/i,
|
|
@@ -2184,9 +2212,9 @@ function matchesPattern(text, patterns) {
|
|
|
2184
2212
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
2185
2213
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
2186
2214
|
}
|
|
2187
|
-
function getNestedValue(obj,
|
|
2215
|
+
function getNestedValue(obj, path46) {
|
|
2188
2216
|
if (!obj || typeof obj !== "object") return null;
|
|
2189
|
-
return
|
|
2217
|
+
return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
2190
2218
|
}
|
|
2191
2219
|
function normalizeCommandForPolicy(command) {
|
|
2192
2220
|
try {
|
|
@@ -9153,14 +9181,30 @@ var init_ui = __esm({
|
|
|
9153
9181
|
// \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
|
|
9154
9182
|
if (leaksByPattern.length) {
|
|
9155
9183
|
html +=
|
|
9156
|
-
'<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>';
|
|
9157
9185
|
html += leaksByPattern
|
|
9158
9186
|
.map(([pattern, group]) => {
|
|
9159
9187
|
const count = group.length;
|
|
9160
9188
|
const barPct = Math.round((count / maxBar) * 100);
|
|
9161
9189
|
const detailId = 'detail-' + Math.random().toString(36).slice(2);
|
|
9162
9190
|
const rows = group
|
|
9163
|
-
.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
|
+
})
|
|
9164
9208
|
.join('');
|
|
9165
9209
|
return (
|
|
9166
9210
|
'<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
|
|
@@ -13655,10 +13699,10 @@ __export(tail_exports, {
|
|
|
13655
13699
|
startTail: () => startTail
|
|
13656
13700
|
});
|
|
13657
13701
|
import http2 from "http";
|
|
13658
|
-
import
|
|
13659
|
-
import
|
|
13660
|
-
import
|
|
13661
|
-
import
|
|
13702
|
+
import chalk26 from "chalk";
|
|
13703
|
+
import fs40 from "fs";
|
|
13704
|
+
import os36 from "os";
|
|
13705
|
+
import path43 from "path";
|
|
13662
13706
|
import readline5 from "readline";
|
|
13663
13707
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
13664
13708
|
function getIcon(tool) {
|
|
@@ -13676,20 +13720,20 @@ function getModelContextLimit(model) {
|
|
|
13676
13720
|
return 2e5;
|
|
13677
13721
|
}
|
|
13678
13722
|
function readSessionUsage() {
|
|
13679
|
-
const projectsDir =
|
|
13680
|
-
if (!
|
|
13723
|
+
const projectsDir = path43.join(os36.homedir(), ".claude", "projects");
|
|
13724
|
+
if (!fs40.existsSync(projectsDir)) return null;
|
|
13681
13725
|
let latestFile = null;
|
|
13682
13726
|
let latestMtime = 0;
|
|
13683
13727
|
try {
|
|
13684
|
-
for (const dir of
|
|
13685
|
-
const dirPath =
|
|
13728
|
+
for (const dir of fs40.readdirSync(projectsDir)) {
|
|
13729
|
+
const dirPath = path43.join(projectsDir, dir);
|
|
13686
13730
|
try {
|
|
13687
|
-
if (!
|
|
13688
|
-
for (const file of
|
|
13731
|
+
if (!fs40.statSync(dirPath).isDirectory()) continue;
|
|
13732
|
+
for (const file of fs40.readdirSync(dirPath)) {
|
|
13689
13733
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
13690
|
-
const filePath =
|
|
13734
|
+
const filePath = path43.join(dirPath, file);
|
|
13691
13735
|
try {
|
|
13692
|
-
const mtime =
|
|
13736
|
+
const mtime = fs40.statSync(filePath).mtimeMs;
|
|
13693
13737
|
if (mtime > latestMtime) {
|
|
13694
13738
|
latestMtime = mtime;
|
|
13695
13739
|
latestFile = filePath;
|
|
@@ -13704,7 +13748,7 @@ function readSessionUsage() {
|
|
|
13704
13748
|
}
|
|
13705
13749
|
if (!latestFile) return null;
|
|
13706
13750
|
try {
|
|
13707
|
-
const lines =
|
|
13751
|
+
const lines = fs40.readFileSync(latestFile, "utf-8").split("\n");
|
|
13708
13752
|
let lastModel = "";
|
|
13709
13753
|
let lastInput = 0;
|
|
13710
13754
|
let lastOutput = 0;
|
|
@@ -13729,10 +13773,10 @@ function readSessionUsage() {
|
|
|
13729
13773
|
}
|
|
13730
13774
|
}
|
|
13731
13775
|
function formatContextStat(stat) {
|
|
13732
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
13776
|
+
const pctColor = stat.fillPct >= 80 ? chalk26.red : stat.fillPct >= 50 ? chalk26.yellow : chalk26.cyan;
|
|
13733
13777
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
13734
13778
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
13735
|
-
return
|
|
13779
|
+
return chalk26.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk26.dim(
|
|
13736
13780
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
13737
13781
|
);
|
|
13738
13782
|
}
|
|
@@ -13748,28 +13792,28 @@ function wrappedLineCount(text) {
|
|
|
13748
13792
|
function agentLabel(agent) {
|
|
13749
13793
|
if (!agent || agent === "Terminal") return "";
|
|
13750
13794
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
13751
|
-
return short ?
|
|
13795
|
+
return short ? chalk26.dim(`[${short}] `) : "";
|
|
13752
13796
|
}
|
|
13753
13797
|
function formatBase(activity) {
|
|
13754
13798
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
13755
13799
|
const icon = getIcon(activity.tool);
|
|
13756
13800
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
13757
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
13801
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os36.homedir(), "~");
|
|
13758
13802
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
13759
|
-
return `${
|
|
13803
|
+
return `${chalk26.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk26.white.bold(toolName)} ${chalk26.dim(argsPreview)}`;
|
|
13760
13804
|
}
|
|
13761
13805
|
function renderResult(activity, result) {
|
|
13762
13806
|
const base = formatBase(activity);
|
|
13763
13807
|
let status;
|
|
13764
13808
|
if (result.status === "allow") {
|
|
13765
|
-
status =
|
|
13809
|
+
status = chalk26.green("\u2713 ALLOW");
|
|
13766
13810
|
} else if (result.status === "dlp") {
|
|
13767
|
-
status =
|
|
13811
|
+
status = chalk26.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
13768
13812
|
} else {
|
|
13769
|
-
status =
|
|
13813
|
+
status = chalk26.red("\u2717 BLOCK");
|
|
13770
13814
|
}
|
|
13771
13815
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
13772
|
-
const costSuffix = cost == null ? "" :
|
|
13816
|
+
const costSuffix = cost == null ? "" : chalk26.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
13773
13817
|
if (process.stdout.isTTY) {
|
|
13774
13818
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
13775
13819
|
readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -13786,19 +13830,19 @@ function renderResult(activity, result) {
|
|
|
13786
13830
|
}
|
|
13787
13831
|
function renderPending(activity) {
|
|
13788
13832
|
if (!process.stdout.isTTY) return;
|
|
13789
|
-
const line = `${formatBase(activity)} ${
|
|
13833
|
+
const line = `${formatBase(activity)} ${chalk26.yellow("\u25CF \u2026")}`;
|
|
13790
13834
|
pendingShownForId = activity.id;
|
|
13791
13835
|
pendingWrappedLines = wrappedLineCount(line);
|
|
13792
13836
|
process.stdout.write(`${line}\r`);
|
|
13793
13837
|
}
|
|
13794
13838
|
async function ensureDaemon() {
|
|
13795
13839
|
let pidPort = null;
|
|
13796
|
-
if (
|
|
13840
|
+
if (fs40.existsSync(PID_FILE)) {
|
|
13797
13841
|
try {
|
|
13798
|
-
const { port } = JSON.parse(
|
|
13842
|
+
const { port } = JSON.parse(fs40.readFileSync(PID_FILE, "utf-8"));
|
|
13799
13843
|
pidPort = port;
|
|
13800
13844
|
} catch {
|
|
13801
|
-
console.error(
|
|
13845
|
+
console.error(chalk26.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
13802
13846
|
}
|
|
13803
13847
|
}
|
|
13804
13848
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -13809,7 +13853,7 @@ async function ensureDaemon() {
|
|
|
13809
13853
|
if (res.ok) return checkPort;
|
|
13810
13854
|
} catch {
|
|
13811
13855
|
}
|
|
13812
|
-
console.log(
|
|
13856
|
+
console.log(chalk26.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
13813
13857
|
const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
|
|
13814
13858
|
detached: true,
|
|
13815
13859
|
stdio: "ignore",
|
|
@@ -13826,7 +13870,7 @@ async function ensureDaemon() {
|
|
|
13826
13870
|
} catch {
|
|
13827
13871
|
}
|
|
13828
13872
|
}
|
|
13829
|
-
console.error(
|
|
13873
|
+
console.error(chalk26.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
13830
13874
|
process.exit(1);
|
|
13831
13875
|
}
|
|
13832
13876
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -13892,7 +13936,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
13892
13936
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
13893
13937
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
13894
13938
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
13895
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
13939
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk26.dim(`(${req.agent})`)}` : "";
|
|
13896
13940
|
const lines = [
|
|
13897
13941
|
``,
|
|
13898
13942
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -13948,9 +13992,9 @@ function buildRecoveryCardLines(req) {
|
|
|
13948
13992
|
];
|
|
13949
13993
|
}
|
|
13950
13994
|
function readApproversFromDisk() {
|
|
13951
|
-
const configPath =
|
|
13995
|
+
const configPath = path43.join(os36.homedir(), ".node9", "config.json");
|
|
13952
13996
|
try {
|
|
13953
|
-
const raw = JSON.parse(
|
|
13997
|
+
const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
|
|
13954
13998
|
const settings = raw.settings ?? {};
|
|
13955
13999
|
return settings.approvers ?? {};
|
|
13956
14000
|
} catch {
|
|
@@ -13961,20 +14005,20 @@ function approverStatusLine() {
|
|
|
13961
14005
|
const a = readApproversFromDisk();
|
|
13962
14006
|
const fmt = (label, key) => {
|
|
13963
14007
|
const on = a[key] !== false;
|
|
13964
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
14008
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk26.green("\u2713") : chalk26.dim("\u2717")}`;
|
|
13965
14009
|
};
|
|
13966
14010
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
13967
14011
|
}
|
|
13968
14012
|
function toggleApprover(channel) {
|
|
13969
|
-
const configPath =
|
|
14013
|
+
const configPath = path43.join(os36.homedir(), ".node9", "config.json");
|
|
13970
14014
|
try {
|
|
13971
|
-
const raw = JSON.parse(
|
|
14015
|
+
const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
|
|
13972
14016
|
const settings = raw.settings ?? {};
|
|
13973
14017
|
const approvers = settings.approvers ?? {};
|
|
13974
14018
|
approvers[channel] = approvers[channel] === false;
|
|
13975
14019
|
settings.approvers = approvers;
|
|
13976
14020
|
raw.settings = settings;
|
|
13977
|
-
|
|
14021
|
+
fs40.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
13978
14022
|
} catch (err2) {
|
|
13979
14023
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
13980
14024
|
`);
|
|
@@ -14006,7 +14050,7 @@ async function startTail(options = {}) {
|
|
|
14006
14050
|
req2.end();
|
|
14007
14051
|
});
|
|
14008
14052
|
if (result.ok) {
|
|
14009
|
-
console.log(
|
|
14053
|
+
console.log(chalk26.green("\u2713 Flight Recorder buffer cleared."));
|
|
14010
14054
|
} else if (result.code === "ECONNREFUSED") {
|
|
14011
14055
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
14012
14056
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -14050,7 +14094,7 @@ async function startTail(options = {}) {
|
|
|
14050
14094
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
14051
14095
|
if (channel) {
|
|
14052
14096
|
toggleApprover(channel);
|
|
14053
|
-
console.log(
|
|
14097
|
+
console.log(chalk26.dim(` Approvers: ${approverStatusLine()}`));
|
|
14054
14098
|
}
|
|
14055
14099
|
};
|
|
14056
14100
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -14116,7 +14160,7 @@ async function startTail(options = {}) {
|
|
|
14116
14160
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
14117
14161
|
)
|
|
14118
14162
|
);
|
|
14119
|
-
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");
|
|
14120
14164
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
14121
14165
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
14122
14166
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -14144,8 +14188,8 @@ async function startTail(options = {}) {
|
|
|
14144
14188
|
}
|
|
14145
14189
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
14146
14190
|
try {
|
|
14147
|
-
|
|
14148
|
-
|
|
14191
|
+
fs40.appendFileSync(
|
|
14192
|
+
path43.join(os36.homedir(), ".node9", "hook-debug.log"),
|
|
14149
14193
|
`[tail] POST /decision failed: ${String(err2)}
|
|
14150
14194
|
`
|
|
14151
14195
|
);
|
|
@@ -14167,7 +14211,7 @@ async function startTail(options = {}) {
|
|
|
14167
14211
|
);
|
|
14168
14212
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
14169
14213
|
if (externalDecision) {
|
|
14170
|
-
const source = externalDecision === "allow" ?
|
|
14214
|
+
const source = externalDecision === "allow" ? chalk26.green("\u2713 ALLOWED") : chalk26.red("\u2717 DENIED");
|
|
14171
14215
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
14172
14216
|
}
|
|
14173
14217
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -14226,31 +14270,31 @@ async function startTail(options = {}) {
|
|
|
14226
14270
|
}
|
|
14227
14271
|
} catch {
|
|
14228
14272
|
}
|
|
14229
|
-
const auditLog =
|
|
14273
|
+
const auditLog = path43.join(os36.homedir(), ".node9", "audit.log");
|
|
14230
14274
|
try {
|
|
14231
|
-
const unackedDlp =
|
|
14275
|
+
const unackedDlp = fs40.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
14232
14276
|
if (unackedDlp > 0) {
|
|
14233
14277
|
console.log("");
|
|
14234
14278
|
console.log(
|
|
14235
|
-
|
|
14279
|
+
chalk26.bgRed.white.bold(
|
|
14236
14280
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
14237
14281
|
)
|
|
14238
14282
|
);
|
|
14239
14283
|
}
|
|
14240
14284
|
} catch {
|
|
14241
14285
|
}
|
|
14242
|
-
console.log(
|
|
14243
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
14286
|
+
console.log(chalk26.cyan.bold(`
|
|
14287
|
+
\u{1F6F0}\uFE0F Node9 tail `) + chalk26.dim(`\u2192 ${dashboardUrl}`));
|
|
14244
14288
|
if (canApprove) {
|
|
14245
|
-
console.log(
|
|
14246
|
-
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`));
|
|
14247
14291
|
}
|
|
14248
14292
|
const ctxStat = readSessionUsage();
|
|
14249
14293
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
14250
14294
|
if (options.history) {
|
|
14251
|
-
console.log(
|
|
14295
|
+
console.log(chalk26.dim("Showing history + live events.\n"));
|
|
14252
14296
|
} else {
|
|
14253
|
-
console.log(
|
|
14297
|
+
console.log(chalk26.dim("Showing live events only. Use --history to include past.\n"));
|
|
14254
14298
|
}
|
|
14255
14299
|
process.on("SIGINT", () => {
|
|
14256
14300
|
exitIdleMode();
|
|
@@ -14260,13 +14304,13 @@ async function startTail(options = {}) {
|
|
|
14260
14304
|
readline5.clearLine(process.stdout, 0);
|
|
14261
14305
|
readline5.cursorTo(process.stdout, 0);
|
|
14262
14306
|
}
|
|
14263
|
-
console.log(
|
|
14307
|
+
console.log(chalk26.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
14264
14308
|
process.exit(0);
|
|
14265
14309
|
});
|
|
14266
14310
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
14267
14311
|
const req = http2.get(sseUrl, (res) => {
|
|
14268
14312
|
if (res.statusCode !== 200) {
|
|
14269
|
-
console.error(
|
|
14313
|
+
console.error(chalk26.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
14270
14314
|
process.exit(1);
|
|
14271
14315
|
}
|
|
14272
14316
|
if (canApprove) enterIdleMode();
|
|
@@ -14297,7 +14341,7 @@ async function startTail(options = {}) {
|
|
|
14297
14341
|
readline5.clearLine(process.stdout, 0);
|
|
14298
14342
|
readline5.cursorTo(process.stdout, 0);
|
|
14299
14343
|
}
|
|
14300
|
-
console.log(
|
|
14344
|
+
console.log(chalk26.red("\n\u274C Daemon disconnected."));
|
|
14301
14345
|
process.exit(1);
|
|
14302
14346
|
});
|
|
14303
14347
|
});
|
|
@@ -14389,9 +14433,9 @@ async function startTail(options = {}) {
|
|
|
14389
14433
|
const hash = data.hash ?? "";
|
|
14390
14434
|
const summary = data.argsSummary ?? data.tool;
|
|
14391
14435
|
const fileCount = data.fileCount ?? 0;
|
|
14392
|
-
const files = fileCount > 0 ?
|
|
14436
|
+
const files = fileCount > 0 ? chalk26.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
14393
14437
|
process.stdout.write(
|
|
14394
|
-
`${
|
|
14438
|
+
`${chalk26.dim(time)} ${chalk26.cyan("\u{1F4F8} snapshot")} ${chalk26.dim(hash)} ${summary}${files}
|
|
14395
14439
|
`
|
|
14396
14440
|
);
|
|
14397
14441
|
return;
|
|
@@ -14408,7 +14452,7 @@ async function startTail(options = {}) {
|
|
|
14408
14452
|
}
|
|
14409
14453
|
req.on("error", (err2) => {
|
|
14410
14454
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
14411
|
-
console.error(
|
|
14455
|
+
console.error(chalk26.red(`
|
|
14412
14456
|
\u274C ${msg}`));
|
|
14413
14457
|
process.exit(1);
|
|
14414
14458
|
});
|
|
@@ -14420,7 +14464,7 @@ var init_tail = __esm({
|
|
|
14420
14464
|
init_daemon2();
|
|
14421
14465
|
init_daemon();
|
|
14422
14466
|
init_core();
|
|
14423
|
-
PID_FILE =
|
|
14467
|
+
PID_FILE = path43.join(os36.homedir(), ".node9", "daemon.pid");
|
|
14424
14468
|
ICONS = {
|
|
14425
14469
|
bash: "\u{1F4BB}",
|
|
14426
14470
|
shell: "\u{1F4BB}",
|
|
@@ -14468,9 +14512,9 @@ __export(hud_exports, {
|
|
|
14468
14512
|
main: () => main,
|
|
14469
14513
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
14470
14514
|
});
|
|
14471
|
-
import
|
|
14472
|
-
import
|
|
14473
|
-
import
|
|
14515
|
+
import fs41 from "fs";
|
|
14516
|
+
import path44 from "path";
|
|
14517
|
+
import os37 from "os";
|
|
14474
14518
|
import http3 from "http";
|
|
14475
14519
|
async function readStdin() {
|
|
14476
14520
|
const chunks = [];
|
|
@@ -14546,9 +14590,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
14546
14590
|
return ` (${m}m left)`;
|
|
14547
14591
|
}
|
|
14548
14592
|
function safeReadJson(filePath) {
|
|
14549
|
-
if (!
|
|
14593
|
+
if (!fs41.existsSync(filePath)) return null;
|
|
14550
14594
|
try {
|
|
14551
|
-
return JSON.parse(
|
|
14595
|
+
return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
|
|
14552
14596
|
} catch {
|
|
14553
14597
|
return null;
|
|
14554
14598
|
}
|
|
@@ -14569,12 +14613,12 @@ function countHooksInFile(filePath) {
|
|
|
14569
14613
|
return Object.keys(cfg.hooks).length;
|
|
14570
14614
|
}
|
|
14571
14615
|
function countRulesInDir(rulesDir) {
|
|
14572
|
-
if (!
|
|
14616
|
+
if (!fs41.existsSync(rulesDir)) return 0;
|
|
14573
14617
|
let count = 0;
|
|
14574
14618
|
try {
|
|
14575
|
-
for (const entry of
|
|
14619
|
+
for (const entry of fs41.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
14576
14620
|
if (entry.isDirectory()) {
|
|
14577
|
-
count += countRulesInDir(
|
|
14621
|
+
count += countRulesInDir(path44.join(rulesDir, entry.name));
|
|
14578
14622
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
14579
14623
|
count++;
|
|
14580
14624
|
}
|
|
@@ -14585,46 +14629,46 @@ function countRulesInDir(rulesDir) {
|
|
|
14585
14629
|
}
|
|
14586
14630
|
function isSamePath(a, b) {
|
|
14587
14631
|
try {
|
|
14588
|
-
return
|
|
14632
|
+
return path44.resolve(a) === path44.resolve(b);
|
|
14589
14633
|
} catch {
|
|
14590
14634
|
return false;
|
|
14591
14635
|
}
|
|
14592
14636
|
}
|
|
14593
14637
|
function countConfigs(cwd) {
|
|
14594
|
-
const homeDir2 =
|
|
14595
|
-
const claudeDir =
|
|
14638
|
+
const homeDir2 = os37.homedir();
|
|
14639
|
+
const claudeDir = path44.join(homeDir2, ".claude");
|
|
14596
14640
|
let claudeMdCount = 0;
|
|
14597
14641
|
let rulesCount = 0;
|
|
14598
14642
|
let hooksCount = 0;
|
|
14599
14643
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
14600
14644
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
14601
|
-
if (
|
|
14602
|
-
rulesCount += countRulesInDir(
|
|
14603
|
-
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");
|
|
14604
14648
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
14605
14649
|
hooksCount += countHooksInFile(userSettings);
|
|
14606
|
-
const userClaudeJson =
|
|
14650
|
+
const userClaudeJson = path44.join(homeDir2, ".claude.json");
|
|
14607
14651
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
14608
14652
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
14609
14653
|
userMcpServers.delete(name);
|
|
14610
14654
|
}
|
|
14611
14655
|
if (cwd) {
|
|
14612
|
-
if (
|
|
14613
|
-
if (
|
|
14614
|
-
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");
|
|
14615
14659
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
14616
14660
|
if (!overlapsUserScope) {
|
|
14617
|
-
if (
|
|
14618
|
-
rulesCount += countRulesInDir(
|
|
14619
|
-
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");
|
|
14620
14664
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
14621
14665
|
hooksCount += countHooksInFile(projSettings);
|
|
14622
14666
|
}
|
|
14623
|
-
if (
|
|
14624
|
-
const localSettings =
|
|
14667
|
+
if (fs41.existsSync(path44.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
14668
|
+
const localSettings = path44.join(projectClaudeDir, "settings.local.json");
|
|
14625
14669
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
14626
14670
|
hooksCount += countHooksInFile(localSettings);
|
|
14627
|
-
const mcpJsonServers = getMcpServerNames(
|
|
14671
|
+
const mcpJsonServers = getMcpServerNames(path44.join(cwd, ".mcp.json"));
|
|
14628
14672
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
14629
14673
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
14630
14674
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -14657,12 +14701,12 @@ function readActiveShieldsHud() {
|
|
|
14657
14701
|
return shieldsCache.value;
|
|
14658
14702
|
}
|
|
14659
14703
|
try {
|
|
14660
|
-
const shieldsPath =
|
|
14661
|
-
if (!
|
|
14704
|
+
const shieldsPath = path44.join(os37.homedir(), ".node9", "shields.json");
|
|
14705
|
+
if (!fs41.existsSync(shieldsPath)) {
|
|
14662
14706
|
shieldsCache = { value: [], ts: now };
|
|
14663
14707
|
return [];
|
|
14664
14708
|
}
|
|
14665
|
-
const parsed = JSON.parse(
|
|
14709
|
+
const parsed = JSON.parse(fs41.readFileSync(shieldsPath, "utf-8"));
|
|
14666
14710
|
if (!Array.isArray(parsed.active)) {
|
|
14667
14711
|
shieldsCache = { value: [], ts: now };
|
|
14668
14712
|
return [];
|
|
@@ -14764,17 +14808,17 @@ function renderContextLine(stdin) {
|
|
|
14764
14808
|
async function main() {
|
|
14765
14809
|
try {
|
|
14766
14810
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
14767
|
-
if (
|
|
14811
|
+
if (fs41.existsSync(path44.join(os37.homedir(), ".node9", "hud-debug"))) {
|
|
14768
14812
|
try {
|
|
14769
|
-
const logPath =
|
|
14813
|
+
const logPath = path44.join(os37.homedir(), ".node9", "hud-debug.log");
|
|
14770
14814
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
14771
14815
|
let size = 0;
|
|
14772
14816
|
try {
|
|
14773
|
-
size =
|
|
14817
|
+
size = fs41.statSync(logPath).size;
|
|
14774
14818
|
} catch {
|
|
14775
14819
|
}
|
|
14776
14820
|
if (size < MAX_LOG_SIZE) {
|
|
14777
|
-
|
|
14821
|
+
fs41.appendFileSync(
|
|
14778
14822
|
logPath,
|
|
14779
14823
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
14780
14824
|
);
|
|
@@ -14795,11 +14839,11 @@ async function main() {
|
|
|
14795
14839
|
try {
|
|
14796
14840
|
const cwd = stdin.cwd ?? process.cwd();
|
|
14797
14841
|
for (const configPath of [
|
|
14798
|
-
|
|
14799
|
-
|
|
14842
|
+
path44.join(cwd, "node9.config.json"),
|
|
14843
|
+
path44.join(os37.homedir(), ".node9", "config.json")
|
|
14800
14844
|
]) {
|
|
14801
|
-
if (!
|
|
14802
|
-
const cfg = JSON.parse(
|
|
14845
|
+
if (!fs41.existsSync(configPath)) continue;
|
|
14846
|
+
const cfg = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
|
|
14803
14847
|
const hud = cfg.settings?.hud;
|
|
14804
14848
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
14805
14849
|
}
|
|
@@ -14845,10 +14889,10 @@ init_core();
|
|
|
14845
14889
|
init_setup();
|
|
14846
14890
|
init_daemon2();
|
|
14847
14891
|
import { Command } from "commander";
|
|
14848
|
-
import
|
|
14849
|
-
import
|
|
14850
|
-
import
|
|
14851
|
-
import
|
|
14892
|
+
import chalk27 from "chalk";
|
|
14893
|
+
import fs42 from "fs";
|
|
14894
|
+
import path45 from "path";
|
|
14895
|
+
import os38 from "os";
|
|
14852
14896
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
14853
14897
|
|
|
14854
14898
|
// src/utils/duration.ts
|
|
@@ -20558,22 +20602,207 @@ function registerDlpCommand(program2) {
|
|
|
20558
20602
|
});
|
|
20559
20603
|
}
|
|
20560
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
|
+
|
|
20561
20790
|
// src/cli.ts
|
|
20562
20791
|
var { version } = JSON.parse(
|
|
20563
|
-
|
|
20792
|
+
fs42.readFileSync(path45.join(__dirname, "../package.json"), "utf-8")
|
|
20564
20793
|
);
|
|
20565
20794
|
var program = new Command();
|
|
20566
20795
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
20567
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) => {
|
|
20568
20797
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
20569
|
-
const credPath =
|
|
20570
|
-
if (!
|
|
20571
|
-
|
|
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 });
|
|
20572
20801
|
const profileName = options.profile || "default";
|
|
20573
20802
|
let existingCreds = {};
|
|
20574
20803
|
try {
|
|
20575
|
-
if (
|
|
20576
|
-
const raw = JSON.parse(
|
|
20804
|
+
if (fs42.existsSync(credPath)) {
|
|
20805
|
+
const raw = JSON.parse(fs42.readFileSync(credPath, "utf-8"));
|
|
20577
20806
|
if (raw.apiKey) {
|
|
20578
20807
|
existingCreds = {
|
|
20579
20808
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -20585,13 +20814,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20585
20814
|
} catch {
|
|
20586
20815
|
}
|
|
20587
20816
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
20588
|
-
|
|
20817
|
+
fs42.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
20589
20818
|
if (profileName === "default") {
|
|
20590
|
-
const configPath =
|
|
20819
|
+
const configPath = path45.join(os38.homedir(), ".node9", "config.json");
|
|
20591
20820
|
let config = {};
|
|
20592
20821
|
try {
|
|
20593
|
-
if (
|
|
20594
|
-
config = JSON.parse(
|
|
20822
|
+
if (fs42.existsSync(configPath))
|
|
20823
|
+
config = JSON.parse(fs42.readFileSync(configPath, "utf-8"));
|
|
20595
20824
|
} catch {
|
|
20596
20825
|
}
|
|
20597
20826
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -20606,19 +20835,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20606
20835
|
approvers.cloud = false;
|
|
20607
20836
|
}
|
|
20608
20837
|
s.approvers = approvers;
|
|
20609
|
-
if (!
|
|
20610
|
-
|
|
20611
|
-
|
|
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 });
|
|
20612
20841
|
}
|
|
20613
20842
|
if (options.profile && profileName !== "default") {
|
|
20614
|
-
console.log(
|
|
20615
|
-
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`));
|
|
20616
20845
|
} else if (options.local) {
|
|
20617
|
-
console.log(
|
|
20618
|
-
console.log(
|
|
20846
|
+
console.log(chalk27.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
20847
|
+
console.log(chalk27.gray(` All decisions stay on this machine.`));
|
|
20619
20848
|
} else {
|
|
20620
|
-
console.log(
|
|
20621
|
-
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.`));
|
|
20622
20851
|
}
|
|
20623
20852
|
});
|
|
20624
20853
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
@@ -20636,7 +20865,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
20636
20865
|
if (target === "vscode") return await setupVSCode();
|
|
20637
20866
|
if (target === "hud") return setupHud();
|
|
20638
20867
|
console.error(
|
|
20639
|
-
|
|
20868
|
+
chalk27.red(
|
|
20640
20869
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20641
20870
|
)
|
|
20642
20871
|
);
|
|
@@ -20650,17 +20879,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
20650
20879
|
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
20651
20880
|
).action(async (target) => {
|
|
20652
20881
|
if (!target) {
|
|
20653
|
-
console.log(
|
|
20654
|
-
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");
|
|
20655
20884
|
console.log(" Targets:");
|
|
20656
|
-
console.log(" " +
|
|
20657
|
-
console.log(" " +
|
|
20658
|
-
console.log(" " +
|
|
20659
|
-
console.log(" " +
|
|
20660
|
-
console.log(" " +
|
|
20661
|
-
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)");
|
|
20662
20891
|
process.stdout.write(
|
|
20663
|
-
" " +
|
|
20892
|
+
" " + chalk27.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
20664
20893
|
);
|
|
20665
20894
|
console.log("");
|
|
20666
20895
|
return;
|
|
@@ -20674,7 +20903,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
20674
20903
|
if (t === "vscode") return await setupVSCode();
|
|
20675
20904
|
if (t === "hud") return setupHud();
|
|
20676
20905
|
console.error(
|
|
20677
|
-
|
|
20906
|
+
chalk27.red(
|
|
20678
20907
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20679
20908
|
)
|
|
20680
20909
|
);
|
|
@@ -20697,33 +20926,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
20697
20926
|
else if (target === "hud") fn = teardownHud;
|
|
20698
20927
|
else {
|
|
20699
20928
|
console.error(
|
|
20700
|
-
|
|
20929
|
+
chalk27.red(
|
|
20701
20930
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
20702
20931
|
)
|
|
20703
20932
|
);
|
|
20704
20933
|
process.exit(1);
|
|
20705
20934
|
}
|
|
20706
|
-
console.log(
|
|
20935
|
+
console.log(chalk27.cyan(`
|
|
20707
20936
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
20708
20937
|
`));
|
|
20709
20938
|
try {
|
|
20710
20939
|
fn();
|
|
20711
20940
|
} catch (err2) {
|
|
20712
|
-
console.error(
|
|
20941
|
+
console.error(chalk27.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20713
20942
|
process.exit(1);
|
|
20714
20943
|
}
|
|
20715
|
-
console.log(
|
|
20944
|
+
console.log(chalk27.gray("\n Restart the agent for changes to take effect."));
|
|
20716
20945
|
});
|
|
20717
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) => {
|
|
20718
|
-
console.log(
|
|
20719
|
-
console.log(
|
|
20947
|
+
console.log(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
20948
|
+
console.log(chalk27.bold("Stopping daemon..."));
|
|
20720
20949
|
try {
|
|
20721
20950
|
stopDaemon();
|
|
20722
|
-
console.log(
|
|
20951
|
+
console.log(chalk27.green(" \u2705 Daemon stopped"));
|
|
20723
20952
|
} catch {
|
|
20724
|
-
console.log(
|
|
20953
|
+
console.log(chalk27.blue(" \u2139\uFE0F Daemon was not running"));
|
|
20725
20954
|
}
|
|
20726
|
-
console.log(
|
|
20955
|
+
console.log(chalk27.bold("\nRemoving hooks..."));
|
|
20727
20956
|
let teardownFailed = false;
|
|
20728
20957
|
for (const [label, fn] of [
|
|
20729
20958
|
["Claude", teardownClaude],
|
|
@@ -20738,45 +20967,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
20738
20967
|
} catch (err2) {
|
|
20739
20968
|
teardownFailed = true;
|
|
20740
20969
|
console.error(
|
|
20741
|
-
|
|
20970
|
+
chalk27.red(
|
|
20742
20971
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
20743
20972
|
)
|
|
20744
20973
|
);
|
|
20745
20974
|
}
|
|
20746
20975
|
}
|
|
20747
20976
|
if (options.purge) {
|
|
20748
|
-
const node9Dir =
|
|
20749
|
-
if (
|
|
20977
|
+
const node9Dir = path45.join(os38.homedir(), ".node9");
|
|
20978
|
+
if (fs42.existsSync(node9Dir)) {
|
|
20750
20979
|
const confirmed = await confirm2({
|
|
20751
20980
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
20752
20981
|
default: false
|
|
20753
20982
|
});
|
|
20754
20983
|
if (confirmed) {
|
|
20755
|
-
|
|
20756
|
-
if (
|
|
20984
|
+
fs42.rmSync(node9Dir, { recursive: true });
|
|
20985
|
+
if (fs42.existsSync(node9Dir)) {
|
|
20757
20986
|
console.error(
|
|
20758
|
-
|
|
20987
|
+
chalk27.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
20759
20988
|
);
|
|
20760
20989
|
} else {
|
|
20761
|
-
console.log(
|
|
20990
|
+
console.log(chalk27.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
20762
20991
|
}
|
|
20763
20992
|
} else {
|
|
20764
|
-
console.log(
|
|
20993
|
+
console.log(chalk27.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
20765
20994
|
}
|
|
20766
20995
|
} else {
|
|
20767
|
-
console.log(
|
|
20996
|
+
console.log(chalk27.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
20768
20997
|
}
|
|
20769
20998
|
} else {
|
|
20770
20999
|
console.log(
|
|
20771
|
-
|
|
21000
|
+
chalk27.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
20772
21001
|
);
|
|
20773
21002
|
}
|
|
20774
21003
|
if (teardownFailed) {
|
|
20775
|
-
console.error(
|
|
21004
|
+
console.error(chalk27.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
20776
21005
|
process.exit(1);
|
|
20777
21006
|
}
|
|
20778
|
-
console.log(
|
|
20779
|
-
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"));
|
|
20780
21009
|
});
|
|
20781
21010
|
registerDoctorCommand(program, version);
|
|
20782
21011
|
program.command("explain").description(
|
|
@@ -20789,7 +21018,7 @@ program.command("explain").description(
|
|
|
20789
21018
|
try {
|
|
20790
21019
|
args = JSON.parse(trimmed);
|
|
20791
21020
|
} catch {
|
|
20792
|
-
console.error(
|
|
21021
|
+
console.error(chalk27.red(`
|
|
20793
21022
|
\u274C Invalid JSON: ${trimmed}
|
|
20794
21023
|
`));
|
|
20795
21024
|
process.exit(1);
|
|
@@ -20800,54 +21029,54 @@ program.command("explain").description(
|
|
|
20800
21029
|
}
|
|
20801
21030
|
const result = await explainPolicy(tool, args);
|
|
20802
21031
|
console.log("");
|
|
20803
|
-
console.log(
|
|
21032
|
+
console.log(chalk27.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
20804
21033
|
console.log("");
|
|
20805
|
-
console.log(` ${
|
|
21034
|
+
console.log(` ${chalk27.bold("Tool:")} ${chalk27.white(result.tool)}`);
|
|
20806
21035
|
if (argsRaw) {
|
|
20807
21036
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
20808
|
-
console.log(` ${
|
|
21037
|
+
console.log(` ${chalk27.bold("Input:")} ${chalk27.gray(preview2)}`);
|
|
20809
21038
|
}
|
|
20810
21039
|
console.log("");
|
|
20811
|
-
console.log(
|
|
21040
|
+
console.log(chalk27.bold("Config Sources (Waterfall):"));
|
|
20812
21041
|
for (const tier of result.waterfall) {
|
|
20813
|
-
const num3 =
|
|
21042
|
+
const num3 = chalk27.gray(` ${tier.tier}.`);
|
|
20814
21043
|
const label = tier.label.padEnd(16);
|
|
20815
21044
|
let statusStr;
|
|
20816
21045
|
if (tier.tier === 1) {
|
|
20817
|
-
statusStr =
|
|
21046
|
+
statusStr = chalk27.gray(tier.note ?? "");
|
|
20818
21047
|
} else if (tier.status === "active") {
|
|
20819
|
-
const loc = tier.path ?
|
|
20820
|
-
const note = tier.note ?
|
|
20821
|
-
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 : "");
|
|
20822
21051
|
} else {
|
|
20823
|
-
statusStr =
|
|
21052
|
+
statusStr = chalk27.gray("\u25CB " + (tier.note ?? "not found"));
|
|
20824
21053
|
}
|
|
20825
|
-
console.log(`${num3} ${
|
|
21054
|
+
console.log(`${num3} ${chalk27.white(label)} ${statusStr}`);
|
|
20826
21055
|
}
|
|
20827
21056
|
console.log("");
|
|
20828
|
-
console.log(
|
|
21057
|
+
console.log(chalk27.bold("Policy Evaluation:"));
|
|
20829
21058
|
for (const step of result.steps) {
|
|
20830
21059
|
const isFinal = step.isFinal;
|
|
20831
21060
|
let icon;
|
|
20832
|
-
if (step.outcome === "allow") icon =
|
|
20833
|
-
else if (step.outcome === "review") icon =
|
|
20834
|
-
else if (step.outcome === "skip") icon =
|
|
20835
|
-
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 ");
|
|
20836
21065
|
const name = step.name.padEnd(18);
|
|
20837
|
-
const nameStr = isFinal ?
|
|
20838
|
-
const detail = isFinal ?
|
|
20839
|
-
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") : "";
|
|
20840
21069
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
20841
21070
|
}
|
|
20842
21071
|
console.log("");
|
|
20843
21072
|
if (result.decision === "allow") {
|
|
20844
|
-
console.log(
|
|
21073
|
+
console.log(chalk27.green.bold(" Decision: \u2705 ALLOW") + chalk27.gray(" \u2014 no approval needed"));
|
|
20845
21074
|
} else {
|
|
20846
21075
|
console.log(
|
|
20847
|
-
|
|
21076
|
+
chalk27.red.bold(" Decision: \u{1F534} REVIEW") + chalk27.gray(" \u2014 human approval required")
|
|
20848
21077
|
);
|
|
20849
21078
|
if (result.blockedByLabel) {
|
|
20850
|
-
console.log(
|
|
21079
|
+
console.log(chalk27.gray(` Reason: ${result.blockedByLabel}`));
|
|
20851
21080
|
}
|
|
20852
21081
|
}
|
|
20853
21082
|
console.log("");
|
|
@@ -20862,7 +21091,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20862
21091
|
try {
|
|
20863
21092
|
await startTail2(options);
|
|
20864
21093
|
} catch (err2) {
|
|
20865
|
-
console.error(
|
|
21094
|
+
console.error(chalk27.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20866
21095
|
process.exit(1);
|
|
20867
21096
|
}
|
|
20868
21097
|
});
|
|
@@ -20895,14 +21124,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
20895
21124
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
20896
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) => {
|
|
20897
21126
|
if (subcommand === "debug") {
|
|
20898
|
-
const flagFile =
|
|
21127
|
+
const flagFile = path45.join(os38.homedir(), ".node9", "hud-debug");
|
|
20899
21128
|
if (state === "on") {
|
|
20900
|
-
|
|
20901
|
-
|
|
21129
|
+
fs42.mkdirSync(path45.dirname(flagFile), { recursive: true });
|
|
21130
|
+
fs42.writeFileSync(flagFile, "");
|
|
20902
21131
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
20903
21132
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
20904
21133
|
} else if (state === "off") {
|
|
20905
|
-
if (
|
|
21134
|
+
if (fs42.existsSync(flagFile)) fs42.unlinkSync(flagFile);
|
|
20906
21135
|
console.log("HUD debug logging disabled.");
|
|
20907
21136
|
} else {
|
|
20908
21137
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -20917,7 +21146,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
20917
21146
|
const ms = parseDuration(options.duration);
|
|
20918
21147
|
if (ms === null) {
|
|
20919
21148
|
console.error(
|
|
20920
|
-
|
|
21149
|
+
chalk27.red(`
|
|
20921
21150
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
20922
21151
|
`)
|
|
20923
21152
|
);
|
|
@@ -20925,20 +21154,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
20925
21154
|
}
|
|
20926
21155
|
pauseNode9(ms, options.duration);
|
|
20927
21156
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
20928
|
-
console.log(
|
|
21157
|
+
console.log(chalk27.yellow(`
|
|
20929
21158
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
20930
|
-
console.log(
|
|
20931
|
-
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.
|
|
20932
21161
|
`));
|
|
20933
21162
|
});
|
|
20934
21163
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
20935
21164
|
const { paused } = checkPause();
|
|
20936
21165
|
if (!paused) {
|
|
20937
|
-
console.log(
|
|
21166
|
+
console.log(chalk27.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
20938
21167
|
return;
|
|
20939
21168
|
}
|
|
20940
21169
|
resumeNode9();
|
|
20941
|
-
console.log(
|
|
21170
|
+
console.log(chalk27.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
20942
21171
|
});
|
|
20943
21172
|
var HOOK_BASED_AGENTS = {
|
|
20944
21173
|
claude: "claude",
|
|
@@ -20951,15 +21180,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20951
21180
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
20952
21181
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
20953
21182
|
console.error(
|
|
20954
|
-
|
|
21183
|
+
chalk27.yellow(`
|
|
20955
21184
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
20956
21185
|
);
|
|
20957
|
-
console.error(
|
|
21186
|
+
console.error(chalk27.white(`
|
|
20958
21187
|
"${target}" uses its own hook system. Use:`));
|
|
20959
21188
|
console.error(
|
|
20960
|
-
|
|
21189
|
+
chalk27.green(` node9 addto ${target} `) + chalk27.gray("# one-time setup")
|
|
20961
21190
|
);
|
|
20962
|
-
console.error(
|
|
21191
|
+
console.error(chalk27.green(` ${target} `) + chalk27.gray("# run normally"));
|
|
20963
21192
|
process.exit(1);
|
|
20964
21193
|
}
|
|
20965
21194
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -20976,7 +21205,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20976
21205
|
}
|
|
20977
21206
|
);
|
|
20978
21207
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
20979
|
-
console.error(
|
|
21208
|
+
console.error(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
20980
21209
|
const daemonReady = await autoStartDaemonAndWait();
|
|
20981
21210
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
20982
21211
|
}
|
|
@@ -20989,12 +21218,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
20989
21218
|
}
|
|
20990
21219
|
if (!result.approved) {
|
|
20991
21220
|
console.error(
|
|
20992
|
-
|
|
21221
|
+
chalk27.red(`
|
|
20993
21222
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
20994
21223
|
);
|
|
20995
21224
|
process.exit(1);
|
|
20996
21225
|
}
|
|
20997
|
-
console.error(
|
|
21226
|
+
console.error(chalk27.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
20998
21227
|
await runProxy(fullCommand);
|
|
20999
21228
|
} else {
|
|
21000
21229
|
program.help();
|
|
@@ -21009,14 +21238,15 @@ registerAgentsCommand(program);
|
|
|
21009
21238
|
registerScanCommand(program);
|
|
21010
21239
|
registerSessionsCommand(program);
|
|
21011
21240
|
registerDlpCommand(program);
|
|
21241
|
+
registerMaskCommand(program);
|
|
21012
21242
|
if (process.argv[2] !== "daemon") {
|
|
21013
21243
|
process.on("unhandledRejection", (reason) => {
|
|
21014
21244
|
const isCheckHook = process.argv[2] === "check";
|
|
21015
21245
|
if (isCheckHook) {
|
|
21016
21246
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
21017
|
-
const logPath =
|
|
21247
|
+
const logPath = path45.join(os38.homedir(), ".node9", "hook-debug.log");
|
|
21018
21248
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
21019
|
-
|
|
21249
|
+
fs42.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
21020
21250
|
`);
|
|
21021
21251
|
}
|
|
21022
21252
|
process.exit(0);
|