@node9/proxy 1.10.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +550 -281
- package/dist/cli.mjs +546 -277
- package/dist/index.js +61 -20
- package/dist/index.mjs +61 -20
- 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 path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
151
|
+
return ` \u2022 ${path35}: ${issue.message}`;
|
|
152
152
|
});
|
|
153
153
|
return {
|
|
154
154
|
sanitized,
|
|
@@ -231,7 +231,8 @@ var init_config_schema = __esm({
|
|
|
231
231
|
slackEnabled: z.boolean().optional(),
|
|
232
232
|
enableTrustSessions: z.boolean().optional(),
|
|
233
233
|
allowGlobalPause: z.boolean().optional(),
|
|
234
|
-
auditHashArgs: z.boolean().optional()
|
|
234
|
+
auditHashArgs: z.boolean().optional(),
|
|
235
|
+
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
|
|
235
236
|
}).optional(),
|
|
236
237
|
policy: z.object({
|
|
237
238
|
sandboxPaths: z.array(z.string()).optional(),
|
|
@@ -1709,9 +1710,9 @@ function matchesPattern(text, patterns) {
|
|
|
1709
1710
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1710
1711
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1711
1712
|
}
|
|
1712
|
-
function getNestedValue(obj,
|
|
1713
|
+
function getNestedValue(obj, path35) {
|
|
1713
1714
|
if (!obj || typeof obj !== "object") return null;
|
|
1714
|
-
return
|
|
1715
|
+
return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1715
1716
|
}
|
|
1716
1717
|
function shouldSnapshot(toolName, args, config) {
|
|
1717
1718
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2439,19 +2440,44 @@ function getInternalToken() {
|
|
|
2439
2440
|
function isDaemonRunning() {
|
|
2440
2441
|
const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
|
|
2441
2442
|
if (fs9.existsSync(pidFile)) {
|
|
2443
|
+
let pid;
|
|
2444
|
+
let port;
|
|
2442
2445
|
try {
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
return true;
|
|
2446
|
+
const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
|
|
2447
|
+
pid = data.pid;
|
|
2448
|
+
port = data.port;
|
|
2447
2449
|
} catch {
|
|
2448
2450
|
return false;
|
|
2449
2451
|
}
|
|
2452
|
+
if (port !== DAEMON_PORT) {
|
|
2453
|
+
return false;
|
|
2454
|
+
}
|
|
2455
|
+
const MAX_PID = 4194304;
|
|
2456
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
try {
|
|
2460
|
+
process.kill(pid, 0);
|
|
2461
|
+
} catch (err2) {
|
|
2462
|
+
if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
|
|
2463
|
+
try {
|
|
2464
|
+
fs9.unlinkSync(pidFile);
|
|
2465
|
+
} catch {
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return false;
|
|
2469
|
+
}
|
|
2470
|
+
const r = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
2471
|
+
encoding: "utf8",
|
|
2472
|
+
timeout: 300
|
|
2473
|
+
});
|
|
2474
|
+
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
|
|
2475
|
+
return false;
|
|
2450
2476
|
}
|
|
2451
2477
|
try {
|
|
2452
2478
|
const r = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
2453
2479
|
encoding: "utf8",
|
|
2454
|
-
timeout:
|
|
2480
|
+
timeout: 300
|
|
2455
2481
|
});
|
|
2456
2482
|
return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
|
|
2457
2483
|
} catch {
|
|
@@ -2791,11 +2817,12 @@ ${smartTruncate(str, 500)}`
|
|
|
2791
2817
|
function escapePango(text) {
|
|
2792
2818
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2793
2819
|
}
|
|
2794
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2820
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2795
2821
|
const lines = [];
|
|
2796
2822
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2797
2823
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2798
2824
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2825
|
+
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2799
2826
|
lines.push("");
|
|
2800
2827
|
lines.push(formattedArgs);
|
|
2801
2828
|
if (allowCount >= 3) {
|
|
@@ -2808,7 +2835,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2808
2835
|
}
|
|
2809
2836
|
return lines.join("\n");
|
|
2810
2837
|
}
|
|
2811
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2838
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2812
2839
|
const lines = [];
|
|
2813
2840
|
if (locked) {
|
|
2814
2841
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2818,6 +2845,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2818
2845
|
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
2819
2846
|
);
|
|
2820
2847
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2848
|
+
if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
|
|
2821
2849
|
lines.push("");
|
|
2822
2850
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2823
2851
|
if (allowCount >= 3) {
|
|
@@ -2834,7 +2862,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2834
2862
|
}
|
|
2835
2863
|
return lines.join("\n");
|
|
2836
2864
|
}
|
|
2837
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2865
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
|
|
2838
2866
|
if (isTestEnv()) return "deny";
|
|
2839
2867
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2840
2868
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
@@ -2845,7 +2873,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
2845
2873
|
agent,
|
|
2846
2874
|
explainableLabel,
|
|
2847
2875
|
locked,
|
|
2848
|
-
allowCount
|
|
2876
|
+
allowCount,
|
|
2877
|
+
ruleDescription
|
|
2849
2878
|
);
|
|
2850
2879
|
return new Promise((resolve) => {
|
|
2851
2880
|
let childProcess = null;
|
|
@@ -2879,7 +2908,8 @@ end run`;
|
|
|
2879
2908
|
agent,
|
|
2880
2909
|
explainableLabel,
|
|
2881
2910
|
locked,
|
|
2882
|
-
allowCount
|
|
2911
|
+
allowCount,
|
|
2912
|
+
ruleDescription
|
|
2883
2913
|
);
|
|
2884
2914
|
const argsList = [
|
|
2885
2915
|
locked ? "--info" : "--question",
|
|
@@ -2953,7 +2983,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2953
2983
|
}).catch(() => {
|
|
2954
2984
|
});
|
|
2955
2985
|
}
|
|
2956
|
-
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2986
|
+
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
2957
2987
|
const controller = new AbortController();
|
|
2958
2988
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2959
2989
|
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
@@ -2998,7 +3028,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2998
3028
|
platform: os9.platform()
|
|
2999
3029
|
},
|
|
3000
3030
|
...riskMetadata && { riskMetadata },
|
|
3001
|
-
...ciContext && { ciContext }
|
|
3031
|
+
...ciContext && { ciContext },
|
|
3032
|
+
...agentPolicy && { policy: agentPolicy },
|
|
3033
|
+
...forceReview && { forceReview: true }
|
|
3002
3034
|
}),
|
|
3003
3035
|
signal: controller.signal
|
|
3004
3036
|
});
|
|
@@ -3392,6 +3424,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3392
3424
|
policyMatchedWord,
|
|
3393
3425
|
policyResult.ruleName
|
|
3394
3426
|
);
|
|
3427
|
+
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
3395
3428
|
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
3396
3429
|
if (persistent === "allow") {
|
|
3397
3430
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3426,9 +3459,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3426
3459
|
}
|
|
3427
3460
|
let cloudRequestId = null;
|
|
3428
3461
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
3429
|
-
|
|
3462
|
+
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
3463
|
+
if (cloudEnforced) {
|
|
3430
3464
|
try {
|
|
3431
|
-
const initResult = await initNode9SaaS(
|
|
3465
|
+
const initResult = await initNode9SaaS(
|
|
3466
|
+
toolName,
|
|
3467
|
+
args,
|
|
3468
|
+
creds,
|
|
3469
|
+
meta,
|
|
3470
|
+
riskMetadata,
|
|
3471
|
+
config.settings.agentPolicy,
|
|
3472
|
+
forceReview
|
|
3473
|
+
);
|
|
3432
3474
|
if (!initResult.pending) {
|
|
3433
3475
|
if (initResult.shadowMode) {
|
|
3434
3476
|
return { approved: true, checkedBy: "cloud" };
|
|
@@ -3443,9 +3485,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3443
3485
|
};
|
|
3444
3486
|
}
|
|
3445
3487
|
}
|
|
3446
|
-
if (
|
|
3447
|
-
cloudRequestId = initResult.requestId || null;
|
|
3448
|
-
}
|
|
3488
|
+
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
3449
3489
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3450
3490
|
} catch {
|
|
3451
3491
|
}
|
|
@@ -3502,7 +3542,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3502
3542
|
}
|
|
3503
3543
|
}
|
|
3504
3544
|
}
|
|
3505
|
-
if (cloudEnforced && cloudRequestId
|
|
3545
|
+
if (cloudEnforced && cloudRequestId) {
|
|
3506
3546
|
racePromises.push(
|
|
3507
3547
|
(async () => {
|
|
3508
3548
|
try {
|
|
@@ -3534,7 +3574,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3534
3574
|
signal,
|
|
3535
3575
|
policyMatchedField,
|
|
3536
3576
|
policyMatchedWord,
|
|
3537
|
-
daemonAllowCount
|
|
3577
|
+
daemonAllowCount,
|
|
3578
|
+
riskMetadata?.ruleDescription
|
|
3538
3579
|
);
|
|
3539
3580
|
if (decision === "always_allow") {
|
|
3540
3581
|
writeTrustSession(toolName, 36e5);
|
|
@@ -6036,14 +6077,181 @@ var init_patch = __esm({
|
|
|
6036
6077
|
}
|
|
6037
6078
|
});
|
|
6038
6079
|
|
|
6039
|
-
// src/
|
|
6040
|
-
import http from "http";
|
|
6080
|
+
// src/costSync.ts
|
|
6041
6081
|
import fs16 from "fs";
|
|
6042
6082
|
import path19 from "path";
|
|
6083
|
+
import os14 from "os";
|
|
6084
|
+
function normalizeModel(raw) {
|
|
6085
|
+
return raw.replace(/-\d{8}$/, "");
|
|
6086
|
+
}
|
|
6087
|
+
function pricingFor(model) {
|
|
6088
|
+
const norm = normalizeModel(model);
|
|
6089
|
+
if (PRICING[norm]) return PRICING[norm];
|
|
6090
|
+
let best = null;
|
|
6091
|
+
for (const key of Object.keys(PRICING)) {
|
|
6092
|
+
if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
|
|
6093
|
+
}
|
|
6094
|
+
return best ? PRICING[best] : null;
|
|
6095
|
+
}
|
|
6096
|
+
function parseJSONLFile(filePath) {
|
|
6097
|
+
let content;
|
|
6098
|
+
try {
|
|
6099
|
+
content = fs16.readFileSync(filePath, "utf8");
|
|
6100
|
+
} catch {
|
|
6101
|
+
return /* @__PURE__ */ new Map();
|
|
6102
|
+
}
|
|
6103
|
+
const daily = /* @__PURE__ */ new Map();
|
|
6104
|
+
for (const line of content.split("\n")) {
|
|
6105
|
+
if (!line.trim()) continue;
|
|
6106
|
+
let row;
|
|
6107
|
+
try {
|
|
6108
|
+
row = JSON.parse(line);
|
|
6109
|
+
} catch {
|
|
6110
|
+
continue;
|
|
6111
|
+
}
|
|
6112
|
+
if (row["type"] !== "assistant") continue;
|
|
6113
|
+
const msg = row["message"];
|
|
6114
|
+
if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
|
|
6115
|
+
const usage = msg["usage"];
|
|
6116
|
+
const model = msg["model"];
|
|
6117
|
+
const timestamp = row["timestamp"];
|
|
6118
|
+
if (typeof timestamp !== "string" || timestamp.length < 10) continue;
|
|
6119
|
+
const date = timestamp.slice(0, 10);
|
|
6120
|
+
const p = pricingFor(model);
|
|
6121
|
+
if (!p) continue;
|
|
6122
|
+
const inp = Number(usage["input_tokens"] ?? 0);
|
|
6123
|
+
const out = Number(usage["output_tokens"] ?? 0);
|
|
6124
|
+
const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
|
|
6125
|
+
const cr = Number(usage["cache_read_input_tokens"] ?? 0);
|
|
6126
|
+
const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
|
|
6127
|
+
const norm = normalizeModel(model);
|
|
6128
|
+
const key = `${date}::${norm}`;
|
|
6129
|
+
const prev = daily.get(key);
|
|
6130
|
+
if (prev) {
|
|
6131
|
+
prev.costUSD += cost;
|
|
6132
|
+
prev.inputTokens += inp;
|
|
6133
|
+
prev.outputTokens += out;
|
|
6134
|
+
prev.cacheWriteTokens += cw;
|
|
6135
|
+
prev.cacheReadTokens += cr;
|
|
6136
|
+
} else {
|
|
6137
|
+
daily.set(key, {
|
|
6138
|
+
date,
|
|
6139
|
+
model: norm,
|
|
6140
|
+
costUSD: cost,
|
|
6141
|
+
inputTokens: inp,
|
|
6142
|
+
outputTokens: out,
|
|
6143
|
+
cacheWriteTokens: cw,
|
|
6144
|
+
cacheReadTokens: cr
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
return daily;
|
|
6149
|
+
}
|
|
6150
|
+
function collectEntries() {
|
|
6151
|
+
const projectsDir = path19.join(os14.homedir(), ".claude", "projects");
|
|
6152
|
+
if (!fs16.existsSync(projectsDir)) return [];
|
|
6153
|
+
const combined = /* @__PURE__ */ new Map();
|
|
6154
|
+
let dirs;
|
|
6155
|
+
try {
|
|
6156
|
+
dirs = fs16.readdirSync(projectsDir);
|
|
6157
|
+
} catch {
|
|
6158
|
+
return [];
|
|
6159
|
+
}
|
|
6160
|
+
for (const dir of dirs) {
|
|
6161
|
+
const dirPath = path19.join(projectsDir, dir);
|
|
6162
|
+
try {
|
|
6163
|
+
if (!fs16.statSync(dirPath).isDirectory()) continue;
|
|
6164
|
+
} catch {
|
|
6165
|
+
continue;
|
|
6166
|
+
}
|
|
6167
|
+
let files;
|
|
6168
|
+
try {
|
|
6169
|
+
files = fs16.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
6170
|
+
} catch {
|
|
6171
|
+
continue;
|
|
6172
|
+
}
|
|
6173
|
+
for (const file of files) {
|
|
6174
|
+
const entries = parseJSONLFile(path19.join(dirPath, file));
|
|
6175
|
+
for (const [key, e] of entries) {
|
|
6176
|
+
const prev = combined.get(key);
|
|
6177
|
+
if (prev) {
|
|
6178
|
+
prev.costUSD += e.costUSD;
|
|
6179
|
+
prev.inputTokens += e.inputTokens;
|
|
6180
|
+
prev.outputTokens += e.outputTokens;
|
|
6181
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
6182
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
6183
|
+
} else {
|
|
6184
|
+
combined.set(key, { ...e });
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
return [...combined.values()];
|
|
6190
|
+
}
|
|
6191
|
+
async function syncCost() {
|
|
6192
|
+
const creds = getCredentials();
|
|
6193
|
+
if (!creds?.apiKey || !creds?.apiUrl) return;
|
|
6194
|
+
const entries = collectEntries();
|
|
6195
|
+
if (entries.length === 0) return;
|
|
6196
|
+
let username = "unknown";
|
|
6197
|
+
try {
|
|
6198
|
+
username = os14.userInfo().username;
|
|
6199
|
+
} catch {
|
|
6200
|
+
}
|
|
6201
|
+
const machineId = `${os14.hostname()}:${username}`;
|
|
6202
|
+
try {
|
|
6203
|
+
const res = await fetch(`${creds.apiUrl}/cost-sync`, {
|
|
6204
|
+
method: "POST",
|
|
6205
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
6206
|
+
body: JSON.stringify({ machineId, entries }),
|
|
6207
|
+
signal: AbortSignal.timeout(15e3)
|
|
6208
|
+
});
|
|
6209
|
+
if (!res.ok) {
|
|
6210
|
+
fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
6211
|
+
`);
|
|
6212
|
+
}
|
|
6213
|
+
} catch (err2) {
|
|
6214
|
+
fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
|
|
6215
|
+
`);
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
function startCostSync() {
|
|
6219
|
+
syncCost().catch(() => {
|
|
6220
|
+
});
|
|
6221
|
+
const timer = setInterval(() => {
|
|
6222
|
+
syncCost().catch(() => {
|
|
6223
|
+
});
|
|
6224
|
+
}, SYNC_INTERVAL_MS);
|
|
6225
|
+
timer.unref();
|
|
6226
|
+
}
|
|
6227
|
+
var SYNC_INTERVAL_MS, PRICING;
|
|
6228
|
+
var init_costSync = __esm({
|
|
6229
|
+
"src/costSync.ts"() {
|
|
6230
|
+
"use strict";
|
|
6231
|
+
init_config();
|
|
6232
|
+
init_audit();
|
|
6233
|
+
SYNC_INTERVAL_MS = 10 * 60 * 1e3;
|
|
6234
|
+
PRICING = {
|
|
6235
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
6236
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6237
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6238
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6239
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6240
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6241
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
});
|
|
6245
|
+
|
|
6246
|
+
// src/daemon/server.ts
|
|
6247
|
+
import http from "http";
|
|
6248
|
+
import fs17 from "fs";
|
|
6249
|
+
import path20 from "path";
|
|
6043
6250
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6044
6251
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6045
6252
|
import chalk2 from "chalk";
|
|
6046
6253
|
function startDaemon() {
|
|
6254
|
+
startCostSync();
|
|
6047
6255
|
loadInsightCounts();
|
|
6048
6256
|
const csrfToken = randomUUID4();
|
|
6049
6257
|
const internalToken = randomUUID4();
|
|
@@ -6059,7 +6267,7 @@ function startDaemon() {
|
|
|
6059
6267
|
idleTimer = setTimeout(() => {
|
|
6060
6268
|
if (autoStarted) {
|
|
6061
6269
|
try {
|
|
6062
|
-
|
|
6270
|
+
fs17.unlinkSync(DAEMON_PID_FILE);
|
|
6063
6271
|
} catch {
|
|
6064
6272
|
}
|
|
6065
6273
|
}
|
|
@@ -6222,7 +6430,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6222
6430
|
status: "pending"
|
|
6223
6431
|
});
|
|
6224
6432
|
}
|
|
6225
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6433
|
+
const projectCwd = typeof cwd === "string" && path20.isAbsolute(cwd) ? cwd : void 0;
|
|
6226
6434
|
const projectConfig = getConfig(projectCwd);
|
|
6227
6435
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6228
6436
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6612,8 +6820,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6612
6820
|
const body = await readBody(req);
|
|
6613
6821
|
const data = body ? JSON.parse(body) : {};
|
|
6614
6822
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6615
|
-
const node9Dir =
|
|
6616
|
-
if (!
|
|
6823
|
+
const node9Dir = path20.dirname(GLOBAL_CONFIG_PATH);
|
|
6824
|
+
if (!path20.resolve(configPath).startsWith(node9Dir + path20.sep)) {
|
|
6617
6825
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6618
6826
|
return res.end(
|
|
6619
6827
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6724,14 +6932,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6724
6932
|
server.on("error", (e) => {
|
|
6725
6933
|
if (e.code === "EADDRINUSE") {
|
|
6726
6934
|
try {
|
|
6727
|
-
if (
|
|
6728
|
-
const { pid } = JSON.parse(
|
|
6935
|
+
if (fs17.existsSync(DAEMON_PID_FILE)) {
|
|
6936
|
+
const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6729
6937
|
process.kill(pid, 0);
|
|
6730
6938
|
return process.exit(0);
|
|
6731
6939
|
}
|
|
6732
6940
|
} catch {
|
|
6733
6941
|
try {
|
|
6734
|
-
|
|
6942
|
+
fs17.unlinkSync(DAEMON_PID_FILE);
|
|
6735
6943
|
} catch {
|
|
6736
6944
|
}
|
|
6737
6945
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6799,32 +7007,33 @@ var init_server = __esm({
|
|
|
6799
7007
|
init_state2();
|
|
6800
7008
|
init_patch();
|
|
6801
7009
|
init_config_schema();
|
|
7010
|
+
init_costSync();
|
|
6802
7011
|
}
|
|
6803
7012
|
});
|
|
6804
7013
|
|
|
6805
7014
|
// src/daemon/index.ts
|
|
6806
|
-
import
|
|
7015
|
+
import fs18 from "fs";
|
|
6807
7016
|
import chalk3 from "chalk";
|
|
6808
7017
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
6809
7018
|
function stopDaemon() {
|
|
6810
|
-
if (!
|
|
7019
|
+
if (!fs18.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
|
|
6811
7020
|
try {
|
|
6812
|
-
const { pid } = JSON.parse(
|
|
7021
|
+
const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6813
7022
|
process.kill(pid, "SIGTERM");
|
|
6814
7023
|
console.log(chalk3.green("\u2705 Stopped."));
|
|
6815
7024
|
} catch {
|
|
6816
7025
|
console.log(chalk3.gray("Cleaned up stale PID file."));
|
|
6817
7026
|
} finally {
|
|
6818
7027
|
try {
|
|
6819
|
-
|
|
7028
|
+
fs18.unlinkSync(DAEMON_PID_FILE);
|
|
6820
7029
|
} catch {
|
|
6821
7030
|
}
|
|
6822
7031
|
}
|
|
6823
7032
|
}
|
|
6824
7033
|
function daemonStatus() {
|
|
6825
|
-
if (
|
|
7034
|
+
if (fs18.existsSync(DAEMON_PID_FILE)) {
|
|
6826
7035
|
try {
|
|
6827
|
-
const { pid } = JSON.parse(
|
|
7036
|
+
const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6828
7037
|
process.kill(pid, 0);
|
|
6829
7038
|
console.log(chalk3.green("Node9 daemon: running"));
|
|
6830
7039
|
return;
|
|
@@ -6859,9 +7068,9 @@ __export(tail_exports, {
|
|
|
6859
7068
|
});
|
|
6860
7069
|
import http2 from "http";
|
|
6861
7070
|
import chalk19 from "chalk";
|
|
6862
|
-
import
|
|
6863
|
-
import
|
|
6864
|
-
import
|
|
7071
|
+
import fs29 from "fs";
|
|
7072
|
+
import os25 from "os";
|
|
7073
|
+
import path32 from "path";
|
|
6865
7074
|
import readline5 from "readline";
|
|
6866
7075
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
6867
7076
|
function getIcon(tool) {
|
|
@@ -6884,7 +7093,7 @@ function formatBase(activity) {
|
|
|
6884
7093
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6885
7094
|
const icon = getIcon(activity.tool);
|
|
6886
7095
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6887
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7096
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os25.homedir(), "~");
|
|
6888
7097
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6889
7098
|
return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
|
|
6890
7099
|
}
|
|
@@ -6923,9 +7132,9 @@ function renderPending(activity) {
|
|
|
6923
7132
|
}
|
|
6924
7133
|
async function ensureDaemon() {
|
|
6925
7134
|
let pidPort = null;
|
|
6926
|
-
if (
|
|
7135
|
+
if (fs29.existsSync(PID_FILE)) {
|
|
6927
7136
|
try {
|
|
6928
|
-
const { port } = JSON.parse(
|
|
7137
|
+
const { port } = JSON.parse(fs29.readFileSync(PID_FILE, "utf-8"));
|
|
6929
7138
|
pidPort = port;
|
|
6930
7139
|
} catch {
|
|
6931
7140
|
console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -7002,6 +7211,9 @@ function buildCardLines(req, localCount = 0) {
|
|
|
7002
7211
|
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
7003
7212
|
`${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
|
|
7004
7213
|
];
|
|
7214
|
+
if (req.riskMetadata?.ruleDescription) {
|
|
7215
|
+
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
|
|
7216
|
+
}
|
|
7005
7217
|
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
7006
7218
|
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
7007
7219
|
}
|
|
@@ -7045,9 +7257,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7045
7257
|
];
|
|
7046
7258
|
}
|
|
7047
7259
|
function readApproversFromDisk() {
|
|
7048
|
-
const configPath =
|
|
7260
|
+
const configPath = path32.join(os25.homedir(), ".node9", "config.json");
|
|
7049
7261
|
try {
|
|
7050
|
-
const raw = JSON.parse(
|
|
7262
|
+
const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
|
|
7051
7263
|
const settings = raw.settings ?? {};
|
|
7052
7264
|
return settings.approvers ?? {};
|
|
7053
7265
|
} catch {
|
|
@@ -7063,15 +7275,15 @@ function approverStatusLine() {
|
|
|
7063
7275
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7064
7276
|
}
|
|
7065
7277
|
function toggleApprover(channel) {
|
|
7066
|
-
const configPath =
|
|
7278
|
+
const configPath = path32.join(os25.homedir(), ".node9", "config.json");
|
|
7067
7279
|
try {
|
|
7068
|
-
const raw = JSON.parse(
|
|
7280
|
+
const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
|
|
7069
7281
|
const settings = raw.settings ?? {};
|
|
7070
7282
|
const approvers = settings.approvers ?? {};
|
|
7071
7283
|
approvers[channel] = approvers[channel] === false;
|
|
7072
7284
|
settings.approvers = approvers;
|
|
7073
7285
|
raw.settings = settings;
|
|
7074
|
-
|
|
7286
|
+
fs29.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7075
7287
|
} catch (err2) {
|
|
7076
7288
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7077
7289
|
`);
|
|
@@ -7241,8 +7453,8 @@ async function startTail(options = {}) {
|
|
|
7241
7453
|
}
|
|
7242
7454
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7243
7455
|
try {
|
|
7244
|
-
|
|
7245
|
-
|
|
7456
|
+
fs29.appendFileSync(
|
|
7457
|
+
path32.join(os25.homedir(), ".node9", "hook-debug.log"),
|
|
7246
7458
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7247
7459
|
`
|
|
7248
7460
|
);
|
|
@@ -7502,7 +7714,7 @@ var init_tail = __esm({
|
|
|
7502
7714
|
init_daemon2();
|
|
7503
7715
|
init_daemon();
|
|
7504
7716
|
init_core();
|
|
7505
|
-
PID_FILE =
|
|
7717
|
+
PID_FILE = path32.join(os25.homedir(), ".node9", "daemon.pid");
|
|
7506
7718
|
ICONS = {
|
|
7507
7719
|
bash: "\u{1F4BB}",
|
|
7508
7720
|
shell: "\u{1F4BB}",
|
|
@@ -7543,9 +7755,9 @@ __export(hud_exports, {
|
|
|
7543
7755
|
main: () => main,
|
|
7544
7756
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
7545
7757
|
});
|
|
7546
|
-
import
|
|
7547
|
-
import
|
|
7548
|
-
import
|
|
7758
|
+
import fs30 from "fs";
|
|
7759
|
+
import path33 from "path";
|
|
7760
|
+
import os26 from "os";
|
|
7549
7761
|
import http3 from "http";
|
|
7550
7762
|
async function readStdin() {
|
|
7551
7763
|
const chunks = [];
|
|
@@ -7621,9 +7833,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7621
7833
|
return ` (${m}m left)`;
|
|
7622
7834
|
}
|
|
7623
7835
|
function safeReadJson(filePath) {
|
|
7624
|
-
if (!
|
|
7836
|
+
if (!fs30.existsSync(filePath)) return null;
|
|
7625
7837
|
try {
|
|
7626
|
-
return JSON.parse(
|
|
7838
|
+
return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
|
|
7627
7839
|
} catch {
|
|
7628
7840
|
return null;
|
|
7629
7841
|
}
|
|
@@ -7644,12 +7856,12 @@ function countHooksInFile(filePath) {
|
|
|
7644
7856
|
return Object.keys(cfg.hooks).length;
|
|
7645
7857
|
}
|
|
7646
7858
|
function countRulesInDir(rulesDir) {
|
|
7647
|
-
if (!
|
|
7859
|
+
if (!fs30.existsSync(rulesDir)) return 0;
|
|
7648
7860
|
let count = 0;
|
|
7649
7861
|
try {
|
|
7650
|
-
for (const entry of
|
|
7862
|
+
for (const entry of fs30.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7651
7863
|
if (entry.isDirectory()) {
|
|
7652
|
-
count += countRulesInDir(
|
|
7864
|
+
count += countRulesInDir(path33.join(rulesDir, entry.name));
|
|
7653
7865
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7654
7866
|
count++;
|
|
7655
7867
|
}
|
|
@@ -7660,46 +7872,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7660
7872
|
}
|
|
7661
7873
|
function isSamePath(a, b) {
|
|
7662
7874
|
try {
|
|
7663
|
-
return
|
|
7875
|
+
return path33.resolve(a) === path33.resolve(b);
|
|
7664
7876
|
} catch {
|
|
7665
7877
|
return false;
|
|
7666
7878
|
}
|
|
7667
7879
|
}
|
|
7668
7880
|
function countConfigs(cwd) {
|
|
7669
|
-
const homeDir2 =
|
|
7670
|
-
const claudeDir =
|
|
7881
|
+
const homeDir2 = os26.homedir();
|
|
7882
|
+
const claudeDir = path33.join(homeDir2, ".claude");
|
|
7671
7883
|
let claudeMdCount = 0;
|
|
7672
7884
|
let rulesCount = 0;
|
|
7673
7885
|
let hooksCount = 0;
|
|
7674
7886
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7675
7887
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7676
|
-
if (
|
|
7677
|
-
rulesCount += countRulesInDir(
|
|
7678
|
-
const userSettings =
|
|
7888
|
+
if (fs30.existsSync(path33.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7889
|
+
rulesCount += countRulesInDir(path33.join(claudeDir, "rules"));
|
|
7890
|
+
const userSettings = path33.join(claudeDir, "settings.json");
|
|
7679
7891
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7680
7892
|
hooksCount += countHooksInFile(userSettings);
|
|
7681
|
-
const userClaudeJson =
|
|
7893
|
+
const userClaudeJson = path33.join(homeDir2, ".claude.json");
|
|
7682
7894
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7683
7895
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7684
7896
|
userMcpServers.delete(name);
|
|
7685
7897
|
}
|
|
7686
7898
|
if (cwd) {
|
|
7687
|
-
if (
|
|
7688
|
-
if (
|
|
7689
|
-
const projectClaudeDir =
|
|
7899
|
+
if (fs30.existsSync(path33.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7900
|
+
if (fs30.existsSync(path33.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7901
|
+
const projectClaudeDir = path33.join(cwd, ".claude");
|
|
7690
7902
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7691
7903
|
if (!overlapsUserScope) {
|
|
7692
|
-
if (
|
|
7693
|
-
rulesCount += countRulesInDir(
|
|
7694
|
-
const projSettings =
|
|
7904
|
+
if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7905
|
+
rulesCount += countRulesInDir(path33.join(projectClaudeDir, "rules"));
|
|
7906
|
+
const projSettings = path33.join(projectClaudeDir, "settings.json");
|
|
7695
7907
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7696
7908
|
hooksCount += countHooksInFile(projSettings);
|
|
7697
7909
|
}
|
|
7698
|
-
if (
|
|
7699
|
-
const localSettings =
|
|
7910
|
+
if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7911
|
+
const localSettings = path33.join(projectClaudeDir, "settings.local.json");
|
|
7700
7912
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7701
7913
|
hooksCount += countHooksInFile(localSettings);
|
|
7702
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7914
|
+
const mcpJsonServers = getMcpServerNames(path33.join(cwd, ".mcp.json"));
|
|
7703
7915
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7704
7916
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7705
7917
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7732,12 +7944,12 @@ function readActiveShieldsHud() {
|
|
|
7732
7944
|
return shieldsCache.value;
|
|
7733
7945
|
}
|
|
7734
7946
|
try {
|
|
7735
|
-
const shieldsPath =
|
|
7736
|
-
if (!
|
|
7947
|
+
const shieldsPath = path33.join(os26.homedir(), ".node9", "shields.json");
|
|
7948
|
+
if (!fs30.existsSync(shieldsPath)) {
|
|
7737
7949
|
shieldsCache = { value: [], ts: now };
|
|
7738
7950
|
return [];
|
|
7739
7951
|
}
|
|
7740
|
-
const parsed = JSON.parse(
|
|
7952
|
+
const parsed = JSON.parse(fs30.readFileSync(shieldsPath, "utf-8"));
|
|
7741
7953
|
if (!Array.isArray(parsed.active)) {
|
|
7742
7954
|
shieldsCache = { value: [], ts: now };
|
|
7743
7955
|
return [];
|
|
@@ -7839,17 +8051,17 @@ function renderContextLine(stdin) {
|
|
|
7839
8051
|
async function main() {
|
|
7840
8052
|
try {
|
|
7841
8053
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7842
|
-
if (
|
|
8054
|
+
if (fs30.existsSync(path33.join(os26.homedir(), ".node9", "hud-debug"))) {
|
|
7843
8055
|
try {
|
|
7844
|
-
const logPath =
|
|
8056
|
+
const logPath = path33.join(os26.homedir(), ".node9", "hud-debug.log");
|
|
7845
8057
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7846
8058
|
let size = 0;
|
|
7847
8059
|
try {
|
|
7848
|
-
size =
|
|
8060
|
+
size = fs30.statSync(logPath).size;
|
|
7849
8061
|
} catch {
|
|
7850
8062
|
}
|
|
7851
8063
|
if (size < MAX_LOG_SIZE) {
|
|
7852
|
-
|
|
8064
|
+
fs30.appendFileSync(
|
|
7853
8065
|
logPath,
|
|
7854
8066
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7855
8067
|
);
|
|
@@ -7870,11 +8082,11 @@ async function main() {
|
|
|
7870
8082
|
try {
|
|
7871
8083
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7872
8084
|
for (const configPath of [
|
|
7873
|
-
|
|
7874
|
-
|
|
8085
|
+
path33.join(cwd, "node9.config.json"),
|
|
8086
|
+
path33.join(os26.homedir(), ".node9", "config.json")
|
|
7875
8087
|
]) {
|
|
7876
|
-
if (!
|
|
7877
|
-
const cfg = JSON.parse(
|
|
8088
|
+
if (!fs30.existsSync(configPath)) continue;
|
|
8089
|
+
const cfg = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
7878
8090
|
const hud = cfg.settings?.hud;
|
|
7879
8091
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7880
8092
|
}
|
|
@@ -8504,9 +8716,9 @@ function teardownHud() {
|
|
|
8504
8716
|
// src/cli.ts
|
|
8505
8717
|
init_daemon2();
|
|
8506
8718
|
import chalk20 from "chalk";
|
|
8507
|
-
import
|
|
8508
|
-
import
|
|
8509
|
-
import
|
|
8719
|
+
import fs31 from "fs";
|
|
8720
|
+
import path34 from "path";
|
|
8721
|
+
import os27 from "os";
|
|
8510
8722
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
8511
8723
|
|
|
8512
8724
|
// src/utils/duration.ts
|
|
@@ -8735,19 +8947,19 @@ init_daemon();
|
|
|
8735
8947
|
init_config();
|
|
8736
8948
|
init_policy();
|
|
8737
8949
|
import chalk5 from "chalk";
|
|
8738
|
-
import
|
|
8950
|
+
import fs20 from "fs";
|
|
8739
8951
|
import { spawn as spawn6 } from "child_process";
|
|
8740
|
-
import
|
|
8741
|
-
import
|
|
8952
|
+
import path22 from "path";
|
|
8953
|
+
import os16 from "os";
|
|
8742
8954
|
|
|
8743
8955
|
// src/undo.ts
|
|
8744
8956
|
import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
|
|
8745
8957
|
import crypto3 from "crypto";
|
|
8746
|
-
import
|
|
8958
|
+
import fs19 from "fs";
|
|
8747
8959
|
import net3 from "net";
|
|
8748
|
-
import
|
|
8749
|
-
import
|
|
8750
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
8960
|
+
import path21 from "path";
|
|
8961
|
+
import os15 from "os";
|
|
8962
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path21.join(os15.tmpdir(), "node9-activity.sock");
|
|
8751
8963
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8752
8964
|
try {
|
|
8753
8965
|
const payload = JSON.stringify({
|
|
@@ -8767,22 +8979,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8767
8979
|
} catch {
|
|
8768
8980
|
}
|
|
8769
8981
|
}
|
|
8770
|
-
var SNAPSHOT_STACK_PATH =
|
|
8771
|
-
var UNDO_LATEST_PATH =
|
|
8982
|
+
var SNAPSHOT_STACK_PATH = path21.join(os15.homedir(), ".node9", "snapshots.json");
|
|
8983
|
+
var UNDO_LATEST_PATH = path21.join(os15.homedir(), ".node9", "undo_latest.txt");
|
|
8772
8984
|
var MAX_SNAPSHOTS = 10;
|
|
8773
8985
|
var GIT_TIMEOUT = 15e3;
|
|
8774
8986
|
function readStack() {
|
|
8775
8987
|
try {
|
|
8776
|
-
if (
|
|
8777
|
-
return JSON.parse(
|
|
8988
|
+
if (fs19.existsSync(SNAPSHOT_STACK_PATH))
|
|
8989
|
+
return JSON.parse(fs19.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
8778
8990
|
} catch {
|
|
8779
8991
|
}
|
|
8780
8992
|
return [];
|
|
8781
8993
|
}
|
|
8782
8994
|
function writeStack(stack) {
|
|
8783
|
-
const dir =
|
|
8784
|
-
if (!
|
|
8785
|
-
|
|
8995
|
+
const dir = path21.dirname(SNAPSHOT_STACK_PATH);
|
|
8996
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
8997
|
+
fs19.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8786
8998
|
}
|
|
8787
8999
|
function extractFilePath(args) {
|
|
8788
9000
|
if (!args || typeof args !== "object") return null;
|
|
@@ -8802,12 +9014,12 @@ function buildArgsSummary(tool, args) {
|
|
|
8802
9014
|
return "";
|
|
8803
9015
|
}
|
|
8804
9016
|
function findProjectRoot(filePath) {
|
|
8805
|
-
let dir =
|
|
9017
|
+
let dir = path21.dirname(filePath);
|
|
8806
9018
|
while (true) {
|
|
8807
|
-
if (
|
|
9019
|
+
if (fs19.existsSync(path21.join(dir, ".git")) || fs19.existsSync(path21.join(dir, "package.json"))) {
|
|
8808
9020
|
return dir;
|
|
8809
9021
|
}
|
|
8810
|
-
const parent =
|
|
9022
|
+
const parent = path21.dirname(dir);
|
|
8811
9023
|
if (parent === dir) return process.cwd();
|
|
8812
9024
|
dir = parent;
|
|
8813
9025
|
}
|
|
@@ -8815,7 +9027,7 @@ function findProjectRoot(filePath) {
|
|
|
8815
9027
|
function normalizeCwdForHash(cwd) {
|
|
8816
9028
|
let normalized;
|
|
8817
9029
|
try {
|
|
8818
|
-
normalized =
|
|
9030
|
+
normalized = fs19.realpathSync(cwd);
|
|
8819
9031
|
} catch {
|
|
8820
9032
|
normalized = cwd;
|
|
8821
9033
|
}
|
|
@@ -8825,16 +9037,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
8825
9037
|
}
|
|
8826
9038
|
function getShadowRepoDir(cwd) {
|
|
8827
9039
|
const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
8828
|
-
return
|
|
9040
|
+
return path21.join(os15.homedir(), ".node9", "snapshots", hash);
|
|
8829
9041
|
}
|
|
8830
9042
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8831
9043
|
try {
|
|
8832
9044
|
const cutoff = Date.now() - 6e4;
|
|
8833
|
-
for (const f of
|
|
9045
|
+
for (const f of fs19.readdirSync(shadowDir)) {
|
|
8834
9046
|
if (f.startsWith("index_")) {
|
|
8835
|
-
const fp =
|
|
9047
|
+
const fp = path21.join(shadowDir, f);
|
|
8836
9048
|
try {
|
|
8837
|
-
if (
|
|
9049
|
+
if (fs19.statSync(fp).mtimeMs < cutoff) fs19.unlinkSync(fp);
|
|
8838
9050
|
} catch {
|
|
8839
9051
|
}
|
|
8840
9052
|
}
|
|
@@ -8846,7 +9058,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8846
9058
|
const hardcoded = [".git", ".node9"];
|
|
8847
9059
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8848
9060
|
try {
|
|
8849
|
-
|
|
9061
|
+
fs19.writeFileSync(path21.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8850
9062
|
} catch {
|
|
8851
9063
|
}
|
|
8852
9064
|
}
|
|
@@ -8859,25 +9071,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8859
9071
|
timeout: 3e3
|
|
8860
9072
|
});
|
|
8861
9073
|
if (check.status === 0) {
|
|
8862
|
-
const ptPath =
|
|
9074
|
+
const ptPath = path21.join(shadowDir, "project-path.txt");
|
|
8863
9075
|
try {
|
|
8864
|
-
const stored =
|
|
9076
|
+
const stored = fs19.readFileSync(ptPath, "utf8").trim();
|
|
8865
9077
|
if (stored === normalizedCwd) return true;
|
|
8866
9078
|
if (process.env.NODE9_DEBUG === "1")
|
|
8867
9079
|
console.error(
|
|
8868
9080
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
8869
9081
|
);
|
|
8870
|
-
|
|
9082
|
+
fs19.rmSync(shadowDir, { recursive: true, force: true });
|
|
8871
9083
|
} catch {
|
|
8872
9084
|
try {
|
|
8873
|
-
|
|
9085
|
+
fs19.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
8874
9086
|
} catch {
|
|
8875
9087
|
}
|
|
8876
9088
|
return true;
|
|
8877
9089
|
}
|
|
8878
9090
|
}
|
|
8879
9091
|
try {
|
|
8880
|
-
|
|
9092
|
+
fs19.mkdirSync(shadowDir, { recursive: true });
|
|
8881
9093
|
} catch {
|
|
8882
9094
|
}
|
|
8883
9095
|
const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -8886,7 +9098,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8886
9098
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8887
9099
|
return false;
|
|
8888
9100
|
}
|
|
8889
|
-
const configFile =
|
|
9101
|
+
const configFile = path21.join(shadowDir, "config");
|
|
8890
9102
|
spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8891
9103
|
timeout: 3e3
|
|
8892
9104
|
});
|
|
@@ -8894,7 +9106,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8894
9106
|
timeout: 3e3
|
|
8895
9107
|
});
|
|
8896
9108
|
try {
|
|
8897
|
-
|
|
9109
|
+
fs19.writeFileSync(path21.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8898
9110
|
} catch {
|
|
8899
9111
|
}
|
|
8900
9112
|
return true;
|
|
@@ -8914,12 +9126,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8914
9126
|
let indexFile = null;
|
|
8915
9127
|
try {
|
|
8916
9128
|
const rawFilePath = extractFilePath(args);
|
|
8917
|
-
const absFilePath = rawFilePath &&
|
|
9129
|
+
const absFilePath = rawFilePath && path21.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8918
9130
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8919
9131
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8920
9132
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8921
9133
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8922
|
-
indexFile =
|
|
9134
|
+
indexFile = path21.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8923
9135
|
const shadowEnv = {
|
|
8924
9136
|
...process.env,
|
|
8925
9137
|
GIT_DIR: shadowDir,
|
|
@@ -8991,7 +9203,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8991
9203
|
writeStack(stack);
|
|
8992
9204
|
const entry = stack[stack.length - 1];
|
|
8993
9205
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8994
|
-
|
|
9206
|
+
fs19.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8995
9207
|
if (shouldGc) {
|
|
8996
9208
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8997
9209
|
}
|
|
@@ -9002,7 +9214,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9002
9214
|
} finally {
|
|
9003
9215
|
if (indexFile) {
|
|
9004
9216
|
try {
|
|
9005
|
-
|
|
9217
|
+
fs19.unlinkSync(indexFile);
|
|
9006
9218
|
} catch {
|
|
9007
9219
|
}
|
|
9008
9220
|
}
|
|
@@ -9078,9 +9290,9 @@ function applyUndo(hash, cwd) {
|
|
|
9078
9290
|
timeout: GIT_TIMEOUT
|
|
9079
9291
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
9080
9292
|
for (const file of [...tracked, ...untracked]) {
|
|
9081
|
-
const fullPath =
|
|
9082
|
-
if (!snapshotFiles.has(file) &&
|
|
9083
|
-
|
|
9293
|
+
const fullPath = path21.join(dir, file);
|
|
9294
|
+
if (!snapshotFiles.has(file) && fs19.existsSync(fullPath)) {
|
|
9295
|
+
fs19.unlinkSync(fullPath);
|
|
9084
9296
|
}
|
|
9085
9297
|
}
|
|
9086
9298
|
return true;
|
|
@@ -9104,9 +9316,9 @@ function registerCheckCommand(program2) {
|
|
|
9104
9316
|
} catch (err2) {
|
|
9105
9317
|
const tempConfig = getConfig();
|
|
9106
9318
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9107
|
-
const logPath =
|
|
9319
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9108
9320
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9109
|
-
|
|
9321
|
+
fs20.appendFileSync(
|
|
9110
9322
|
logPath,
|
|
9111
9323
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9112
9324
|
RAW: ${raw}
|
|
@@ -9119,13 +9331,13 @@ RAW: ${raw}
|
|
|
9119
9331
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9120
9332
|
try {
|
|
9121
9333
|
const scriptPath = process.argv[1];
|
|
9122
|
-
if (typeof scriptPath !== "string" || !
|
|
9334
|
+
if (typeof scriptPath !== "string" || !path22.isAbsolute(scriptPath))
|
|
9123
9335
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9124
|
-
const resolvedScript =
|
|
9125
|
-
const
|
|
9126
|
-
if (resolvedScript !==
|
|
9336
|
+
const resolvedScript = fs20.realpathSync(scriptPath);
|
|
9337
|
+
const packageDist = fs20.realpathSync(path22.resolve(__dirname, "../.."));
|
|
9338
|
+
if (!resolvedScript.startsWith(packageDist + path22.sep) && resolvedScript !== packageDist)
|
|
9127
9339
|
throw new Error(
|
|
9128
|
-
|
|
9340
|
+
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
9129
9341
|
);
|
|
9130
9342
|
const safeEnv = { ...process.env };
|
|
9131
9343
|
for (const key of [
|
|
@@ -9144,14 +9356,24 @@ RAW: ${raw}
|
|
|
9144
9356
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
9145
9357
|
});
|
|
9146
9358
|
d.unref();
|
|
9147
|
-
} catch {
|
|
9359
|
+
} catch (spawnErr) {
|
|
9360
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9361
|
+
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
9362
|
+
try {
|
|
9363
|
+
fs20.appendFileSync(
|
|
9364
|
+
logPath,
|
|
9365
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
9366
|
+
`
|
|
9367
|
+
);
|
|
9368
|
+
} catch {
|
|
9369
|
+
}
|
|
9148
9370
|
}
|
|
9149
9371
|
}
|
|
9150
9372
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9151
|
-
const logPath =
|
|
9152
|
-
if (!
|
|
9153
|
-
|
|
9154
|
-
|
|
9373
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9374
|
+
if (!fs20.existsSync(path22.dirname(logPath)))
|
|
9375
|
+
fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
|
|
9376
|
+
fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
9155
9377
|
`);
|
|
9156
9378
|
}
|
|
9157
9379
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9164,8 +9386,8 @@ RAW: ${raw}
|
|
|
9164
9386
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9165
9387
|
let ttyFd = null;
|
|
9166
9388
|
try {
|
|
9167
|
-
ttyFd =
|
|
9168
|
-
const writeTty = (line) =>
|
|
9389
|
+
ttyFd = fs20.openSync("/dev/tty", "w");
|
|
9390
|
+
const writeTty = (line) => fs20.writeSync(ttyFd, line + "\n");
|
|
9169
9391
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9170
9392
|
writeTty(chalk5.bgRed.white.bold(`
|
|
9171
9393
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9184,7 +9406,7 @@ RAW: ${raw}
|
|
|
9184
9406
|
} finally {
|
|
9185
9407
|
if (ttyFd !== null)
|
|
9186
9408
|
try {
|
|
9187
|
-
|
|
9409
|
+
fs20.closeSync(ttyFd);
|
|
9188
9410
|
} catch {
|
|
9189
9411
|
}
|
|
9190
9412
|
}
|
|
@@ -9216,7 +9438,7 @@ RAW: ${raw}
|
|
|
9216
9438
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9217
9439
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9218
9440
|
}
|
|
9219
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
9441
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9220
9442
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9221
9443
|
cwd: safeCwdForAuth
|
|
9222
9444
|
});
|
|
@@ -9228,12 +9450,12 @@ RAW: ${raw}
|
|
|
9228
9450
|
}
|
|
9229
9451
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9230
9452
|
try {
|
|
9231
|
-
const tty =
|
|
9232
|
-
|
|
9453
|
+
const tty = fs20.openSync("/dev/tty", "w");
|
|
9454
|
+
fs20.writeSync(
|
|
9233
9455
|
tty,
|
|
9234
9456
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9235
9457
|
);
|
|
9236
|
-
|
|
9458
|
+
fs20.closeSync(tty);
|
|
9237
9459
|
} catch {
|
|
9238
9460
|
}
|
|
9239
9461
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9260,9 +9482,9 @@ RAW: ${raw}
|
|
|
9260
9482
|
});
|
|
9261
9483
|
} catch (err2) {
|
|
9262
9484
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9263
|
-
const logPath =
|
|
9485
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9264
9486
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9265
|
-
|
|
9487
|
+
fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9266
9488
|
`);
|
|
9267
9489
|
}
|
|
9268
9490
|
process.exit(0);
|
|
@@ -9299,9 +9521,9 @@ RAW: ${raw}
|
|
|
9299
9521
|
init_audit();
|
|
9300
9522
|
init_config();
|
|
9301
9523
|
init_policy();
|
|
9302
|
-
import
|
|
9303
|
-
import
|
|
9304
|
-
import
|
|
9524
|
+
import fs21 from "fs";
|
|
9525
|
+
import path23 from "path";
|
|
9526
|
+
import os17 from "os";
|
|
9305
9527
|
init_daemon();
|
|
9306
9528
|
|
|
9307
9529
|
// src/utils/cp-mv-parser.ts
|
|
@@ -9374,10 +9596,10 @@ function registerLogCommand(program2) {
|
|
|
9374
9596
|
decision: "allowed",
|
|
9375
9597
|
source: "post-hook"
|
|
9376
9598
|
};
|
|
9377
|
-
const logPath =
|
|
9378
|
-
if (!
|
|
9379
|
-
|
|
9380
|
-
|
|
9599
|
+
const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
|
|
9600
|
+
if (!fs21.existsSync(path23.dirname(logPath)))
|
|
9601
|
+
fs21.mkdirSync(path23.dirname(logPath), { recursive: true });
|
|
9602
|
+
fs21.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
9381
9603
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9382
9604
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9383
9605
|
if (command) {
|
|
@@ -9410,7 +9632,7 @@ function registerLogCommand(program2) {
|
|
|
9410
9632
|
}
|
|
9411
9633
|
}
|
|
9412
9634
|
}
|
|
9413
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
9635
|
+
const safeCwd = typeof payload.cwd === "string" && path23.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9414
9636
|
const config = getConfig(safeCwd);
|
|
9415
9637
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9416
9638
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9419,9 +9641,9 @@ function registerLogCommand(program2) {
|
|
|
9419
9641
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9420
9642
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9421
9643
|
`);
|
|
9422
|
-
const debugPath =
|
|
9644
|
+
const debugPath = path23.join(os17.homedir(), ".node9", "hook-debug.log");
|
|
9423
9645
|
try {
|
|
9424
|
-
|
|
9646
|
+
fs21.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9425
9647
|
`);
|
|
9426
9648
|
} catch {
|
|
9427
9649
|
}
|
|
@@ -9822,13 +10044,13 @@ function registerConfigShowCommand(program2) {
|
|
|
9822
10044
|
// src/cli/commands/doctor.ts
|
|
9823
10045
|
init_daemon();
|
|
9824
10046
|
import chalk7 from "chalk";
|
|
9825
|
-
import
|
|
9826
|
-
import
|
|
9827
|
-
import
|
|
10047
|
+
import fs22 from "fs";
|
|
10048
|
+
import path24 from "path";
|
|
10049
|
+
import os18 from "os";
|
|
9828
10050
|
import { execSync as execSync2 } from "child_process";
|
|
9829
10051
|
function registerDoctorCommand(program2, version2) {
|
|
9830
10052
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
9831
|
-
const homeDir2 =
|
|
10053
|
+
const homeDir2 = os18.homedir();
|
|
9832
10054
|
let failures = 0;
|
|
9833
10055
|
function pass(msg) {
|
|
9834
10056
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -9877,10 +10099,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9877
10099
|
);
|
|
9878
10100
|
}
|
|
9879
10101
|
section("Configuration");
|
|
9880
|
-
const globalConfigPath =
|
|
9881
|
-
if (
|
|
10102
|
+
const globalConfigPath = path24.join(homeDir2, ".node9", "config.json");
|
|
10103
|
+
if (fs22.existsSync(globalConfigPath)) {
|
|
9882
10104
|
try {
|
|
9883
|
-
JSON.parse(
|
|
10105
|
+
JSON.parse(fs22.readFileSync(globalConfigPath, "utf-8"));
|
|
9884
10106
|
pass("~/.node9/config.json found and valid");
|
|
9885
10107
|
} catch {
|
|
9886
10108
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -9888,10 +10110,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9888
10110
|
} else {
|
|
9889
10111
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9890
10112
|
}
|
|
9891
|
-
const projectConfigPath =
|
|
9892
|
-
if (
|
|
10113
|
+
const projectConfigPath = path24.join(process.cwd(), "node9.config.json");
|
|
10114
|
+
if (fs22.existsSync(projectConfigPath)) {
|
|
9893
10115
|
try {
|
|
9894
|
-
JSON.parse(
|
|
10116
|
+
JSON.parse(fs22.readFileSync(projectConfigPath, "utf-8"));
|
|
9895
10117
|
pass("node9.config.json found and valid (project)");
|
|
9896
10118
|
} catch {
|
|
9897
10119
|
fail(
|
|
@@ -9900,8 +10122,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9900
10122
|
);
|
|
9901
10123
|
}
|
|
9902
10124
|
}
|
|
9903
|
-
const credsPath =
|
|
9904
|
-
if (
|
|
10125
|
+
const credsPath = path24.join(homeDir2, ".node9", "credentials.json");
|
|
10126
|
+
if (fs22.existsSync(credsPath)) {
|
|
9905
10127
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9906
10128
|
} else {
|
|
9907
10129
|
warn(
|
|
@@ -9910,10 +10132,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9910
10132
|
);
|
|
9911
10133
|
}
|
|
9912
10134
|
section("Agent Hooks");
|
|
9913
|
-
const claudeSettingsPath =
|
|
9914
|
-
if (
|
|
10135
|
+
const claudeSettingsPath = path24.join(homeDir2, ".claude", "settings.json");
|
|
10136
|
+
if (fs22.existsSync(claudeSettingsPath)) {
|
|
9915
10137
|
try {
|
|
9916
|
-
const cs = JSON.parse(
|
|
10138
|
+
const cs = JSON.parse(fs22.readFileSync(claudeSettingsPath, "utf-8"));
|
|
9917
10139
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
9918
10140
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9919
10141
|
);
|
|
@@ -9929,10 +10151,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9929
10151
|
} else {
|
|
9930
10152
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9931
10153
|
}
|
|
9932
|
-
const geminiSettingsPath =
|
|
9933
|
-
if (
|
|
10154
|
+
const geminiSettingsPath = path24.join(homeDir2, ".gemini", "settings.json");
|
|
10155
|
+
if (fs22.existsSync(geminiSettingsPath)) {
|
|
9934
10156
|
try {
|
|
9935
|
-
const gs = JSON.parse(
|
|
10157
|
+
const gs = JSON.parse(fs22.readFileSync(geminiSettingsPath, "utf-8"));
|
|
9936
10158
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
9937
10159
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9938
10160
|
);
|
|
@@ -9948,10 +10170,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9948
10170
|
} else {
|
|
9949
10171
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9950
10172
|
}
|
|
9951
|
-
const cursorHooksPath =
|
|
9952
|
-
if (
|
|
10173
|
+
const cursorHooksPath = path24.join(homeDir2, ".cursor", "hooks.json");
|
|
10174
|
+
if (fs22.existsSync(cursorHooksPath)) {
|
|
9953
10175
|
try {
|
|
9954
|
-
const cur = JSON.parse(
|
|
10176
|
+
const cur = JSON.parse(fs22.readFileSync(cursorHooksPath, "utf-8"));
|
|
9955
10177
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
9956
10178
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
9957
10179
|
);
|
|
@@ -9989,9 +10211,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9989
10211
|
|
|
9990
10212
|
// src/cli/commands/audit.ts
|
|
9991
10213
|
import chalk8 from "chalk";
|
|
9992
|
-
import
|
|
9993
|
-
import
|
|
9994
|
-
import
|
|
10214
|
+
import fs23 from "fs";
|
|
10215
|
+
import path25 from "path";
|
|
10216
|
+
import os19 from "os";
|
|
9995
10217
|
function formatRelativeTime(timestamp) {
|
|
9996
10218
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
9997
10219
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -10004,14 +10226,14 @@ function formatRelativeTime(timestamp) {
|
|
|
10004
10226
|
}
|
|
10005
10227
|
function registerAuditCommand(program2) {
|
|
10006
10228
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
10007
|
-
const logPath =
|
|
10008
|
-
if (!
|
|
10229
|
+
const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
|
|
10230
|
+
if (!fs23.existsSync(logPath)) {
|
|
10009
10231
|
console.log(
|
|
10010
10232
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
10011
10233
|
);
|
|
10012
10234
|
return;
|
|
10013
10235
|
}
|
|
10014
|
-
const raw =
|
|
10236
|
+
const raw = fs23.readFileSync(logPath, "utf-8");
|
|
10015
10237
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
10016
10238
|
let entries = lines.flatMap((line) => {
|
|
10017
10239
|
try {
|
|
@@ -10065,9 +10287,9 @@ function registerAuditCommand(program2) {
|
|
|
10065
10287
|
|
|
10066
10288
|
// src/cli/commands/report.ts
|
|
10067
10289
|
import chalk9 from "chalk";
|
|
10068
|
-
import
|
|
10069
|
-
import
|
|
10070
|
-
import
|
|
10290
|
+
import fs24 from "fs";
|
|
10291
|
+
import path26 from "path";
|
|
10292
|
+
import os20 from "os";
|
|
10071
10293
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
10072
10294
|
function buildTestTimestamps(allEntries) {
|
|
10073
10295
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -10114,8 +10336,8 @@ function getDateRange(period) {
|
|
|
10114
10336
|
}
|
|
10115
10337
|
}
|
|
10116
10338
|
function parseAuditLog(logPath) {
|
|
10117
|
-
if (!
|
|
10118
|
-
const raw =
|
|
10339
|
+
if (!fs24.existsSync(logPath)) return [];
|
|
10340
|
+
const raw = fs24.readFileSync(logPath, "utf-8");
|
|
10119
10341
|
return raw.split("\n").flatMap((line) => {
|
|
10120
10342
|
if (!line.trim()) return [];
|
|
10121
10343
|
try {
|
|
@@ -10177,29 +10399,39 @@ function claudeModelPrice(model) {
|
|
|
10177
10399
|
return null;
|
|
10178
10400
|
}
|
|
10179
10401
|
function loadClaudeCost(start, end) {
|
|
10180
|
-
const
|
|
10181
|
-
|
|
10402
|
+
const empty = {
|
|
10403
|
+
total: 0,
|
|
10404
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
10405
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
10406
|
+
inputTokens: 0,
|
|
10407
|
+
cacheReadTokens: 0
|
|
10408
|
+
};
|
|
10409
|
+
const projectsDir = path26.join(os20.homedir(), ".claude", "projects");
|
|
10410
|
+
if (!fs24.existsSync(projectsDir)) return empty;
|
|
10182
10411
|
let dirs;
|
|
10183
10412
|
try {
|
|
10184
|
-
dirs =
|
|
10413
|
+
dirs = fs24.readdirSync(projectsDir);
|
|
10185
10414
|
} catch {
|
|
10186
|
-
return
|
|
10415
|
+
return empty;
|
|
10187
10416
|
}
|
|
10188
10417
|
let total = 0;
|
|
10418
|
+
let inputTokens = 0;
|
|
10419
|
+
let cacheReadTokens = 0;
|
|
10189
10420
|
const byDay = /* @__PURE__ */ new Map();
|
|
10421
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
10190
10422
|
for (const proj of dirs) {
|
|
10191
|
-
const projPath =
|
|
10423
|
+
const projPath = path26.join(projectsDir, proj);
|
|
10192
10424
|
let files;
|
|
10193
10425
|
try {
|
|
10194
|
-
const stat =
|
|
10426
|
+
const stat = fs24.statSync(projPath);
|
|
10195
10427
|
if (!stat.isDirectory()) continue;
|
|
10196
|
-
files =
|
|
10428
|
+
files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10197
10429
|
} catch {
|
|
10198
10430
|
continue;
|
|
10199
10431
|
}
|
|
10200
10432
|
for (const file of files) {
|
|
10201
10433
|
try {
|
|
10202
|
-
const raw =
|
|
10434
|
+
const raw = fs24.readFileSync(path26.join(projPath, file), "utf-8");
|
|
10203
10435
|
for (const line of raw.split("\n")) {
|
|
10204
10436
|
if (!line.trim()) continue;
|
|
10205
10437
|
let entry;
|
|
@@ -10217,24 +10449,32 @@ function loadClaudeCost(start, end) {
|
|
|
10217
10449
|
if (!usage || !model) continue;
|
|
10218
10450
|
const p = claudeModelPrice(model);
|
|
10219
10451
|
if (!p) continue;
|
|
10220
|
-
const
|
|
10452
|
+
const inp = usage.input_tokens ?? 0;
|
|
10453
|
+
const out = usage.output_tokens ?? 0;
|
|
10454
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
10455
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
10456
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
10221
10457
|
total += cost;
|
|
10458
|
+
inputTokens += inp;
|
|
10459
|
+
cacheReadTokens += cr;
|
|
10222
10460
|
const dateKey = entry.timestamp.slice(0, 10);
|
|
10223
10461
|
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
10462
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
10463
|
+
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
10224
10464
|
}
|
|
10225
10465
|
} catch {
|
|
10226
10466
|
continue;
|
|
10227
10467
|
}
|
|
10228
10468
|
}
|
|
10229
10469
|
}
|
|
10230
|
-
return { total, byDay };
|
|
10470
|
+
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
10231
10471
|
}
|
|
10232
10472
|
function registerReportCommand(program2) {
|
|
10233
10473
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
|
|
10234
10474
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
10235
10475
|
options.period
|
|
10236
10476
|
) ? options.period : "7d";
|
|
10237
|
-
const logPath =
|
|
10477
|
+
const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
|
|
10238
10478
|
const allEntries = parseAuditLog(logPath);
|
|
10239
10479
|
if (allEntries.length === 0) {
|
|
10240
10480
|
console.log(
|
|
@@ -10243,7 +10483,13 @@ function registerReportCommand(program2) {
|
|
|
10243
10483
|
return;
|
|
10244
10484
|
}
|
|
10245
10485
|
const { start, end } = getDateRange(period);
|
|
10246
|
-
const {
|
|
10486
|
+
const {
|
|
10487
|
+
total: costUSD,
|
|
10488
|
+
byDay: costByDay,
|
|
10489
|
+
byModel: costByModel,
|
|
10490
|
+
inputTokens: costInputTokens,
|
|
10491
|
+
cacheReadTokens: costCacheRead
|
|
10492
|
+
} = loadClaudeCost(start, end);
|
|
10247
10493
|
const periodMs = end.getTime() - start.getTime();
|
|
10248
10494
|
const priorEnd = new Date(start.getTime() - 1);
|
|
10249
10495
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
@@ -10345,7 +10591,6 @@ function registerReportCommand(program2) {
|
|
|
10345
10591
|
const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
|
|
10346
10592
|
const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
|
|
10347
10593
|
const loopLabel = loopHits > 0 ? chalk9.yellow(`\u{1F504} ${loopHits} loops`) : chalk9.dim("\u{1F504} 0 loops");
|
|
10348
|
-
const costLabel = costUSD > 0 ? chalk9.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : chalk9.dim("\u{1F4B0} \u2013");
|
|
10349
10594
|
const currentRate = total > 0 ? blocked / total : 0;
|
|
10350
10595
|
const trendLabel = (() => {
|
|
10351
10596
|
if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
|
|
@@ -10358,7 +10603,7 @@ function registerReportCommand(program2) {
|
|
|
10358
10603
|
const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
|
|
10359
10604
|
const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
|
|
10360
10605
|
console.log(
|
|
10361
|
-
" " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
|
|
10606
|
+
" " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
|
|
10362
10607
|
);
|
|
10363
10608
|
console.log(" " + ratioLabel + " " + testLabel);
|
|
10364
10609
|
console.log("");
|
|
@@ -10443,6 +10688,30 @@ function registerReportCommand(program2) {
|
|
|
10443
10688
|
);
|
|
10444
10689
|
}
|
|
10445
10690
|
}
|
|
10691
|
+
if (costUSD > 0) {
|
|
10692
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
10693
|
+
const avgPerDay = costUSD / periodDays;
|
|
10694
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
10695
|
+
const costHeaderRight = [
|
|
10696
|
+
chalk9.yellow(fmtCost(costUSD)),
|
|
10697
|
+
chalk9.dim(`avg ${fmtCost(avgPerDay)}/day`),
|
|
10698
|
+
cacheHitPct > 0 ? chalk9.dim(`${cacheHitPct}% cache hit`) : null
|
|
10699
|
+
].filter(Boolean).join(chalk9.dim(" \xB7 "));
|
|
10700
|
+
console.log("");
|
|
10701
|
+
console.log(" " + chalk9.bold("Cost") + " " + costHeaderRight);
|
|
10702
|
+
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10703
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
10704
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
10705
|
+
const MODEL_LABEL = 22;
|
|
10706
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
10707
|
+
for (const [model, cost] of modelList) {
|
|
10708
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
10709
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
10710
|
+
console.log(
|
|
10711
|
+
" " + chalk9.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk9.yellow(fmtCost(cost))
|
|
10712
|
+
);
|
|
10713
|
+
}
|
|
10714
|
+
}
|
|
10446
10715
|
console.log("");
|
|
10447
10716
|
console.log(
|
|
10448
10717
|
" " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
@@ -10519,12 +10788,12 @@ function registerDaemonCommand(program2) {
|
|
|
10519
10788
|
init_core();
|
|
10520
10789
|
init_daemon();
|
|
10521
10790
|
import chalk11 from "chalk";
|
|
10522
|
-
import
|
|
10523
|
-
import
|
|
10524
|
-
import
|
|
10791
|
+
import fs25 from "fs";
|
|
10792
|
+
import path27 from "path";
|
|
10793
|
+
import os21 from "os";
|
|
10525
10794
|
function readJson2(filePath) {
|
|
10526
10795
|
try {
|
|
10527
|
-
if (
|
|
10796
|
+
if (fs25.existsSync(filePath)) return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
10528
10797
|
} catch {
|
|
10529
10798
|
}
|
|
10530
10799
|
return null;
|
|
@@ -10589,28 +10858,28 @@ function registerStatusCommand(program2) {
|
|
|
10589
10858
|
console.log("");
|
|
10590
10859
|
const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
|
|
10591
10860
|
console.log(` Mode: ${modeLabel}`);
|
|
10592
|
-
const projectConfig =
|
|
10593
|
-
const globalConfig =
|
|
10861
|
+
const projectConfig = path27.join(process.cwd(), "node9.config.json");
|
|
10862
|
+
const globalConfig = path27.join(os21.homedir(), ".node9", "config.json");
|
|
10594
10863
|
console.log(
|
|
10595
|
-
` Local: ${
|
|
10864
|
+
` Local: ${fs25.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
|
|
10596
10865
|
);
|
|
10597
10866
|
console.log(
|
|
10598
|
-
` Global: ${
|
|
10867
|
+
` Global: ${fs25.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
|
|
10599
10868
|
);
|
|
10600
10869
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10601
10870
|
console.log(
|
|
10602
10871
|
` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10603
10872
|
);
|
|
10604
10873
|
}
|
|
10605
|
-
const homeDir2 =
|
|
10874
|
+
const homeDir2 = os21.homedir();
|
|
10606
10875
|
const claudeSettings = readJson2(
|
|
10607
|
-
|
|
10876
|
+
path27.join(homeDir2, ".claude", "settings.json")
|
|
10608
10877
|
);
|
|
10609
|
-
const claudeConfig = readJson2(
|
|
10878
|
+
const claudeConfig = readJson2(path27.join(homeDir2, ".claude.json"));
|
|
10610
10879
|
const geminiSettings = readJson2(
|
|
10611
|
-
|
|
10880
|
+
path27.join(homeDir2, ".gemini", "settings.json")
|
|
10612
10881
|
);
|
|
10613
|
-
const cursorConfig = readJson2(
|
|
10882
|
+
const cursorConfig = readJson2(path27.join(homeDir2, ".cursor", "mcp.json"));
|
|
10614
10883
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10615
10884
|
if (agentFound) {
|
|
10616
10885
|
console.log("");
|
|
@@ -10670,9 +10939,9 @@ function registerStatusCommand(program2) {
|
|
|
10670
10939
|
// src/cli/commands/init.ts
|
|
10671
10940
|
init_core();
|
|
10672
10941
|
import chalk12 from "chalk";
|
|
10673
|
-
import
|
|
10674
|
-
import
|
|
10675
|
-
import
|
|
10942
|
+
import fs26 from "fs";
|
|
10943
|
+
import path28 from "path";
|
|
10944
|
+
import os22 from "os";
|
|
10676
10945
|
import https2 from "https";
|
|
10677
10946
|
init_shields();
|
|
10678
10947
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
|
|
@@ -10731,15 +11000,15 @@ function registerInitCommand(program2) {
|
|
|
10731
11000
|
}
|
|
10732
11001
|
console.log("");
|
|
10733
11002
|
}
|
|
10734
|
-
const configPath =
|
|
10735
|
-
if (
|
|
11003
|
+
const configPath = path28.join(os22.homedir(), ".node9", "config.json");
|
|
11004
|
+
if (fs26.existsSync(configPath) && !options.force) {
|
|
10736
11005
|
try {
|
|
10737
|
-
const existing = JSON.parse(
|
|
11006
|
+
const existing = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
|
|
10738
11007
|
const settings = existing.settings ?? {};
|
|
10739
11008
|
if (settings.mode !== chosenMode) {
|
|
10740
11009
|
settings.mode = chosenMode;
|
|
10741
11010
|
existing.settings = settings;
|
|
10742
|
-
|
|
11011
|
+
fs26.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
10743
11012
|
console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
10744
11013
|
} else {
|
|
10745
11014
|
console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -10752,9 +11021,9 @@ function registerInitCommand(program2) {
|
|
|
10752
11021
|
...DEFAULT_CONFIG,
|
|
10753
11022
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
10754
11023
|
};
|
|
10755
|
-
const dir =
|
|
10756
|
-
if (!
|
|
10757
|
-
|
|
11024
|
+
const dir = path28.dirname(configPath);
|
|
11025
|
+
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
11026
|
+
fs26.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
10758
11027
|
console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
|
|
10759
11028
|
console.log(chalk12.gray(` Mode: ${chosenMode}`));
|
|
10760
11029
|
}
|
|
@@ -10808,7 +11077,7 @@ function registerInitCommand(program2) {
|
|
|
10808
11077
|
}
|
|
10809
11078
|
|
|
10810
11079
|
// src/cli/commands/undo.ts
|
|
10811
|
-
import
|
|
11080
|
+
import path29 from "path";
|
|
10812
11081
|
import chalk14 from "chalk";
|
|
10813
11082
|
|
|
10814
11083
|
// src/tui/undo-navigator.ts
|
|
@@ -10967,7 +11236,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
10967
11236
|
let dir = startDir;
|
|
10968
11237
|
while (true) {
|
|
10969
11238
|
if (cwds.has(dir)) return dir;
|
|
10970
|
-
const parent =
|
|
11239
|
+
const parent = path29.dirname(dir);
|
|
10971
11240
|
if (parent === dir) return null;
|
|
10972
11241
|
dir = parent;
|
|
10973
11242
|
}
|
|
@@ -11163,12 +11432,12 @@ import { execa as execa2 } from "execa";
|
|
|
11163
11432
|
init_provenance();
|
|
11164
11433
|
|
|
11165
11434
|
// src/mcp-pin.ts
|
|
11166
|
-
import
|
|
11167
|
-
import
|
|
11168
|
-
import
|
|
11435
|
+
import fs27 from "fs";
|
|
11436
|
+
import path30 from "path";
|
|
11437
|
+
import os23 from "os";
|
|
11169
11438
|
import crypto4 from "crypto";
|
|
11170
11439
|
function getPinsFilePath() {
|
|
11171
|
-
return
|
|
11440
|
+
return path30.join(os23.homedir(), ".node9", "mcp-pins.json");
|
|
11172
11441
|
}
|
|
11173
11442
|
function hashToolDefinitions(tools) {
|
|
11174
11443
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -11185,7 +11454,7 @@ function getServerKey(upstreamCommand) {
|
|
|
11185
11454
|
function readMcpPinsSafe() {
|
|
11186
11455
|
const filePath = getPinsFilePath();
|
|
11187
11456
|
try {
|
|
11188
|
-
const raw =
|
|
11457
|
+
const raw = fs27.readFileSync(filePath, "utf-8");
|
|
11189
11458
|
if (!raw.trim()) {
|
|
11190
11459
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
11191
11460
|
}
|
|
@@ -11209,10 +11478,10 @@ function readMcpPins() {
|
|
|
11209
11478
|
}
|
|
11210
11479
|
function writeMcpPins(data) {
|
|
11211
11480
|
const filePath = getPinsFilePath();
|
|
11212
|
-
|
|
11481
|
+
fs27.mkdirSync(path30.dirname(filePath), { recursive: true });
|
|
11213
11482
|
const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
|
|
11214
|
-
|
|
11215
|
-
|
|
11483
|
+
fs27.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
11484
|
+
fs27.renameSync(tmp, filePath);
|
|
11216
11485
|
}
|
|
11217
11486
|
function checkPin(serverKey, currentHash) {
|
|
11218
11487
|
const result = readMcpPinsSafe();
|
|
@@ -11584,9 +11853,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11584
11853
|
|
|
11585
11854
|
// src/mcp-server/index.ts
|
|
11586
11855
|
import readline4 from "readline";
|
|
11587
|
-
import
|
|
11588
|
-
import
|
|
11589
|
-
import
|
|
11856
|
+
import fs28 from "fs";
|
|
11857
|
+
import os24 from "os";
|
|
11858
|
+
import path31 from "path";
|
|
11590
11859
|
init_core();
|
|
11591
11860
|
init_daemon();
|
|
11592
11861
|
init_shields();
|
|
@@ -11761,13 +12030,13 @@ function handleStatus() {
|
|
|
11761
12030
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
11762
12031
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
11763
12032
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
11764
|
-
const projectConfig =
|
|
11765
|
-
const globalConfig =
|
|
12033
|
+
const projectConfig = path31.join(process.cwd(), "node9.config.json");
|
|
12034
|
+
const globalConfig = path31.join(os24.homedir(), ".node9", "config.json");
|
|
11766
12035
|
lines.push(
|
|
11767
|
-
`Project config (node9.config.json): ${
|
|
12036
|
+
`Project config (node9.config.json): ${fs28.existsSync(projectConfig) ? "present" : "not found"}`
|
|
11768
12037
|
);
|
|
11769
12038
|
lines.push(
|
|
11770
|
-
`Global config (~/.node9/config.json): ${
|
|
12039
|
+
`Global config (~/.node9/config.json): ${fs28.existsSync(globalConfig) ? "present" : "not found"}`
|
|
11771
12040
|
);
|
|
11772
12041
|
return lines.join("\n");
|
|
11773
12042
|
}
|
|
@@ -11841,21 +12110,21 @@ function handleShieldDisable(args) {
|
|
|
11841
12110
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11842
12111
|
return `Shield "${name}" disabled.`;
|
|
11843
12112
|
}
|
|
11844
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
12113
|
+
var GLOBAL_CONFIG_PATH2 = path31.join(os24.homedir(), ".node9", "config.json");
|
|
11845
12114
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11846
12115
|
function readGlobalConfigRaw() {
|
|
11847
12116
|
try {
|
|
11848
|
-
if (
|
|
11849
|
-
return JSON.parse(
|
|
12117
|
+
if (fs28.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
12118
|
+
return JSON.parse(fs28.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
11850
12119
|
}
|
|
11851
12120
|
} catch {
|
|
11852
12121
|
}
|
|
11853
12122
|
return {};
|
|
11854
12123
|
}
|
|
11855
12124
|
function writeGlobalConfigRaw(data) {
|
|
11856
|
-
const dir =
|
|
11857
|
-
if (!
|
|
11858
|
-
|
|
12125
|
+
const dir = path31.dirname(GLOBAL_CONFIG_PATH2);
|
|
12126
|
+
if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
|
|
12127
|
+
fs28.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
11859
12128
|
}
|
|
11860
12129
|
function handleApproverList() {
|
|
11861
12130
|
const config = getConfig();
|
|
@@ -11898,9 +12167,9 @@ function handleApproverSet(args) {
|
|
|
11898
12167
|
}
|
|
11899
12168
|
function handleAuditGet(args) {
|
|
11900
12169
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11901
|
-
const auditPath =
|
|
11902
|
-
if (!
|
|
11903
|
-
const lines =
|
|
12170
|
+
const auditPath = path31.join(os24.homedir(), ".node9", "audit.log");
|
|
12171
|
+
if (!fs28.existsSync(auditPath)) return "No audit log found.";
|
|
12172
|
+
const lines = fs28.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
11904
12173
|
const recent = lines.slice(-limit);
|
|
11905
12174
|
const entries = recent.map((line) => {
|
|
11906
12175
|
try {
|
|
@@ -12220,20 +12489,20 @@ function registerMcpPinCommand(program2) {
|
|
|
12220
12489
|
|
|
12221
12490
|
// src/cli.ts
|
|
12222
12491
|
var { version } = JSON.parse(
|
|
12223
|
-
|
|
12492
|
+
fs31.readFileSync(path34.join(__dirname, "../package.json"), "utf-8")
|
|
12224
12493
|
);
|
|
12225
12494
|
var program = new Command();
|
|
12226
12495
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
12227
12496
|
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) => {
|
|
12228
12497
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
12229
|
-
const credPath =
|
|
12230
|
-
if (!
|
|
12231
|
-
|
|
12498
|
+
const credPath = path34.join(os27.homedir(), ".node9", "credentials.json");
|
|
12499
|
+
if (!fs31.existsSync(path34.dirname(credPath)))
|
|
12500
|
+
fs31.mkdirSync(path34.dirname(credPath), { recursive: true });
|
|
12232
12501
|
const profileName = options.profile || "default";
|
|
12233
12502
|
let existingCreds = {};
|
|
12234
12503
|
try {
|
|
12235
|
-
if (
|
|
12236
|
-
const raw = JSON.parse(
|
|
12504
|
+
if (fs31.existsSync(credPath)) {
|
|
12505
|
+
const raw = JSON.parse(fs31.readFileSync(credPath, "utf-8"));
|
|
12237
12506
|
if (raw.apiKey) {
|
|
12238
12507
|
existingCreds = {
|
|
12239
12508
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -12245,13 +12514,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12245
12514
|
} catch {
|
|
12246
12515
|
}
|
|
12247
12516
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
12248
|
-
|
|
12517
|
+
fs31.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
12249
12518
|
if (profileName === "default") {
|
|
12250
|
-
const configPath =
|
|
12519
|
+
const configPath = path34.join(os27.homedir(), ".node9", "config.json");
|
|
12251
12520
|
let config = {};
|
|
12252
12521
|
try {
|
|
12253
|
-
if (
|
|
12254
|
-
config = JSON.parse(
|
|
12522
|
+
if (fs31.existsSync(configPath))
|
|
12523
|
+
config = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
|
|
12255
12524
|
} catch {
|
|
12256
12525
|
}
|
|
12257
12526
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -12266,9 +12535,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12266
12535
|
approvers.cloud = false;
|
|
12267
12536
|
}
|
|
12268
12537
|
s.approvers = approvers;
|
|
12269
|
-
if (!
|
|
12270
|
-
|
|
12271
|
-
|
|
12538
|
+
if (!fs31.existsSync(path34.dirname(configPath)))
|
|
12539
|
+
fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
|
|
12540
|
+
fs31.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
12272
12541
|
}
|
|
12273
12542
|
if (options.profile && profileName !== "default") {
|
|
12274
12543
|
console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -12362,15 +12631,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
12362
12631
|
}
|
|
12363
12632
|
}
|
|
12364
12633
|
if (options.purge) {
|
|
12365
|
-
const node9Dir =
|
|
12366
|
-
if (
|
|
12634
|
+
const node9Dir = path34.join(os27.homedir(), ".node9");
|
|
12635
|
+
if (fs31.existsSync(node9Dir)) {
|
|
12367
12636
|
const confirmed = await confirm2({
|
|
12368
12637
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
12369
12638
|
default: false
|
|
12370
12639
|
});
|
|
12371
12640
|
if (confirmed) {
|
|
12372
|
-
|
|
12373
|
-
if (
|
|
12641
|
+
fs31.rmSync(node9Dir, { recursive: true });
|
|
12642
|
+
if (fs31.existsSync(node9Dir)) {
|
|
12374
12643
|
console.error(
|
|
12375
12644
|
chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
12376
12645
|
);
|
|
@@ -12511,14 +12780,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12511
12780
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12512
12781
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
12513
12782
|
if (subcommand === "debug") {
|
|
12514
|
-
const flagFile =
|
|
12783
|
+
const flagFile = path34.join(os27.homedir(), ".node9", "hud-debug");
|
|
12515
12784
|
if (state === "on") {
|
|
12516
|
-
|
|
12517
|
-
|
|
12785
|
+
fs31.mkdirSync(path34.dirname(flagFile), { recursive: true });
|
|
12786
|
+
fs31.writeFileSync(flagFile, "");
|
|
12518
12787
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12519
12788
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12520
12789
|
} else if (state === "off") {
|
|
12521
|
-
if (
|
|
12790
|
+
if (fs31.existsSync(flagFile)) fs31.unlinkSync(flagFile);
|
|
12522
12791
|
console.log("HUD debug logging disabled.");
|
|
12523
12792
|
} else {
|
|
12524
12793
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12625,9 +12894,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
12625
12894
|
const isCheckHook = process.argv[2] === "check";
|
|
12626
12895
|
if (isCheckHook) {
|
|
12627
12896
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12628
|
-
const logPath =
|
|
12897
|
+
const logPath = path34.join(os27.homedir(), ".node9", "hook-debug.log");
|
|
12629
12898
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12630
|
-
|
|
12899
|
+
fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12631
12900
|
`);
|
|
12632
12901
|
}
|
|
12633
12902
|
process.exit(0);
|