@node9/proxy 1.0.17 → 1.0.19
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 +3 -0
- package/dist/cli.js +252 -16
- package/dist/cli.mjs +252 -16
- package/dist/index.js +10 -3
- package/dist/index.mjs +10 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@node9/proxy)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://huggingface.co/spaces/Node9ai/node9-security-demo)
|
|
8
|
+
[](https://node9.ai/docs)
|
|
8
9
|
|
|
9
10
|
**Node9** is the execution security layer for the Agentic Era. It encases autonomous AI Agents (Claude Code, Gemini CLI, Cursor, MCP Servers) in a deterministic security wrapper, intercepting dangerous shell commands and tool calls before they execute.
|
|
10
11
|
|
|
11
12
|
While others try to _guess_ if a prompt is malicious (Semantic Security), Node9 _governs_ the actual action (Execution Security).
|
|
12
13
|
|
|
14
|
+
📖 **[Full Documentation →](https://node9.ai/docs)**
|
|
15
|
+
|
|
13
16
|
---
|
|
14
17
|
|
|
15
18
|
## 💎 The "Aha!" Moment
|
package/dist/cli.js
CHANGED
|
@@ -2404,7 +2404,7 @@ var init_core = __esm({
|
|
|
2404
2404
|
{
|
|
2405
2405
|
field: "command",
|
|
2406
2406
|
op: "matches",
|
|
2407
|
-
value: "git
|
|
2407
|
+
value: "^\\s*git\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
2408
2408
|
flags: "i"
|
|
2409
2409
|
}
|
|
2410
2410
|
],
|
|
@@ -2415,7 +2415,14 @@ var init_core = __esm({
|
|
|
2415
2415
|
{
|
|
2416
2416
|
name: "review-git-push",
|
|
2417
2417
|
tool: "bash",
|
|
2418
|
-
conditions: [
|
|
2418
|
+
conditions: [
|
|
2419
|
+
{
|
|
2420
|
+
field: "command",
|
|
2421
|
+
op: "matches",
|
|
2422
|
+
value: "^\\s*git\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
2423
|
+
flags: "i"
|
|
2424
|
+
}
|
|
2425
|
+
],
|
|
2419
2426
|
conditionMode: "all",
|
|
2420
2427
|
verdict: "review",
|
|
2421
2428
|
reason: "git push sends changes to a shared remote"
|
|
@@ -2427,7 +2434,7 @@ var init_core = __esm({
|
|
|
2427
2434
|
{
|
|
2428
2435
|
field: "command",
|
|
2429
2436
|
op: "matches",
|
|
2430
|
-
value: "git\\
|
|
2437
|
+
value: "^\\s*git\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
2431
2438
|
flags: "i"
|
|
2432
2439
|
}
|
|
2433
2440
|
],
|
|
@@ -4730,18 +4737,21 @@ function renderPending(activity) {
|
|
|
4730
4737
|
process.stdout.write(`${formatBase(activity)} ${import_chalk5.default.yellow("\u25CF \u2026")}\r`);
|
|
4731
4738
|
}
|
|
4732
4739
|
async function ensureDaemon() {
|
|
4740
|
+
let pidPort = null;
|
|
4733
4741
|
if (import_fs6.default.existsSync(PID_FILE)) {
|
|
4734
4742
|
try {
|
|
4735
4743
|
const { port } = JSON.parse(import_fs6.default.readFileSync(PID_FILE, "utf-8"));
|
|
4736
|
-
|
|
4744
|
+
pidPort = port;
|
|
4737
4745
|
} catch {
|
|
4746
|
+
console.error(import_chalk5.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
4738
4747
|
}
|
|
4739
4748
|
}
|
|
4749
|
+
const checkPort = pidPort ?? DAEMON_PORT2;
|
|
4740
4750
|
try {
|
|
4741
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
4751
|
+
const res = await fetch(`http://127.0.0.1:${checkPort}/settings`, {
|
|
4742
4752
|
signal: AbortSignal.timeout(500)
|
|
4743
4753
|
});
|
|
4744
|
-
if (res.ok) return
|
|
4754
|
+
if (res.ok) return checkPort;
|
|
4745
4755
|
} catch {
|
|
4746
4756
|
}
|
|
4747
4757
|
console.log(import_chalk5.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
@@ -4767,25 +4777,44 @@ async function ensureDaemon() {
|
|
|
4767
4777
|
async function startTail(options = {}) {
|
|
4768
4778
|
const port = await ensureDaemon();
|
|
4769
4779
|
if (options.clear) {
|
|
4770
|
-
await new Promise((resolve) => {
|
|
4780
|
+
const result = await new Promise((resolve) => {
|
|
4771
4781
|
const req2 = import_http2.default.request(
|
|
4772
4782
|
{ method: "POST", hostname: "127.0.0.1", port, path: "/events/clear" },
|
|
4773
4783
|
(res) => {
|
|
4784
|
+
const status = res.statusCode ?? 0;
|
|
4785
|
+
res.on(
|
|
4786
|
+
"end",
|
|
4787
|
+
() => resolve({
|
|
4788
|
+
ok: status >= 200 && status < 300,
|
|
4789
|
+
code: status >= 200 && status < 300 ? void 0 : `HTTP ${status}`
|
|
4790
|
+
})
|
|
4791
|
+
);
|
|
4774
4792
|
res.resume();
|
|
4775
|
-
res.on("end", resolve);
|
|
4776
4793
|
}
|
|
4777
4794
|
);
|
|
4778
|
-
req2.
|
|
4795
|
+
req2.once("error", (err) => resolve({ ok: false, code: err.code }));
|
|
4796
|
+
req2.setTimeout(2e3, () => {
|
|
4797
|
+
resolve({ ok: false, code: "ETIMEDOUT" });
|
|
4798
|
+
req2.destroy();
|
|
4799
|
+
});
|
|
4779
4800
|
req2.end();
|
|
4780
4801
|
});
|
|
4802
|
+
if (result.ok) {
|
|
4803
|
+
console.log(import_chalk5.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
4804
|
+
} else if (result.code === "ECONNREFUSED") {
|
|
4805
|
+
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
4806
|
+
} else if (result.code === "ETIMEDOUT") {
|
|
4807
|
+
throw new Error("Daemon did not respond in time. Try: node9 daemon restart");
|
|
4808
|
+
} else {
|
|
4809
|
+
throw new Error(`Failed to clear buffer (${result.code ?? "unknown error"})`);
|
|
4810
|
+
}
|
|
4811
|
+
return;
|
|
4781
4812
|
}
|
|
4782
4813
|
const connectionTime = Date.now();
|
|
4783
4814
|
const pending2 = /* @__PURE__ */ new Map();
|
|
4784
4815
|
console.log(import_chalk5.default.cyan.bold(`
|
|
4785
4816
|
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk5.default.dim(`\u2192 localhost:${port}`));
|
|
4786
|
-
if (options.
|
|
4787
|
-
console.log(import_chalk5.default.dim("History cleared. Showing live events. Press Ctrl+C to exit.\n"));
|
|
4788
|
-
} else if (options.history) {
|
|
4817
|
+
if (options.history) {
|
|
4789
4818
|
console.log(import_chalk5.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
4790
4819
|
} else {
|
|
4791
4820
|
console.log(
|
|
@@ -4918,6 +4947,7 @@ function fullPathCommand(subcommand) {
|
|
|
4918
4947
|
if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
|
|
4919
4948
|
const nodeExec = process.execPath;
|
|
4920
4949
|
const cliScript = process.argv[1];
|
|
4950
|
+
if (!cliScript.endsWith(".js")) return `${cliScript} ${subcommand}`;
|
|
4921
4951
|
return `${nodeExec} ${cliScript} ${subcommand}`;
|
|
4922
4952
|
}
|
|
4923
4953
|
function readJson(filePath) {
|
|
@@ -4934,6 +4964,126 @@ function writeJson(filePath, data) {
|
|
|
4934
4964
|
if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
4935
4965
|
import_fs3.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
4936
4966
|
}
|
|
4967
|
+
function isNode9Hook(cmd) {
|
|
4968
|
+
if (!cmd) return false;
|
|
4969
|
+
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
4970
|
+
}
|
|
4971
|
+
function teardownClaude() {
|
|
4972
|
+
const homeDir2 = import_os3.default.homedir();
|
|
4973
|
+
const hooksPath = import_path5.default.join(homeDir2, ".claude", "settings.json");
|
|
4974
|
+
const mcpPath = import_path5.default.join(homeDir2, ".claude.json");
|
|
4975
|
+
let changed = false;
|
|
4976
|
+
const settings = readJson(hooksPath);
|
|
4977
|
+
if (settings?.hooks) {
|
|
4978
|
+
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
4979
|
+
const before = settings.hooks[event]?.length ?? 0;
|
|
4980
|
+
settings.hooks[event] = settings.hooks[event]?.filter(
|
|
4981
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
4982
|
+
);
|
|
4983
|
+
if ((settings.hooks[event]?.length ?? 0) < before) changed = true;
|
|
4984
|
+
if (settings.hooks[event]?.length === 0) delete settings.hooks[event];
|
|
4985
|
+
}
|
|
4986
|
+
if (changed) {
|
|
4987
|
+
writeJson(hooksPath, settings);
|
|
4988
|
+
console.log(
|
|
4989
|
+
import_chalk3.default.green(" \u2705 Removed PreToolUse / PostToolUse hooks from ~/.claude/settings.json")
|
|
4990
|
+
);
|
|
4991
|
+
} else {
|
|
4992
|
+
console.log(import_chalk3.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.claude/settings.json"));
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
const claudeConfig = readJson(mcpPath);
|
|
4996
|
+
if (claudeConfig?.mcpServers) {
|
|
4997
|
+
let mcpChanged = false;
|
|
4998
|
+
for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
|
|
4999
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
5000
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
5001
|
+
claudeConfig.mcpServers[name] = {
|
|
5002
|
+
...server,
|
|
5003
|
+
command: originalCmd,
|
|
5004
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
5005
|
+
};
|
|
5006
|
+
mcpChanged = true;
|
|
5007
|
+
} else if (server.command === "node9") {
|
|
5008
|
+
console.warn(
|
|
5009
|
+
import_chalk3.default.yellow(
|
|
5010
|
+
` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
|
|
5011
|
+
)
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
if (mcpChanged) {
|
|
5016
|
+
writeJson(mcpPath, claudeConfig);
|
|
5017
|
+
console.log(import_chalk3.default.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
function teardownGemini() {
|
|
5022
|
+
const homeDir2 = import_os3.default.homedir();
|
|
5023
|
+
const settingsPath = import_path5.default.join(homeDir2, ".gemini", "settings.json");
|
|
5024
|
+
const settings = readJson(settingsPath);
|
|
5025
|
+
if (!settings) {
|
|
5026
|
+
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
5027
|
+
return;
|
|
5028
|
+
}
|
|
5029
|
+
let changed = false;
|
|
5030
|
+
for (const event of ["BeforeTool", "AfterTool"]) {
|
|
5031
|
+
const before = settings.hooks?.[event]?.length ?? 0;
|
|
5032
|
+
if (settings.hooks?.[event]) {
|
|
5033
|
+
settings.hooks[event] = settings.hooks[event].filter(
|
|
5034
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
5035
|
+
);
|
|
5036
|
+
if ((settings.hooks[event]?.length ?? 0) < before) changed = true;
|
|
5037
|
+
if (settings.hooks[event]?.length === 0) delete settings.hooks[event];
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
if (settings.mcpServers) {
|
|
5041
|
+
for (const [name, server] of Object.entries(settings.mcpServers)) {
|
|
5042
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
5043
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
5044
|
+
settings.mcpServers[name] = {
|
|
5045
|
+
...server,
|
|
5046
|
+
command: originalCmd,
|
|
5047
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
5048
|
+
};
|
|
5049
|
+
changed = true;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
if (changed) {
|
|
5054
|
+
writeJson(settingsPath, settings);
|
|
5055
|
+
console.log(import_chalk3.default.green(" \u2705 Removed Node9 hooks from ~/.gemini/settings.json"));
|
|
5056
|
+
} else {
|
|
5057
|
+
console.log(import_chalk3.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.gemini/settings.json"));
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
function teardownCursor() {
|
|
5061
|
+
const homeDir2 = import_os3.default.homedir();
|
|
5062
|
+
const mcpPath = import_path5.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5063
|
+
const mcpConfig = readJson(mcpPath);
|
|
5064
|
+
if (!mcpConfig?.mcpServers) {
|
|
5065
|
+
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
let changed = false;
|
|
5069
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
5070
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
5071
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
5072
|
+
mcpConfig.mcpServers[name] = {
|
|
5073
|
+
...server,
|
|
5074
|
+
command: originalCmd,
|
|
5075
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
5076
|
+
};
|
|
5077
|
+
changed = true;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
if (changed) {
|
|
5081
|
+
writeJson(mcpPath, mcpConfig);
|
|
5082
|
+
console.log(import_chalk3.default.green(" \u2705 Unwrapped MCP servers in ~/.cursor/mcp.json"));
|
|
5083
|
+
} else {
|
|
5084
|
+
console.log(import_chalk3.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.cursor/mcp.json"));
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
4937
5087
|
async function setupClaude() {
|
|
4938
5088
|
const homeDir2 = import_os3.default.homedir();
|
|
4939
5089
|
const mcpPath = import_path5.default.join(homeDir2, ".claude.json");
|
|
@@ -5557,6 +5707,87 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
5557
5707
|
console.error(import_chalk6.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
5558
5708
|
process.exit(1);
|
|
5559
5709
|
});
|
|
5710
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
5711
|
+
let fn;
|
|
5712
|
+
if (target === "claude") fn = teardownClaude;
|
|
5713
|
+
else if (target === "gemini") fn = teardownGemini;
|
|
5714
|
+
else if (target === "cursor") fn = teardownCursor;
|
|
5715
|
+
else {
|
|
5716
|
+
console.error(import_chalk6.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
5717
|
+
process.exit(1);
|
|
5718
|
+
}
|
|
5719
|
+
console.log(import_chalk6.default.cyan(`
|
|
5720
|
+
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
5721
|
+
`));
|
|
5722
|
+
try {
|
|
5723
|
+
fn();
|
|
5724
|
+
} catch (err) {
|
|
5725
|
+
console.error(import_chalk6.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
5726
|
+
process.exit(1);
|
|
5727
|
+
}
|
|
5728
|
+
console.log(import_chalk6.default.gray("\n Restart the agent for changes to take effect."));
|
|
5729
|
+
});
|
|
5730
|
+
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
5731
|
+
console.log(import_chalk6.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
5732
|
+
console.log(import_chalk6.default.bold("Stopping daemon..."));
|
|
5733
|
+
try {
|
|
5734
|
+
stopDaemon();
|
|
5735
|
+
console.log(import_chalk6.default.green(" \u2705 Daemon stopped"));
|
|
5736
|
+
} catch {
|
|
5737
|
+
console.log(import_chalk6.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
5738
|
+
}
|
|
5739
|
+
console.log(import_chalk6.default.bold("\nRemoving hooks..."));
|
|
5740
|
+
let teardownFailed = false;
|
|
5741
|
+
for (const [label, fn] of [
|
|
5742
|
+
["Claude", teardownClaude],
|
|
5743
|
+
["Gemini", teardownGemini],
|
|
5744
|
+
["Cursor", teardownCursor]
|
|
5745
|
+
]) {
|
|
5746
|
+
try {
|
|
5747
|
+
fn();
|
|
5748
|
+
} catch (err) {
|
|
5749
|
+
teardownFailed = true;
|
|
5750
|
+
console.error(
|
|
5751
|
+
import_chalk6.default.red(
|
|
5752
|
+
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
5753
|
+
)
|
|
5754
|
+
);
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
if (options.purge) {
|
|
5758
|
+
const node9Dir = import_path9.default.join(import_os7.default.homedir(), ".node9");
|
|
5759
|
+
if (import_fs7.default.existsSync(node9Dir)) {
|
|
5760
|
+
const confirmed = await (0, import_prompts3.confirm)({
|
|
5761
|
+
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
5762
|
+
default: false
|
|
5763
|
+
});
|
|
5764
|
+
if (confirmed) {
|
|
5765
|
+
import_fs7.default.rmSync(node9Dir, { recursive: true });
|
|
5766
|
+
if (import_fs7.default.existsSync(node9Dir)) {
|
|
5767
|
+
console.error(
|
|
5768
|
+
import_chalk6.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
5769
|
+
);
|
|
5770
|
+
} else {
|
|
5771
|
+
console.log(import_chalk6.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
5772
|
+
}
|
|
5773
|
+
} else {
|
|
5774
|
+
console.log(import_chalk6.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
5775
|
+
}
|
|
5776
|
+
} else {
|
|
5777
|
+
console.log(import_chalk6.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
5778
|
+
}
|
|
5779
|
+
} else {
|
|
5780
|
+
console.log(
|
|
5781
|
+
import_chalk6.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
5782
|
+
);
|
|
5783
|
+
}
|
|
5784
|
+
if (teardownFailed) {
|
|
5785
|
+
console.error(import_chalk6.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
5786
|
+
process.exit(1);
|
|
5787
|
+
}
|
|
5788
|
+
console.log(import_chalk6.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
5789
|
+
console.log(import_chalk6.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
5790
|
+
});
|
|
5560
5791
|
program.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
5561
5792
|
const homeDir2 = import_os7.default.homedir();
|
|
5562
5793
|
let failures = 0;
|
|
@@ -5967,9 +6198,14 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
5967
6198
|
startDaemon();
|
|
5968
6199
|
}
|
|
5969
6200
|
);
|
|
5970
|
-
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "
|
|
6201
|
+
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
|
|
5971
6202
|
const { startTail: startTail2 } = await Promise.resolve().then(() => (init_tail(), tail_exports));
|
|
5972
|
-
|
|
6203
|
+
try {
|
|
6204
|
+
await startTail2(options);
|
|
6205
|
+
} catch (err) {
|
|
6206
|
+
console.error(import_chalk6.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
6207
|
+
process.exit(1);
|
|
6208
|
+
}
|
|
5973
6209
|
});
|
|
5974
6210
|
program.command("check").description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
5975
6211
|
const processPayload = async (raw) => {
|
|
@@ -6052,7 +6288,7 @@ RAW: ${raw}
|
|
|
6052
6288
|
}
|
|
6053
6289
|
const result = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6054
6290
|
if (result.approved) {
|
|
6055
|
-
if (result.checkedBy)
|
|
6291
|
+
if (result.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6056
6292
|
process.stderr.write(`\u2713 node9 [${result.checkedBy}]: "${toolName}" allowed
|
|
6057
6293
|
`);
|
|
6058
6294
|
process.exit(0);
|
|
@@ -6063,7 +6299,7 @@ RAW: ${raw}
|
|
|
6063
6299
|
if (daemonReady) {
|
|
6064
6300
|
const retry = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6065
6301
|
if (retry.approved) {
|
|
6066
|
-
if (retry.checkedBy)
|
|
6302
|
+
if (retry.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6067
6303
|
process.stderr.write(`\u2713 node9 [${retry.checkedBy}]: "${toolName}" allowed
|
|
6068
6304
|
`);
|
|
6069
6305
|
process.exit(0);
|
package/dist/cli.mjs
CHANGED
|
@@ -2383,7 +2383,7 @@ var init_core = __esm({
|
|
|
2383
2383
|
{
|
|
2384
2384
|
field: "command",
|
|
2385
2385
|
op: "matches",
|
|
2386
|
-
value: "git
|
|
2386
|
+
value: "^\\s*git\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
2387
2387
|
flags: "i"
|
|
2388
2388
|
}
|
|
2389
2389
|
],
|
|
@@ -2394,7 +2394,14 @@ var init_core = __esm({
|
|
|
2394
2394
|
{
|
|
2395
2395
|
name: "review-git-push",
|
|
2396
2396
|
tool: "bash",
|
|
2397
|
-
conditions: [
|
|
2397
|
+
conditions: [
|
|
2398
|
+
{
|
|
2399
|
+
field: "command",
|
|
2400
|
+
op: "matches",
|
|
2401
|
+
value: "^\\s*git\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
2402
|
+
flags: "i"
|
|
2403
|
+
}
|
|
2404
|
+
],
|
|
2398
2405
|
conditionMode: "all",
|
|
2399
2406
|
verdict: "review",
|
|
2400
2407
|
reason: "git push sends changes to a shared remote"
|
|
@@ -2406,7 +2413,7 @@ var init_core = __esm({
|
|
|
2406
2413
|
{
|
|
2407
2414
|
field: "command",
|
|
2408
2415
|
op: "matches",
|
|
2409
|
-
value: "git\\
|
|
2416
|
+
value: "^\\s*git\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
2410
2417
|
flags: "i"
|
|
2411
2418
|
}
|
|
2412
2419
|
],
|
|
@@ -4716,18 +4723,21 @@ function renderPending(activity) {
|
|
|
4716
4723
|
process.stdout.write(`${formatBase(activity)} ${chalk5.yellow("\u25CF \u2026")}\r`);
|
|
4717
4724
|
}
|
|
4718
4725
|
async function ensureDaemon() {
|
|
4726
|
+
let pidPort = null;
|
|
4719
4727
|
if (fs6.existsSync(PID_FILE)) {
|
|
4720
4728
|
try {
|
|
4721
4729
|
const { port } = JSON.parse(fs6.readFileSync(PID_FILE, "utf-8"));
|
|
4722
|
-
|
|
4730
|
+
pidPort = port;
|
|
4723
4731
|
} catch {
|
|
4732
|
+
console.error(chalk5.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
4724
4733
|
}
|
|
4725
4734
|
}
|
|
4735
|
+
const checkPort = pidPort ?? DAEMON_PORT2;
|
|
4726
4736
|
try {
|
|
4727
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
4737
|
+
const res = await fetch(`http://127.0.0.1:${checkPort}/settings`, {
|
|
4728
4738
|
signal: AbortSignal.timeout(500)
|
|
4729
4739
|
});
|
|
4730
|
-
if (res.ok) return
|
|
4740
|
+
if (res.ok) return checkPort;
|
|
4731
4741
|
} catch {
|
|
4732
4742
|
}
|
|
4733
4743
|
console.log(chalk5.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
@@ -4753,25 +4763,44 @@ async function ensureDaemon() {
|
|
|
4753
4763
|
async function startTail(options = {}) {
|
|
4754
4764
|
const port = await ensureDaemon();
|
|
4755
4765
|
if (options.clear) {
|
|
4756
|
-
await new Promise((resolve) => {
|
|
4766
|
+
const result = await new Promise((resolve) => {
|
|
4757
4767
|
const req2 = http2.request(
|
|
4758
4768
|
{ method: "POST", hostname: "127.0.0.1", port, path: "/events/clear" },
|
|
4759
4769
|
(res) => {
|
|
4770
|
+
const status = res.statusCode ?? 0;
|
|
4771
|
+
res.on(
|
|
4772
|
+
"end",
|
|
4773
|
+
() => resolve({
|
|
4774
|
+
ok: status >= 200 && status < 300,
|
|
4775
|
+
code: status >= 200 && status < 300 ? void 0 : `HTTP ${status}`
|
|
4776
|
+
})
|
|
4777
|
+
);
|
|
4760
4778
|
res.resume();
|
|
4761
|
-
res.on("end", resolve);
|
|
4762
4779
|
}
|
|
4763
4780
|
);
|
|
4764
|
-
req2.
|
|
4781
|
+
req2.once("error", (err) => resolve({ ok: false, code: err.code }));
|
|
4782
|
+
req2.setTimeout(2e3, () => {
|
|
4783
|
+
resolve({ ok: false, code: "ETIMEDOUT" });
|
|
4784
|
+
req2.destroy();
|
|
4785
|
+
});
|
|
4765
4786
|
req2.end();
|
|
4766
4787
|
});
|
|
4788
|
+
if (result.ok) {
|
|
4789
|
+
console.log(chalk5.green("\u2713 Flight Recorder buffer cleared."));
|
|
4790
|
+
} else if (result.code === "ECONNREFUSED") {
|
|
4791
|
+
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
4792
|
+
} else if (result.code === "ETIMEDOUT") {
|
|
4793
|
+
throw new Error("Daemon did not respond in time. Try: node9 daemon restart");
|
|
4794
|
+
} else {
|
|
4795
|
+
throw new Error(`Failed to clear buffer (${result.code ?? "unknown error"})`);
|
|
4796
|
+
}
|
|
4797
|
+
return;
|
|
4767
4798
|
}
|
|
4768
4799
|
const connectionTime = Date.now();
|
|
4769
4800
|
const pending2 = /* @__PURE__ */ new Map();
|
|
4770
4801
|
console.log(chalk5.cyan.bold(`
|
|
4771
4802
|
\u{1F6F0}\uFE0F Node9 tail `) + chalk5.dim(`\u2192 localhost:${port}`));
|
|
4772
|
-
if (options.
|
|
4773
|
-
console.log(chalk5.dim("History cleared. Showing live events. Press Ctrl+C to exit.\n"));
|
|
4774
|
-
} else if (options.history) {
|
|
4803
|
+
if (options.history) {
|
|
4775
4804
|
console.log(chalk5.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
4776
4805
|
} else {
|
|
4777
4806
|
console.log(
|
|
@@ -4897,6 +4926,7 @@ function fullPathCommand(subcommand) {
|
|
|
4897
4926
|
if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
|
|
4898
4927
|
const nodeExec = process.execPath;
|
|
4899
4928
|
const cliScript = process.argv[1];
|
|
4929
|
+
if (!cliScript.endsWith(".js")) return `${cliScript} ${subcommand}`;
|
|
4900
4930
|
return `${nodeExec} ${cliScript} ${subcommand}`;
|
|
4901
4931
|
}
|
|
4902
4932
|
function readJson(filePath) {
|
|
@@ -4913,6 +4943,126 @@ function writeJson(filePath, data) {
|
|
|
4913
4943
|
if (!fs3.existsSync(dir)) fs3.mkdirSync(dir, { recursive: true });
|
|
4914
4944
|
fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
4915
4945
|
}
|
|
4946
|
+
function isNode9Hook(cmd) {
|
|
4947
|
+
if (!cmd) return false;
|
|
4948
|
+
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
4949
|
+
}
|
|
4950
|
+
function teardownClaude() {
|
|
4951
|
+
const homeDir2 = os3.homedir();
|
|
4952
|
+
const hooksPath = path5.join(homeDir2, ".claude", "settings.json");
|
|
4953
|
+
const mcpPath = path5.join(homeDir2, ".claude.json");
|
|
4954
|
+
let changed = false;
|
|
4955
|
+
const settings = readJson(hooksPath);
|
|
4956
|
+
if (settings?.hooks) {
|
|
4957
|
+
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
4958
|
+
const before = settings.hooks[event]?.length ?? 0;
|
|
4959
|
+
settings.hooks[event] = settings.hooks[event]?.filter(
|
|
4960
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
4961
|
+
);
|
|
4962
|
+
if ((settings.hooks[event]?.length ?? 0) < before) changed = true;
|
|
4963
|
+
if (settings.hooks[event]?.length === 0) delete settings.hooks[event];
|
|
4964
|
+
}
|
|
4965
|
+
if (changed) {
|
|
4966
|
+
writeJson(hooksPath, settings);
|
|
4967
|
+
console.log(
|
|
4968
|
+
chalk3.green(" \u2705 Removed PreToolUse / PostToolUse hooks from ~/.claude/settings.json")
|
|
4969
|
+
);
|
|
4970
|
+
} else {
|
|
4971
|
+
console.log(chalk3.blue(" \u2139\uFE0F No Node9 hooks found in ~/.claude/settings.json"));
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
const claudeConfig = readJson(mcpPath);
|
|
4975
|
+
if (claudeConfig?.mcpServers) {
|
|
4976
|
+
let mcpChanged = false;
|
|
4977
|
+
for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
|
|
4978
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
4979
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
4980
|
+
claudeConfig.mcpServers[name] = {
|
|
4981
|
+
...server,
|
|
4982
|
+
command: originalCmd,
|
|
4983
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
4984
|
+
};
|
|
4985
|
+
mcpChanged = true;
|
|
4986
|
+
} else if (server.command === "node9") {
|
|
4987
|
+
console.warn(
|
|
4988
|
+
chalk3.yellow(
|
|
4989
|
+
` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
|
|
4990
|
+
)
|
|
4991
|
+
);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
if (mcpChanged) {
|
|
4995
|
+
writeJson(mcpPath, claudeConfig);
|
|
4996
|
+
console.log(chalk3.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
function teardownGemini() {
|
|
5001
|
+
const homeDir2 = os3.homedir();
|
|
5002
|
+
const settingsPath = path5.join(homeDir2, ".gemini", "settings.json");
|
|
5003
|
+
const settings = readJson(settingsPath);
|
|
5004
|
+
if (!settings) {
|
|
5005
|
+
console.log(chalk3.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
5008
|
+
let changed = false;
|
|
5009
|
+
for (const event of ["BeforeTool", "AfterTool"]) {
|
|
5010
|
+
const before = settings.hooks?.[event]?.length ?? 0;
|
|
5011
|
+
if (settings.hooks?.[event]) {
|
|
5012
|
+
settings.hooks[event] = settings.hooks[event].filter(
|
|
5013
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
5014
|
+
);
|
|
5015
|
+
if ((settings.hooks[event]?.length ?? 0) < before) changed = true;
|
|
5016
|
+
if (settings.hooks[event]?.length === 0) delete settings.hooks[event];
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (settings.mcpServers) {
|
|
5020
|
+
for (const [name, server] of Object.entries(settings.mcpServers)) {
|
|
5021
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
5022
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
5023
|
+
settings.mcpServers[name] = {
|
|
5024
|
+
...server,
|
|
5025
|
+
command: originalCmd,
|
|
5026
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
5027
|
+
};
|
|
5028
|
+
changed = true;
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
if (changed) {
|
|
5033
|
+
writeJson(settingsPath, settings);
|
|
5034
|
+
console.log(chalk3.green(" \u2705 Removed Node9 hooks from ~/.gemini/settings.json"));
|
|
5035
|
+
} else {
|
|
5036
|
+
console.log(chalk3.blue(" \u2139\uFE0F No Node9 hooks found in ~/.gemini/settings.json"));
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
function teardownCursor() {
|
|
5040
|
+
const homeDir2 = os3.homedir();
|
|
5041
|
+
const mcpPath = path5.join(homeDir2, ".cursor", "mcp.json");
|
|
5042
|
+
const mcpConfig = readJson(mcpPath);
|
|
5043
|
+
if (!mcpConfig?.mcpServers) {
|
|
5044
|
+
console.log(chalk3.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
5045
|
+
return;
|
|
5046
|
+
}
|
|
5047
|
+
let changed = false;
|
|
5048
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
5049
|
+
if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
|
|
5050
|
+
const [originalCmd, ...originalArgs] = server.args;
|
|
5051
|
+
mcpConfig.mcpServers[name] = {
|
|
5052
|
+
...server,
|
|
5053
|
+
command: originalCmd,
|
|
5054
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
5055
|
+
};
|
|
5056
|
+
changed = true;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
if (changed) {
|
|
5060
|
+
writeJson(mcpPath, mcpConfig);
|
|
5061
|
+
console.log(chalk3.green(" \u2705 Unwrapped MCP servers in ~/.cursor/mcp.json"));
|
|
5062
|
+
} else {
|
|
5063
|
+
console.log(chalk3.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.cursor/mcp.json"));
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
4916
5066
|
async function setupClaude() {
|
|
4917
5067
|
const homeDir2 = os3.homedir();
|
|
4918
5068
|
const mcpPath = path5.join(homeDir2, ".claude.json");
|
|
@@ -5536,6 +5686,87 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
5536
5686
|
console.error(chalk6.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
5537
5687
|
process.exit(1);
|
|
5538
5688
|
});
|
|
5689
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
5690
|
+
let fn;
|
|
5691
|
+
if (target === "claude") fn = teardownClaude;
|
|
5692
|
+
else if (target === "gemini") fn = teardownGemini;
|
|
5693
|
+
else if (target === "cursor") fn = teardownCursor;
|
|
5694
|
+
else {
|
|
5695
|
+
console.error(chalk6.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
5696
|
+
process.exit(1);
|
|
5697
|
+
}
|
|
5698
|
+
console.log(chalk6.cyan(`
|
|
5699
|
+
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
5700
|
+
`));
|
|
5701
|
+
try {
|
|
5702
|
+
fn();
|
|
5703
|
+
} catch (err) {
|
|
5704
|
+
console.error(chalk6.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
5705
|
+
process.exit(1);
|
|
5706
|
+
}
|
|
5707
|
+
console.log(chalk6.gray("\n Restart the agent for changes to take effect."));
|
|
5708
|
+
});
|
|
5709
|
+
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
5710
|
+
console.log(chalk6.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
5711
|
+
console.log(chalk6.bold("Stopping daemon..."));
|
|
5712
|
+
try {
|
|
5713
|
+
stopDaemon();
|
|
5714
|
+
console.log(chalk6.green(" \u2705 Daemon stopped"));
|
|
5715
|
+
} catch {
|
|
5716
|
+
console.log(chalk6.blue(" \u2139\uFE0F Daemon was not running"));
|
|
5717
|
+
}
|
|
5718
|
+
console.log(chalk6.bold("\nRemoving hooks..."));
|
|
5719
|
+
let teardownFailed = false;
|
|
5720
|
+
for (const [label, fn] of [
|
|
5721
|
+
["Claude", teardownClaude],
|
|
5722
|
+
["Gemini", teardownGemini],
|
|
5723
|
+
["Cursor", teardownCursor]
|
|
5724
|
+
]) {
|
|
5725
|
+
try {
|
|
5726
|
+
fn();
|
|
5727
|
+
} catch (err) {
|
|
5728
|
+
teardownFailed = true;
|
|
5729
|
+
console.error(
|
|
5730
|
+
chalk6.red(
|
|
5731
|
+
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
5732
|
+
)
|
|
5733
|
+
);
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
if (options.purge) {
|
|
5737
|
+
const node9Dir = path9.join(os7.homedir(), ".node9");
|
|
5738
|
+
if (fs7.existsSync(node9Dir)) {
|
|
5739
|
+
const confirmed = await confirm3({
|
|
5740
|
+
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
5741
|
+
default: false
|
|
5742
|
+
});
|
|
5743
|
+
if (confirmed) {
|
|
5744
|
+
fs7.rmSync(node9Dir, { recursive: true });
|
|
5745
|
+
if (fs7.existsSync(node9Dir)) {
|
|
5746
|
+
console.error(
|
|
5747
|
+
chalk6.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
5748
|
+
);
|
|
5749
|
+
} else {
|
|
5750
|
+
console.log(chalk6.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
5751
|
+
}
|
|
5752
|
+
} else {
|
|
5753
|
+
console.log(chalk6.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
5754
|
+
}
|
|
5755
|
+
} else {
|
|
5756
|
+
console.log(chalk6.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
5757
|
+
}
|
|
5758
|
+
} else {
|
|
5759
|
+
console.log(
|
|
5760
|
+
chalk6.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
5761
|
+
);
|
|
5762
|
+
}
|
|
5763
|
+
if (teardownFailed) {
|
|
5764
|
+
console.error(chalk6.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
5765
|
+
process.exit(1);
|
|
5766
|
+
}
|
|
5767
|
+
console.log(chalk6.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
5768
|
+
console.log(chalk6.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
5769
|
+
});
|
|
5539
5770
|
program.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
5540
5771
|
const homeDir2 = os7.homedir();
|
|
5541
5772
|
let failures = 0;
|
|
@@ -5946,9 +6177,14 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
5946
6177
|
startDaemon();
|
|
5947
6178
|
}
|
|
5948
6179
|
);
|
|
5949
|
-
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "
|
|
6180
|
+
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
|
|
5950
6181
|
const { startTail: startTail2 } = await Promise.resolve().then(() => (init_tail(), tail_exports));
|
|
5951
|
-
|
|
6182
|
+
try {
|
|
6183
|
+
await startTail2(options);
|
|
6184
|
+
} catch (err) {
|
|
6185
|
+
console.error(chalk6.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
6186
|
+
process.exit(1);
|
|
6187
|
+
}
|
|
5952
6188
|
});
|
|
5953
6189
|
program.command("check").description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
5954
6190
|
const processPayload = async (raw) => {
|
|
@@ -6031,7 +6267,7 @@ RAW: ${raw}
|
|
|
6031
6267
|
}
|
|
6032
6268
|
const result = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6033
6269
|
if (result.approved) {
|
|
6034
|
-
if (result.checkedBy)
|
|
6270
|
+
if (result.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6035
6271
|
process.stderr.write(`\u2713 node9 [${result.checkedBy}]: "${toolName}" allowed
|
|
6036
6272
|
`);
|
|
6037
6273
|
process.exit(0);
|
|
@@ -6042,7 +6278,7 @@ RAW: ${raw}
|
|
|
6042
6278
|
if (daemonReady) {
|
|
6043
6279
|
const retry = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6044
6280
|
if (retry.approved) {
|
|
6045
|
-
if (retry.checkedBy)
|
|
6281
|
+
if (retry.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6046
6282
|
process.stderr.write(`\u2713 node9 [${retry.checkedBy}]: "${toolName}" allowed
|
|
6047
6283
|
`);
|
|
6048
6284
|
process.exit(0);
|
package/dist/index.js
CHANGED
|
@@ -1115,7 +1115,7 @@ var DEFAULT_CONFIG = {
|
|
|
1115
1115
|
{
|
|
1116
1116
|
field: "command",
|
|
1117
1117
|
op: "matches",
|
|
1118
|
-
value: "git
|
|
1118
|
+
value: "^\\s*git\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
1119
1119
|
flags: "i"
|
|
1120
1120
|
}
|
|
1121
1121
|
],
|
|
@@ -1126,7 +1126,14 @@ var DEFAULT_CONFIG = {
|
|
|
1126
1126
|
{
|
|
1127
1127
|
name: "review-git-push",
|
|
1128
1128
|
tool: "bash",
|
|
1129
|
-
conditions: [
|
|
1129
|
+
conditions: [
|
|
1130
|
+
{
|
|
1131
|
+
field: "command",
|
|
1132
|
+
op: "matches",
|
|
1133
|
+
value: "^\\s*git\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
1134
|
+
flags: "i"
|
|
1135
|
+
}
|
|
1136
|
+
],
|
|
1130
1137
|
conditionMode: "all",
|
|
1131
1138
|
verdict: "review",
|
|
1132
1139
|
reason: "git push sends changes to a shared remote"
|
|
@@ -1138,7 +1145,7 @@ var DEFAULT_CONFIG = {
|
|
|
1138
1145
|
{
|
|
1139
1146
|
field: "command",
|
|
1140
1147
|
op: "matches",
|
|
1141
|
-
value: "git\\
|
|
1148
|
+
value: "^\\s*git\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
1142
1149
|
flags: "i"
|
|
1143
1150
|
}
|
|
1144
1151
|
],
|
package/dist/index.mjs
CHANGED
|
@@ -1079,7 +1079,7 @@ var DEFAULT_CONFIG = {
|
|
|
1079
1079
|
{
|
|
1080
1080
|
field: "command",
|
|
1081
1081
|
op: "matches",
|
|
1082
|
-
value: "git
|
|
1082
|
+
value: "^\\s*git\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
1083
1083
|
flags: "i"
|
|
1084
1084
|
}
|
|
1085
1085
|
],
|
|
@@ -1090,7 +1090,14 @@ var DEFAULT_CONFIG = {
|
|
|
1090
1090
|
{
|
|
1091
1091
|
name: "review-git-push",
|
|
1092
1092
|
tool: "bash",
|
|
1093
|
-
conditions: [
|
|
1093
|
+
conditions: [
|
|
1094
|
+
{
|
|
1095
|
+
field: "command",
|
|
1096
|
+
op: "matches",
|
|
1097
|
+
value: "^\\s*git\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
1098
|
+
flags: "i"
|
|
1099
|
+
}
|
|
1100
|
+
],
|
|
1094
1101
|
conditionMode: "all",
|
|
1095
1102
|
verdict: "review",
|
|
1096
1103
|
reason: "git push sends changes to a shared remote"
|
|
@@ -1102,7 +1109,7 @@ var DEFAULT_CONFIG = {
|
|
|
1102
1109
|
{
|
|
1103
1110
|
field: "command",
|
|
1104
1111
|
op: "matches",
|
|
1105
|
-
value: "git\\
|
|
1112
|
+
value: "^\\s*git\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
1106
1113
|
flags: "i"
|
|
1107
1114
|
}
|
|
1108
1115
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"fix": "npm run format && npm run lint:fix",
|
|
60
60
|
"validate": "npm run format && npm run lint && npm run typecheck && npm run test && npm run test:e2e && npm run build",
|
|
61
61
|
"test:e2e": "NODE9_TESTING=1 bash scripts/e2e.sh",
|
|
62
|
+
"preuninstall": "node9 uninstall || echo 'node9 uninstall failed — remove hooks manually from ~/.claude/settings.json'",
|
|
62
63
|
"prepublishOnly": "npm run validate",
|
|
63
64
|
"test": "NODE_ENV=test vitest --run",
|
|
64
65
|
"test:watch": "NODE_ENV=test vitest",
|