@node9/proxy 1.10.0 → 1.10.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/dist/cli.js +507 -273
- package/dist/cli.mjs +503 -269
- package/dist/index.js +31 -15
- package/dist/index.mjs +31 -15
- 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;
|
|
@@ -2791,11 +2792,12 @@ ${smartTruncate(str, 500)}`
|
|
|
2791
2792
|
function escapePango(text) {
|
|
2792
2793
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2793
2794
|
}
|
|
2794
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2795
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2795
2796
|
const lines = [];
|
|
2796
2797
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2797
2798
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2798
2799
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2800
|
+
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2799
2801
|
lines.push("");
|
|
2800
2802
|
lines.push(formattedArgs);
|
|
2801
2803
|
if (allowCount >= 3) {
|
|
@@ -2808,7 +2810,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2808
2810
|
}
|
|
2809
2811
|
return lines.join("\n");
|
|
2810
2812
|
}
|
|
2811
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2813
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2812
2814
|
const lines = [];
|
|
2813
2815
|
if (locked) {
|
|
2814
2816
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2818,6 +2820,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2818
2820
|
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
2819
2821
|
);
|
|
2820
2822
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2823
|
+
if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
|
|
2821
2824
|
lines.push("");
|
|
2822
2825
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2823
2826
|
if (allowCount >= 3) {
|
|
@@ -2834,7 +2837,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2834
2837
|
}
|
|
2835
2838
|
return lines.join("\n");
|
|
2836
2839
|
}
|
|
2837
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2840
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
|
|
2838
2841
|
if (isTestEnv()) return "deny";
|
|
2839
2842
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2840
2843
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
@@ -2845,7 +2848,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
2845
2848
|
agent,
|
|
2846
2849
|
explainableLabel,
|
|
2847
2850
|
locked,
|
|
2848
|
-
allowCount
|
|
2851
|
+
allowCount,
|
|
2852
|
+
ruleDescription
|
|
2849
2853
|
);
|
|
2850
2854
|
return new Promise((resolve) => {
|
|
2851
2855
|
let childProcess = null;
|
|
@@ -2879,7 +2883,8 @@ end run`;
|
|
|
2879
2883
|
agent,
|
|
2880
2884
|
explainableLabel,
|
|
2881
2885
|
locked,
|
|
2882
|
-
allowCount
|
|
2886
|
+
allowCount,
|
|
2887
|
+
ruleDescription
|
|
2883
2888
|
);
|
|
2884
2889
|
const argsList = [
|
|
2885
2890
|
locked ? "--info" : "--question",
|
|
@@ -2953,7 +2958,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2953
2958
|
}).catch(() => {
|
|
2954
2959
|
});
|
|
2955
2960
|
}
|
|
2956
|
-
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2961
|
+
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
2957
2962
|
const controller = new AbortController();
|
|
2958
2963
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2959
2964
|
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
@@ -2998,7 +3003,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2998
3003
|
platform: os9.platform()
|
|
2999
3004
|
},
|
|
3000
3005
|
...riskMetadata && { riskMetadata },
|
|
3001
|
-
...ciContext && { ciContext }
|
|
3006
|
+
...ciContext && { ciContext },
|
|
3007
|
+
...agentPolicy && { policy: agentPolicy },
|
|
3008
|
+
...forceReview && { forceReview: true }
|
|
3002
3009
|
}),
|
|
3003
3010
|
signal: controller.signal
|
|
3004
3011
|
});
|
|
@@ -3392,6 +3399,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3392
3399
|
policyMatchedWord,
|
|
3393
3400
|
policyResult.ruleName
|
|
3394
3401
|
);
|
|
3402
|
+
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
3395
3403
|
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
3396
3404
|
if (persistent === "allow") {
|
|
3397
3405
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3426,9 +3434,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3426
3434
|
}
|
|
3427
3435
|
let cloudRequestId = null;
|
|
3428
3436
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
3429
|
-
|
|
3437
|
+
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
3438
|
+
if (cloudEnforced) {
|
|
3430
3439
|
try {
|
|
3431
|
-
const initResult = await initNode9SaaS(
|
|
3440
|
+
const initResult = await initNode9SaaS(
|
|
3441
|
+
toolName,
|
|
3442
|
+
args,
|
|
3443
|
+
creds,
|
|
3444
|
+
meta,
|
|
3445
|
+
riskMetadata,
|
|
3446
|
+
config.settings.agentPolicy,
|
|
3447
|
+
forceReview
|
|
3448
|
+
);
|
|
3432
3449
|
if (!initResult.pending) {
|
|
3433
3450
|
if (initResult.shadowMode) {
|
|
3434
3451
|
return { approved: true, checkedBy: "cloud" };
|
|
@@ -3443,9 +3460,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3443
3460
|
};
|
|
3444
3461
|
}
|
|
3445
3462
|
}
|
|
3446
|
-
if (
|
|
3447
|
-
cloudRequestId = initResult.requestId || null;
|
|
3448
|
-
}
|
|
3463
|
+
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
3449
3464
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3450
3465
|
} catch {
|
|
3451
3466
|
}
|
|
@@ -3502,7 +3517,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3502
3517
|
}
|
|
3503
3518
|
}
|
|
3504
3519
|
}
|
|
3505
|
-
if (cloudEnforced && cloudRequestId
|
|
3520
|
+
if (cloudEnforced && cloudRequestId) {
|
|
3506
3521
|
racePromises.push(
|
|
3507
3522
|
(async () => {
|
|
3508
3523
|
try {
|
|
@@ -3534,7 +3549,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3534
3549
|
signal,
|
|
3535
3550
|
policyMatchedField,
|
|
3536
3551
|
policyMatchedWord,
|
|
3537
|
-
daemonAllowCount
|
|
3552
|
+
daemonAllowCount,
|
|
3553
|
+
riskMetadata?.ruleDescription
|
|
3538
3554
|
);
|
|
3539
3555
|
if (decision === "always_allow") {
|
|
3540
3556
|
writeTrustSession(toolName, 36e5);
|
|
@@ -6036,14 +6052,181 @@ var init_patch = __esm({
|
|
|
6036
6052
|
}
|
|
6037
6053
|
});
|
|
6038
6054
|
|
|
6039
|
-
// src/
|
|
6040
|
-
import http from "http";
|
|
6055
|
+
// src/costSync.ts
|
|
6041
6056
|
import fs16 from "fs";
|
|
6042
6057
|
import path19 from "path";
|
|
6058
|
+
import os14 from "os";
|
|
6059
|
+
function normalizeModel(raw) {
|
|
6060
|
+
return raw.replace(/-\d{8}$/, "");
|
|
6061
|
+
}
|
|
6062
|
+
function pricingFor(model) {
|
|
6063
|
+
const norm = normalizeModel(model);
|
|
6064
|
+
if (PRICING[norm]) return PRICING[norm];
|
|
6065
|
+
let best = null;
|
|
6066
|
+
for (const key of Object.keys(PRICING)) {
|
|
6067
|
+
if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
|
|
6068
|
+
}
|
|
6069
|
+
return best ? PRICING[best] : null;
|
|
6070
|
+
}
|
|
6071
|
+
function parseJSONLFile(filePath) {
|
|
6072
|
+
let content;
|
|
6073
|
+
try {
|
|
6074
|
+
content = fs16.readFileSync(filePath, "utf8");
|
|
6075
|
+
} catch {
|
|
6076
|
+
return /* @__PURE__ */ new Map();
|
|
6077
|
+
}
|
|
6078
|
+
const daily = /* @__PURE__ */ new Map();
|
|
6079
|
+
for (const line of content.split("\n")) {
|
|
6080
|
+
if (!line.trim()) continue;
|
|
6081
|
+
let row;
|
|
6082
|
+
try {
|
|
6083
|
+
row = JSON.parse(line);
|
|
6084
|
+
} catch {
|
|
6085
|
+
continue;
|
|
6086
|
+
}
|
|
6087
|
+
if (row["type"] !== "assistant") continue;
|
|
6088
|
+
const msg = row["message"];
|
|
6089
|
+
if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
|
|
6090
|
+
const usage = msg["usage"];
|
|
6091
|
+
const model = msg["model"];
|
|
6092
|
+
const timestamp = row["timestamp"];
|
|
6093
|
+
if (typeof timestamp !== "string" || timestamp.length < 10) continue;
|
|
6094
|
+
const date = timestamp.slice(0, 10);
|
|
6095
|
+
const p = pricingFor(model);
|
|
6096
|
+
if (!p) continue;
|
|
6097
|
+
const inp = Number(usage["input_tokens"] ?? 0);
|
|
6098
|
+
const out = Number(usage["output_tokens"] ?? 0);
|
|
6099
|
+
const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
|
|
6100
|
+
const cr = Number(usage["cache_read_input_tokens"] ?? 0);
|
|
6101
|
+
const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
|
|
6102
|
+
const norm = normalizeModel(model);
|
|
6103
|
+
const key = `${date}::${norm}`;
|
|
6104
|
+
const prev = daily.get(key);
|
|
6105
|
+
if (prev) {
|
|
6106
|
+
prev.costUSD += cost;
|
|
6107
|
+
prev.inputTokens += inp;
|
|
6108
|
+
prev.outputTokens += out;
|
|
6109
|
+
prev.cacheWriteTokens += cw;
|
|
6110
|
+
prev.cacheReadTokens += cr;
|
|
6111
|
+
} else {
|
|
6112
|
+
daily.set(key, {
|
|
6113
|
+
date,
|
|
6114
|
+
model: norm,
|
|
6115
|
+
costUSD: cost,
|
|
6116
|
+
inputTokens: inp,
|
|
6117
|
+
outputTokens: out,
|
|
6118
|
+
cacheWriteTokens: cw,
|
|
6119
|
+
cacheReadTokens: cr
|
|
6120
|
+
});
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
return daily;
|
|
6124
|
+
}
|
|
6125
|
+
function collectEntries() {
|
|
6126
|
+
const projectsDir = path19.join(os14.homedir(), ".claude", "projects");
|
|
6127
|
+
if (!fs16.existsSync(projectsDir)) return [];
|
|
6128
|
+
const combined = /* @__PURE__ */ new Map();
|
|
6129
|
+
let dirs;
|
|
6130
|
+
try {
|
|
6131
|
+
dirs = fs16.readdirSync(projectsDir);
|
|
6132
|
+
} catch {
|
|
6133
|
+
return [];
|
|
6134
|
+
}
|
|
6135
|
+
for (const dir of dirs) {
|
|
6136
|
+
const dirPath = path19.join(projectsDir, dir);
|
|
6137
|
+
try {
|
|
6138
|
+
if (!fs16.statSync(dirPath).isDirectory()) continue;
|
|
6139
|
+
} catch {
|
|
6140
|
+
continue;
|
|
6141
|
+
}
|
|
6142
|
+
let files;
|
|
6143
|
+
try {
|
|
6144
|
+
files = fs16.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
6145
|
+
} catch {
|
|
6146
|
+
continue;
|
|
6147
|
+
}
|
|
6148
|
+
for (const file of files) {
|
|
6149
|
+
const entries = parseJSONLFile(path19.join(dirPath, file));
|
|
6150
|
+
for (const [key, e] of entries) {
|
|
6151
|
+
const prev = combined.get(key);
|
|
6152
|
+
if (prev) {
|
|
6153
|
+
prev.costUSD += e.costUSD;
|
|
6154
|
+
prev.inputTokens += e.inputTokens;
|
|
6155
|
+
prev.outputTokens += e.outputTokens;
|
|
6156
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
6157
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
6158
|
+
} else {
|
|
6159
|
+
combined.set(key, { ...e });
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
return [...combined.values()];
|
|
6165
|
+
}
|
|
6166
|
+
async function syncCost() {
|
|
6167
|
+
const creds = getCredentials();
|
|
6168
|
+
if (!creds?.apiKey || !creds?.apiUrl) return;
|
|
6169
|
+
const entries = collectEntries();
|
|
6170
|
+
if (entries.length === 0) return;
|
|
6171
|
+
let username = "unknown";
|
|
6172
|
+
try {
|
|
6173
|
+
username = os14.userInfo().username;
|
|
6174
|
+
} catch {
|
|
6175
|
+
}
|
|
6176
|
+
const machineId = `${os14.hostname()}:${username}`;
|
|
6177
|
+
try {
|
|
6178
|
+
const res = await fetch(`${creds.apiUrl}/cost-sync`, {
|
|
6179
|
+
method: "POST",
|
|
6180
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
6181
|
+
body: JSON.stringify({ machineId, entries }),
|
|
6182
|
+
signal: AbortSignal.timeout(15e3)
|
|
6183
|
+
});
|
|
6184
|
+
if (!res.ok) {
|
|
6185
|
+
fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
6186
|
+
`);
|
|
6187
|
+
}
|
|
6188
|
+
} catch (err2) {
|
|
6189
|
+
fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
|
|
6190
|
+
`);
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
function startCostSync() {
|
|
6194
|
+
syncCost().catch(() => {
|
|
6195
|
+
});
|
|
6196
|
+
const timer = setInterval(() => {
|
|
6197
|
+
syncCost().catch(() => {
|
|
6198
|
+
});
|
|
6199
|
+
}, SYNC_INTERVAL_MS);
|
|
6200
|
+
timer.unref();
|
|
6201
|
+
}
|
|
6202
|
+
var SYNC_INTERVAL_MS, PRICING;
|
|
6203
|
+
var init_costSync = __esm({
|
|
6204
|
+
"src/costSync.ts"() {
|
|
6205
|
+
"use strict";
|
|
6206
|
+
init_config();
|
|
6207
|
+
init_audit();
|
|
6208
|
+
SYNC_INTERVAL_MS = 10 * 60 * 1e3;
|
|
6209
|
+
PRICING = {
|
|
6210
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
6211
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6212
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6213
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6214
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6215
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6216
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
|
|
6221
|
+
// src/daemon/server.ts
|
|
6222
|
+
import http from "http";
|
|
6223
|
+
import fs17 from "fs";
|
|
6224
|
+
import path20 from "path";
|
|
6043
6225
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6044
6226
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6045
6227
|
import chalk2 from "chalk";
|
|
6046
6228
|
function startDaemon() {
|
|
6229
|
+
startCostSync();
|
|
6047
6230
|
loadInsightCounts();
|
|
6048
6231
|
const csrfToken = randomUUID4();
|
|
6049
6232
|
const internalToken = randomUUID4();
|
|
@@ -6059,7 +6242,7 @@ function startDaemon() {
|
|
|
6059
6242
|
idleTimer = setTimeout(() => {
|
|
6060
6243
|
if (autoStarted) {
|
|
6061
6244
|
try {
|
|
6062
|
-
|
|
6245
|
+
fs17.unlinkSync(DAEMON_PID_FILE);
|
|
6063
6246
|
} catch {
|
|
6064
6247
|
}
|
|
6065
6248
|
}
|
|
@@ -6222,7 +6405,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6222
6405
|
status: "pending"
|
|
6223
6406
|
});
|
|
6224
6407
|
}
|
|
6225
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6408
|
+
const projectCwd = typeof cwd === "string" && path20.isAbsolute(cwd) ? cwd : void 0;
|
|
6226
6409
|
const projectConfig = getConfig(projectCwd);
|
|
6227
6410
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6228
6411
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6612,8 +6795,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6612
6795
|
const body = await readBody(req);
|
|
6613
6796
|
const data = body ? JSON.parse(body) : {};
|
|
6614
6797
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6615
|
-
const node9Dir =
|
|
6616
|
-
if (!
|
|
6798
|
+
const node9Dir = path20.dirname(GLOBAL_CONFIG_PATH);
|
|
6799
|
+
if (!path20.resolve(configPath).startsWith(node9Dir + path20.sep)) {
|
|
6617
6800
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6618
6801
|
return res.end(
|
|
6619
6802
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6724,14 +6907,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6724
6907
|
server.on("error", (e) => {
|
|
6725
6908
|
if (e.code === "EADDRINUSE") {
|
|
6726
6909
|
try {
|
|
6727
|
-
if (
|
|
6728
|
-
const { pid } = JSON.parse(
|
|
6910
|
+
if (fs17.existsSync(DAEMON_PID_FILE)) {
|
|
6911
|
+
const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6729
6912
|
process.kill(pid, 0);
|
|
6730
6913
|
return process.exit(0);
|
|
6731
6914
|
}
|
|
6732
6915
|
} catch {
|
|
6733
6916
|
try {
|
|
6734
|
-
|
|
6917
|
+
fs17.unlinkSync(DAEMON_PID_FILE);
|
|
6735
6918
|
} catch {
|
|
6736
6919
|
}
|
|
6737
6920
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6799,32 +6982,33 @@ var init_server = __esm({
|
|
|
6799
6982
|
init_state2();
|
|
6800
6983
|
init_patch();
|
|
6801
6984
|
init_config_schema();
|
|
6985
|
+
init_costSync();
|
|
6802
6986
|
}
|
|
6803
6987
|
});
|
|
6804
6988
|
|
|
6805
6989
|
// src/daemon/index.ts
|
|
6806
|
-
import
|
|
6990
|
+
import fs18 from "fs";
|
|
6807
6991
|
import chalk3 from "chalk";
|
|
6808
6992
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
6809
6993
|
function stopDaemon() {
|
|
6810
|
-
if (!
|
|
6994
|
+
if (!fs18.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
|
|
6811
6995
|
try {
|
|
6812
|
-
const { pid } = JSON.parse(
|
|
6996
|
+
const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6813
6997
|
process.kill(pid, "SIGTERM");
|
|
6814
6998
|
console.log(chalk3.green("\u2705 Stopped."));
|
|
6815
6999
|
} catch {
|
|
6816
7000
|
console.log(chalk3.gray("Cleaned up stale PID file."));
|
|
6817
7001
|
} finally {
|
|
6818
7002
|
try {
|
|
6819
|
-
|
|
7003
|
+
fs18.unlinkSync(DAEMON_PID_FILE);
|
|
6820
7004
|
} catch {
|
|
6821
7005
|
}
|
|
6822
7006
|
}
|
|
6823
7007
|
}
|
|
6824
7008
|
function daemonStatus() {
|
|
6825
|
-
if (
|
|
7009
|
+
if (fs18.existsSync(DAEMON_PID_FILE)) {
|
|
6826
7010
|
try {
|
|
6827
|
-
const { pid } = JSON.parse(
|
|
7011
|
+
const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6828
7012
|
process.kill(pid, 0);
|
|
6829
7013
|
console.log(chalk3.green("Node9 daemon: running"));
|
|
6830
7014
|
return;
|
|
@@ -6859,9 +7043,9 @@ __export(tail_exports, {
|
|
|
6859
7043
|
});
|
|
6860
7044
|
import http2 from "http";
|
|
6861
7045
|
import chalk19 from "chalk";
|
|
6862
|
-
import
|
|
6863
|
-
import
|
|
6864
|
-
import
|
|
7046
|
+
import fs29 from "fs";
|
|
7047
|
+
import os25 from "os";
|
|
7048
|
+
import path32 from "path";
|
|
6865
7049
|
import readline5 from "readline";
|
|
6866
7050
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
6867
7051
|
function getIcon(tool) {
|
|
@@ -6884,7 +7068,7 @@ function formatBase(activity) {
|
|
|
6884
7068
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6885
7069
|
const icon = getIcon(activity.tool);
|
|
6886
7070
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6887
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7071
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os25.homedir(), "~");
|
|
6888
7072
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6889
7073
|
return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
|
|
6890
7074
|
}
|
|
@@ -6923,9 +7107,9 @@ function renderPending(activity) {
|
|
|
6923
7107
|
}
|
|
6924
7108
|
async function ensureDaemon() {
|
|
6925
7109
|
let pidPort = null;
|
|
6926
|
-
if (
|
|
7110
|
+
if (fs29.existsSync(PID_FILE)) {
|
|
6927
7111
|
try {
|
|
6928
|
-
const { port } = JSON.parse(
|
|
7112
|
+
const { port } = JSON.parse(fs29.readFileSync(PID_FILE, "utf-8"));
|
|
6929
7113
|
pidPort = port;
|
|
6930
7114
|
} catch {
|
|
6931
7115
|
console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -7002,6 +7186,9 @@ function buildCardLines(req, localCount = 0) {
|
|
|
7002
7186
|
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
7003
7187
|
`${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
|
|
7004
7188
|
];
|
|
7189
|
+
if (req.riskMetadata?.ruleDescription) {
|
|
7190
|
+
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
|
|
7191
|
+
}
|
|
7005
7192
|
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
7006
7193
|
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
7007
7194
|
}
|
|
@@ -7045,9 +7232,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7045
7232
|
];
|
|
7046
7233
|
}
|
|
7047
7234
|
function readApproversFromDisk() {
|
|
7048
|
-
const configPath =
|
|
7235
|
+
const configPath = path32.join(os25.homedir(), ".node9", "config.json");
|
|
7049
7236
|
try {
|
|
7050
|
-
const raw = JSON.parse(
|
|
7237
|
+
const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
|
|
7051
7238
|
const settings = raw.settings ?? {};
|
|
7052
7239
|
return settings.approvers ?? {};
|
|
7053
7240
|
} catch {
|
|
@@ -7063,15 +7250,15 @@ function approverStatusLine() {
|
|
|
7063
7250
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7064
7251
|
}
|
|
7065
7252
|
function toggleApprover(channel) {
|
|
7066
|
-
const configPath =
|
|
7253
|
+
const configPath = path32.join(os25.homedir(), ".node9", "config.json");
|
|
7067
7254
|
try {
|
|
7068
|
-
const raw = JSON.parse(
|
|
7255
|
+
const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
|
|
7069
7256
|
const settings = raw.settings ?? {};
|
|
7070
7257
|
const approvers = settings.approvers ?? {};
|
|
7071
7258
|
approvers[channel] = approvers[channel] === false;
|
|
7072
7259
|
settings.approvers = approvers;
|
|
7073
7260
|
raw.settings = settings;
|
|
7074
|
-
|
|
7261
|
+
fs29.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7075
7262
|
} catch (err2) {
|
|
7076
7263
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7077
7264
|
`);
|
|
@@ -7241,8 +7428,8 @@ async function startTail(options = {}) {
|
|
|
7241
7428
|
}
|
|
7242
7429
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7243
7430
|
try {
|
|
7244
|
-
|
|
7245
|
-
|
|
7431
|
+
fs29.appendFileSync(
|
|
7432
|
+
path32.join(os25.homedir(), ".node9", "hook-debug.log"),
|
|
7246
7433
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7247
7434
|
`
|
|
7248
7435
|
);
|
|
@@ -7502,7 +7689,7 @@ var init_tail = __esm({
|
|
|
7502
7689
|
init_daemon2();
|
|
7503
7690
|
init_daemon();
|
|
7504
7691
|
init_core();
|
|
7505
|
-
PID_FILE =
|
|
7692
|
+
PID_FILE = path32.join(os25.homedir(), ".node9", "daemon.pid");
|
|
7506
7693
|
ICONS = {
|
|
7507
7694
|
bash: "\u{1F4BB}",
|
|
7508
7695
|
shell: "\u{1F4BB}",
|
|
@@ -7543,9 +7730,9 @@ __export(hud_exports, {
|
|
|
7543
7730
|
main: () => main,
|
|
7544
7731
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
7545
7732
|
});
|
|
7546
|
-
import
|
|
7547
|
-
import
|
|
7548
|
-
import
|
|
7733
|
+
import fs30 from "fs";
|
|
7734
|
+
import path33 from "path";
|
|
7735
|
+
import os26 from "os";
|
|
7549
7736
|
import http3 from "http";
|
|
7550
7737
|
async function readStdin() {
|
|
7551
7738
|
const chunks = [];
|
|
@@ -7621,9 +7808,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7621
7808
|
return ` (${m}m left)`;
|
|
7622
7809
|
}
|
|
7623
7810
|
function safeReadJson(filePath) {
|
|
7624
|
-
if (!
|
|
7811
|
+
if (!fs30.existsSync(filePath)) return null;
|
|
7625
7812
|
try {
|
|
7626
|
-
return JSON.parse(
|
|
7813
|
+
return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
|
|
7627
7814
|
} catch {
|
|
7628
7815
|
return null;
|
|
7629
7816
|
}
|
|
@@ -7644,12 +7831,12 @@ function countHooksInFile(filePath) {
|
|
|
7644
7831
|
return Object.keys(cfg.hooks).length;
|
|
7645
7832
|
}
|
|
7646
7833
|
function countRulesInDir(rulesDir) {
|
|
7647
|
-
if (!
|
|
7834
|
+
if (!fs30.existsSync(rulesDir)) return 0;
|
|
7648
7835
|
let count = 0;
|
|
7649
7836
|
try {
|
|
7650
|
-
for (const entry of
|
|
7837
|
+
for (const entry of fs30.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7651
7838
|
if (entry.isDirectory()) {
|
|
7652
|
-
count += countRulesInDir(
|
|
7839
|
+
count += countRulesInDir(path33.join(rulesDir, entry.name));
|
|
7653
7840
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7654
7841
|
count++;
|
|
7655
7842
|
}
|
|
@@ -7660,46 +7847,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7660
7847
|
}
|
|
7661
7848
|
function isSamePath(a, b) {
|
|
7662
7849
|
try {
|
|
7663
|
-
return
|
|
7850
|
+
return path33.resolve(a) === path33.resolve(b);
|
|
7664
7851
|
} catch {
|
|
7665
7852
|
return false;
|
|
7666
7853
|
}
|
|
7667
7854
|
}
|
|
7668
7855
|
function countConfigs(cwd) {
|
|
7669
|
-
const homeDir2 =
|
|
7670
|
-
const claudeDir =
|
|
7856
|
+
const homeDir2 = os26.homedir();
|
|
7857
|
+
const claudeDir = path33.join(homeDir2, ".claude");
|
|
7671
7858
|
let claudeMdCount = 0;
|
|
7672
7859
|
let rulesCount = 0;
|
|
7673
7860
|
let hooksCount = 0;
|
|
7674
7861
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7675
7862
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7676
|
-
if (
|
|
7677
|
-
rulesCount += countRulesInDir(
|
|
7678
|
-
const userSettings =
|
|
7863
|
+
if (fs30.existsSync(path33.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7864
|
+
rulesCount += countRulesInDir(path33.join(claudeDir, "rules"));
|
|
7865
|
+
const userSettings = path33.join(claudeDir, "settings.json");
|
|
7679
7866
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7680
7867
|
hooksCount += countHooksInFile(userSettings);
|
|
7681
|
-
const userClaudeJson =
|
|
7868
|
+
const userClaudeJson = path33.join(homeDir2, ".claude.json");
|
|
7682
7869
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7683
7870
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7684
7871
|
userMcpServers.delete(name);
|
|
7685
7872
|
}
|
|
7686
7873
|
if (cwd) {
|
|
7687
|
-
if (
|
|
7688
|
-
if (
|
|
7689
|
-
const projectClaudeDir =
|
|
7874
|
+
if (fs30.existsSync(path33.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7875
|
+
if (fs30.existsSync(path33.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7876
|
+
const projectClaudeDir = path33.join(cwd, ".claude");
|
|
7690
7877
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7691
7878
|
if (!overlapsUserScope) {
|
|
7692
|
-
if (
|
|
7693
|
-
rulesCount += countRulesInDir(
|
|
7694
|
-
const projSettings =
|
|
7879
|
+
if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7880
|
+
rulesCount += countRulesInDir(path33.join(projectClaudeDir, "rules"));
|
|
7881
|
+
const projSettings = path33.join(projectClaudeDir, "settings.json");
|
|
7695
7882
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7696
7883
|
hooksCount += countHooksInFile(projSettings);
|
|
7697
7884
|
}
|
|
7698
|
-
if (
|
|
7699
|
-
const localSettings =
|
|
7885
|
+
if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7886
|
+
const localSettings = path33.join(projectClaudeDir, "settings.local.json");
|
|
7700
7887
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7701
7888
|
hooksCount += countHooksInFile(localSettings);
|
|
7702
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7889
|
+
const mcpJsonServers = getMcpServerNames(path33.join(cwd, ".mcp.json"));
|
|
7703
7890
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7704
7891
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7705
7892
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7732,12 +7919,12 @@ function readActiveShieldsHud() {
|
|
|
7732
7919
|
return shieldsCache.value;
|
|
7733
7920
|
}
|
|
7734
7921
|
try {
|
|
7735
|
-
const shieldsPath =
|
|
7736
|
-
if (!
|
|
7922
|
+
const shieldsPath = path33.join(os26.homedir(), ".node9", "shields.json");
|
|
7923
|
+
if (!fs30.existsSync(shieldsPath)) {
|
|
7737
7924
|
shieldsCache = { value: [], ts: now };
|
|
7738
7925
|
return [];
|
|
7739
7926
|
}
|
|
7740
|
-
const parsed = JSON.parse(
|
|
7927
|
+
const parsed = JSON.parse(fs30.readFileSync(shieldsPath, "utf-8"));
|
|
7741
7928
|
if (!Array.isArray(parsed.active)) {
|
|
7742
7929
|
shieldsCache = { value: [], ts: now };
|
|
7743
7930
|
return [];
|
|
@@ -7839,17 +8026,17 @@ function renderContextLine(stdin) {
|
|
|
7839
8026
|
async function main() {
|
|
7840
8027
|
try {
|
|
7841
8028
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7842
|
-
if (
|
|
8029
|
+
if (fs30.existsSync(path33.join(os26.homedir(), ".node9", "hud-debug"))) {
|
|
7843
8030
|
try {
|
|
7844
|
-
const logPath =
|
|
8031
|
+
const logPath = path33.join(os26.homedir(), ".node9", "hud-debug.log");
|
|
7845
8032
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7846
8033
|
let size = 0;
|
|
7847
8034
|
try {
|
|
7848
|
-
size =
|
|
8035
|
+
size = fs30.statSync(logPath).size;
|
|
7849
8036
|
} catch {
|
|
7850
8037
|
}
|
|
7851
8038
|
if (size < MAX_LOG_SIZE) {
|
|
7852
|
-
|
|
8039
|
+
fs30.appendFileSync(
|
|
7853
8040
|
logPath,
|
|
7854
8041
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7855
8042
|
);
|
|
@@ -7870,11 +8057,11 @@ async function main() {
|
|
|
7870
8057
|
try {
|
|
7871
8058
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7872
8059
|
for (const configPath of [
|
|
7873
|
-
|
|
7874
|
-
|
|
8060
|
+
path33.join(cwd, "node9.config.json"),
|
|
8061
|
+
path33.join(os26.homedir(), ".node9", "config.json")
|
|
7875
8062
|
]) {
|
|
7876
|
-
if (!
|
|
7877
|
-
const cfg = JSON.parse(
|
|
8063
|
+
if (!fs30.existsSync(configPath)) continue;
|
|
8064
|
+
const cfg = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
7878
8065
|
const hud = cfg.settings?.hud;
|
|
7879
8066
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7880
8067
|
}
|
|
@@ -8504,9 +8691,9 @@ function teardownHud() {
|
|
|
8504
8691
|
// src/cli.ts
|
|
8505
8692
|
init_daemon2();
|
|
8506
8693
|
import chalk20 from "chalk";
|
|
8507
|
-
import
|
|
8508
|
-
import
|
|
8509
|
-
import
|
|
8694
|
+
import fs31 from "fs";
|
|
8695
|
+
import path34 from "path";
|
|
8696
|
+
import os27 from "os";
|
|
8510
8697
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
8511
8698
|
|
|
8512
8699
|
// src/utils/duration.ts
|
|
@@ -8735,19 +8922,19 @@ init_daemon();
|
|
|
8735
8922
|
init_config();
|
|
8736
8923
|
init_policy();
|
|
8737
8924
|
import chalk5 from "chalk";
|
|
8738
|
-
import
|
|
8925
|
+
import fs20 from "fs";
|
|
8739
8926
|
import { spawn as spawn6 } from "child_process";
|
|
8740
|
-
import
|
|
8741
|
-
import
|
|
8927
|
+
import path22 from "path";
|
|
8928
|
+
import os16 from "os";
|
|
8742
8929
|
|
|
8743
8930
|
// src/undo.ts
|
|
8744
8931
|
import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
|
|
8745
8932
|
import crypto3 from "crypto";
|
|
8746
|
-
import
|
|
8933
|
+
import fs19 from "fs";
|
|
8747
8934
|
import net3 from "net";
|
|
8748
|
-
import
|
|
8749
|
-
import
|
|
8750
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
8935
|
+
import path21 from "path";
|
|
8936
|
+
import os15 from "os";
|
|
8937
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path21.join(os15.tmpdir(), "node9-activity.sock");
|
|
8751
8938
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8752
8939
|
try {
|
|
8753
8940
|
const payload = JSON.stringify({
|
|
@@ -8767,22 +8954,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8767
8954
|
} catch {
|
|
8768
8955
|
}
|
|
8769
8956
|
}
|
|
8770
|
-
var SNAPSHOT_STACK_PATH =
|
|
8771
|
-
var UNDO_LATEST_PATH =
|
|
8957
|
+
var SNAPSHOT_STACK_PATH = path21.join(os15.homedir(), ".node9", "snapshots.json");
|
|
8958
|
+
var UNDO_LATEST_PATH = path21.join(os15.homedir(), ".node9", "undo_latest.txt");
|
|
8772
8959
|
var MAX_SNAPSHOTS = 10;
|
|
8773
8960
|
var GIT_TIMEOUT = 15e3;
|
|
8774
8961
|
function readStack() {
|
|
8775
8962
|
try {
|
|
8776
|
-
if (
|
|
8777
|
-
return JSON.parse(
|
|
8963
|
+
if (fs19.existsSync(SNAPSHOT_STACK_PATH))
|
|
8964
|
+
return JSON.parse(fs19.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
8778
8965
|
} catch {
|
|
8779
8966
|
}
|
|
8780
8967
|
return [];
|
|
8781
8968
|
}
|
|
8782
8969
|
function writeStack(stack) {
|
|
8783
|
-
const dir =
|
|
8784
|
-
if (!
|
|
8785
|
-
|
|
8970
|
+
const dir = path21.dirname(SNAPSHOT_STACK_PATH);
|
|
8971
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
8972
|
+
fs19.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8786
8973
|
}
|
|
8787
8974
|
function extractFilePath(args) {
|
|
8788
8975
|
if (!args || typeof args !== "object") return null;
|
|
@@ -8802,12 +8989,12 @@ function buildArgsSummary(tool, args) {
|
|
|
8802
8989
|
return "";
|
|
8803
8990
|
}
|
|
8804
8991
|
function findProjectRoot(filePath) {
|
|
8805
|
-
let dir =
|
|
8992
|
+
let dir = path21.dirname(filePath);
|
|
8806
8993
|
while (true) {
|
|
8807
|
-
if (
|
|
8994
|
+
if (fs19.existsSync(path21.join(dir, ".git")) || fs19.existsSync(path21.join(dir, "package.json"))) {
|
|
8808
8995
|
return dir;
|
|
8809
8996
|
}
|
|
8810
|
-
const parent =
|
|
8997
|
+
const parent = path21.dirname(dir);
|
|
8811
8998
|
if (parent === dir) return process.cwd();
|
|
8812
8999
|
dir = parent;
|
|
8813
9000
|
}
|
|
@@ -8815,7 +9002,7 @@ function findProjectRoot(filePath) {
|
|
|
8815
9002
|
function normalizeCwdForHash(cwd) {
|
|
8816
9003
|
let normalized;
|
|
8817
9004
|
try {
|
|
8818
|
-
normalized =
|
|
9005
|
+
normalized = fs19.realpathSync(cwd);
|
|
8819
9006
|
} catch {
|
|
8820
9007
|
normalized = cwd;
|
|
8821
9008
|
}
|
|
@@ -8825,16 +9012,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
8825
9012
|
}
|
|
8826
9013
|
function getShadowRepoDir(cwd) {
|
|
8827
9014
|
const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
8828
|
-
return
|
|
9015
|
+
return path21.join(os15.homedir(), ".node9", "snapshots", hash);
|
|
8829
9016
|
}
|
|
8830
9017
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8831
9018
|
try {
|
|
8832
9019
|
const cutoff = Date.now() - 6e4;
|
|
8833
|
-
for (const f of
|
|
9020
|
+
for (const f of fs19.readdirSync(shadowDir)) {
|
|
8834
9021
|
if (f.startsWith("index_")) {
|
|
8835
|
-
const fp =
|
|
9022
|
+
const fp = path21.join(shadowDir, f);
|
|
8836
9023
|
try {
|
|
8837
|
-
if (
|
|
9024
|
+
if (fs19.statSync(fp).mtimeMs < cutoff) fs19.unlinkSync(fp);
|
|
8838
9025
|
} catch {
|
|
8839
9026
|
}
|
|
8840
9027
|
}
|
|
@@ -8846,7 +9033,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8846
9033
|
const hardcoded = [".git", ".node9"];
|
|
8847
9034
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8848
9035
|
try {
|
|
8849
|
-
|
|
9036
|
+
fs19.writeFileSync(path21.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8850
9037
|
} catch {
|
|
8851
9038
|
}
|
|
8852
9039
|
}
|
|
@@ -8859,25 +9046,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8859
9046
|
timeout: 3e3
|
|
8860
9047
|
});
|
|
8861
9048
|
if (check.status === 0) {
|
|
8862
|
-
const ptPath =
|
|
9049
|
+
const ptPath = path21.join(shadowDir, "project-path.txt");
|
|
8863
9050
|
try {
|
|
8864
|
-
const stored =
|
|
9051
|
+
const stored = fs19.readFileSync(ptPath, "utf8").trim();
|
|
8865
9052
|
if (stored === normalizedCwd) return true;
|
|
8866
9053
|
if (process.env.NODE9_DEBUG === "1")
|
|
8867
9054
|
console.error(
|
|
8868
9055
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
8869
9056
|
);
|
|
8870
|
-
|
|
9057
|
+
fs19.rmSync(shadowDir, { recursive: true, force: true });
|
|
8871
9058
|
} catch {
|
|
8872
9059
|
try {
|
|
8873
|
-
|
|
9060
|
+
fs19.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
8874
9061
|
} catch {
|
|
8875
9062
|
}
|
|
8876
9063
|
return true;
|
|
8877
9064
|
}
|
|
8878
9065
|
}
|
|
8879
9066
|
try {
|
|
8880
|
-
|
|
9067
|
+
fs19.mkdirSync(shadowDir, { recursive: true });
|
|
8881
9068
|
} catch {
|
|
8882
9069
|
}
|
|
8883
9070
|
const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -8886,7 +9073,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8886
9073
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8887
9074
|
return false;
|
|
8888
9075
|
}
|
|
8889
|
-
const configFile =
|
|
9076
|
+
const configFile = path21.join(shadowDir, "config");
|
|
8890
9077
|
spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8891
9078
|
timeout: 3e3
|
|
8892
9079
|
});
|
|
@@ -8894,7 +9081,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8894
9081
|
timeout: 3e3
|
|
8895
9082
|
});
|
|
8896
9083
|
try {
|
|
8897
|
-
|
|
9084
|
+
fs19.writeFileSync(path21.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8898
9085
|
} catch {
|
|
8899
9086
|
}
|
|
8900
9087
|
return true;
|
|
@@ -8914,12 +9101,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8914
9101
|
let indexFile = null;
|
|
8915
9102
|
try {
|
|
8916
9103
|
const rawFilePath = extractFilePath(args);
|
|
8917
|
-
const absFilePath = rawFilePath &&
|
|
9104
|
+
const absFilePath = rawFilePath && path21.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8918
9105
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8919
9106
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8920
9107
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8921
9108
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8922
|
-
indexFile =
|
|
9109
|
+
indexFile = path21.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8923
9110
|
const shadowEnv = {
|
|
8924
9111
|
...process.env,
|
|
8925
9112
|
GIT_DIR: shadowDir,
|
|
@@ -8991,7 +9178,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8991
9178
|
writeStack(stack);
|
|
8992
9179
|
const entry = stack[stack.length - 1];
|
|
8993
9180
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8994
|
-
|
|
9181
|
+
fs19.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8995
9182
|
if (shouldGc) {
|
|
8996
9183
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8997
9184
|
}
|
|
@@ -9002,7 +9189,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9002
9189
|
} finally {
|
|
9003
9190
|
if (indexFile) {
|
|
9004
9191
|
try {
|
|
9005
|
-
|
|
9192
|
+
fs19.unlinkSync(indexFile);
|
|
9006
9193
|
} catch {
|
|
9007
9194
|
}
|
|
9008
9195
|
}
|
|
@@ -9078,9 +9265,9 @@ function applyUndo(hash, cwd) {
|
|
|
9078
9265
|
timeout: GIT_TIMEOUT
|
|
9079
9266
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
9080
9267
|
for (const file of [...tracked, ...untracked]) {
|
|
9081
|
-
const fullPath =
|
|
9082
|
-
if (!snapshotFiles.has(file) &&
|
|
9083
|
-
|
|
9268
|
+
const fullPath = path21.join(dir, file);
|
|
9269
|
+
if (!snapshotFiles.has(file) && fs19.existsSync(fullPath)) {
|
|
9270
|
+
fs19.unlinkSync(fullPath);
|
|
9084
9271
|
}
|
|
9085
9272
|
}
|
|
9086
9273
|
return true;
|
|
@@ -9104,9 +9291,9 @@ function registerCheckCommand(program2) {
|
|
|
9104
9291
|
} catch (err2) {
|
|
9105
9292
|
const tempConfig = getConfig();
|
|
9106
9293
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9107
|
-
const logPath =
|
|
9294
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9108
9295
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9109
|
-
|
|
9296
|
+
fs20.appendFileSync(
|
|
9110
9297
|
logPath,
|
|
9111
9298
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9112
9299
|
RAW: ${raw}
|
|
@@ -9119,10 +9306,10 @@ RAW: ${raw}
|
|
|
9119
9306
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9120
9307
|
try {
|
|
9121
9308
|
const scriptPath = process.argv[1];
|
|
9122
|
-
if (typeof scriptPath !== "string" || !
|
|
9309
|
+
if (typeof scriptPath !== "string" || !path22.isAbsolute(scriptPath))
|
|
9123
9310
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9124
|
-
const resolvedScript =
|
|
9125
|
-
const expectedCli =
|
|
9311
|
+
const resolvedScript = fs20.realpathSync(scriptPath);
|
|
9312
|
+
const expectedCli = fs20.realpathSync(path22.resolve(__dirname, "../../cli.js"));
|
|
9126
9313
|
if (resolvedScript !== expectedCli)
|
|
9127
9314
|
throw new Error(
|
|
9128
9315
|
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
@@ -9148,10 +9335,10 @@ RAW: ${raw}
|
|
|
9148
9335
|
}
|
|
9149
9336
|
}
|
|
9150
9337
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9151
|
-
const logPath =
|
|
9152
|
-
if (!
|
|
9153
|
-
|
|
9154
|
-
|
|
9338
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9339
|
+
if (!fs20.existsSync(path22.dirname(logPath)))
|
|
9340
|
+
fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
|
|
9341
|
+
fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
9155
9342
|
`);
|
|
9156
9343
|
}
|
|
9157
9344
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9164,8 +9351,8 @@ RAW: ${raw}
|
|
|
9164
9351
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9165
9352
|
let ttyFd = null;
|
|
9166
9353
|
try {
|
|
9167
|
-
ttyFd =
|
|
9168
|
-
const writeTty = (line) =>
|
|
9354
|
+
ttyFd = fs20.openSync("/dev/tty", "w");
|
|
9355
|
+
const writeTty = (line) => fs20.writeSync(ttyFd, line + "\n");
|
|
9169
9356
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9170
9357
|
writeTty(chalk5.bgRed.white.bold(`
|
|
9171
9358
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9184,7 +9371,7 @@ RAW: ${raw}
|
|
|
9184
9371
|
} finally {
|
|
9185
9372
|
if (ttyFd !== null)
|
|
9186
9373
|
try {
|
|
9187
|
-
|
|
9374
|
+
fs20.closeSync(ttyFd);
|
|
9188
9375
|
} catch {
|
|
9189
9376
|
}
|
|
9190
9377
|
}
|
|
@@ -9216,7 +9403,7 @@ RAW: ${raw}
|
|
|
9216
9403
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9217
9404
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9218
9405
|
}
|
|
9219
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
9406
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9220
9407
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9221
9408
|
cwd: safeCwdForAuth
|
|
9222
9409
|
});
|
|
@@ -9228,12 +9415,12 @@ RAW: ${raw}
|
|
|
9228
9415
|
}
|
|
9229
9416
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9230
9417
|
try {
|
|
9231
|
-
const tty =
|
|
9232
|
-
|
|
9418
|
+
const tty = fs20.openSync("/dev/tty", "w");
|
|
9419
|
+
fs20.writeSync(
|
|
9233
9420
|
tty,
|
|
9234
9421
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9235
9422
|
);
|
|
9236
|
-
|
|
9423
|
+
fs20.closeSync(tty);
|
|
9237
9424
|
} catch {
|
|
9238
9425
|
}
|
|
9239
9426
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9260,9 +9447,9 @@ RAW: ${raw}
|
|
|
9260
9447
|
});
|
|
9261
9448
|
} catch (err2) {
|
|
9262
9449
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9263
|
-
const logPath =
|
|
9450
|
+
const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
|
|
9264
9451
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9265
|
-
|
|
9452
|
+
fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9266
9453
|
`);
|
|
9267
9454
|
}
|
|
9268
9455
|
process.exit(0);
|
|
@@ -9299,9 +9486,9 @@ RAW: ${raw}
|
|
|
9299
9486
|
init_audit();
|
|
9300
9487
|
init_config();
|
|
9301
9488
|
init_policy();
|
|
9302
|
-
import
|
|
9303
|
-
import
|
|
9304
|
-
import
|
|
9489
|
+
import fs21 from "fs";
|
|
9490
|
+
import path23 from "path";
|
|
9491
|
+
import os17 from "os";
|
|
9305
9492
|
init_daemon();
|
|
9306
9493
|
|
|
9307
9494
|
// src/utils/cp-mv-parser.ts
|
|
@@ -9374,10 +9561,10 @@ function registerLogCommand(program2) {
|
|
|
9374
9561
|
decision: "allowed",
|
|
9375
9562
|
source: "post-hook"
|
|
9376
9563
|
};
|
|
9377
|
-
const logPath =
|
|
9378
|
-
if (!
|
|
9379
|
-
|
|
9380
|
-
|
|
9564
|
+
const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
|
|
9565
|
+
if (!fs21.existsSync(path23.dirname(logPath)))
|
|
9566
|
+
fs21.mkdirSync(path23.dirname(logPath), { recursive: true });
|
|
9567
|
+
fs21.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
9381
9568
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9382
9569
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9383
9570
|
if (command) {
|
|
@@ -9410,7 +9597,7 @@ function registerLogCommand(program2) {
|
|
|
9410
9597
|
}
|
|
9411
9598
|
}
|
|
9412
9599
|
}
|
|
9413
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
9600
|
+
const safeCwd = typeof payload.cwd === "string" && path23.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9414
9601
|
const config = getConfig(safeCwd);
|
|
9415
9602
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9416
9603
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9419,9 +9606,9 @@ function registerLogCommand(program2) {
|
|
|
9419
9606
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9420
9607
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9421
9608
|
`);
|
|
9422
|
-
const debugPath =
|
|
9609
|
+
const debugPath = path23.join(os17.homedir(), ".node9", "hook-debug.log");
|
|
9423
9610
|
try {
|
|
9424
|
-
|
|
9611
|
+
fs21.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9425
9612
|
`);
|
|
9426
9613
|
} catch {
|
|
9427
9614
|
}
|
|
@@ -9822,13 +10009,13 @@ function registerConfigShowCommand(program2) {
|
|
|
9822
10009
|
// src/cli/commands/doctor.ts
|
|
9823
10010
|
init_daemon();
|
|
9824
10011
|
import chalk7 from "chalk";
|
|
9825
|
-
import
|
|
9826
|
-
import
|
|
9827
|
-
import
|
|
10012
|
+
import fs22 from "fs";
|
|
10013
|
+
import path24 from "path";
|
|
10014
|
+
import os18 from "os";
|
|
9828
10015
|
import { execSync as execSync2 } from "child_process";
|
|
9829
10016
|
function registerDoctorCommand(program2, version2) {
|
|
9830
10017
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
9831
|
-
const homeDir2 =
|
|
10018
|
+
const homeDir2 = os18.homedir();
|
|
9832
10019
|
let failures = 0;
|
|
9833
10020
|
function pass(msg) {
|
|
9834
10021
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -9877,10 +10064,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9877
10064
|
);
|
|
9878
10065
|
}
|
|
9879
10066
|
section("Configuration");
|
|
9880
|
-
const globalConfigPath =
|
|
9881
|
-
if (
|
|
10067
|
+
const globalConfigPath = path24.join(homeDir2, ".node9", "config.json");
|
|
10068
|
+
if (fs22.existsSync(globalConfigPath)) {
|
|
9882
10069
|
try {
|
|
9883
|
-
JSON.parse(
|
|
10070
|
+
JSON.parse(fs22.readFileSync(globalConfigPath, "utf-8"));
|
|
9884
10071
|
pass("~/.node9/config.json found and valid");
|
|
9885
10072
|
} catch {
|
|
9886
10073
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -9888,10 +10075,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9888
10075
|
} else {
|
|
9889
10076
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9890
10077
|
}
|
|
9891
|
-
const projectConfigPath =
|
|
9892
|
-
if (
|
|
10078
|
+
const projectConfigPath = path24.join(process.cwd(), "node9.config.json");
|
|
10079
|
+
if (fs22.existsSync(projectConfigPath)) {
|
|
9893
10080
|
try {
|
|
9894
|
-
JSON.parse(
|
|
10081
|
+
JSON.parse(fs22.readFileSync(projectConfigPath, "utf-8"));
|
|
9895
10082
|
pass("node9.config.json found and valid (project)");
|
|
9896
10083
|
} catch {
|
|
9897
10084
|
fail(
|
|
@@ -9900,8 +10087,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9900
10087
|
);
|
|
9901
10088
|
}
|
|
9902
10089
|
}
|
|
9903
|
-
const credsPath =
|
|
9904
|
-
if (
|
|
10090
|
+
const credsPath = path24.join(homeDir2, ".node9", "credentials.json");
|
|
10091
|
+
if (fs22.existsSync(credsPath)) {
|
|
9905
10092
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9906
10093
|
} else {
|
|
9907
10094
|
warn(
|
|
@@ -9910,10 +10097,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9910
10097
|
);
|
|
9911
10098
|
}
|
|
9912
10099
|
section("Agent Hooks");
|
|
9913
|
-
const claudeSettingsPath =
|
|
9914
|
-
if (
|
|
10100
|
+
const claudeSettingsPath = path24.join(homeDir2, ".claude", "settings.json");
|
|
10101
|
+
if (fs22.existsSync(claudeSettingsPath)) {
|
|
9915
10102
|
try {
|
|
9916
|
-
const cs = JSON.parse(
|
|
10103
|
+
const cs = JSON.parse(fs22.readFileSync(claudeSettingsPath, "utf-8"));
|
|
9917
10104
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
9918
10105
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9919
10106
|
);
|
|
@@ -9929,10 +10116,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9929
10116
|
} else {
|
|
9930
10117
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9931
10118
|
}
|
|
9932
|
-
const geminiSettingsPath =
|
|
9933
|
-
if (
|
|
10119
|
+
const geminiSettingsPath = path24.join(homeDir2, ".gemini", "settings.json");
|
|
10120
|
+
if (fs22.existsSync(geminiSettingsPath)) {
|
|
9934
10121
|
try {
|
|
9935
|
-
const gs = JSON.parse(
|
|
10122
|
+
const gs = JSON.parse(fs22.readFileSync(geminiSettingsPath, "utf-8"));
|
|
9936
10123
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
9937
10124
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9938
10125
|
);
|
|
@@ -9948,10 +10135,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9948
10135
|
} else {
|
|
9949
10136
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9950
10137
|
}
|
|
9951
|
-
const cursorHooksPath =
|
|
9952
|
-
if (
|
|
10138
|
+
const cursorHooksPath = path24.join(homeDir2, ".cursor", "hooks.json");
|
|
10139
|
+
if (fs22.existsSync(cursorHooksPath)) {
|
|
9953
10140
|
try {
|
|
9954
|
-
const cur = JSON.parse(
|
|
10141
|
+
const cur = JSON.parse(fs22.readFileSync(cursorHooksPath, "utf-8"));
|
|
9955
10142
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
9956
10143
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
9957
10144
|
);
|
|
@@ -9989,9 +10176,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9989
10176
|
|
|
9990
10177
|
// src/cli/commands/audit.ts
|
|
9991
10178
|
import chalk8 from "chalk";
|
|
9992
|
-
import
|
|
9993
|
-
import
|
|
9994
|
-
import
|
|
10179
|
+
import fs23 from "fs";
|
|
10180
|
+
import path25 from "path";
|
|
10181
|
+
import os19 from "os";
|
|
9995
10182
|
function formatRelativeTime(timestamp) {
|
|
9996
10183
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
9997
10184
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -10004,14 +10191,14 @@ function formatRelativeTime(timestamp) {
|
|
|
10004
10191
|
}
|
|
10005
10192
|
function registerAuditCommand(program2) {
|
|
10006
10193
|
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 (!
|
|
10194
|
+
const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
|
|
10195
|
+
if (!fs23.existsSync(logPath)) {
|
|
10009
10196
|
console.log(
|
|
10010
10197
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
10011
10198
|
);
|
|
10012
10199
|
return;
|
|
10013
10200
|
}
|
|
10014
|
-
const raw =
|
|
10201
|
+
const raw = fs23.readFileSync(logPath, "utf-8");
|
|
10015
10202
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
10016
10203
|
let entries = lines.flatMap((line) => {
|
|
10017
10204
|
try {
|
|
@@ -10065,9 +10252,9 @@ function registerAuditCommand(program2) {
|
|
|
10065
10252
|
|
|
10066
10253
|
// src/cli/commands/report.ts
|
|
10067
10254
|
import chalk9 from "chalk";
|
|
10068
|
-
import
|
|
10069
|
-
import
|
|
10070
|
-
import
|
|
10255
|
+
import fs24 from "fs";
|
|
10256
|
+
import path26 from "path";
|
|
10257
|
+
import os20 from "os";
|
|
10071
10258
|
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
10259
|
function buildTestTimestamps(allEntries) {
|
|
10073
10260
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -10114,8 +10301,8 @@ function getDateRange(period) {
|
|
|
10114
10301
|
}
|
|
10115
10302
|
}
|
|
10116
10303
|
function parseAuditLog(logPath) {
|
|
10117
|
-
if (!
|
|
10118
|
-
const raw =
|
|
10304
|
+
if (!fs24.existsSync(logPath)) return [];
|
|
10305
|
+
const raw = fs24.readFileSync(logPath, "utf-8");
|
|
10119
10306
|
return raw.split("\n").flatMap((line) => {
|
|
10120
10307
|
if (!line.trim()) return [];
|
|
10121
10308
|
try {
|
|
@@ -10177,29 +10364,39 @@ function claudeModelPrice(model) {
|
|
|
10177
10364
|
return null;
|
|
10178
10365
|
}
|
|
10179
10366
|
function loadClaudeCost(start, end) {
|
|
10180
|
-
const
|
|
10181
|
-
|
|
10367
|
+
const empty = {
|
|
10368
|
+
total: 0,
|
|
10369
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
10370
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
10371
|
+
inputTokens: 0,
|
|
10372
|
+
cacheReadTokens: 0
|
|
10373
|
+
};
|
|
10374
|
+
const projectsDir = path26.join(os20.homedir(), ".claude", "projects");
|
|
10375
|
+
if (!fs24.existsSync(projectsDir)) return empty;
|
|
10182
10376
|
let dirs;
|
|
10183
10377
|
try {
|
|
10184
|
-
dirs =
|
|
10378
|
+
dirs = fs24.readdirSync(projectsDir);
|
|
10185
10379
|
} catch {
|
|
10186
|
-
return
|
|
10380
|
+
return empty;
|
|
10187
10381
|
}
|
|
10188
10382
|
let total = 0;
|
|
10383
|
+
let inputTokens = 0;
|
|
10384
|
+
let cacheReadTokens = 0;
|
|
10189
10385
|
const byDay = /* @__PURE__ */ new Map();
|
|
10386
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
10190
10387
|
for (const proj of dirs) {
|
|
10191
|
-
const projPath =
|
|
10388
|
+
const projPath = path26.join(projectsDir, proj);
|
|
10192
10389
|
let files;
|
|
10193
10390
|
try {
|
|
10194
|
-
const stat =
|
|
10391
|
+
const stat = fs24.statSync(projPath);
|
|
10195
10392
|
if (!stat.isDirectory()) continue;
|
|
10196
|
-
files =
|
|
10393
|
+
files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10197
10394
|
} catch {
|
|
10198
10395
|
continue;
|
|
10199
10396
|
}
|
|
10200
10397
|
for (const file of files) {
|
|
10201
10398
|
try {
|
|
10202
|
-
const raw =
|
|
10399
|
+
const raw = fs24.readFileSync(path26.join(projPath, file), "utf-8");
|
|
10203
10400
|
for (const line of raw.split("\n")) {
|
|
10204
10401
|
if (!line.trim()) continue;
|
|
10205
10402
|
let entry;
|
|
@@ -10217,24 +10414,32 @@ function loadClaudeCost(start, end) {
|
|
|
10217
10414
|
if (!usage || !model) continue;
|
|
10218
10415
|
const p = claudeModelPrice(model);
|
|
10219
10416
|
if (!p) continue;
|
|
10220
|
-
const
|
|
10417
|
+
const inp = usage.input_tokens ?? 0;
|
|
10418
|
+
const out = usage.output_tokens ?? 0;
|
|
10419
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
10420
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
10421
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
10221
10422
|
total += cost;
|
|
10423
|
+
inputTokens += inp;
|
|
10424
|
+
cacheReadTokens += cr;
|
|
10222
10425
|
const dateKey = entry.timestamp.slice(0, 10);
|
|
10223
10426
|
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
10427
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
10428
|
+
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
10224
10429
|
}
|
|
10225
10430
|
} catch {
|
|
10226
10431
|
continue;
|
|
10227
10432
|
}
|
|
10228
10433
|
}
|
|
10229
10434
|
}
|
|
10230
|
-
return { total, byDay };
|
|
10435
|
+
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
10231
10436
|
}
|
|
10232
10437
|
function registerReportCommand(program2) {
|
|
10233
10438
|
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
10439
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
10235
10440
|
options.period
|
|
10236
10441
|
) ? options.period : "7d";
|
|
10237
|
-
const logPath =
|
|
10442
|
+
const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
|
|
10238
10443
|
const allEntries = parseAuditLog(logPath);
|
|
10239
10444
|
if (allEntries.length === 0) {
|
|
10240
10445
|
console.log(
|
|
@@ -10243,7 +10448,13 @@ function registerReportCommand(program2) {
|
|
|
10243
10448
|
return;
|
|
10244
10449
|
}
|
|
10245
10450
|
const { start, end } = getDateRange(period);
|
|
10246
|
-
const {
|
|
10451
|
+
const {
|
|
10452
|
+
total: costUSD,
|
|
10453
|
+
byDay: costByDay,
|
|
10454
|
+
byModel: costByModel,
|
|
10455
|
+
inputTokens: costInputTokens,
|
|
10456
|
+
cacheReadTokens: costCacheRead
|
|
10457
|
+
} = loadClaudeCost(start, end);
|
|
10247
10458
|
const periodMs = end.getTime() - start.getTime();
|
|
10248
10459
|
const priorEnd = new Date(start.getTime() - 1);
|
|
10249
10460
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
@@ -10345,7 +10556,6 @@ function registerReportCommand(program2) {
|
|
|
10345
10556
|
const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
|
|
10346
10557
|
const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
|
|
10347
10558
|
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
10559
|
const currentRate = total > 0 ? blocked / total : 0;
|
|
10350
10560
|
const trendLabel = (() => {
|
|
10351
10561
|
if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
|
|
@@ -10358,7 +10568,7 @@ function registerReportCommand(program2) {
|
|
|
10358
10568
|
const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
|
|
10359
10569
|
const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
|
|
10360
10570
|
console.log(
|
|
10361
|
-
" " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
|
|
10571
|
+
" " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
|
|
10362
10572
|
);
|
|
10363
10573
|
console.log(" " + ratioLabel + " " + testLabel);
|
|
10364
10574
|
console.log("");
|
|
@@ -10443,6 +10653,30 @@ function registerReportCommand(program2) {
|
|
|
10443
10653
|
);
|
|
10444
10654
|
}
|
|
10445
10655
|
}
|
|
10656
|
+
if (costUSD > 0) {
|
|
10657
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
10658
|
+
const avgPerDay = costUSD / periodDays;
|
|
10659
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
10660
|
+
const costHeaderRight = [
|
|
10661
|
+
chalk9.yellow(fmtCost(costUSD)),
|
|
10662
|
+
chalk9.dim(`avg ${fmtCost(avgPerDay)}/day`),
|
|
10663
|
+
cacheHitPct > 0 ? chalk9.dim(`${cacheHitPct}% cache hit`) : null
|
|
10664
|
+
].filter(Boolean).join(chalk9.dim(" \xB7 "));
|
|
10665
|
+
console.log("");
|
|
10666
|
+
console.log(" " + chalk9.bold("Cost") + " " + costHeaderRight);
|
|
10667
|
+
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10668
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
10669
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
10670
|
+
const MODEL_LABEL = 22;
|
|
10671
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
10672
|
+
for (const [model, cost] of modelList) {
|
|
10673
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
10674
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
10675
|
+
console.log(
|
|
10676
|
+
" " + chalk9.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk9.yellow(fmtCost(cost))
|
|
10677
|
+
);
|
|
10678
|
+
}
|
|
10679
|
+
}
|
|
10446
10680
|
console.log("");
|
|
10447
10681
|
console.log(
|
|
10448
10682
|
" " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
@@ -10519,12 +10753,12 @@ function registerDaemonCommand(program2) {
|
|
|
10519
10753
|
init_core();
|
|
10520
10754
|
init_daemon();
|
|
10521
10755
|
import chalk11 from "chalk";
|
|
10522
|
-
import
|
|
10523
|
-
import
|
|
10524
|
-
import
|
|
10756
|
+
import fs25 from "fs";
|
|
10757
|
+
import path27 from "path";
|
|
10758
|
+
import os21 from "os";
|
|
10525
10759
|
function readJson2(filePath) {
|
|
10526
10760
|
try {
|
|
10527
|
-
if (
|
|
10761
|
+
if (fs25.existsSync(filePath)) return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
10528
10762
|
} catch {
|
|
10529
10763
|
}
|
|
10530
10764
|
return null;
|
|
@@ -10589,28 +10823,28 @@ function registerStatusCommand(program2) {
|
|
|
10589
10823
|
console.log("");
|
|
10590
10824
|
const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
|
|
10591
10825
|
console.log(` Mode: ${modeLabel}`);
|
|
10592
|
-
const projectConfig =
|
|
10593
|
-
const globalConfig =
|
|
10826
|
+
const projectConfig = path27.join(process.cwd(), "node9.config.json");
|
|
10827
|
+
const globalConfig = path27.join(os21.homedir(), ".node9", "config.json");
|
|
10594
10828
|
console.log(
|
|
10595
|
-
` Local: ${
|
|
10829
|
+
` Local: ${fs25.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
|
|
10596
10830
|
);
|
|
10597
10831
|
console.log(
|
|
10598
|
-
` Global: ${
|
|
10832
|
+
` Global: ${fs25.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
|
|
10599
10833
|
);
|
|
10600
10834
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10601
10835
|
console.log(
|
|
10602
10836
|
` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10603
10837
|
);
|
|
10604
10838
|
}
|
|
10605
|
-
const homeDir2 =
|
|
10839
|
+
const homeDir2 = os21.homedir();
|
|
10606
10840
|
const claudeSettings = readJson2(
|
|
10607
|
-
|
|
10841
|
+
path27.join(homeDir2, ".claude", "settings.json")
|
|
10608
10842
|
);
|
|
10609
|
-
const claudeConfig = readJson2(
|
|
10843
|
+
const claudeConfig = readJson2(path27.join(homeDir2, ".claude.json"));
|
|
10610
10844
|
const geminiSettings = readJson2(
|
|
10611
|
-
|
|
10845
|
+
path27.join(homeDir2, ".gemini", "settings.json")
|
|
10612
10846
|
);
|
|
10613
|
-
const cursorConfig = readJson2(
|
|
10847
|
+
const cursorConfig = readJson2(path27.join(homeDir2, ".cursor", "mcp.json"));
|
|
10614
10848
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10615
10849
|
if (agentFound) {
|
|
10616
10850
|
console.log("");
|
|
@@ -10670,9 +10904,9 @@ function registerStatusCommand(program2) {
|
|
|
10670
10904
|
// src/cli/commands/init.ts
|
|
10671
10905
|
init_core();
|
|
10672
10906
|
import chalk12 from "chalk";
|
|
10673
|
-
import
|
|
10674
|
-
import
|
|
10675
|
-
import
|
|
10907
|
+
import fs26 from "fs";
|
|
10908
|
+
import path28 from "path";
|
|
10909
|
+
import os22 from "os";
|
|
10676
10910
|
import https2 from "https";
|
|
10677
10911
|
init_shields();
|
|
10678
10912
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
|
|
@@ -10731,15 +10965,15 @@ function registerInitCommand(program2) {
|
|
|
10731
10965
|
}
|
|
10732
10966
|
console.log("");
|
|
10733
10967
|
}
|
|
10734
|
-
const configPath =
|
|
10735
|
-
if (
|
|
10968
|
+
const configPath = path28.join(os22.homedir(), ".node9", "config.json");
|
|
10969
|
+
if (fs26.existsSync(configPath) && !options.force) {
|
|
10736
10970
|
try {
|
|
10737
|
-
const existing = JSON.parse(
|
|
10971
|
+
const existing = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
|
|
10738
10972
|
const settings = existing.settings ?? {};
|
|
10739
10973
|
if (settings.mode !== chosenMode) {
|
|
10740
10974
|
settings.mode = chosenMode;
|
|
10741
10975
|
existing.settings = settings;
|
|
10742
|
-
|
|
10976
|
+
fs26.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
10743
10977
|
console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
10744
10978
|
} else {
|
|
10745
10979
|
console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -10752,9 +10986,9 @@ function registerInitCommand(program2) {
|
|
|
10752
10986
|
...DEFAULT_CONFIG,
|
|
10753
10987
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
10754
10988
|
};
|
|
10755
|
-
const dir =
|
|
10756
|
-
if (!
|
|
10757
|
-
|
|
10989
|
+
const dir = path28.dirname(configPath);
|
|
10990
|
+
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
10991
|
+
fs26.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
10758
10992
|
console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
|
|
10759
10993
|
console.log(chalk12.gray(` Mode: ${chosenMode}`));
|
|
10760
10994
|
}
|
|
@@ -10808,7 +11042,7 @@ function registerInitCommand(program2) {
|
|
|
10808
11042
|
}
|
|
10809
11043
|
|
|
10810
11044
|
// src/cli/commands/undo.ts
|
|
10811
|
-
import
|
|
11045
|
+
import path29 from "path";
|
|
10812
11046
|
import chalk14 from "chalk";
|
|
10813
11047
|
|
|
10814
11048
|
// src/tui/undo-navigator.ts
|
|
@@ -10967,7 +11201,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
10967
11201
|
let dir = startDir;
|
|
10968
11202
|
while (true) {
|
|
10969
11203
|
if (cwds.has(dir)) return dir;
|
|
10970
|
-
const parent =
|
|
11204
|
+
const parent = path29.dirname(dir);
|
|
10971
11205
|
if (parent === dir) return null;
|
|
10972
11206
|
dir = parent;
|
|
10973
11207
|
}
|
|
@@ -11163,12 +11397,12 @@ import { execa as execa2 } from "execa";
|
|
|
11163
11397
|
init_provenance();
|
|
11164
11398
|
|
|
11165
11399
|
// src/mcp-pin.ts
|
|
11166
|
-
import
|
|
11167
|
-
import
|
|
11168
|
-
import
|
|
11400
|
+
import fs27 from "fs";
|
|
11401
|
+
import path30 from "path";
|
|
11402
|
+
import os23 from "os";
|
|
11169
11403
|
import crypto4 from "crypto";
|
|
11170
11404
|
function getPinsFilePath() {
|
|
11171
|
-
return
|
|
11405
|
+
return path30.join(os23.homedir(), ".node9", "mcp-pins.json");
|
|
11172
11406
|
}
|
|
11173
11407
|
function hashToolDefinitions(tools) {
|
|
11174
11408
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -11185,7 +11419,7 @@ function getServerKey(upstreamCommand) {
|
|
|
11185
11419
|
function readMcpPinsSafe() {
|
|
11186
11420
|
const filePath = getPinsFilePath();
|
|
11187
11421
|
try {
|
|
11188
|
-
const raw =
|
|
11422
|
+
const raw = fs27.readFileSync(filePath, "utf-8");
|
|
11189
11423
|
if (!raw.trim()) {
|
|
11190
11424
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
11191
11425
|
}
|
|
@@ -11209,10 +11443,10 @@ function readMcpPins() {
|
|
|
11209
11443
|
}
|
|
11210
11444
|
function writeMcpPins(data) {
|
|
11211
11445
|
const filePath = getPinsFilePath();
|
|
11212
|
-
|
|
11446
|
+
fs27.mkdirSync(path30.dirname(filePath), { recursive: true });
|
|
11213
11447
|
const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
|
|
11214
|
-
|
|
11215
|
-
|
|
11448
|
+
fs27.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
11449
|
+
fs27.renameSync(tmp, filePath);
|
|
11216
11450
|
}
|
|
11217
11451
|
function checkPin(serverKey, currentHash) {
|
|
11218
11452
|
const result = readMcpPinsSafe();
|
|
@@ -11584,9 +11818,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11584
11818
|
|
|
11585
11819
|
// src/mcp-server/index.ts
|
|
11586
11820
|
import readline4 from "readline";
|
|
11587
|
-
import
|
|
11588
|
-
import
|
|
11589
|
-
import
|
|
11821
|
+
import fs28 from "fs";
|
|
11822
|
+
import os24 from "os";
|
|
11823
|
+
import path31 from "path";
|
|
11590
11824
|
init_core();
|
|
11591
11825
|
init_daemon();
|
|
11592
11826
|
init_shields();
|
|
@@ -11761,13 +11995,13 @@ function handleStatus() {
|
|
|
11761
11995
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
11762
11996
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
11763
11997
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
11764
|
-
const projectConfig =
|
|
11765
|
-
const globalConfig =
|
|
11998
|
+
const projectConfig = path31.join(process.cwd(), "node9.config.json");
|
|
11999
|
+
const globalConfig = path31.join(os24.homedir(), ".node9", "config.json");
|
|
11766
12000
|
lines.push(
|
|
11767
|
-
`Project config (node9.config.json): ${
|
|
12001
|
+
`Project config (node9.config.json): ${fs28.existsSync(projectConfig) ? "present" : "not found"}`
|
|
11768
12002
|
);
|
|
11769
12003
|
lines.push(
|
|
11770
|
-
`Global config (~/.node9/config.json): ${
|
|
12004
|
+
`Global config (~/.node9/config.json): ${fs28.existsSync(globalConfig) ? "present" : "not found"}`
|
|
11771
12005
|
);
|
|
11772
12006
|
return lines.join("\n");
|
|
11773
12007
|
}
|
|
@@ -11841,21 +12075,21 @@ function handleShieldDisable(args) {
|
|
|
11841
12075
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11842
12076
|
return `Shield "${name}" disabled.`;
|
|
11843
12077
|
}
|
|
11844
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
12078
|
+
var GLOBAL_CONFIG_PATH2 = path31.join(os24.homedir(), ".node9", "config.json");
|
|
11845
12079
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11846
12080
|
function readGlobalConfigRaw() {
|
|
11847
12081
|
try {
|
|
11848
|
-
if (
|
|
11849
|
-
return JSON.parse(
|
|
12082
|
+
if (fs28.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
12083
|
+
return JSON.parse(fs28.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
11850
12084
|
}
|
|
11851
12085
|
} catch {
|
|
11852
12086
|
}
|
|
11853
12087
|
return {};
|
|
11854
12088
|
}
|
|
11855
12089
|
function writeGlobalConfigRaw(data) {
|
|
11856
|
-
const dir =
|
|
11857
|
-
if (!
|
|
11858
|
-
|
|
12090
|
+
const dir = path31.dirname(GLOBAL_CONFIG_PATH2);
|
|
12091
|
+
if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
|
|
12092
|
+
fs28.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
11859
12093
|
}
|
|
11860
12094
|
function handleApproverList() {
|
|
11861
12095
|
const config = getConfig();
|
|
@@ -11898,9 +12132,9 @@ function handleApproverSet(args) {
|
|
|
11898
12132
|
}
|
|
11899
12133
|
function handleAuditGet(args) {
|
|
11900
12134
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11901
|
-
const auditPath =
|
|
11902
|
-
if (!
|
|
11903
|
-
const lines =
|
|
12135
|
+
const auditPath = path31.join(os24.homedir(), ".node9", "audit.log");
|
|
12136
|
+
if (!fs28.existsSync(auditPath)) return "No audit log found.";
|
|
12137
|
+
const lines = fs28.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
11904
12138
|
const recent = lines.slice(-limit);
|
|
11905
12139
|
const entries = recent.map((line) => {
|
|
11906
12140
|
try {
|
|
@@ -12220,20 +12454,20 @@ function registerMcpPinCommand(program2) {
|
|
|
12220
12454
|
|
|
12221
12455
|
// src/cli.ts
|
|
12222
12456
|
var { version } = JSON.parse(
|
|
12223
|
-
|
|
12457
|
+
fs31.readFileSync(path34.join(__dirname, "../package.json"), "utf-8")
|
|
12224
12458
|
);
|
|
12225
12459
|
var program = new Command();
|
|
12226
12460
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
12227
12461
|
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
12462
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
12229
|
-
const credPath =
|
|
12230
|
-
if (!
|
|
12231
|
-
|
|
12463
|
+
const credPath = path34.join(os27.homedir(), ".node9", "credentials.json");
|
|
12464
|
+
if (!fs31.existsSync(path34.dirname(credPath)))
|
|
12465
|
+
fs31.mkdirSync(path34.dirname(credPath), { recursive: true });
|
|
12232
12466
|
const profileName = options.profile || "default";
|
|
12233
12467
|
let existingCreds = {};
|
|
12234
12468
|
try {
|
|
12235
|
-
if (
|
|
12236
|
-
const raw = JSON.parse(
|
|
12469
|
+
if (fs31.existsSync(credPath)) {
|
|
12470
|
+
const raw = JSON.parse(fs31.readFileSync(credPath, "utf-8"));
|
|
12237
12471
|
if (raw.apiKey) {
|
|
12238
12472
|
existingCreds = {
|
|
12239
12473
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -12245,13 +12479,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12245
12479
|
} catch {
|
|
12246
12480
|
}
|
|
12247
12481
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
12248
|
-
|
|
12482
|
+
fs31.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
12249
12483
|
if (profileName === "default") {
|
|
12250
|
-
const configPath =
|
|
12484
|
+
const configPath = path34.join(os27.homedir(), ".node9", "config.json");
|
|
12251
12485
|
let config = {};
|
|
12252
12486
|
try {
|
|
12253
|
-
if (
|
|
12254
|
-
config = JSON.parse(
|
|
12487
|
+
if (fs31.existsSync(configPath))
|
|
12488
|
+
config = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
|
|
12255
12489
|
} catch {
|
|
12256
12490
|
}
|
|
12257
12491
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -12266,9 +12500,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
12266
12500
|
approvers.cloud = false;
|
|
12267
12501
|
}
|
|
12268
12502
|
s.approvers = approvers;
|
|
12269
|
-
if (!
|
|
12270
|
-
|
|
12271
|
-
|
|
12503
|
+
if (!fs31.existsSync(path34.dirname(configPath)))
|
|
12504
|
+
fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
|
|
12505
|
+
fs31.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
12272
12506
|
}
|
|
12273
12507
|
if (options.profile && profileName !== "default") {
|
|
12274
12508
|
console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -12362,15 +12596,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
12362
12596
|
}
|
|
12363
12597
|
}
|
|
12364
12598
|
if (options.purge) {
|
|
12365
|
-
const node9Dir =
|
|
12366
|
-
if (
|
|
12599
|
+
const node9Dir = path34.join(os27.homedir(), ".node9");
|
|
12600
|
+
if (fs31.existsSync(node9Dir)) {
|
|
12367
12601
|
const confirmed = await confirm2({
|
|
12368
12602
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
12369
12603
|
default: false
|
|
12370
12604
|
});
|
|
12371
12605
|
if (confirmed) {
|
|
12372
|
-
|
|
12373
|
-
if (
|
|
12606
|
+
fs31.rmSync(node9Dir, { recursive: true });
|
|
12607
|
+
if (fs31.existsSync(node9Dir)) {
|
|
12374
12608
|
console.error(
|
|
12375
12609
|
chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
12376
12610
|
);
|
|
@@ -12511,14 +12745,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12511
12745
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12512
12746
|
).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
12747
|
if (subcommand === "debug") {
|
|
12514
|
-
const flagFile =
|
|
12748
|
+
const flagFile = path34.join(os27.homedir(), ".node9", "hud-debug");
|
|
12515
12749
|
if (state === "on") {
|
|
12516
|
-
|
|
12517
|
-
|
|
12750
|
+
fs31.mkdirSync(path34.dirname(flagFile), { recursive: true });
|
|
12751
|
+
fs31.writeFileSync(flagFile, "");
|
|
12518
12752
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12519
12753
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12520
12754
|
} else if (state === "off") {
|
|
12521
|
-
if (
|
|
12755
|
+
if (fs31.existsSync(flagFile)) fs31.unlinkSync(flagFile);
|
|
12522
12756
|
console.log("HUD debug logging disabled.");
|
|
12523
12757
|
} else {
|
|
12524
12758
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12625,9 +12859,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
12625
12859
|
const isCheckHook = process.argv[2] === "check";
|
|
12626
12860
|
if (isCheckHook) {
|
|
12627
12861
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12628
|
-
const logPath =
|
|
12862
|
+
const logPath = path34.join(os27.homedir(), ".node9", "hook-debug.log");
|
|
12629
12863
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12630
|
-
|
|
12864
|
+
fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12631
12865
|
`);
|
|
12632
12866
|
}
|
|
12633
12867
|
process.exit(0);
|