@node9/proxy 1.5.5 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -8
- package/dist/cli.js +784 -171
- package/dist/cli.mjs +782 -169
- package/dist/index.js +2 -1
- package/dist/index.mjs +2 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -160,8 +160,8 @@ function sanitizeConfig(raw) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
163
|
+
const path31 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
164
|
+
return ` \u2022 ${path31}: ${issue.message}`;
|
|
165
165
|
});
|
|
166
166
|
return {
|
|
167
167
|
sanitized,
|
|
@@ -315,9 +315,9 @@ function readShieldsFile() {
|
|
|
315
315
|
(e) => typeof e === "string" && e.length > 0 && e in SHIELDS
|
|
316
316
|
) : [];
|
|
317
317
|
return { active, overrides: validateOverrides(parsed.overrides) };
|
|
318
|
-
} catch (
|
|
319
|
-
if (
|
|
320
|
-
process.stderr.write(`[node9] Warning: could not read shields state: ${String(
|
|
318
|
+
} catch (err2) {
|
|
319
|
+
if (err2.code !== "ENOENT") {
|
|
320
|
+
process.stderr.write(`[node9] Warning: could not read shields state: ${String(err2)}
|
|
321
321
|
`);
|
|
322
322
|
}
|
|
323
323
|
return { active: [] };
|
|
@@ -733,8 +733,8 @@ function tryLoadConfig(filePath) {
|
|
|
733
733
|
let raw;
|
|
734
734
|
try {
|
|
735
735
|
raw = JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
736
|
-
} catch (
|
|
737
|
-
const msg =
|
|
736
|
+
} catch (err2) {
|
|
737
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
738
738
|
process.stderr.write(
|
|
739
739
|
`
|
|
740
740
|
\u26A0\uFE0F Node9: Failed to parse ${filePath}
|
|
@@ -799,7 +799,7 @@ var init_config = __esm({
|
|
|
799
799
|
DEFAULT_CONFIG = {
|
|
800
800
|
version: "1.0",
|
|
801
801
|
settings: {
|
|
802
|
-
mode: "
|
|
802
|
+
mode: "standard",
|
|
803
803
|
autoStartDaemon: true,
|
|
804
804
|
enableUndo: true,
|
|
805
805
|
// 🔥 ALWAYS TRUE BY DEFAULT for the safety net
|
|
@@ -1064,10 +1064,10 @@ function getCompiledRegex(pattern, flags = "") {
|
|
|
1064
1064
|
regexCache.set(key, cached);
|
|
1065
1065
|
return cached;
|
|
1066
1066
|
}
|
|
1067
|
-
const
|
|
1068
|
-
if (
|
|
1067
|
+
const err2 = validateRegex(pattern);
|
|
1068
|
+
if (err2) {
|
|
1069
1069
|
if (process.env.NODE9_DEBUG === "1")
|
|
1070
|
-
console.error(`[Node9] Regex blocked: ${
|
|
1070
|
+
console.error(`[Node9] Regex blocked: ${err2} \u2014 pattern: "${pattern}"`);
|
|
1071
1071
|
return null;
|
|
1072
1072
|
}
|
|
1073
1073
|
try {
|
|
@@ -1101,8 +1101,8 @@ function scanFilePath(filePath, cwd = process.cwd()) {
|
|
|
1101
1101
|
try {
|
|
1102
1102
|
const absolute = import_path4.default.resolve(cwd, filePath);
|
|
1103
1103
|
resolved = import_fs4.default.realpathSync.native(absolute);
|
|
1104
|
-
} catch (
|
|
1105
|
-
const code =
|
|
1104
|
+
} catch (err2) {
|
|
1105
|
+
const code = err2.code;
|
|
1106
1106
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
1107
1107
|
resolved = import_path4.default.resolve(cwd, filePath);
|
|
1108
1108
|
} else {
|
|
@@ -1774,9 +1774,9 @@ function matchesPattern(text, patterns) {
|
|
|
1774
1774
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1775
1775
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1776
1776
|
}
|
|
1777
|
-
function getNestedValue(obj,
|
|
1777
|
+
function getNestedValue(obj, path31) {
|
|
1778
1778
|
if (!obj || typeof obj !== "object") return null;
|
|
1779
|
-
return
|
|
1779
|
+
return path31.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1780
1780
|
}
|
|
1781
1781
|
function shouldSnapshot(toolName, args, config) {
|
|
1782
1782
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2428,9 +2428,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
2428
2428
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > now);
|
|
2429
2429
|
trust.entries.push({ tool: toolName, expiry: now + durationMs });
|
|
2430
2430
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
2431
|
-
} catch (
|
|
2431
|
+
} catch (err2) {
|
|
2432
2432
|
if (process.env.NODE9_DEBUG === "1") {
|
|
2433
|
-
console.error("[Node9 Trust Error]:",
|
|
2433
|
+
console.error("[Node9 Trust Error]:", err2);
|
|
2434
2434
|
}
|
|
2435
2435
|
}
|
|
2436
2436
|
}
|
|
@@ -2627,13 +2627,13 @@ async function checkTaint(paths) {
|
|
|
2627
2627
|
signal: AbortSignal.timeout(2e3)
|
|
2628
2628
|
});
|
|
2629
2629
|
return await res.json();
|
|
2630
|
-
} catch (
|
|
2630
|
+
} catch (err2) {
|
|
2631
2631
|
try {
|
|
2632
2632
|
const { appendToLog: appendToLog2, HOOK_DEBUG_LOG: HOOK_DEBUG_LOG2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
2633
2633
|
appendToLog2(HOOK_DEBUG_LOG2, {
|
|
2634
2634
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2635
2635
|
event: "checkTaint-error",
|
|
2636
|
-
error: String(
|
|
2636
|
+
error: String(err2),
|
|
2637
2637
|
paths
|
|
2638
2638
|
});
|
|
2639
2639
|
} catch {
|
|
@@ -3128,10 +3128,10 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
3128
3128
|
`
|
|
3129
3129
|
);
|
|
3130
3130
|
}
|
|
3131
|
-
} catch (
|
|
3131
|
+
} catch (err2) {
|
|
3132
3132
|
import_fs10.default.appendFileSync(
|
|
3133
3133
|
HOOK_DEBUG_LOG,
|
|
3134
|
-
`[resolve-cloud] PATCH failed for ${requestId}: ${
|
|
3134
|
+
`[resolve-cloud] PATCH failed for ${requestId}: ${err2.message}
|
|
3135
3135
|
`
|
|
3136
3136
|
);
|
|
3137
3137
|
}
|
|
@@ -3198,6 +3198,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3198
3198
|
await notifyActivity({
|
|
3199
3199
|
id: actId,
|
|
3200
3200
|
tool: toolName,
|
|
3201
|
+
args,
|
|
3201
3202
|
ts: actTs,
|
|
3202
3203
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
3203
3204
|
label: result.blockedByLabel,
|
|
@@ -3501,10 +3502,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3501
3502
|
blockedBy: cloudResult.approved ? void 0 : "team-policy",
|
|
3502
3503
|
blockedByLabel: "Organization Policy (SaaS)"
|
|
3503
3504
|
};
|
|
3504
|
-
} catch (
|
|
3505
|
-
const error =
|
|
3506
|
-
if (error.name === "AbortError" || error.message?.includes("Aborted")) throw
|
|
3507
|
-
throw
|
|
3505
|
+
} catch (err2) {
|
|
3506
|
+
const error = err2;
|
|
3507
|
+
if (error.name === "AbortError" || error.message?.includes("Aborted")) throw err2;
|
|
3508
|
+
throw err2;
|
|
3508
3509
|
}
|
|
3509
3510
|
})()
|
|
3510
3511
|
);
|
|
@@ -3597,10 +3598,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3597
3598
|
}
|
|
3598
3599
|
};
|
|
3599
3600
|
for (const p of racePromises) {
|
|
3600
|
-
p.then(finish).catch((
|
|
3601
|
-
if (
|
|
3601
|
+
p.then(finish).catch((err2) => {
|
|
3602
|
+
if (err2.name === "AbortError" || err2.message?.includes("canceled") || err2.message?.includes("Aborted"))
|
|
3602
3603
|
return;
|
|
3603
|
-
if (
|
|
3604
|
+
if (err2.message === "Abandoned") {
|
|
3604
3605
|
finish({
|
|
3605
3606
|
approved: false,
|
|
3606
3607
|
reason: "Browser dashboard closed without making a decision.",
|
|
@@ -5476,6 +5477,7 @@ var init_session_counters = __esm({
|
|
|
5476
5477
|
_blocked = 0;
|
|
5477
5478
|
_dlpHits = 0;
|
|
5478
5479
|
_wouldBlock = 0;
|
|
5480
|
+
_estimatedCost = 0;
|
|
5479
5481
|
_lastRuleHit = null;
|
|
5480
5482
|
_lastBlockedTool = null;
|
|
5481
5483
|
incrementAllowed() {
|
|
@@ -5490,6 +5492,10 @@ var init_session_counters = __esm({
|
|
|
5490
5492
|
incrementWouldBlock() {
|
|
5491
5493
|
this._wouldBlock++;
|
|
5492
5494
|
}
|
|
5495
|
+
addCost(amount) {
|
|
5496
|
+
if (!isFinite(amount) || amount < 0) return;
|
|
5497
|
+
this._estimatedCost += amount;
|
|
5498
|
+
}
|
|
5493
5499
|
recordRuleHit(label) {
|
|
5494
5500
|
this._lastRuleHit = label;
|
|
5495
5501
|
}
|
|
@@ -5502,6 +5508,7 @@ var init_session_counters = __esm({
|
|
|
5502
5508
|
blocked: this._blocked,
|
|
5503
5509
|
dlpHits: this._dlpHits,
|
|
5504
5510
|
wouldBlock: this._wouldBlock,
|
|
5511
|
+
estimatedCost: this._estimatedCost,
|
|
5505
5512
|
lastRuleHit: this._lastRuleHit,
|
|
5506
5513
|
lastBlockedTool: this._lastBlockedTool
|
|
5507
5514
|
};
|
|
@@ -5511,6 +5518,7 @@ var init_session_counters = __esm({
|
|
|
5511
5518
|
this._blocked = 0;
|
|
5512
5519
|
this._dlpHits = 0;
|
|
5513
5520
|
this._wouldBlock = 0;
|
|
5521
|
+
this._estimatedCost = 0;
|
|
5514
5522
|
this._lastRuleHit = null;
|
|
5515
5523
|
this._lastBlockedTool = null;
|
|
5516
5524
|
}
|
|
@@ -5615,21 +5623,21 @@ function atomicWriteSync2(filePath, data, options) {
|
|
|
5615
5623
|
const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
|
|
5616
5624
|
try {
|
|
5617
5625
|
import_fs13.default.writeFileSync(tmpPath, data, options);
|
|
5618
|
-
} catch (
|
|
5626
|
+
} catch (err2) {
|
|
5619
5627
|
try {
|
|
5620
5628
|
import_fs13.default.unlinkSync(tmpPath);
|
|
5621
5629
|
} catch {
|
|
5622
5630
|
}
|
|
5623
|
-
throw
|
|
5631
|
+
throw err2;
|
|
5624
5632
|
}
|
|
5625
5633
|
try {
|
|
5626
5634
|
import_fs13.default.renameSync(tmpPath, filePath);
|
|
5627
|
-
} catch (
|
|
5635
|
+
} catch (err2) {
|
|
5628
5636
|
try {
|
|
5629
5637
|
import_fs13.default.unlinkSync(tmpPath);
|
|
5630
5638
|
} catch {
|
|
5631
5639
|
}
|
|
5632
|
-
throw
|
|
5640
|
+
throw err2;
|
|
5633
5641
|
}
|
|
5634
5642
|
}
|
|
5635
5643
|
function redactArgs(value) {
|
|
@@ -5735,15 +5743,38 @@ function openBrowser(url) {
|
|
|
5735
5743
|
} catch {
|
|
5736
5744
|
}
|
|
5737
5745
|
}
|
|
5746
|
+
function estimateToolCost(tool, args) {
|
|
5747
|
+
const a = args ?? {};
|
|
5748
|
+
const t = tool.toLowerCase().replace(/[^a-z_]/g, "_");
|
|
5749
|
+
if (t.includes("read") || t === "glob" || t === "grep") {
|
|
5750
|
+
const filePath = a.file_path ?? a.path;
|
|
5751
|
+
if (filePath) {
|
|
5752
|
+
try {
|
|
5753
|
+
const bytes = import_fs13.default.statSync(filePath).size;
|
|
5754
|
+
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
5755
|
+
} catch {
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5759
|
+
if (t.includes("write")) {
|
|
5760
|
+
const content = a.content ?? "";
|
|
5761
|
+
return String(content).length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5762
|
+
}
|
|
5763
|
+
if (t.includes("edit") || t === "str_replace_based_edit_tool") {
|
|
5764
|
+
const newStr = a.new_string ?? "";
|
|
5765
|
+
return String(newStr).length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5766
|
+
}
|
|
5767
|
+
return void 0;
|
|
5768
|
+
}
|
|
5738
5769
|
function broadcast(event, data) {
|
|
5739
5770
|
if (event === "activity") {
|
|
5740
5771
|
activityRing.push({ event, data });
|
|
5741
5772
|
if (activityRing.length > ACTIVITY_RING_SIZE) activityRing.shift();
|
|
5742
5773
|
} else if (event === "activity-result") {
|
|
5743
|
-
const { id, status, label } = data;
|
|
5774
|
+
const { id, status, label, costEstimate } = data;
|
|
5744
5775
|
for (let i = activityRing.length - 1; i >= 0; i--) {
|
|
5745
5776
|
if (activityRing[i].data.id === id) {
|
|
5746
|
-
Object.assign(activityRing[i].data, { status, label });
|
|
5777
|
+
Object.assign(activityRing[i].data, { status, label, costEstimate });
|
|
5747
5778
|
break;
|
|
5748
5779
|
}
|
|
5749
5780
|
}
|
|
@@ -5808,6 +5839,16 @@ function startActivitySocket() {
|
|
|
5808
5839
|
sessionHistory.recordTestFail(data.ts);
|
|
5809
5840
|
return;
|
|
5810
5841
|
}
|
|
5842
|
+
if (data.status === "snapshot") {
|
|
5843
|
+
broadcast("snapshot", {
|
|
5844
|
+
hash: data.hash,
|
|
5845
|
+
tool: data.tool,
|
|
5846
|
+
argsSummary: data.argsSummary,
|
|
5847
|
+
fileCount: data.fileCount,
|
|
5848
|
+
ts: data.ts
|
|
5849
|
+
});
|
|
5850
|
+
return;
|
|
5851
|
+
}
|
|
5811
5852
|
if (data.status === "pending") {
|
|
5812
5853
|
broadcast("activity", {
|
|
5813
5854
|
id: data.id,
|
|
@@ -5835,10 +5876,13 @@ function startActivitySocket() {
|
|
|
5835
5876
|
sessionCounters.incrementBlocked();
|
|
5836
5877
|
sessionCounters.recordBlockedTool(data.tool);
|
|
5837
5878
|
}
|
|
5879
|
+
const costEstimate = data.status === "allow" ? estimateToolCost(data.tool, data.args) : void 0;
|
|
5880
|
+
if (costEstimate != null && costEstimate > 0) sessionCounters.addCost(costEstimate);
|
|
5838
5881
|
broadcast("activity-result", {
|
|
5839
5882
|
id: data.id,
|
|
5840
5883
|
status: data.status,
|
|
5841
|
-
label: data.label
|
|
5884
|
+
label: data.label,
|
|
5885
|
+
costEstimate
|
|
5842
5886
|
});
|
|
5843
5887
|
}
|
|
5844
5888
|
} catch {
|
|
@@ -5855,7 +5899,7 @@ function startActivitySocket() {
|
|
|
5855
5899
|
}
|
|
5856
5900
|
});
|
|
5857
5901
|
}
|
|
5858
|
-
var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, WRITE_TOOL_NAMES;
|
|
5902
|
+
var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
|
|
5859
5903
|
var init_state2 = __esm({
|
|
5860
5904
|
"src/daemon/state.ts"() {
|
|
5861
5905
|
"use strict";
|
|
@@ -5899,6 +5943,9 @@ var init_state2 = __esm({
|
|
|
5899
5943
|
ACTIVITY_RING_SIZE = 100;
|
|
5900
5944
|
activityRing = [];
|
|
5901
5945
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
5946
|
+
INPUT_PRICE_PER_1M = 3;
|
|
5947
|
+
OUTPUT_PRICE_PER_1M = 15;
|
|
5948
|
+
BYTES_PER_TOKEN = 4;
|
|
5902
5949
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5903
5950
|
"write",
|
|
5904
5951
|
"write_file",
|
|
@@ -5942,21 +5989,21 @@ function patchConfig(configPath, patch) {
|
|
|
5942
5989
|
const tmp = configPath + ".node9-tmp";
|
|
5943
5990
|
try {
|
|
5944
5991
|
import_fs14.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5945
|
-
} catch (
|
|
5992
|
+
} catch (err2) {
|
|
5946
5993
|
try {
|
|
5947
5994
|
import_fs14.default.unlinkSync(tmp);
|
|
5948
5995
|
} catch {
|
|
5949
5996
|
}
|
|
5950
|
-
throw
|
|
5997
|
+
throw err2;
|
|
5951
5998
|
}
|
|
5952
5999
|
try {
|
|
5953
6000
|
import_fs14.default.renameSync(tmp, configPath);
|
|
5954
|
-
} catch (
|
|
6001
|
+
} catch (err2) {
|
|
5955
6002
|
try {
|
|
5956
6003
|
import_fs14.default.unlinkSync(tmp);
|
|
5957
6004
|
} catch {
|
|
5958
6005
|
}
|
|
5959
|
-
throw
|
|
6006
|
+
throw err2;
|
|
5960
6007
|
}
|
|
5961
6008
|
}
|
|
5962
6009
|
var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
|
|
@@ -6208,11 +6255,11 @@ data: ${JSON.stringify(item.data)}
|
|
|
6208
6255
|
e.earlyDecision = decision;
|
|
6209
6256
|
e.earlyReason = result.reason;
|
|
6210
6257
|
}
|
|
6211
|
-
}).catch((
|
|
6258
|
+
}).catch((err2) => {
|
|
6212
6259
|
const e = pending.get(id);
|
|
6213
6260
|
if (!e) return;
|
|
6214
6261
|
clearTimeout(e.timer);
|
|
6215
|
-
const reason =
|
|
6262
|
+
const reason = err2?.reason || "No response \u2014 request timed out";
|
|
6216
6263
|
if (e.waiter) e.waiter("deny", reason);
|
|
6217
6264
|
else {
|
|
6218
6265
|
e.earlyDecision = "deny";
|
|
@@ -6339,8 +6386,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6339
6386
|
const s = getGlobalSettings();
|
|
6340
6387
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6341
6388
|
return res.end(JSON.stringify({ ...s, autoStarted }));
|
|
6342
|
-
} catch (
|
|
6343
|
-
console.error(import_chalk2.default.red("[node9 daemon] GET /settings failed:"),
|
|
6389
|
+
} catch (err2) {
|
|
6390
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /settings failed:"), err2);
|
|
6344
6391
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6345
6392
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
6346
6393
|
}
|
|
@@ -6356,7 +6403,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6356
6403
|
allowed: counters.allowed,
|
|
6357
6404
|
blocked: counters.blocked,
|
|
6358
6405
|
dlpHits: counters.dlpHits,
|
|
6359
|
-
wouldBlock: counters.wouldBlock
|
|
6406
|
+
wouldBlock: counters.wouldBlock,
|
|
6407
|
+
estimatedCost: counters.estimatedCost
|
|
6360
6408
|
},
|
|
6361
6409
|
taintedCount: taintStore.list().length,
|
|
6362
6410
|
lastRuleHit: counters.lastRuleHit,
|
|
@@ -6364,8 +6412,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6364
6412
|
};
|
|
6365
6413
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6366
6414
|
return res.end(JSON.stringify(status));
|
|
6367
|
-
} catch (
|
|
6368
|
-
console.error(import_chalk2.default.red("[node9 daemon] GET /status failed:"),
|
|
6415
|
+
} catch (err2) {
|
|
6416
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /status failed:"), err2);
|
|
6369
6417
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6370
6418
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
6371
6419
|
}
|
|
@@ -6405,8 +6453,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6405
6453
|
const s = getGlobalSettings();
|
|
6406
6454
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6407
6455
|
return res.end(JSON.stringify({ hasKey: hasStoredSlackKey(), enabled: s.slackEnabled }));
|
|
6408
|
-
} catch (
|
|
6409
|
-
console.error(import_chalk2.default.red("[node9 daemon] GET /slack-status failed:"),
|
|
6456
|
+
} catch (err2) {
|
|
6457
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /slack-status failed:"), err2);
|
|
6410
6458
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6411
6459
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
6412
6460
|
}
|
|
@@ -6488,6 +6536,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6488
6536
|
}
|
|
6489
6537
|
if (req.method === "POST" && pathname === "/events/clear") {
|
|
6490
6538
|
activityRing.length = 0;
|
|
6539
|
+
sessionCounters.reset();
|
|
6491
6540
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6492
6541
|
return res.end(JSON.stringify({ ok: true }));
|
|
6493
6542
|
}
|
|
@@ -6566,10 +6615,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
6566
6615
|
broadcast("suggestion:resolved", { id, status: "applied" });
|
|
6567
6616
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6568
6617
|
return res.end(JSON.stringify({ ok: true }));
|
|
6569
|
-
} catch (
|
|
6570
|
-
console.error(import_chalk2.default.red("[node9 daemon] POST /suggestions/:id/apply failed:"),
|
|
6618
|
+
} catch (err2) {
|
|
6619
|
+
console.error(import_chalk2.default.red("[node9 daemon] POST /suggestions/:id/apply failed:"), err2);
|
|
6571
6620
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6572
|
-
return res.end(JSON.stringify({ error: String(
|
|
6621
|
+
return res.end(JSON.stringify({ error: String(err2) }));
|
|
6573
6622
|
}
|
|
6574
6623
|
}
|
|
6575
6624
|
if (req.method === "POST" && pathname.startsWith("/suggestions/") && pathname.endsWith("/dismiss")) {
|
|
@@ -6801,7 +6850,7 @@ function formatBase(activity) {
|
|
|
6801
6850
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6802
6851
|
const icon = getIcon(activity.tool);
|
|
6803
6852
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6804
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
6853
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os21.default.homedir(), "~");
|
|
6805
6854
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6806
6855
|
return `${import_chalk17.default.gray(time)} ${icon} ${import_chalk17.default.white.bold(toolName)} ${import_chalk17.default.dim(argsPreview)}`;
|
|
6807
6856
|
}
|
|
@@ -6815,11 +6864,13 @@ function renderResult(activity, result) {
|
|
|
6815
6864
|
} else {
|
|
6816
6865
|
status = import_chalk17.default.red("\u2717 BLOCK");
|
|
6817
6866
|
}
|
|
6867
|
+
const cost = result.costEstimate ?? activity.costEstimate;
|
|
6868
|
+
const costSuffix = cost == null ? "" : import_chalk17.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
6818
6869
|
if (process.stdout.isTTY) {
|
|
6819
|
-
|
|
6820
|
-
|
|
6870
|
+
import_readline5.default.clearLine(process.stdout, 0);
|
|
6871
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
6821
6872
|
}
|
|
6822
|
-
console.log(`${base} ${status}`);
|
|
6873
|
+
console.log(`${base} ${status}${costSuffix}`);
|
|
6823
6874
|
}
|
|
6824
6875
|
function renderPending(activity) {
|
|
6825
6876
|
if (!process.stdout.isTTY) return;
|
|
@@ -6827,9 +6878,9 @@ function renderPending(activity) {
|
|
|
6827
6878
|
}
|
|
6828
6879
|
async function ensureDaemon() {
|
|
6829
6880
|
let pidPort = null;
|
|
6830
|
-
if (
|
|
6881
|
+
if (import_fs25.default.existsSync(PID_FILE)) {
|
|
6831
6882
|
try {
|
|
6832
|
-
const { port } = JSON.parse(
|
|
6883
|
+
const { port } = JSON.parse(import_fs25.default.readFileSync(PID_FILE, "utf-8"));
|
|
6833
6884
|
pidPort = port;
|
|
6834
6885
|
} catch {
|
|
6835
6886
|
console.error(import_chalk17.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -6948,6 +6999,39 @@ function buildRecoveryCardLines(req) {
|
|
|
6948
6999
|
``
|
|
6949
7000
|
];
|
|
6950
7001
|
}
|
|
7002
|
+
function readApproversFromDisk() {
|
|
7003
|
+
const configPath = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
7004
|
+
try {
|
|
7005
|
+
const raw = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
7006
|
+
const settings = raw.settings ?? {};
|
|
7007
|
+
return settings.approvers ?? {};
|
|
7008
|
+
} catch {
|
|
7009
|
+
return {};
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
function approverStatusLine() {
|
|
7013
|
+
const a = readApproversFromDisk();
|
|
7014
|
+
const fmt = (label, key) => {
|
|
7015
|
+
const on = a[key] !== false;
|
|
7016
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk17.default.green("\u2713") : import_chalk17.default.dim("\u2717")}`;
|
|
7017
|
+
};
|
|
7018
|
+
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7019
|
+
}
|
|
7020
|
+
function toggleApprover(channel) {
|
|
7021
|
+
const configPath = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
7022
|
+
try {
|
|
7023
|
+
const raw = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
7024
|
+
const settings = raw.settings ?? {};
|
|
7025
|
+
const approvers = settings.approvers ?? {};
|
|
7026
|
+
approvers[channel] = approvers[channel] === false;
|
|
7027
|
+
settings.approvers = approvers;
|
|
7028
|
+
raw.settings = settings;
|
|
7029
|
+
import_fs25.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7030
|
+
} catch (err2) {
|
|
7031
|
+
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7032
|
+
`);
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
6951
7035
|
async function startTail(options = {}) {
|
|
6952
7036
|
const port = await ensureDaemon();
|
|
6953
7037
|
if (options.clear) {
|
|
@@ -6966,7 +7050,7 @@ async function startTail(options = {}) {
|
|
|
6966
7050
|
res.resume();
|
|
6967
7051
|
}
|
|
6968
7052
|
);
|
|
6969
|
-
req2.once("error", (
|
|
7053
|
+
req2.once("error", (err2) => resolve({ ok: false, code: err2.code }));
|
|
6970
7054
|
req2.setTimeout(2e3, () => {
|
|
6971
7055
|
resolve({ ok: false, code: "ETIMEDOUT" });
|
|
6972
7056
|
req2.destroy();
|
|
@@ -6993,10 +7077,48 @@ async function startTail(options = {}) {
|
|
|
6993
7077
|
let cancelActiveCard = null;
|
|
6994
7078
|
const localAllowCounts = /* @__PURE__ */ new Map();
|
|
6995
7079
|
const canApprove = process.stdout.isTTY && process.stdin.isTTY;
|
|
6996
|
-
if (canApprove)
|
|
7080
|
+
if (canApprove) import_readline5.default.emitKeypressEvents(process.stdin);
|
|
7081
|
+
let idleKeypressHandler = null;
|
|
7082
|
+
function enterIdleMode() {
|
|
7083
|
+
if (!canApprove || idleKeypressHandler !== null) return;
|
|
7084
|
+
try {
|
|
7085
|
+
process.stdin.setRawMode(true);
|
|
7086
|
+
} catch {
|
|
7087
|
+
return;
|
|
7088
|
+
}
|
|
7089
|
+
process.stdin.resume();
|
|
7090
|
+
idleKeypressHandler = (_str, key) => {
|
|
7091
|
+
const name = key?.name ?? "";
|
|
7092
|
+
if (key?.ctrl && name === "c") {
|
|
7093
|
+
process.kill(process.pid, "SIGINT");
|
|
7094
|
+
return;
|
|
7095
|
+
}
|
|
7096
|
+
if (name === "q") {
|
|
7097
|
+
process.kill(process.pid, "SIGINT");
|
|
7098
|
+
return;
|
|
7099
|
+
}
|
|
7100
|
+
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7101
|
+
if (channel) {
|
|
7102
|
+
toggleApprover(channel);
|
|
7103
|
+
console.log(import_chalk17.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7104
|
+
}
|
|
7105
|
+
};
|
|
7106
|
+
process.stdin.on("keypress", idleKeypressHandler);
|
|
7107
|
+
}
|
|
7108
|
+
function exitIdleMode() {
|
|
7109
|
+
if (idleKeypressHandler) {
|
|
7110
|
+
process.stdin.removeListener("keypress", idleKeypressHandler);
|
|
7111
|
+
idleKeypressHandler = null;
|
|
7112
|
+
}
|
|
7113
|
+
try {
|
|
7114
|
+
process.stdin.setRawMode(false);
|
|
7115
|
+
} catch {
|
|
7116
|
+
}
|
|
7117
|
+
process.stdin.pause();
|
|
7118
|
+
}
|
|
6997
7119
|
function clearCard() {
|
|
6998
7120
|
if (cardLineCount > 0) {
|
|
6999
|
-
|
|
7121
|
+
import_readline5.default.moveCursor(process.stdout, 0, -cardLineCount);
|
|
7000
7122
|
process.stdout.write(ERASE_DOWN);
|
|
7001
7123
|
cardLineCount = 0;
|
|
7002
7124
|
}
|
|
@@ -7012,10 +7134,12 @@ async function startTail(options = {}) {
|
|
|
7012
7134
|
}
|
|
7013
7135
|
function showNextCard() {
|
|
7014
7136
|
if (cardActive || approvalQueue.length === 0 || !canApprove) return;
|
|
7137
|
+
exitIdleMode();
|
|
7015
7138
|
try {
|
|
7016
7139
|
process.stdin.setRawMode(true);
|
|
7017
7140
|
} catch {
|
|
7018
7141
|
cardActive = false;
|
|
7142
|
+
enterIdleMode();
|
|
7019
7143
|
return;
|
|
7020
7144
|
}
|
|
7021
7145
|
cardActive = true;
|
|
@@ -7027,12 +7151,8 @@ async function startTail(options = {}) {
|
|
|
7027
7151
|
const handler = onKeypress;
|
|
7028
7152
|
onKeypress = null;
|
|
7029
7153
|
if (handler) process.stdin.removeListener("keypress", handler);
|
|
7030
|
-
try {
|
|
7031
|
-
process.stdin.setRawMode(false);
|
|
7032
|
-
} catch {
|
|
7033
|
-
}
|
|
7034
|
-
process.stdin.pause();
|
|
7035
7154
|
cancelActiveCard = null;
|
|
7155
|
+
enterIdleMode();
|
|
7036
7156
|
};
|
|
7037
7157
|
const settle = (action) => {
|
|
7038
7158
|
if (settled) return;
|
|
@@ -7072,11 +7192,11 @@ async function startTail(options = {}) {
|
|
|
7072
7192
|
} else {
|
|
7073
7193
|
httpDecision = action;
|
|
7074
7194
|
}
|
|
7075
|
-
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((
|
|
7195
|
+
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7076
7196
|
try {
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
`[tail] POST /decision failed: ${String(
|
|
7197
|
+
import_fs25.default.appendFileSync(
|
|
7198
|
+
import_path28.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log"),
|
|
7199
|
+
`[tail] POST /decision failed: ${String(err2)}
|
|
7080
7200
|
`
|
|
7081
7201
|
);
|
|
7082
7202
|
} catch {
|
|
@@ -7159,23 +7279,21 @@ async function startTail(options = {}) {
|
|
|
7159
7279
|
console.log(import_chalk17.default.cyan.bold(`
|
|
7160
7280
|
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk17.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7161
7281
|
if (canApprove) {
|
|
7162
|
-
console.log(
|
|
7163
|
-
|
|
7164
|
-
);
|
|
7282
|
+
console.log(import_chalk17.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
7283
|
+
console.log(import_chalk17.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7165
7284
|
}
|
|
7166
7285
|
if (options.history) {
|
|
7167
|
-
console.log(import_chalk17.default.dim("Showing history + live events
|
|
7286
|
+
console.log(import_chalk17.default.dim("Showing history + live events.\n"));
|
|
7168
7287
|
} else {
|
|
7169
|
-
console.log(
|
|
7170
|
-
import_chalk17.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
7171
|
-
);
|
|
7288
|
+
console.log(import_chalk17.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7172
7289
|
}
|
|
7173
7290
|
process.on("SIGINT", () => {
|
|
7291
|
+
exitIdleMode();
|
|
7174
7292
|
clearCard();
|
|
7175
7293
|
process.stdout.write(SHOW_CURSOR);
|
|
7176
7294
|
if (process.stdout.isTTY) {
|
|
7177
|
-
|
|
7178
|
-
|
|
7295
|
+
import_readline5.default.clearLine(process.stdout, 0);
|
|
7296
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7179
7297
|
}
|
|
7180
7298
|
console.log(import_chalk17.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7181
7299
|
process.exit(0);
|
|
@@ -7186,11 +7304,12 @@ async function startTail(options = {}) {
|
|
|
7186
7304
|
console.error(import_chalk17.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7187
7305
|
process.exit(1);
|
|
7188
7306
|
}
|
|
7307
|
+
if (canApprove) enterIdleMode();
|
|
7189
7308
|
let currentEvent = "";
|
|
7190
7309
|
let currentData = "";
|
|
7191
7310
|
res.on("error", () => {
|
|
7192
7311
|
});
|
|
7193
|
-
const rl =
|
|
7312
|
+
const rl = import_readline5.default.createInterface({ input: res, crlfDelay: Infinity });
|
|
7194
7313
|
rl.on("error", () => {
|
|
7195
7314
|
});
|
|
7196
7315
|
rl.on("line", (line) => {
|
|
@@ -7210,8 +7329,8 @@ async function startTail(options = {}) {
|
|
|
7210
7329
|
clearCard();
|
|
7211
7330
|
process.stdout.write(SHOW_CURSOR);
|
|
7212
7331
|
if (process.stdout.isTTY) {
|
|
7213
|
-
|
|
7214
|
-
|
|
7332
|
+
import_readline5.default.clearLine(process.stdout, 0);
|
|
7333
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7215
7334
|
}
|
|
7216
7335
|
console.log(import_chalk17.default.red("\n\u274C Daemon disconnected."));
|
|
7217
7336
|
process.exit(1);
|
|
@@ -7289,6 +7408,18 @@ async function startTail(options = {}) {
|
|
|
7289
7408
|
const slowTool = /bash|shell|query|sql|agent/i.test(data.tool);
|
|
7290
7409
|
if (slowTool) renderPending(data);
|
|
7291
7410
|
}
|
|
7411
|
+
if (event === "snapshot") {
|
|
7412
|
+
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
7413
|
+
const hash = data.hash ?? "";
|
|
7414
|
+
const summary = data.argsSummary ?? data.tool;
|
|
7415
|
+
const fileCount = data.fileCount ?? 0;
|
|
7416
|
+
const files = fileCount > 0 ? import_chalk17.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7417
|
+
process.stdout.write(
|
|
7418
|
+
`${import_chalk17.default.dim(time)} ${import_chalk17.default.cyan("\u{1F4F8} snapshot")} ${import_chalk17.default.dim(hash)} ${summary}${files}
|
|
7419
|
+
`
|
|
7420
|
+
);
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7292
7423
|
if (event === "activity-result") {
|
|
7293
7424
|
const original = activityPending.get(data.id);
|
|
7294
7425
|
if (original) {
|
|
@@ -7297,28 +7428,28 @@ async function startTail(options = {}) {
|
|
|
7297
7428
|
}
|
|
7298
7429
|
}
|
|
7299
7430
|
}
|
|
7300
|
-
req.on("error", (
|
|
7301
|
-
const msg =
|
|
7431
|
+
req.on("error", (err2) => {
|
|
7432
|
+
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7302
7433
|
console.error(import_chalk17.default.red(`
|
|
7303
7434
|
\u274C ${msg}`));
|
|
7304
7435
|
process.exit(1);
|
|
7305
7436
|
});
|
|
7306
7437
|
}
|
|
7307
|
-
var import_http2, import_chalk17,
|
|
7438
|
+
var import_http2, import_chalk17, import_fs25, import_os21, import_path28, import_readline5, import_child_process13, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
7308
7439
|
var init_tail = __esm({
|
|
7309
7440
|
"src/tui/tail.ts"() {
|
|
7310
7441
|
"use strict";
|
|
7311
7442
|
import_http2 = __toESM(require("http"));
|
|
7312
7443
|
import_chalk17 = __toESM(require("chalk"));
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7444
|
+
import_fs25 = __toESM(require("fs"));
|
|
7445
|
+
import_os21 = __toESM(require("os"));
|
|
7446
|
+
import_path28 = __toESM(require("path"));
|
|
7447
|
+
import_readline5 = __toESM(require("readline"));
|
|
7317
7448
|
import_child_process13 = require("child_process");
|
|
7318
7449
|
init_daemon2();
|
|
7319
7450
|
init_daemon();
|
|
7320
7451
|
init_core();
|
|
7321
|
-
PID_FILE =
|
|
7452
|
+
PID_FILE = import_path28.default.join(import_os21.default.homedir(), ".node9", "daemon.pid");
|
|
7322
7453
|
ICONS = {
|
|
7323
7454
|
bash: "\u{1F4BB}",
|
|
7324
7455
|
shell: "\u{1F4BB}",
|
|
@@ -7431,9 +7562,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7431
7562
|
return ` (${m}m left)`;
|
|
7432
7563
|
}
|
|
7433
7564
|
function safeReadJson(filePath) {
|
|
7434
|
-
if (!
|
|
7565
|
+
if (!import_fs26.default.existsSync(filePath)) return null;
|
|
7435
7566
|
try {
|
|
7436
|
-
return JSON.parse(
|
|
7567
|
+
return JSON.parse(import_fs26.default.readFileSync(filePath, "utf-8"));
|
|
7437
7568
|
} catch {
|
|
7438
7569
|
return null;
|
|
7439
7570
|
}
|
|
@@ -7454,12 +7585,12 @@ function countHooksInFile(filePath) {
|
|
|
7454
7585
|
return Object.keys(cfg.hooks).length;
|
|
7455
7586
|
}
|
|
7456
7587
|
function countRulesInDir(rulesDir) {
|
|
7457
|
-
if (!
|
|
7588
|
+
if (!import_fs26.default.existsSync(rulesDir)) return 0;
|
|
7458
7589
|
let count = 0;
|
|
7459
7590
|
try {
|
|
7460
|
-
for (const entry of
|
|
7591
|
+
for (const entry of import_fs26.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7461
7592
|
if (entry.isDirectory()) {
|
|
7462
|
-
count += countRulesInDir(
|
|
7593
|
+
count += countRulesInDir(import_path29.default.join(rulesDir, entry.name));
|
|
7463
7594
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7464
7595
|
count++;
|
|
7465
7596
|
}
|
|
@@ -7470,46 +7601,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7470
7601
|
}
|
|
7471
7602
|
function isSamePath(a, b) {
|
|
7472
7603
|
try {
|
|
7473
|
-
return
|
|
7604
|
+
return import_path29.default.resolve(a) === import_path29.default.resolve(b);
|
|
7474
7605
|
} catch {
|
|
7475
7606
|
return false;
|
|
7476
7607
|
}
|
|
7477
7608
|
}
|
|
7478
7609
|
function countConfigs(cwd) {
|
|
7479
|
-
const homeDir2 =
|
|
7480
|
-
const claudeDir =
|
|
7610
|
+
const homeDir2 = import_os22.default.homedir();
|
|
7611
|
+
const claudeDir = import_path29.default.join(homeDir2, ".claude");
|
|
7481
7612
|
let claudeMdCount = 0;
|
|
7482
7613
|
let rulesCount = 0;
|
|
7483
7614
|
let hooksCount = 0;
|
|
7484
7615
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7485
7616
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7486
|
-
if (
|
|
7487
|
-
rulesCount += countRulesInDir(
|
|
7488
|
-
const userSettings =
|
|
7617
|
+
if (import_fs26.default.existsSync(import_path29.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7618
|
+
rulesCount += countRulesInDir(import_path29.default.join(claudeDir, "rules"));
|
|
7619
|
+
const userSettings = import_path29.default.join(claudeDir, "settings.json");
|
|
7489
7620
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7490
7621
|
hooksCount += countHooksInFile(userSettings);
|
|
7491
|
-
const userClaudeJson =
|
|
7622
|
+
const userClaudeJson = import_path29.default.join(homeDir2, ".claude.json");
|
|
7492
7623
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7493
7624
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7494
7625
|
userMcpServers.delete(name);
|
|
7495
7626
|
}
|
|
7496
7627
|
if (cwd) {
|
|
7497
|
-
if (
|
|
7498
|
-
if (
|
|
7499
|
-
const projectClaudeDir =
|
|
7628
|
+
if (import_fs26.default.existsSync(import_path29.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7629
|
+
if (import_fs26.default.existsSync(import_path29.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7630
|
+
const projectClaudeDir = import_path29.default.join(cwd, ".claude");
|
|
7500
7631
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7501
7632
|
if (!overlapsUserScope) {
|
|
7502
|
-
if (
|
|
7503
|
-
rulesCount += countRulesInDir(
|
|
7504
|
-
const projSettings =
|
|
7633
|
+
if (import_fs26.default.existsSync(import_path29.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7634
|
+
rulesCount += countRulesInDir(import_path29.default.join(projectClaudeDir, "rules"));
|
|
7635
|
+
const projSettings = import_path29.default.join(projectClaudeDir, "settings.json");
|
|
7505
7636
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7506
7637
|
hooksCount += countHooksInFile(projSettings);
|
|
7507
7638
|
}
|
|
7508
|
-
if (
|
|
7509
|
-
const localSettings =
|
|
7639
|
+
if (import_fs26.default.existsSync(import_path29.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7640
|
+
const localSettings = import_path29.default.join(projectClaudeDir, "settings.local.json");
|
|
7510
7641
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7511
7642
|
hooksCount += countHooksInFile(localSettings);
|
|
7512
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7643
|
+
const mcpJsonServers = getMcpServerNames(import_path29.default.join(cwd, ".mcp.json"));
|
|
7513
7644
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7514
7645
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7515
7646
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7567,6 +7698,11 @@ function renderSecurityLine(status) {
|
|
|
7567
7698
|
parts.push(color(RED2, `\u{1F6A8} ${status.session.dlpHits} dlp`));
|
|
7568
7699
|
}
|
|
7569
7700
|
}
|
|
7701
|
+
if (status.session.estimatedCost > 0) {
|
|
7702
|
+
const cost = status.session.estimatedCost;
|
|
7703
|
+
const costStr = cost >= 0.01 ? `$${cost.toFixed(2)}` : cost >= 1e-3 ? `$${cost.toFixed(3)}` : "<$0.001";
|
|
7704
|
+
parts.push(color(DIM, `~${costStr}`));
|
|
7705
|
+
}
|
|
7570
7706
|
if (status.taintedCount > 0) {
|
|
7571
7707
|
parts.push(color(YELLOW2, `\u{1F4A7} ${status.taintedCount} tainted`));
|
|
7572
7708
|
}
|
|
@@ -7622,11 +7758,11 @@ async function main() {
|
|
|
7622
7758
|
try {
|
|
7623
7759
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7624
7760
|
for (const configPath of [
|
|
7625
|
-
|
|
7626
|
-
|
|
7761
|
+
import_path29.default.join(cwd, "node9.config.json"),
|
|
7762
|
+
import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json")
|
|
7627
7763
|
]) {
|
|
7628
|
-
if (!
|
|
7629
|
-
const cfg = JSON.parse(
|
|
7764
|
+
if (!import_fs26.default.existsSync(configPath)) continue;
|
|
7765
|
+
const cfg = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
7630
7766
|
const hud = cfg.settings?.hud;
|
|
7631
7767
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7632
7768
|
}
|
|
@@ -7644,13 +7780,13 @@ async function main() {
|
|
|
7644
7780
|
renderOffline();
|
|
7645
7781
|
}
|
|
7646
7782
|
}
|
|
7647
|
-
var
|
|
7783
|
+
var import_fs26, import_path29, import_os22, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
|
|
7648
7784
|
var init_hud = __esm({
|
|
7649
7785
|
"src/cli/hud.ts"() {
|
|
7650
7786
|
"use strict";
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7787
|
+
import_fs26 = __toESM(require("fs"));
|
|
7788
|
+
import_path29 = __toESM(require("path"));
|
|
7789
|
+
import_os22 = __toESM(require("os"));
|
|
7654
7790
|
import_http3 = __toESM(require("http"));
|
|
7655
7791
|
init_daemon();
|
|
7656
7792
|
RESET3 = "\x1B[0m";
|
|
@@ -7679,6 +7815,16 @@ var import_path14 = __toESM(require("path"));
|
|
|
7679
7815
|
var import_os10 = __toESM(require("os"));
|
|
7680
7816
|
var import_chalk = __toESM(require("chalk"));
|
|
7681
7817
|
var import_prompts = require("@inquirer/prompts");
|
|
7818
|
+
var NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
|
|
7819
|
+
function hasNode9McpServer(servers) {
|
|
7820
|
+
const entry = servers["node9"];
|
|
7821
|
+
return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
|
|
7822
|
+
}
|
|
7823
|
+
function removeNode9McpServer(servers) {
|
|
7824
|
+
if (!hasNode9McpServer(servers)) return false;
|
|
7825
|
+
delete servers["node9"];
|
|
7826
|
+
return true;
|
|
7827
|
+
}
|
|
7682
7828
|
function printDaemonTip() {
|
|
7683
7829
|
console.log(
|
|
7684
7830
|
import_chalk.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + import_chalk.default.white("\n To view your history or manage persistent rules, run:") + import_chalk.default.green("\n node9 daemon --openui")
|
|
@@ -7736,6 +7882,10 @@ function teardownClaude() {
|
|
|
7736
7882
|
const claudeConfig = readJson(mcpPath);
|
|
7737
7883
|
if (claudeConfig?.mcpServers) {
|
|
7738
7884
|
let mcpChanged = false;
|
|
7885
|
+
if (removeNode9McpServer(claudeConfig.mcpServers)) {
|
|
7886
|
+
mcpChanged = true;
|
|
7887
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.claude.json"));
|
|
7888
|
+
}
|
|
7739
7889
|
for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
|
|
7740
7890
|
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
7741
7891
|
const [originalCmd, ...originalArgs] = server.args;
|
|
@@ -7779,6 +7929,10 @@ function teardownGemini() {
|
|
|
7779
7929
|
}
|
|
7780
7930
|
}
|
|
7781
7931
|
if (settings.mcpServers) {
|
|
7932
|
+
if (removeNode9McpServer(settings.mcpServers)) {
|
|
7933
|
+
changed = true;
|
|
7934
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/settings.json"));
|
|
7935
|
+
}
|
|
7782
7936
|
for (const [name, server] of Object.entries(settings.mcpServers)) {
|
|
7783
7937
|
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
7784
7938
|
const [originalCmd, ...originalArgs] = server.args;
|
|
@@ -7807,6 +7961,10 @@ function teardownCursor() {
|
|
|
7807
7961
|
return;
|
|
7808
7962
|
}
|
|
7809
7963
|
let changed = false;
|
|
7964
|
+
if (removeNode9McpServer(mcpConfig.mcpServers)) {
|
|
7965
|
+
changed = true;
|
|
7966
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.cursor/mcp.json"));
|
|
7967
|
+
}
|
|
7810
7968
|
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
7811
7969
|
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
7812
7970
|
const [originalCmd, ...originalArgs] = server.args;
|
|
@@ -7832,6 +7990,7 @@ async function setupClaude() {
|
|
|
7832
7990
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
7833
7991
|
const settings = readJson(hooksPath) ?? {};
|
|
7834
7992
|
const servers = claudeConfig.mcpServers ?? {};
|
|
7993
|
+
let hooksChanged = false;
|
|
7835
7994
|
let anythingChanged = false;
|
|
7836
7995
|
if (!settings.hooks) settings.hooks = {};
|
|
7837
7996
|
const hasPreHook = settings.hooks.PreToolUse?.some(
|
|
@@ -7844,6 +8003,7 @@ async function setupClaude() {
|
|
|
7844
8003
|
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
|
|
7845
8004
|
});
|
|
7846
8005
|
console.log(import_chalk.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
|
|
8006
|
+
hooksChanged = true;
|
|
7847
8007
|
anythingChanged = true;
|
|
7848
8008
|
}
|
|
7849
8009
|
const hasPostHook = settings.hooks.PostToolUse?.some(
|
|
@@ -7856,9 +8016,17 @@ async function setupClaude() {
|
|
|
7856
8016
|
hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
|
|
7857
8017
|
});
|
|
7858
8018
|
console.log(import_chalk.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
|
|
8019
|
+
hooksChanged = true;
|
|
7859
8020
|
anythingChanged = true;
|
|
7860
8021
|
}
|
|
7861
|
-
if (
|
|
8022
|
+
if (!hasNode9McpServer(servers)) {
|
|
8023
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
8024
|
+
claudeConfig.mcpServers = servers;
|
|
8025
|
+
writeJson(mcpPath, claudeConfig);
|
|
8026
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
8027
|
+
anythingChanged = true;
|
|
8028
|
+
}
|
|
8029
|
+
if (hooksChanged) {
|
|
7862
8030
|
writeJson(hooksPath, settings);
|
|
7863
8031
|
console.log("");
|
|
7864
8032
|
}
|
|
@@ -7906,6 +8074,7 @@ async function setupGemini() {
|
|
|
7906
8074
|
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
7907
8075
|
const settings = readJson(settingsPath) ?? {};
|
|
7908
8076
|
const servers = settings.mcpServers ?? {};
|
|
8077
|
+
let hooksChanged = false;
|
|
7909
8078
|
let anythingChanged = false;
|
|
7910
8079
|
if (!settings.hooks) settings.hooks = {};
|
|
7911
8080
|
const hasBeforeHook = Array.isArray(settings.hooks.BeforeTool) && settings.hooks.BeforeTool.some(
|
|
@@ -7926,6 +8095,7 @@ async function setupGemini() {
|
|
|
7926
8095
|
]
|
|
7927
8096
|
});
|
|
7928
8097
|
console.log(import_chalk.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
|
|
8098
|
+
hooksChanged = true;
|
|
7929
8099
|
anythingChanged = true;
|
|
7930
8100
|
}
|
|
7931
8101
|
const hasAfterHook = Array.isArray(settings.hooks.AfterTool) && settings.hooks.AfterTool.some(
|
|
@@ -7939,9 +8109,17 @@ async function setupGemini() {
|
|
|
7939
8109
|
hooks: [{ name: "node9-log", type: "command", command: fullPathCommand("log") }]
|
|
7940
8110
|
});
|
|
7941
8111
|
console.log(import_chalk.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
|
|
8112
|
+
hooksChanged = true;
|
|
7942
8113
|
anythingChanged = true;
|
|
7943
8114
|
}
|
|
7944
|
-
if (
|
|
8115
|
+
if (!hasNode9McpServer(servers)) {
|
|
8116
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
8117
|
+
settings.mcpServers = servers;
|
|
8118
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
8119
|
+
hooksChanged = true;
|
|
8120
|
+
anythingChanged = true;
|
|
8121
|
+
}
|
|
8122
|
+
if (hooksChanged) {
|
|
7945
8123
|
writeJson(settingsPath, settings);
|
|
7946
8124
|
console.log("");
|
|
7947
8125
|
}
|
|
@@ -7988,10 +8166,10 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
|
7988
8166
|
const exists = (p) => {
|
|
7989
8167
|
try {
|
|
7990
8168
|
return import_fs11.default.existsSync(p);
|
|
7991
|
-
} catch (
|
|
7992
|
-
const code =
|
|
8169
|
+
} catch (err2) {
|
|
8170
|
+
const code = err2.code;
|
|
7993
8171
|
if (code !== "ENOENT") {
|
|
7994
|
-
process.stderr.write(`[node9] detectAgents: cannot access ${p}: ${code ?? String(
|
|
8172
|
+
process.stderr.write(`[node9] detectAgents: cannot access ${p}: ${code ?? String(err2)}
|
|
7995
8173
|
`);
|
|
7996
8174
|
}
|
|
7997
8175
|
return false;
|
|
@@ -8009,6 +8187,13 @@ async function setupCursor() {
|
|
|
8009
8187
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
8010
8188
|
const servers = mcpConfig.mcpServers ?? {};
|
|
8011
8189
|
let anythingChanged = false;
|
|
8190
|
+
if (!hasNode9McpServer(servers)) {
|
|
8191
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
8192
|
+
mcpConfig.mcpServers = servers;
|
|
8193
|
+
writeJson(mcpPath, mcpConfig);
|
|
8194
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
8195
|
+
anythingChanged = true;
|
|
8196
|
+
}
|
|
8012
8197
|
const serversToWrap = [];
|
|
8013
8198
|
for (const [name, server] of Object.entries(servers)) {
|
|
8014
8199
|
if (!server.command || server.command === "node9") continue;
|
|
@@ -8108,9 +8293,9 @@ function teardownHud() {
|
|
|
8108
8293
|
// src/cli.ts
|
|
8109
8294
|
init_daemon2();
|
|
8110
8295
|
var import_chalk18 = __toESM(require("chalk"));
|
|
8111
|
-
var
|
|
8112
|
-
var
|
|
8113
|
-
var
|
|
8296
|
+
var import_fs27 = __toESM(require("fs"));
|
|
8297
|
+
var import_path30 = __toESM(require("path"));
|
|
8298
|
+
var import_os23 = __toESM(require("os"));
|
|
8114
8299
|
var import_prompts2 = require("@inquirer/prompts");
|
|
8115
8300
|
|
|
8116
8301
|
// src/utils/duration.ts
|
|
@@ -8345,8 +8530,29 @@ init_policy();
|
|
|
8345
8530
|
var import_child_process8 = require("child_process");
|
|
8346
8531
|
var import_crypto7 = __toESM(require("crypto"));
|
|
8347
8532
|
var import_fs17 = __toESM(require("fs"));
|
|
8533
|
+
var import_net3 = __toESM(require("net"));
|
|
8348
8534
|
var import_path19 = __toESM(require("path"));
|
|
8349
8535
|
var import_os13 = __toESM(require("os"));
|
|
8536
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path19.default.join(import_os13.default.tmpdir(), "node9-activity.sock");
|
|
8537
|
+
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8538
|
+
try {
|
|
8539
|
+
const payload = JSON.stringify({
|
|
8540
|
+
status: "snapshot",
|
|
8541
|
+
hash,
|
|
8542
|
+
tool,
|
|
8543
|
+
argsSummary,
|
|
8544
|
+
fileCount,
|
|
8545
|
+
ts: Date.now()
|
|
8546
|
+
});
|
|
8547
|
+
const sock = import_net3.default.createConnection(ACTIVITY_SOCKET_PATH3);
|
|
8548
|
+
sock.on("connect", () => {
|
|
8549
|
+
sock.end(payload);
|
|
8550
|
+
});
|
|
8551
|
+
sock.on("error", () => {
|
|
8552
|
+
});
|
|
8553
|
+
} catch {
|
|
8554
|
+
}
|
|
8555
|
+
}
|
|
8350
8556
|
var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
|
|
8351
8557
|
var UNDO_LATEST_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
|
|
8352
8558
|
var MAX_SNAPSHOTS = 10;
|
|
@@ -8529,6 +8735,9 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8529
8735
|
if (filesRes.status === 0) {
|
|
8530
8736
|
capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8531
8737
|
}
|
|
8738
|
+
if (capturedFiles.length === 0) {
|
|
8739
|
+
return prevEntry.hash;
|
|
8740
|
+
}
|
|
8532
8741
|
const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
|
|
8533
8742
|
env: shadowEnv,
|
|
8534
8743
|
timeout: GIT_TIMEOUT
|
|
@@ -8566,13 +8775,15 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8566
8775
|
}
|
|
8567
8776
|
if (cwdCount > MAX_SNAPSHOTS) stack.splice(oldestCwdIdx, 1);
|
|
8568
8777
|
writeStack(stack);
|
|
8778
|
+
const entry = stack[stack.length - 1];
|
|
8779
|
+
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8569
8780
|
import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8570
8781
|
if (shouldGc) {
|
|
8571
8782
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8572
8783
|
}
|
|
8573
8784
|
return commitHash;
|
|
8574
|
-
} catch (
|
|
8575
|
-
if (process.env.NODE9_DEBUG === "1") console.error("[Node9 Undo Engine Error]:",
|
|
8785
|
+
} catch (err2) {
|
|
8786
|
+
if (process.env.NODE9_DEBUG === "1") console.error("[Node9 Undo Engine Error]:", err2);
|
|
8576
8787
|
return null;
|
|
8577
8788
|
} finally {
|
|
8578
8789
|
if (indexFile) {
|
|
@@ -8676,11 +8887,11 @@ function registerCheckCommand(program2) {
|
|
|
8676
8887
|
let payload = JSON.parse(raw);
|
|
8677
8888
|
try {
|
|
8678
8889
|
payload = JSON.parse(raw);
|
|
8679
|
-
} catch (
|
|
8890
|
+
} catch (err2) {
|
|
8680
8891
|
const tempConfig = getConfig();
|
|
8681
8892
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
8682
8893
|
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8683
|
-
const errMsg =
|
|
8894
|
+
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
8684
8895
|
import_fs18.default.appendFileSync(
|
|
8685
8896
|
logPath,
|
|
8686
8897
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
@@ -8801,10 +9012,10 @@ RAW: ${raw}
|
|
|
8801
9012
|
...result,
|
|
8802
9013
|
blockedByLabel: result.blockedByLabel
|
|
8803
9014
|
});
|
|
8804
|
-
} catch (
|
|
9015
|
+
} catch (err2) {
|
|
8805
9016
|
if (process.env.NODE9_DEBUG === "1") {
|
|
8806
9017
|
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8807
|
-
const errMsg =
|
|
9018
|
+
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
8808
9019
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
8809
9020
|
`);
|
|
8810
9021
|
}
|
|
@@ -8950,8 +9161,8 @@ function registerLogCommand(program2) {
|
|
|
8950
9161
|
if (shouldSnapshot(tool, {}, config)) {
|
|
8951
9162
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
8952
9163
|
}
|
|
8953
|
-
} catch (
|
|
8954
|
-
const msg =
|
|
9164
|
+
} catch (err2) {
|
|
9165
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
8955
9166
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
8956
9167
|
`);
|
|
8957
9168
|
const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
@@ -9723,25 +9934,79 @@ var import_chalk11 = __toESM(require("chalk"));
|
|
|
9723
9934
|
var import_fs23 = __toESM(require("fs"));
|
|
9724
9935
|
var import_path25 = __toESM(require("path"));
|
|
9725
9936
|
var import_os19 = __toESM(require("os"));
|
|
9937
|
+
var import_https = __toESM(require("https"));
|
|
9726
9938
|
init_core();
|
|
9939
|
+
function fireTelemetryPing(agents) {
|
|
9940
|
+
try {
|
|
9941
|
+
const body = JSON.stringify({
|
|
9942
|
+
event: "init_completed",
|
|
9943
|
+
agents_detected: agents,
|
|
9944
|
+
os: process.platform,
|
|
9945
|
+
node9_version: process.env.npm_package_version ?? "unknown"
|
|
9946
|
+
});
|
|
9947
|
+
const req = import_https.default.request(
|
|
9948
|
+
{
|
|
9949
|
+
hostname: "api.node9.ai",
|
|
9950
|
+
path: "/api/v1/telemetry",
|
|
9951
|
+
method: "POST",
|
|
9952
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
9953
|
+
timeout: 3e3
|
|
9954
|
+
},
|
|
9955
|
+
(res) => {
|
|
9956
|
+
res.resume();
|
|
9957
|
+
}
|
|
9958
|
+
);
|
|
9959
|
+
req.on("error", () => {
|
|
9960
|
+
});
|
|
9961
|
+
req.on("timeout", () => {
|
|
9962
|
+
req.destroy();
|
|
9963
|
+
});
|
|
9964
|
+
req.end(body);
|
|
9965
|
+
} catch {
|
|
9966
|
+
}
|
|
9967
|
+
}
|
|
9727
9968
|
function registerInitCommand(program2) {
|
|
9728
9969
|
program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
|
|
9729
9970
|
console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
9971
|
+
let chosenMode = options.mode.toLowerCase();
|
|
9972
|
+
if (!["standard", "strict", "audit"].includes(chosenMode)) {
|
|
9973
|
+
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
9974
|
+
}
|
|
9975
|
+
{
|
|
9976
|
+
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
9977
|
+
const enableShields = await confirm3({
|
|
9978
|
+
message: "Enable recommended safety shields? (blocks rm -rf, SQL drops, pipe-to-shell)",
|
|
9979
|
+
default: true
|
|
9980
|
+
});
|
|
9981
|
+
if (enableShields) chosenMode = "standard";
|
|
9982
|
+
console.log("");
|
|
9983
|
+
}
|
|
9730
9984
|
const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
|
|
9731
9985
|
if (import_fs23.default.existsSync(configPath) && !options.force) {
|
|
9732
|
-
|
|
9986
|
+
try {
|
|
9987
|
+
const existing = JSON.parse(import_fs23.default.readFileSync(configPath, "utf-8"));
|
|
9988
|
+
const settings = existing.settings ?? {};
|
|
9989
|
+
if (settings.mode !== chosenMode) {
|
|
9990
|
+
settings.mode = chosenMode;
|
|
9991
|
+
existing.settings = settings;
|
|
9992
|
+
import_fs23.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
9993
|
+
console.log(import_chalk11.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
9994
|
+
} else {
|
|
9995
|
+
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
9996
|
+
}
|
|
9997
|
+
} catch {
|
|
9998
|
+
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
9999
|
+
}
|
|
9733
10000
|
} else {
|
|
9734
|
-
const requestedMode = options.mode.toLowerCase();
|
|
9735
|
-
const safeMode = ["standard", "strict", "audit"].includes(requestedMode) ? requestedMode : DEFAULT_CONFIG.settings.mode;
|
|
9736
10001
|
const configToSave = {
|
|
9737
10002
|
...DEFAULT_CONFIG,
|
|
9738
|
-
settings: { ...DEFAULT_CONFIG.settings, mode:
|
|
10003
|
+
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
9739
10004
|
};
|
|
9740
10005
|
const dir = import_path25.default.dirname(configPath);
|
|
9741
10006
|
if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
|
|
9742
|
-
import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
10007
|
+
import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
9743
10008
|
console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
|
|
9744
|
-
console.log(import_chalk11.default.gray(` Mode: ${
|
|
10009
|
+
console.log(import_chalk11.default.gray(` Mode: ${chosenMode}`));
|
|
9745
10010
|
}
|
|
9746
10011
|
if (options.skipSetup) return;
|
|
9747
10012
|
console.log("");
|
|
@@ -9768,14 +10033,20 @@ function registerInitCommand(program2) {
|
|
|
9768
10033
|
else if (agent === "cursor") await setupCursor();
|
|
9769
10034
|
console.log("");
|
|
9770
10035
|
}
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
10036
|
+
{
|
|
10037
|
+
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10038
|
+
const sendTelemetry = await confirm3({
|
|
10039
|
+
message: "Send anonymous usage stats to help improve node9? (no code, no args)",
|
|
10040
|
+
default: true
|
|
10041
|
+
});
|
|
10042
|
+
if (sendTelemetry) fireTelemetryPing(found);
|
|
9775
10043
|
console.log("");
|
|
9776
10044
|
}
|
|
9777
10045
|
console.log(import_chalk11.default.green.bold("\u{1F6E1}\uFE0F Node9 is ready!"));
|
|
9778
|
-
console.log(
|
|
10046
|
+
console.log("");
|
|
10047
|
+
console.log(import_chalk11.default.white(" Start watching: ") + import_chalk11.default.cyan("node9 tail"));
|
|
10048
|
+
console.log(import_chalk11.default.white(" Browser view: ") + import_chalk11.default.cyan("node9 daemon --openui"));
|
|
10049
|
+
console.log(import_chalk11.default.white(" Cloud dashboard: ") + import_chalk11.default.cyan("node9.ai"));
|
|
9779
10050
|
});
|
|
9780
10051
|
}
|
|
9781
10052
|
|
|
@@ -10337,6 +10608,347 @@ function registerMcpGatewayCommand(program2) {
|
|
|
10337
10608
|
});
|
|
10338
10609
|
}
|
|
10339
10610
|
|
|
10611
|
+
// src/mcp-server/index.ts
|
|
10612
|
+
var import_readline4 = __toESM(require("readline"));
|
|
10613
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10614
|
+
var import_os20 = __toESM(require("os"));
|
|
10615
|
+
var import_path27 = __toESM(require("path"));
|
|
10616
|
+
init_core();
|
|
10617
|
+
init_daemon();
|
|
10618
|
+
init_shields();
|
|
10619
|
+
function ok(id, result) {
|
|
10620
|
+
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
|
|
10621
|
+
}
|
|
10622
|
+
function err(id, code, message) {
|
|
10623
|
+
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, error: { code, message } });
|
|
10624
|
+
}
|
|
10625
|
+
var TOOLS = [
|
|
10626
|
+
{
|
|
10627
|
+
name: "node9_status",
|
|
10628
|
+
description: "Show the current node9 protection status: mode, daemon state, undo engine, pause state, active shields, and whether agent hooks are wired. Use this to understand what protection is active before doing risky work.",
|
|
10629
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
10630
|
+
},
|
|
10631
|
+
{
|
|
10632
|
+
name: "node9_config_get",
|
|
10633
|
+
description: "Read the current node9 configuration: security mode, approver channels, timeouts, DLP settings, and the number of active smart rules. Returns the merged config (env > cloud > project > global > defaults).",
|
|
10634
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
10635
|
+
},
|
|
10636
|
+
{
|
|
10637
|
+
name: "node9_shield_list",
|
|
10638
|
+
description: "List all available node9 shields and which ones are currently active. Shields are pre-packaged rule sets for specific services (postgres, aws, github, filesystem).",
|
|
10639
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
10640
|
+
},
|
|
10641
|
+
{
|
|
10642
|
+
name: "node9_shield_enable",
|
|
10643
|
+
description: "Enable a node9 shield for a specific service. Shields only add protection \u2014 they cannot be used to weaken or bypass node9. Use node9_shield_list to see available shield names.",
|
|
10644
|
+
inputSchema: {
|
|
10645
|
+
type: "object",
|
|
10646
|
+
properties: {
|
|
10647
|
+
service: {
|
|
10648
|
+
type: "string",
|
|
10649
|
+
description: 'Shield name to enable (e.g. "postgres", "aws", "github", "filesystem").'
|
|
10650
|
+
}
|
|
10651
|
+
},
|
|
10652
|
+
required: ["service"]
|
|
10653
|
+
}
|
|
10654
|
+
},
|
|
10655
|
+
{
|
|
10656
|
+
name: "node9_approver_list",
|
|
10657
|
+
description: "List all node9 approver channels and their current enabled/disabled state. Approvers are the channels through which node9 asks a human to approve risky tool calls. Channels: native (OS popup), browser (web UI), cloud (team policy server), terminal (stdin).",
|
|
10658
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
10659
|
+
},
|
|
10660
|
+
{
|
|
10661
|
+
name: "node9_approver_set",
|
|
10662
|
+
description: "Enable or disable a specific node9 approver channel in the global config (~/.node9/config.json). Use this to turn individual channels on or off without touching other settings. Channels: native, browser, cloud, terminal. WARNING: disabling all approvers means node9 cannot prompt for human approval \u2014 use with care.",
|
|
10663
|
+
inputSchema: {
|
|
10664
|
+
type: "object",
|
|
10665
|
+
properties: {
|
|
10666
|
+
channel: {
|
|
10667
|
+
type: "string",
|
|
10668
|
+
enum: ["native", "browser", "cloud", "terminal"],
|
|
10669
|
+
description: "Approver channel to configure."
|
|
10670
|
+
},
|
|
10671
|
+
enabled: {
|
|
10672
|
+
type: "boolean",
|
|
10673
|
+
description: "true to enable the channel, false to disable it."
|
|
10674
|
+
}
|
|
10675
|
+
},
|
|
10676
|
+
required: ["channel", "enabled"]
|
|
10677
|
+
}
|
|
10678
|
+
},
|
|
10679
|
+
{
|
|
10680
|
+
name: "node9_undo_list",
|
|
10681
|
+
description: "List the node9 snapshot history. Each entry shows the git hash, tool that triggered it, a short summary, affected files, working directory, and timestamp. Use this to find a hash before calling node9_undo_revert.",
|
|
10682
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
10683
|
+
},
|
|
10684
|
+
{
|
|
10685
|
+
name: "node9_undo_revert",
|
|
10686
|
+
description: "Revert the working directory to a specific node9 snapshot. Call node9_undo_list first to find the hash you want to restore. WARNING: this overwrites current files \u2014 any unsaved work will be lost.",
|
|
10687
|
+
inputSchema: {
|
|
10688
|
+
type: "object",
|
|
10689
|
+
properties: {
|
|
10690
|
+
hash: {
|
|
10691
|
+
type: "string",
|
|
10692
|
+
description: "The full git commit hash from node9_undo_list."
|
|
10693
|
+
},
|
|
10694
|
+
cwd: {
|
|
10695
|
+
type: "string",
|
|
10696
|
+
description: "Absolute path to the project directory. Defaults to process.cwd()."
|
|
10697
|
+
}
|
|
10698
|
+
},
|
|
10699
|
+
required: ["hash"]
|
|
10700
|
+
}
|
|
10701
|
+
}
|
|
10702
|
+
];
|
|
10703
|
+
function handleStatus() {
|
|
10704
|
+
const config = getConfig();
|
|
10705
|
+
const settings = config.settings;
|
|
10706
|
+
const paused = checkPause();
|
|
10707
|
+
const daemonUp = isDaemonRunning();
|
|
10708
|
+
const activeShields = readActiveShields();
|
|
10709
|
+
const lines = [];
|
|
10710
|
+
lines.push(`Mode: ${settings.mode}`);
|
|
10711
|
+
lines.push(`Daemon: ${daemonUp ? "running" : "stopped"}`);
|
|
10712
|
+
lines.push(`Undo engine: ${settings.enableUndo ? "enabled" : "disabled"}`);
|
|
10713
|
+
if (paused.paused) {
|
|
10714
|
+
const until = paused.expiresAt ? new Date(paused.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
10715
|
+
lines.push(`PAUSED until ${until} \u2014 all tool calls currently allowed`);
|
|
10716
|
+
} else {
|
|
10717
|
+
lines.push(`Pause state: not paused`);
|
|
10718
|
+
}
|
|
10719
|
+
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
10720
|
+
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
10721
|
+
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
10722
|
+
const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
|
|
10723
|
+
const globalConfig = import_path27.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
10724
|
+
lines.push(
|
|
10725
|
+
`Project config (node9.config.json): ${import_fs24.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
10726
|
+
);
|
|
10727
|
+
lines.push(
|
|
10728
|
+
`Global config (~/.node9/config.json): ${import_fs24.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
10729
|
+
);
|
|
10730
|
+
return lines.join("\n");
|
|
10731
|
+
}
|
|
10732
|
+
function handleConfigGet() {
|
|
10733
|
+
const config = getConfig();
|
|
10734
|
+
const s = config.settings;
|
|
10735
|
+
const lines = [
|
|
10736
|
+
`mode: ${s.mode}`,
|
|
10737
|
+
`enableUndo: ${s.enableUndo}`,
|
|
10738
|
+
`flightRecorder: ${s.flightRecorder}`,
|
|
10739
|
+
`approvalTimeoutMs: ${s.approvalTimeoutMs}`,
|
|
10740
|
+
`approvers:`,
|
|
10741
|
+
` native: ${s.approvers.native}`,
|
|
10742
|
+
` browser: ${s.approvers.browser}`,
|
|
10743
|
+
` cloud: ${s.approvers.cloud}`,
|
|
10744
|
+
` terminal: ${s.approvers.terminal}`,
|
|
10745
|
+
`dlp.enabled: ${config.policy.dlp?.enabled !== false}`,
|
|
10746
|
+
`dlp.scanIgnoredTools: ${config.policy.dlp?.scanIgnoredTools !== false}`,
|
|
10747
|
+
`smartRules: ${config.policy.smartRules.length} active`,
|
|
10748
|
+
`sandboxPaths: ${config.policy.sandboxPaths.length > 0 ? config.policy.sandboxPaths.join(", ") : "none"}`
|
|
10749
|
+
];
|
|
10750
|
+
return lines.join("\n");
|
|
10751
|
+
}
|
|
10752
|
+
function handleShieldList() {
|
|
10753
|
+
const all = listShields();
|
|
10754
|
+
const active = new Set(readActiveShields());
|
|
10755
|
+
if (all.length === 0) return "No shields available.";
|
|
10756
|
+
const lines = all.map((shield) => {
|
|
10757
|
+
const on = active.has(shield.name);
|
|
10758
|
+
const ruleCount = shield.smartRules.length;
|
|
10759
|
+
return `${on ? "[active]" : "[off] "} ${shield.name.padEnd(12)} \u2014 ${shield.description ?? ""} (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`;
|
|
10760
|
+
});
|
|
10761
|
+
lines.unshift(`${active.size} of ${all.length} shields active:
|
|
10762
|
+
`);
|
|
10763
|
+
return lines.join("\n");
|
|
10764
|
+
}
|
|
10765
|
+
function handleShieldEnable(args) {
|
|
10766
|
+
const service = args.service;
|
|
10767
|
+
if (typeof service !== "string" || !service) {
|
|
10768
|
+
throw new Error("service is required");
|
|
10769
|
+
}
|
|
10770
|
+
const name = resolveShieldName(service);
|
|
10771
|
+
if (!name) {
|
|
10772
|
+
throw new Error(
|
|
10773
|
+
`Unknown shield: "${service}". Run node9_shield_list to see available shields.`
|
|
10774
|
+
);
|
|
10775
|
+
}
|
|
10776
|
+
const active = readActiveShields();
|
|
10777
|
+
if (active.includes(name)) {
|
|
10778
|
+
return `Shield "${name}" is already active.`;
|
|
10779
|
+
}
|
|
10780
|
+
writeActiveShields([...active, name]);
|
|
10781
|
+
const shield = getShield(name);
|
|
10782
|
+
return `Shield "${name}" enabled \u2014 ${shield.smartRules.length} smart rule${shield.smartRules.length === 1 ? "" : "s"} now active.`;
|
|
10783
|
+
}
|
|
10784
|
+
var GLOBAL_CONFIG_PATH2 = import_path27.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
10785
|
+
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
10786
|
+
function readGlobalConfigRaw() {
|
|
10787
|
+
try {
|
|
10788
|
+
if (import_fs24.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
10789
|
+
return JSON.parse(import_fs24.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
10790
|
+
}
|
|
10791
|
+
} catch {
|
|
10792
|
+
}
|
|
10793
|
+
return {};
|
|
10794
|
+
}
|
|
10795
|
+
function writeGlobalConfigRaw(data) {
|
|
10796
|
+
const dir = import_path27.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
10797
|
+
if (!import_fs24.default.existsSync(dir)) import_fs24.default.mkdirSync(dir, { recursive: true });
|
|
10798
|
+
import_fs24.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
10799
|
+
}
|
|
10800
|
+
function handleApproverList() {
|
|
10801
|
+
const config = getConfig();
|
|
10802
|
+
const approvers = config.settings.approvers;
|
|
10803
|
+
const lines = ["Approver channels:\n"];
|
|
10804
|
+
for (const ch of APPROVER_CHANNELS) {
|
|
10805
|
+
const on = approvers[ch];
|
|
10806
|
+
lines.push(` ${on ? "[enabled] " : "[disabled]"} ${ch}`);
|
|
10807
|
+
}
|
|
10808
|
+
const enabledCount = APPROVER_CHANNELS.filter((ch) => approvers[ch]).length;
|
|
10809
|
+
if (enabledCount === 0) {
|
|
10810
|
+
lines.push("\nWARNING: all approver channels are disabled \u2014 node9 cannot prompt for approval.");
|
|
10811
|
+
}
|
|
10812
|
+
return lines.join("\n");
|
|
10813
|
+
}
|
|
10814
|
+
function handleApproverSet(args) {
|
|
10815
|
+
const channel = args.channel;
|
|
10816
|
+
const enabled = args.enabled;
|
|
10817
|
+
if (!channel || !APPROVER_CHANNELS.includes(channel)) {
|
|
10818
|
+
throw new Error(
|
|
10819
|
+
`Invalid channel: "${channel}". Must be one of: ${APPROVER_CHANNELS.join(", ")}.`
|
|
10820
|
+
);
|
|
10821
|
+
}
|
|
10822
|
+
if (typeof enabled !== "boolean") {
|
|
10823
|
+
throw new Error("enabled must be a boolean (true or false).");
|
|
10824
|
+
}
|
|
10825
|
+
const raw = readGlobalConfigRaw();
|
|
10826
|
+
const settings = raw.settings ?? {};
|
|
10827
|
+
const approvers = settings.approvers ?? {};
|
|
10828
|
+
approvers[channel] = enabled;
|
|
10829
|
+
settings.approvers = approvers;
|
|
10830
|
+
raw.settings = settings;
|
|
10831
|
+
writeGlobalConfigRaw(raw);
|
|
10832
|
+
const currentApprovers = getConfig().settings.approvers;
|
|
10833
|
+
const anyEnabled = APPROVER_CHANNELS.some(
|
|
10834
|
+
(ch) => ch === channel ? enabled : currentApprovers[ch]
|
|
10835
|
+
);
|
|
10836
|
+
const suffix = anyEnabled ? "" : "\nWARNING: all approver channels are now disabled \u2014 node9 cannot prompt for approval.";
|
|
10837
|
+
return `Approver channel "${channel}" ${enabled ? "enabled" : "disabled"} in ~/.node9/config.json.${suffix}`;
|
|
10838
|
+
}
|
|
10839
|
+
function handleUndoList() {
|
|
10840
|
+
const history = getSnapshotHistory();
|
|
10841
|
+
if (history.length === 0) {
|
|
10842
|
+
return "No snapshots found. Node9 captures snapshots automatically before file edits.";
|
|
10843
|
+
}
|
|
10844
|
+
const lines = history.slice().reverse().map((entry, i) => {
|
|
10845
|
+
const date = new Date(entry.timestamp).toLocaleString();
|
|
10846
|
+
const files = entry.files?.length ? `${entry.files.length} file(s)` : "unknown files";
|
|
10847
|
+
const summary = entry.argsSummary ? ` \u2014 ${entry.argsSummary}` : "";
|
|
10848
|
+
return `[${i + 1}] ${entry.hash.slice(0, 7)} ${date} ${entry.tool}${summary} (${files}) cwd: ${entry.cwd}
|
|
10849
|
+
full hash: ${entry.hash}`;
|
|
10850
|
+
});
|
|
10851
|
+
return lines.join("\n\n");
|
|
10852
|
+
}
|
|
10853
|
+
function handleUndoRevert(args) {
|
|
10854
|
+
const hash = args.hash;
|
|
10855
|
+
if (typeof hash !== "string" || !hash) {
|
|
10856
|
+
throw new Error("hash is required and must be a non-empty string");
|
|
10857
|
+
}
|
|
10858
|
+
if (!/^[0-9a-f]{7,40}$/i.test(hash)) {
|
|
10859
|
+
throw new Error(`Invalid hash format: ${hash}`);
|
|
10860
|
+
}
|
|
10861
|
+
const cwd = typeof args.cwd === "string" && args.cwd ? args.cwd : process.cwd();
|
|
10862
|
+
const success = applyUndo(hash, cwd);
|
|
10863
|
+
if (!success) {
|
|
10864
|
+
throw new Error(
|
|
10865
|
+
`Revert failed for hash ${hash}. The snapshot may not exist for this directory, or git encountered an error.`
|
|
10866
|
+
);
|
|
10867
|
+
}
|
|
10868
|
+
return `Successfully reverted to snapshot ${hash.slice(0, 7)} in ${cwd}.`;
|
|
10869
|
+
}
|
|
10870
|
+
function runMcpServer() {
|
|
10871
|
+
const rl = import_readline4.default.createInterface({ input: process.stdin, terminal: false });
|
|
10872
|
+
rl.on("line", (line) => {
|
|
10873
|
+
let msg;
|
|
10874
|
+
try {
|
|
10875
|
+
msg = JSON.parse(line);
|
|
10876
|
+
} catch {
|
|
10877
|
+
process.stdout.write(err(null, -32700, "Parse error") + "\n");
|
|
10878
|
+
return;
|
|
10879
|
+
}
|
|
10880
|
+
const { method, id, params } = msg;
|
|
10881
|
+
if (method === "initialize") {
|
|
10882
|
+
process.stdout.write(
|
|
10883
|
+
ok(id, {
|
|
10884
|
+
protocolVersion: "2024-11-05",
|
|
10885
|
+
serverInfo: { name: "node9", version: "1.0.0" },
|
|
10886
|
+
capabilities: { tools: {} }
|
|
10887
|
+
}) + "\n"
|
|
10888
|
+
);
|
|
10889
|
+
return;
|
|
10890
|
+
}
|
|
10891
|
+
if (id === void 0 || id === null) {
|
|
10892
|
+
return;
|
|
10893
|
+
}
|
|
10894
|
+
if (method === "tools/list") {
|
|
10895
|
+
process.stdout.write(ok(id, { tools: TOOLS }) + "\n");
|
|
10896
|
+
return;
|
|
10897
|
+
}
|
|
10898
|
+
if (method === "tools/call") {
|
|
10899
|
+
const p = params ?? {};
|
|
10900
|
+
const toolName = p.name;
|
|
10901
|
+
const toolArgs = p.arguments ?? {};
|
|
10902
|
+
try {
|
|
10903
|
+
let text;
|
|
10904
|
+
if (toolName === "node9_status") {
|
|
10905
|
+
text = handleStatus();
|
|
10906
|
+
} else if (toolName === "node9_config_get") {
|
|
10907
|
+
text = handleConfigGet();
|
|
10908
|
+
} else if (toolName === "node9_shield_list") {
|
|
10909
|
+
text = handleShieldList();
|
|
10910
|
+
} else if (toolName === "node9_shield_enable") {
|
|
10911
|
+
text = handleShieldEnable(toolArgs);
|
|
10912
|
+
} else if (toolName === "node9_approver_list") {
|
|
10913
|
+
text = handleApproverList();
|
|
10914
|
+
} else if (toolName === "node9_approver_set") {
|
|
10915
|
+
text = handleApproverSet(toolArgs);
|
|
10916
|
+
} else if (toolName === "node9_undo_list") {
|
|
10917
|
+
text = handleUndoList();
|
|
10918
|
+
} else if (toolName === "node9_undo_revert") {
|
|
10919
|
+
text = handleUndoRevert(toolArgs);
|
|
10920
|
+
} else {
|
|
10921
|
+
process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
|
|
10922
|
+
return;
|
|
10923
|
+
}
|
|
10924
|
+
process.stdout.write(ok(id, { content: [{ type: "text", text }] }) + "\n");
|
|
10925
|
+
} catch (e) {
|
|
10926
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
10927
|
+
process.stdout.write(
|
|
10928
|
+
ok(id, {
|
|
10929
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
10930
|
+
isError: true
|
|
10931
|
+
}) + "\n"
|
|
10932
|
+
);
|
|
10933
|
+
}
|
|
10934
|
+
return;
|
|
10935
|
+
}
|
|
10936
|
+
process.stdout.write(err(id, -32601, `Method not found: ${method}`) + "\n");
|
|
10937
|
+
});
|
|
10938
|
+
rl.on("close", () => {
|
|
10939
|
+
process.exit(0);
|
|
10940
|
+
});
|
|
10941
|
+
}
|
|
10942
|
+
|
|
10943
|
+
// src/cli/commands/mcp-server.ts
|
|
10944
|
+
function registerMcpServerCommand(program2) {
|
|
10945
|
+
program2.command("mcp-server").description(
|
|
10946
|
+
"Run the Node9 MCP server \u2014 exposes node9 tools (undo, rules, \u2026) to Claude, Cursor, and Gemini"
|
|
10947
|
+
).action(() => {
|
|
10948
|
+
runMcpServer();
|
|
10949
|
+
});
|
|
10950
|
+
}
|
|
10951
|
+
|
|
10340
10952
|
// src/cli/commands/trust.ts
|
|
10341
10953
|
var import_chalk16 = __toESM(require("chalk"));
|
|
10342
10954
|
init_trusted_hosts();
|
|
@@ -10394,20 +11006,20 @@ function registerTrustCommand(program2) {
|
|
|
10394
11006
|
|
|
10395
11007
|
// src/cli.ts
|
|
10396
11008
|
var { version } = JSON.parse(
|
|
10397
|
-
|
|
11009
|
+
import_fs27.default.readFileSync(import_path30.default.join(__dirname, "../package.json"), "utf-8")
|
|
10398
11010
|
);
|
|
10399
11011
|
var program = new import_commander.Command();
|
|
10400
11012
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
10401
11013
|
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) => {
|
|
10402
11014
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
10403
|
-
const credPath =
|
|
10404
|
-
if (!
|
|
10405
|
-
|
|
11015
|
+
const credPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "credentials.json");
|
|
11016
|
+
if (!import_fs27.default.existsSync(import_path30.default.dirname(credPath)))
|
|
11017
|
+
import_fs27.default.mkdirSync(import_path30.default.dirname(credPath), { recursive: true });
|
|
10406
11018
|
const profileName = options.profile || "default";
|
|
10407
11019
|
let existingCreds = {};
|
|
10408
11020
|
try {
|
|
10409
|
-
if (
|
|
10410
|
-
const raw = JSON.parse(
|
|
11021
|
+
if (import_fs27.default.existsSync(credPath)) {
|
|
11022
|
+
const raw = JSON.parse(import_fs27.default.readFileSync(credPath, "utf-8"));
|
|
10411
11023
|
if (raw.apiKey) {
|
|
10412
11024
|
existingCreds = {
|
|
10413
11025
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -10419,13 +11031,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
10419
11031
|
} catch {
|
|
10420
11032
|
}
|
|
10421
11033
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
10422
|
-
|
|
11034
|
+
import_fs27.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
10423
11035
|
if (profileName === "default") {
|
|
10424
|
-
const configPath =
|
|
11036
|
+
const configPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
|
|
10425
11037
|
let config = {};
|
|
10426
11038
|
try {
|
|
10427
|
-
if (
|
|
10428
|
-
config = JSON.parse(
|
|
11039
|
+
if (import_fs27.default.existsSync(configPath))
|
|
11040
|
+
config = JSON.parse(import_fs27.default.readFileSync(configPath, "utf-8"));
|
|
10429
11041
|
} catch {
|
|
10430
11042
|
}
|
|
10431
11043
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -10440,9 +11052,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
10440
11052
|
approvers.cloud = false;
|
|
10441
11053
|
}
|
|
10442
11054
|
s.approvers = approvers;
|
|
10443
|
-
if (!
|
|
10444
|
-
|
|
10445
|
-
|
|
11055
|
+
if (!import_fs27.default.existsSync(import_path30.default.dirname(configPath)))
|
|
11056
|
+
import_fs27.default.mkdirSync(import_path30.default.dirname(configPath), { recursive: true });
|
|
11057
|
+
import_fs27.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
10446
11058
|
}
|
|
10447
11059
|
if (options.profile && profileName !== "default") {
|
|
10448
11060
|
console.log(import_chalk18.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -10502,8 +11114,8 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
10502
11114
|
`));
|
|
10503
11115
|
try {
|
|
10504
11116
|
fn();
|
|
10505
|
-
} catch (
|
|
10506
|
-
console.error(import_chalk18.default.red(` \u26A0\uFE0F Failed: ${
|
|
11117
|
+
} catch (err2) {
|
|
11118
|
+
console.error(import_chalk18.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
10507
11119
|
process.exit(1);
|
|
10508
11120
|
}
|
|
10509
11121
|
console.log(import_chalk18.default.gray("\n Restart the agent for changes to take effect."));
|
|
@@ -10526,25 +11138,25 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
10526
11138
|
]) {
|
|
10527
11139
|
try {
|
|
10528
11140
|
fn();
|
|
10529
|
-
} catch (
|
|
11141
|
+
} catch (err2) {
|
|
10530
11142
|
teardownFailed = true;
|
|
10531
11143
|
console.error(
|
|
10532
11144
|
import_chalk18.default.red(
|
|
10533
|
-
` \u26A0\uFE0F Failed to remove ${label} hooks: ${
|
|
11145
|
+
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
10534
11146
|
)
|
|
10535
11147
|
);
|
|
10536
11148
|
}
|
|
10537
11149
|
}
|
|
10538
11150
|
if (options.purge) {
|
|
10539
|
-
const node9Dir =
|
|
10540
|
-
if (
|
|
11151
|
+
const node9Dir = import_path30.default.join(import_os23.default.homedir(), ".node9");
|
|
11152
|
+
if (import_fs27.default.existsSync(node9Dir)) {
|
|
10541
11153
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
10542
11154
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
10543
11155
|
default: false
|
|
10544
11156
|
});
|
|
10545
11157
|
if (confirmed) {
|
|
10546
|
-
|
|
10547
|
-
if (
|
|
11158
|
+
import_fs27.default.rmSync(node9Dir, { recursive: true });
|
|
11159
|
+
if (import_fs27.default.existsSync(node9Dir)) {
|
|
10548
11160
|
console.error(
|
|
10549
11161
|
import_chalk18.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
10550
11162
|
);
|
|
@@ -10651,13 +11263,14 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
10651
11263
|
const { startTail: startTail2 } = await Promise.resolve().then(() => (init_tail(), tail_exports));
|
|
10652
11264
|
try {
|
|
10653
11265
|
await startTail2(options);
|
|
10654
|
-
} catch (
|
|
10655
|
-
console.error(import_chalk18.default.red(`\u274C ${
|
|
11266
|
+
} catch (err2) {
|
|
11267
|
+
console.error(import_chalk18.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
10656
11268
|
process.exit(1);
|
|
10657
11269
|
}
|
|
10658
11270
|
});
|
|
10659
11271
|
registerWatchCommand(program);
|
|
10660
11272
|
registerMcpGatewayCommand(program);
|
|
11273
|
+
registerMcpServerCommand(program);
|
|
10661
11274
|
registerCheckCommand(program);
|
|
10662
11275
|
registerLogCommand(program);
|
|
10663
11276
|
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").action(async () => {
|
|
@@ -10760,9 +11373,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
10760
11373
|
const isCheckHook = process.argv[2] === "check";
|
|
10761
11374
|
if (isCheckHook) {
|
|
10762
11375
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
10763
|
-
const logPath =
|
|
11376
|
+
const logPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
|
|
10764
11377
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
10765
|
-
|
|
11378
|
+
import_fs27.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
10766
11379
|
`);
|
|
10767
11380
|
}
|
|
10768
11381
|
process.exit(0);
|