@node9/proxy 1.27.1 → 1.29.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 +3 -3
- package/dist/cli.js +1347 -124
- package/dist/cli.mjs +1347 -124
- package/dist/dashboard.mjs +19 -1
- package/dist/index.js +10 -1
- package/dist/index.mjs +10 -1
- package/package.json +3 -1
package/dist/cli.mjs
CHANGED
|
@@ -100,9 +100,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
100
100
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
101
101
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
102
102
|
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
103
|
+
const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
|
|
103
104
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
104
105
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
106
|
tool: toolName,
|
|
107
|
+
...agentToolNameField,
|
|
106
108
|
...argsField,
|
|
107
109
|
decision,
|
|
108
110
|
checkedBy,
|
|
@@ -3923,7 +3925,14 @@ var init_config = __esm({
|
|
|
3923
3925
|
"edit_file",
|
|
3924
3926
|
"create_file",
|
|
3925
3927
|
"edit",
|
|
3926
|
-
"replace"
|
|
3928
|
+
"replace",
|
|
3929
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
3930
|
+
// incoming name before set-membership, so we list the lowercase
|
|
3931
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
3932
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
3933
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
3934
|
+
"write",
|
|
3935
|
+
"multiedit"
|
|
3927
3936
|
],
|
|
3928
3937
|
onlyPaths: [],
|
|
3929
3938
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
|
@@ -6823,6 +6832,7 @@ import os12 from "os";
|
|
|
6823
6832
|
import chalk from "chalk";
|
|
6824
6833
|
import { confirm } from "@inquirer/prompts";
|
|
6825
6834
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
6835
|
+
import * as yaml from "yaml";
|
|
6826
6836
|
function hasNode9McpServer(servers) {
|
|
6827
6837
|
const entry = servers["node9"];
|
|
6828
6838
|
return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
|
|
@@ -7162,6 +7172,12 @@ async function setupClaude() {
|
|
|
7162
7172
|
}
|
|
7163
7173
|
}
|
|
7164
7174
|
async function setupGemini() {
|
|
7175
|
+
console.log(
|
|
7176
|
+
chalk.yellow(
|
|
7177
|
+
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18\n (replaced by Antigravity). If you use agy, run: node9 agents add antigravity"
|
|
7178
|
+
)
|
|
7179
|
+
);
|
|
7180
|
+
console.log("");
|
|
7165
7181
|
seedMcpPinsIfMissing();
|
|
7166
7182
|
const homeDir2 = os12.homedir();
|
|
7167
7183
|
const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
|
|
@@ -7259,6 +7275,355 @@ async function setupGemini() {
|
|
|
7259
7275
|
printDaemonTip();
|
|
7260
7276
|
}
|
|
7261
7277
|
}
|
|
7278
|
+
async function setupAntigravity() {
|
|
7279
|
+
seedMcpPinsIfMissing();
|
|
7280
|
+
const homeDir2 = os12.homedir();
|
|
7281
|
+
const hooksPath = path15.join(homeDir2, ".gemini", "config", "hooks.json");
|
|
7282
|
+
const mcpPath = path15.join(homeDir2, ".gemini", "config", "mcp_config.json");
|
|
7283
|
+
const hooksFile = readJson(hooksPath) ?? {};
|
|
7284
|
+
const mcpConfig = readJson(mcpPath) ?? {};
|
|
7285
|
+
const servers = mcpConfig.mcpServers ?? {};
|
|
7286
|
+
let hooksChanged = false;
|
|
7287
|
+
let anythingChanged = false;
|
|
7288
|
+
if (!hooksFile.hooks) hooksFile.hooks = {};
|
|
7289
|
+
const hasPreHook = hooksFile.hooks.PreToolUse?.some(
|
|
7290
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
7291
|
+
);
|
|
7292
|
+
if (!hasPreHook) {
|
|
7293
|
+
if (!Array.isArray(hooksFile.hooks.PreToolUse)) hooksFile.hooks.PreToolUse = [];
|
|
7294
|
+
hooksFile.hooks.PreToolUse.push({
|
|
7295
|
+
matcher: ".*",
|
|
7296
|
+
hooks: [
|
|
7297
|
+
{
|
|
7298
|
+
name: "node9-check",
|
|
7299
|
+
type: "command",
|
|
7300
|
+
command: fullPathCommand("check --agent antigravity"),
|
|
7301
|
+
timeout: 600
|
|
7302
|
+
}
|
|
7303
|
+
]
|
|
7304
|
+
});
|
|
7305
|
+
console.log(chalk.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
|
|
7306
|
+
hooksChanged = true;
|
|
7307
|
+
anythingChanged = true;
|
|
7308
|
+
} else if (hooksFile.hooks.PreToolUse) {
|
|
7309
|
+
for (const matcher of hooksFile.hooks.PreToolUse) {
|
|
7310
|
+
for (const h of matcher.hooks) {
|
|
7311
|
+
const cmd = h.command ?? "";
|
|
7312
|
+
if (isNode9Hook(cmd) && needsRewrite(cmd)) {
|
|
7313
|
+
h.command = fullPathCommand("check --agent antigravity");
|
|
7314
|
+
console.log(chalk.yellow(" \u{1F527} PreToolUse hook repaired (stale path \u2192 current binary)"));
|
|
7315
|
+
hooksChanged = true;
|
|
7316
|
+
anythingChanged = true;
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
const hasPostHook = hooksFile.hooks.PostToolUse?.some(
|
|
7322
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
7323
|
+
);
|
|
7324
|
+
if (!hasPostHook) {
|
|
7325
|
+
if (!Array.isArray(hooksFile.hooks.PostToolUse)) hooksFile.hooks.PostToolUse = [];
|
|
7326
|
+
hooksFile.hooks.PostToolUse.push({
|
|
7327
|
+
matcher: ".*",
|
|
7328
|
+
hooks: [
|
|
7329
|
+
{
|
|
7330
|
+
name: "node9-log",
|
|
7331
|
+
type: "command",
|
|
7332
|
+
command: fullPathCommand("log --agent antigravity"),
|
|
7333
|
+
timeout: 600
|
|
7334
|
+
}
|
|
7335
|
+
]
|
|
7336
|
+
});
|
|
7337
|
+
console.log(chalk.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
|
|
7338
|
+
hooksChanged = true;
|
|
7339
|
+
anythingChanged = true;
|
|
7340
|
+
} else if (hooksFile.hooks.PostToolUse) {
|
|
7341
|
+
for (const matcher of hooksFile.hooks.PostToolUse) {
|
|
7342
|
+
for (const h of matcher.hooks) {
|
|
7343
|
+
const cmd = h.command ?? "";
|
|
7344
|
+
if (isNode9Hook(cmd) && needsRewrite(cmd)) {
|
|
7345
|
+
h.command = fullPathCommand("log --agent antigravity");
|
|
7346
|
+
console.log(chalk.yellow(" \u{1F527} PostToolUse hook repaired (stale path \u2192 current binary)"));
|
|
7347
|
+
hooksChanged = true;
|
|
7348
|
+
anythingChanged = true;
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
if (hooksChanged) {
|
|
7354
|
+
writeJson(hooksPath, hooksFile);
|
|
7355
|
+
console.log("");
|
|
7356
|
+
}
|
|
7357
|
+
if (!hasNode9McpServer(servers)) {
|
|
7358
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
7359
|
+
mcpConfig.mcpServers = servers;
|
|
7360
|
+
writeJson(mcpPath, mcpConfig);
|
|
7361
|
+
console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
7362
|
+
anythingChanged = true;
|
|
7363
|
+
}
|
|
7364
|
+
const legacySettings = readJson(path15.join(homeDir2, ".gemini", "settings.json"));
|
|
7365
|
+
const legacyHasNode9 = ["BeforeTool", "AfterTool"].some(
|
|
7366
|
+
(ev) => legacySettings?.hooks?.[ev]?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)))
|
|
7367
|
+
);
|
|
7368
|
+
if (legacyHasNode9) {
|
|
7369
|
+
console.log(
|
|
7370
|
+
chalk.yellow(
|
|
7371
|
+
" \u26A0\uFE0F Found node9 hooks for the legacy Gemini CLI in ~/.gemini/settings.json.\n Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n Keep them only if you still use Gemini CLI (e.g. enterprise Code Assist)."
|
|
7372
|
+
)
|
|
7373
|
+
);
|
|
7374
|
+
const clean = await confirm({
|
|
7375
|
+
message: "Remove the legacy Gemini CLI hooks?",
|
|
7376
|
+
default: false
|
|
7377
|
+
});
|
|
7378
|
+
if (clean) {
|
|
7379
|
+
teardownGemini();
|
|
7380
|
+
anythingChanged = true;
|
|
7381
|
+
}
|
|
7382
|
+
console.log("");
|
|
7383
|
+
}
|
|
7384
|
+
const serversToWrap = [];
|
|
7385
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
7386
|
+
if (!server.command || server.command === "node9") continue;
|
|
7387
|
+
serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
|
|
7388
|
+
}
|
|
7389
|
+
if (serversToWrap.length > 0) {
|
|
7390
|
+
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
7391
|
+
console.log(chalk.white(` ${mcpPath}`));
|
|
7392
|
+
for (const { name, upstream } of serversToWrap) {
|
|
7393
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
7394
|
+
}
|
|
7395
|
+
console.log("");
|
|
7396
|
+
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
7397
|
+
if (proceed) {
|
|
7398
|
+
for (const { name, upstream } of serversToWrap) {
|
|
7399
|
+
servers[name] = {
|
|
7400
|
+
...servers[name],
|
|
7401
|
+
command: "node9",
|
|
7402
|
+
args: ["mcp", "--upstream", upstream]
|
|
7403
|
+
};
|
|
7404
|
+
}
|
|
7405
|
+
mcpConfig.mcpServers = servers;
|
|
7406
|
+
writeJson(mcpPath, mcpConfig);
|
|
7407
|
+
console.log(chalk.green(`
|
|
7408
|
+
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
7409
|
+
anythingChanged = true;
|
|
7410
|
+
} else {
|
|
7411
|
+
console.log(chalk.yellow(" Skipped MCP server wrapping."));
|
|
7412
|
+
}
|
|
7413
|
+
console.log("");
|
|
7414
|
+
}
|
|
7415
|
+
if (!anythingChanged && serversToWrap.length === 0) {
|
|
7416
|
+
console.log(chalk.blue("\u2139\uFE0F Node9 is already fully configured for Antigravity."));
|
|
7417
|
+
printDaemonTip();
|
|
7418
|
+
return;
|
|
7419
|
+
}
|
|
7420
|
+
if (anythingChanged) {
|
|
7421
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Antigravity!"));
|
|
7422
|
+
console.log(
|
|
7423
|
+
chalk.gray(
|
|
7424
|
+
" Covers both the agy CLI and the Antigravity IDE (shared ~/.gemini/config).\n Restart agy / the IDE for changes to take effect."
|
|
7425
|
+
)
|
|
7426
|
+
);
|
|
7427
|
+
printDaemonTip();
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
function teardownAntigravity() {
|
|
7431
|
+
const homeDir2 = os12.homedir();
|
|
7432
|
+
const hooksPath = path15.join(homeDir2, ".gemini", "config", "hooks.json");
|
|
7433
|
+
const mcpPath = path15.join(homeDir2, ".gemini", "config", "mcp_config.json");
|
|
7434
|
+
let changed = false;
|
|
7435
|
+
const hooksFile = readJson(hooksPath);
|
|
7436
|
+
if (hooksFile?.hooks) {
|
|
7437
|
+
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
7438
|
+
const before = hooksFile.hooks[event]?.length ?? 0;
|
|
7439
|
+
hooksFile.hooks[event] = hooksFile.hooks[event]?.filter(
|
|
7440
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
7441
|
+
);
|
|
7442
|
+
if ((hooksFile.hooks[event]?.length ?? 0) < before) changed = true;
|
|
7443
|
+
if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
|
|
7444
|
+
}
|
|
7445
|
+
if (changed) {
|
|
7446
|
+
writeJson(hooksPath, hooksFile);
|
|
7447
|
+
console.log(
|
|
7448
|
+
chalk.green(" \u2705 Removed PreToolUse / PostToolUse hooks from ~/.gemini/config/hooks.json")
|
|
7449
|
+
);
|
|
7450
|
+
} else {
|
|
7451
|
+
console.log(chalk.blue(" \u2139\uFE0F No Node9 hooks found in ~/.gemini/config/hooks.json"));
|
|
7452
|
+
}
|
|
7453
|
+
} else {
|
|
7454
|
+
console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/config/hooks.json not found \u2014 nothing to remove"));
|
|
7455
|
+
}
|
|
7456
|
+
const mcpConfig = readJson(mcpPath);
|
|
7457
|
+
if (mcpConfig?.mcpServers) {
|
|
7458
|
+
let mcpChanged = false;
|
|
7459
|
+
if (removeNode9McpServer(mcpConfig.mcpServers)) {
|
|
7460
|
+
mcpChanged = true;
|
|
7461
|
+
console.log(
|
|
7462
|
+
chalk.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/config/mcp_config.json")
|
|
7463
|
+
);
|
|
7464
|
+
}
|
|
7465
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
7466
|
+
const args = server.args;
|
|
7467
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7468
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
7469
|
+
mcpConfig.mcpServers[name] = {
|
|
7470
|
+
...server,
|
|
7471
|
+
command: originalCmd,
|
|
7472
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
7473
|
+
};
|
|
7474
|
+
mcpChanged = true;
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7477
|
+
if (mcpChanged) {
|
|
7478
|
+
writeJson(mcpPath, mcpConfig);
|
|
7479
|
+
console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.gemini/config/mcp_config.json"));
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
async function setupCopilot() {
|
|
7484
|
+
seedMcpPinsIfMissing();
|
|
7485
|
+
const homeDir2 = os12.homedir();
|
|
7486
|
+
const hooksPath = path15.join(homeDir2, ".copilot", "hooks", "node9.json");
|
|
7487
|
+
const mcpPath = path15.join(homeDir2, ".copilot", "mcp-config.json");
|
|
7488
|
+
const hooksFile = readJson(hooksPath) ?? { version: 1 };
|
|
7489
|
+
if (!hooksFile.version) hooksFile.version = 1;
|
|
7490
|
+
if (!hooksFile.hooks) hooksFile.hooks = {};
|
|
7491
|
+
const mcpConfig = readJson(mcpPath) ?? {};
|
|
7492
|
+
const servers = mcpConfig.mcpServers ?? {};
|
|
7493
|
+
let hooksChanged = false;
|
|
7494
|
+
let anythingChanged = false;
|
|
7495
|
+
const addHook = (event, subcommand) => {
|
|
7496
|
+
const arr = Array.isArray(hooksFile.hooks[event]) ? hooksFile.hooks[event] : [];
|
|
7497
|
+
const present = arr.some((h) => isNode9Hook(h.command));
|
|
7498
|
+
if (!present) {
|
|
7499
|
+
arr.push({ type: "command", command: fullPathCommand(subcommand), timeoutSec: 600 });
|
|
7500
|
+
hooksFile.hooks[event] = arr;
|
|
7501
|
+
console.log(chalk.green(` \u2705 ${event} hook added \u2192 node9 ${subcommand.split(" ")[0]}`));
|
|
7502
|
+
hooksChanged = true;
|
|
7503
|
+
anythingChanged = true;
|
|
7504
|
+
} else {
|
|
7505
|
+
for (const h of arr) {
|
|
7506
|
+
if (h.command && isNode9Hook(h.command) && needsRewrite(h.command)) {
|
|
7507
|
+
h.command = fullPathCommand(subcommand);
|
|
7508
|
+
console.log(chalk.yellow(` \u{1F527} ${event} hook repaired (stale path \u2192 current binary)`));
|
|
7509
|
+
hooksChanged = true;
|
|
7510
|
+
anythingChanged = true;
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
};
|
|
7515
|
+
addHook("PreToolUse", "check --agent copilot");
|
|
7516
|
+
addHook("PostToolUse", "log --agent copilot");
|
|
7517
|
+
addHook("UserPromptSubmit", "check --agent copilot");
|
|
7518
|
+
if (hooksChanged) {
|
|
7519
|
+
writeJson(hooksPath, hooksFile);
|
|
7520
|
+
console.log("");
|
|
7521
|
+
}
|
|
7522
|
+
if (!hasNode9McpServer(servers)) {
|
|
7523
|
+
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
7524
|
+
mcpConfig.mcpServers = servers;
|
|
7525
|
+
writeJson(mcpPath, mcpConfig);
|
|
7526
|
+
console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
7527
|
+
anythingChanged = true;
|
|
7528
|
+
}
|
|
7529
|
+
const serversToWrap = [];
|
|
7530
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
7531
|
+
if (!server.command || server.command === "node9") continue;
|
|
7532
|
+
serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
|
|
7533
|
+
}
|
|
7534
|
+
if (serversToWrap.length > 0) {
|
|
7535
|
+
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
7536
|
+
console.log(chalk.white(` ${mcpPath}`));
|
|
7537
|
+
for (const { name, upstream } of serversToWrap) {
|
|
7538
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
7539
|
+
}
|
|
7540
|
+
console.log("");
|
|
7541
|
+
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
7542
|
+
if (proceed) {
|
|
7543
|
+
for (const { name, upstream } of serversToWrap) {
|
|
7544
|
+
servers[name] = {
|
|
7545
|
+
...servers[name],
|
|
7546
|
+
command: "node9",
|
|
7547
|
+
args: ["mcp", "--upstream", upstream]
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
mcpConfig.mcpServers = servers;
|
|
7551
|
+
writeJson(mcpPath, mcpConfig);
|
|
7552
|
+
console.log(chalk.green(`
|
|
7553
|
+
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
7554
|
+
anythingChanged = true;
|
|
7555
|
+
} else {
|
|
7556
|
+
console.log(chalk.yellow(" Skipped MCP server wrapping."));
|
|
7557
|
+
}
|
|
7558
|
+
console.log("");
|
|
7559
|
+
}
|
|
7560
|
+
if (!anythingChanged) {
|
|
7561
|
+
console.log(chalk.blue("\u2139\uFE0F Node9 is already fully configured for GitHub Copilot CLI."));
|
|
7562
|
+
printDaemonTip();
|
|
7563
|
+
return;
|
|
7564
|
+
}
|
|
7565
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting GitHub Copilot CLI!"));
|
|
7566
|
+
console.log(chalk.gray(" Restart Copilot CLI for changes to take effect."));
|
|
7567
|
+
printDaemonTip();
|
|
7568
|
+
}
|
|
7569
|
+
function teardownCopilot() {
|
|
7570
|
+
const homeDir2 = os12.homedir();
|
|
7571
|
+
const hooksPath = path15.join(homeDir2, ".copilot", "hooks", "node9.json");
|
|
7572
|
+
const mcpPath = path15.join(homeDir2, ".copilot", "mcp-config.json");
|
|
7573
|
+
const hooksFile = readJson(hooksPath);
|
|
7574
|
+
let changed = false;
|
|
7575
|
+
if (hooksFile?.hooks) {
|
|
7576
|
+
for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
7577
|
+
const before = hooksFile.hooks[event]?.length ?? 0;
|
|
7578
|
+
hooksFile.hooks[event] = hooksFile.hooks[event]?.filter((h) => !isNode9Hook(h.command));
|
|
7579
|
+
if ((hooksFile.hooks[event]?.length ?? 0) < before) changed = true;
|
|
7580
|
+
if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
|
|
7581
|
+
}
|
|
7582
|
+
if (changed) {
|
|
7583
|
+
if (Object.keys(hooksFile.hooks).length === 0) {
|
|
7584
|
+
try {
|
|
7585
|
+
fs13.unlinkSync(hooksPath);
|
|
7586
|
+
console.log(chalk.green(" \u2705 Removed ~/.copilot/hooks/node9.json"));
|
|
7587
|
+
} catch {
|
|
7588
|
+
writeJson(hooksPath, hooksFile);
|
|
7589
|
+
}
|
|
7590
|
+
} else {
|
|
7591
|
+
writeJson(hooksPath, hooksFile);
|
|
7592
|
+
console.log(chalk.green(" \u2705 Removed Node9 hooks from ~/.copilot/hooks/node9.json"));
|
|
7593
|
+
}
|
|
7594
|
+
} else {
|
|
7595
|
+
console.log(chalk.blue(" \u2139\uFE0F No Node9 hooks found in ~/.copilot/hooks/node9.json"));
|
|
7596
|
+
}
|
|
7597
|
+
} else {
|
|
7598
|
+
console.log(chalk.blue(" \u2139\uFE0F ~/.copilot/hooks/node9.json not found \u2014 nothing to remove"));
|
|
7599
|
+
}
|
|
7600
|
+
const mcpConfig = readJson(mcpPath);
|
|
7601
|
+
if (mcpConfig?.mcpServers) {
|
|
7602
|
+
let mcpChanged = false;
|
|
7603
|
+
if (removeNode9McpServer(mcpConfig.mcpServers)) {
|
|
7604
|
+
mcpChanged = true;
|
|
7605
|
+
console.log(
|
|
7606
|
+
chalk.green(" \u2705 Removed node9 MCP server entry from ~/.copilot/mcp-config.json")
|
|
7607
|
+
);
|
|
7608
|
+
}
|
|
7609
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
7610
|
+
const args = server.args;
|
|
7611
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7612
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
7613
|
+
mcpConfig.mcpServers[name] = {
|
|
7614
|
+
...server,
|
|
7615
|
+
command: originalCmd,
|
|
7616
|
+
args: originalArgs.length ? originalArgs : void 0
|
|
7617
|
+
};
|
|
7618
|
+
mcpChanged = true;
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
if (mcpChanged) {
|
|
7622
|
+
writeJson(mcpPath, mcpConfig);
|
|
7623
|
+
console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.copilot/mcp-config.json"));
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7262
7627
|
function claudeDesktopConfigPath(homeDir2 = os12.homedir()) {
|
|
7263
7628
|
if (process.platform === "darwin") {
|
|
7264
7629
|
return path15.join(
|
|
@@ -7306,7 +7671,23 @@ function detectAgents(homeDir2 = os12.homedir()) {
|
|
|
7306
7671
|
const desktopPath = claudeDesktopConfigPath(homeDir2);
|
|
7307
7672
|
return {
|
|
7308
7673
|
claude: exists(path15.join(homeDir2, ".claude")) || exists(path15.join(homeDir2, ".claude.json")),
|
|
7309
|
-
|
|
7674
|
+
// Antigravity (agy) shares the ~/.gemini root, so a bare
|
|
7675
|
+
// `exists(~/.gemini)` would report the (EOL'd) Gemini CLI as
|
|
7676
|
+
// installed on every agy machine — and `node9 init` would then
|
|
7677
|
+
// write BeforeTool hooks into settings.json, a file agy ignores,
|
|
7678
|
+
// while reporting the machine protected (silent protection gap,
|
|
7679
|
+
// verified live on a machine with both installed). Gemini CLI
|
|
7680
|
+
// creates ~/.gemini/settings.json on first run; agy does not touch
|
|
7681
|
+
// it (it uses antigravity-cli/settings.json) — that file is the
|
|
7682
|
+
// legacy-CLI discriminator.
|
|
7683
|
+
gemini: exists(path15.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
|
|
7684
|
+
// agy creates ~/.gemini/antigravity-cli/ on first launch; the IDE
|
|
7685
|
+
// creates antigravity-ide/. PATH fallback covers installed-but-
|
|
7686
|
+
// never-launched (same class as opencode #186).
|
|
7687
|
+
antigravity: exists(path15.join(homeDir2, ".gemini", "antigravity-cli")) || exists(path15.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
|
|
7688
|
+
// GitHub Copilot CLI creates ~/.copilot on first launch; PATH
|
|
7689
|
+
// fallback covers installed-but-never-launched (same as opencode #186).
|
|
7690
|
+
copilot: exists(path15.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
|
|
7310
7691
|
cursor: exists(path15.join(homeDir2, ".cursor")),
|
|
7311
7692
|
codex: exists(path15.join(homeDir2, ".codex")),
|
|
7312
7693
|
windsurf: exists(path15.join(homeDir2, ".codeium", "windsurf")),
|
|
@@ -7321,7 +7702,13 @@ function detectAgents(homeDir2 = os12.homedir()) {
|
|
|
7321
7702
|
// dir lazily on first launch — same class of bug as opencode's #186
|
|
7322
7703
|
// (design R6) — so fall back to PATH lookup for installed-but-never-
|
|
7323
7704
|
// launched pi.
|
|
7324
|
-
pi: exists(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi")
|
|
7705
|
+
pi: exists(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
|
|
7706
|
+
// Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
|
|
7707
|
+
// is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
|
|
7708
|
+
// appears after `hermes setup` has run; the directory alone exists from
|
|
7709
|
+
// install time. PATH fallback covers the rare case where the user blew
|
|
7710
|
+
// away ~/.hermes but kept the binary.
|
|
7711
|
+
hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
|
|
7325
7712
|
};
|
|
7326
7713
|
}
|
|
7327
7714
|
async function setupCursor() {
|
|
@@ -8128,6 +8515,169 @@ function teardownPi() {
|
|
|
8128
8515
|
console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${extensionPath}: ${String(err2)}`));
|
|
8129
8516
|
}
|
|
8130
8517
|
}
|
|
8518
|
+
function hermesHomeDir(homeDir2 = os12.homedir()) {
|
|
8519
|
+
const env = process.env.HERMES_HOME?.trim();
|
|
8520
|
+
if (env && path15.isAbsolute(env)) return env;
|
|
8521
|
+
return path15.join(homeDir2, ".hermes");
|
|
8522
|
+
}
|
|
8523
|
+
function hermesConfigPath(homeDir2 = os12.homedir()) {
|
|
8524
|
+
return path15.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
|
|
8525
|
+
}
|
|
8526
|
+
function hermesAllowlistPath(homeDir2 = os12.homedir()) {
|
|
8527
|
+
return path15.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
|
|
8528
|
+
}
|
|
8529
|
+
function setupHermes() {
|
|
8530
|
+
const homeDir2 = os12.homedir();
|
|
8531
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8532
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8533
|
+
if (!fs13.existsSync(configPath)) {
|
|
8534
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
|
|
8535
|
+
console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 setup hermes."));
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8538
|
+
let anythingChanged = false;
|
|
8539
|
+
const raw = fs13.readFileSync(configPath, "utf-8");
|
|
8540
|
+
const doc = yaml.parseDocument(raw);
|
|
8541
|
+
if (doc.errors.length > 0) {
|
|
8542
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
|
|
8543
|
+
for (const err2 of doc.errors.slice(0, 3)) {
|
|
8544
|
+
console.log(chalk.gray(` \u2022 ${err2.message}`));
|
|
8545
|
+
}
|
|
8546
|
+
console.log(
|
|
8547
|
+
chalk.gray(" Fix the file (or run `hermes config edit`), then re-run node9 setup hermes.")
|
|
8548
|
+
);
|
|
8549
|
+
return;
|
|
8550
|
+
}
|
|
8551
|
+
const current = doc.toJS() ?? {};
|
|
8552
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8553
|
+
const command = fullPathCommand(subcmd);
|
|
8554
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8555
|
+
const node9Idx = existing.findIndex(
|
|
8556
|
+
(e) => typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8557
|
+
);
|
|
8558
|
+
if (node9Idx === -1) {
|
|
8559
|
+
const newEntries = [...existing, { command, timeout: 10 }];
|
|
8560
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8561
|
+
console.log(chalk.green(` \u2705 Hermes ${event} hook added \u2192 node9 ${subcmd}`));
|
|
8562
|
+
anythingChanged = true;
|
|
8563
|
+
} else if (existing[node9Idx].command !== command || isStaleHookCommand(existing[node9Idx].command ?? "")) {
|
|
8564
|
+
const newEntries = [...existing];
|
|
8565
|
+
newEntries[node9Idx] = { ...newEntries[node9Idx], command };
|
|
8566
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8567
|
+
console.log(chalk.yellow(` \u{1F527} Hermes ${event} hook repaired (stale path \u2192 current binary)`));
|
|
8568
|
+
anythingChanged = true;
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
if (current.hooks_auto_accept !== true) {
|
|
8572
|
+
doc.set("hooks_auto_accept", true);
|
|
8573
|
+
console.log(chalk.green(" \u2705 hooks_auto_accept set to true"));
|
|
8574
|
+
anythingChanged = true;
|
|
8575
|
+
}
|
|
8576
|
+
if (anythingChanged) {
|
|
8577
|
+
fs13.writeFileSync(configPath, doc.toString());
|
|
8578
|
+
}
|
|
8579
|
+
let allowlist = {};
|
|
8580
|
+
if (fs13.existsSync(allowlistPath)) {
|
|
8581
|
+
try {
|
|
8582
|
+
allowlist = JSON.parse(fs13.readFileSync(allowlistPath, "utf-8"));
|
|
8583
|
+
} catch {
|
|
8584
|
+
allowlist = {};
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8587
|
+
if (!Array.isArray(allowlist.approvals)) allowlist.approvals = [];
|
|
8588
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
8589
|
+
let allowlistChanged = false;
|
|
8590
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8591
|
+
const command = fullPathCommand(subcmd);
|
|
8592
|
+
const existingIdx = allowlist.approvals.findIndex(
|
|
8593
|
+
(e) => e?.event === event && typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8594
|
+
);
|
|
8595
|
+
if (existingIdx === -1) {
|
|
8596
|
+
allowlist.approvals.push({ event, command, approved_at: nowIso });
|
|
8597
|
+
allowlistChanged = true;
|
|
8598
|
+
} else if (allowlist.approvals[existingIdx].command !== command) {
|
|
8599
|
+
allowlist.approvals[existingIdx] = { event, command, approved_at: nowIso };
|
|
8600
|
+
allowlistChanged = true;
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
if (allowlistChanged) {
|
|
8604
|
+
fs13.mkdirSync(path15.dirname(allowlistPath), { recursive: true });
|
|
8605
|
+
fs13.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8606
|
+
console.log(chalk.green(" \u2705 Hermes shell-hooks allowlist populated"));
|
|
8607
|
+
anythingChanged = true;
|
|
8608
|
+
}
|
|
8609
|
+
if (anythingChanged) {
|
|
8610
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Hermes Agent!"));
|
|
8611
|
+
console.log(chalk.gray(" Restart Hermes for changes to take effect."));
|
|
8612
|
+
printDaemonTip();
|
|
8613
|
+
} else {
|
|
8614
|
+
console.log(chalk.blue("\u2139\uFE0F Node9 is already fully configured for Hermes Agent."));
|
|
8615
|
+
printDaemonTip();
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
function teardownHermes() {
|
|
8619
|
+
const homeDir2 = os12.homedir();
|
|
8620
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8621
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8622
|
+
if (!fs13.existsSync(configPath)) {
|
|
8623
|
+
console.log(chalk.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
|
|
8624
|
+
return;
|
|
8625
|
+
}
|
|
8626
|
+
const raw = fs13.readFileSync(configPath, "utf-8");
|
|
8627
|
+
const doc = yaml.parseDocument(raw);
|
|
8628
|
+
if (doc.errors.length > 0) {
|
|
8629
|
+
console.log(
|
|
8630
|
+
chalk.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
|
|
8631
|
+
);
|
|
8632
|
+
} else {
|
|
8633
|
+
teardownHermesConfigDoc(doc, configPath);
|
|
8634
|
+
}
|
|
8635
|
+
teardownHermesAllowlist(allowlistPath);
|
|
8636
|
+
}
|
|
8637
|
+
function teardownHermesConfigDoc(doc, configPath) {
|
|
8638
|
+
let anythingChanged = false;
|
|
8639
|
+
const current = doc.toJS() ?? {};
|
|
8640
|
+
for (const { event } of HERMES_HOOK_PLAN) {
|
|
8641
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8642
|
+
const filtered = existing.filter(
|
|
8643
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8644
|
+
);
|
|
8645
|
+
if (filtered.length === existing.length) continue;
|
|
8646
|
+
if (filtered.length === 0) {
|
|
8647
|
+
doc.deleteIn(["hooks", event]);
|
|
8648
|
+
} else {
|
|
8649
|
+
doc.setIn(["hooks", event], filtered);
|
|
8650
|
+
}
|
|
8651
|
+
anythingChanged = true;
|
|
8652
|
+
}
|
|
8653
|
+
const afterHooks = doc.toJS()?.hooks;
|
|
8654
|
+
if (afterHooks && typeof afterHooks === "object" && Object.keys(afterHooks).length === 0) {
|
|
8655
|
+
doc.delete("hooks");
|
|
8656
|
+
anythingChanged = true;
|
|
8657
|
+
}
|
|
8658
|
+
if (anythingChanged) {
|
|
8659
|
+
fs13.writeFileSync(configPath, doc.toString());
|
|
8660
|
+
console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath}`));
|
|
8661
|
+
} else {
|
|
8662
|
+
console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
|
|
8663
|
+
}
|
|
8664
|
+
}
|
|
8665
|
+
function teardownHermesAllowlist(allowlistPath) {
|
|
8666
|
+
if (!fs13.existsSync(allowlistPath)) return;
|
|
8667
|
+
try {
|
|
8668
|
+
const raw = fs13.readFileSync(allowlistPath, "utf-8");
|
|
8669
|
+
const allowlist = JSON.parse(raw);
|
|
8670
|
+
if (!Array.isArray(allowlist.approvals)) return;
|
|
8671
|
+
const before = allowlist.approvals.length;
|
|
8672
|
+
allowlist.approvals = allowlist.approvals.filter(
|
|
8673
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8674
|
+
);
|
|
8675
|
+
if (allowlist.approvals.length === before) return;
|
|
8676
|
+
fs13.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8677
|
+
console.log(chalk.green(` \u2705 Removed Node9 entries from ${allowlistPath}`));
|
|
8678
|
+
} catch {
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8131
8681
|
function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
8132
8682
|
const detected = detectAgents(homeDir2);
|
|
8133
8683
|
const claudeWired = (() => {
|
|
@@ -8138,6 +8688,18 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
|
8138
8688
|
const settings = readJson(path15.join(homeDir2, ".gemini", "settings.json"));
|
|
8139
8689
|
return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
|
|
8140
8690
|
})();
|
|
8691
|
+
const antigravityWired = (() => {
|
|
8692
|
+
const hooksFile = readJson(
|
|
8693
|
+
path15.join(homeDir2, ".gemini", "config", "hooks.json")
|
|
8694
|
+
);
|
|
8695
|
+
return !!hooksFile?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
|
|
8696
|
+
})();
|
|
8697
|
+
const copilotWired = (() => {
|
|
8698
|
+
const hooksFile = readJson(
|
|
8699
|
+
path15.join(homeDir2, ".copilot", "hooks", "node9.json")
|
|
8700
|
+
);
|
|
8701
|
+
return !!hooksFile?.hooks?.PreToolUse?.some((h) => isNode9Hook(h.command));
|
|
8702
|
+
})();
|
|
8141
8703
|
const cursorWired = (() => {
|
|
8142
8704
|
const cfg = readJson(path15.join(homeDir2, ".cursor", "mcp.json"));
|
|
8143
8705
|
return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
|
|
@@ -8171,6 +8733,20 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
|
8171
8733
|
wired: geminiWired,
|
|
8172
8734
|
mode: detected.gemini ? "hooks" : null
|
|
8173
8735
|
},
|
|
8736
|
+
{
|
|
8737
|
+
name: "antigravity",
|
|
8738
|
+
label: "Antigravity",
|
|
8739
|
+
installed: detected.antigravity,
|
|
8740
|
+
wired: antigravityWired,
|
|
8741
|
+
mode: detected.antigravity ? "hooks" : null
|
|
8742
|
+
},
|
|
8743
|
+
{
|
|
8744
|
+
name: "copilot",
|
|
8745
|
+
label: "GitHub Copilot",
|
|
8746
|
+
installed: detected.copilot,
|
|
8747
|
+
wired: copilotWired,
|
|
8748
|
+
mode: detected.copilot ? "hooks" : null
|
|
8749
|
+
},
|
|
8174
8750
|
{
|
|
8175
8751
|
name: "cursor",
|
|
8176
8752
|
label: "Cursor",
|
|
@@ -8239,10 +8815,29 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
|
8239
8815
|
// simple existence check on the canonical install location.
|
|
8240
8816
|
wired: fs13.existsSync(path15.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
|
|
8241
8817
|
mode: detected.pi ? "hooks" : null
|
|
8818
|
+
},
|
|
8819
|
+
{
|
|
8820
|
+
name: "hermes",
|
|
8821
|
+
label: "Hermes Agent",
|
|
8822
|
+
installed: detected.hermes,
|
|
8823
|
+
// Wired = node9 hook entry exists in the parsed config.yaml.
|
|
8824
|
+
// Reading the YAML cheaply via yaml.parse — we don't need the
|
|
8825
|
+
// Document API for a boolean status check.
|
|
8826
|
+
wired: (() => {
|
|
8827
|
+
try {
|
|
8828
|
+
const raw = fs13.readFileSync(hermesConfigPath(homeDir2), "utf-8");
|
|
8829
|
+
const cfg = yaml.parse(raw);
|
|
8830
|
+
const pre = cfg?.hooks?.["pre_tool_call"] ?? [];
|
|
8831
|
+
return pre.some((e) => typeof e?.command === "string" && isNode9Hook(e.command));
|
|
8832
|
+
} catch {
|
|
8833
|
+
return false;
|
|
8834
|
+
}
|
|
8835
|
+
})(),
|
|
8836
|
+
mode: detected.hermes ? "hooks" : null
|
|
8242
8837
|
}
|
|
8243
8838
|
];
|
|
8244
8839
|
}
|
|
8245
|
-
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME;
|
|
8840
|
+
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME, HERMES_CONFIG_FILENAME, HERMES_ALLOWLIST_FILENAME, HERMES_HOOK_PLAN;
|
|
8246
8841
|
var init_setup = __esm({
|
|
8247
8842
|
"src/setup.ts"() {
|
|
8248
8843
|
"use strict";
|
|
@@ -8253,10 +8848,93 @@ var init_setup = __esm({
|
|
|
8253
8848
|
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
8254
8849
|
OPENCODE_PLUGIN_NAME = "node9.js";
|
|
8255
8850
|
PI_EXTENSION_NAME = "node9.js";
|
|
8851
|
+
HERMES_CONFIG_FILENAME = "config.yaml";
|
|
8852
|
+
HERMES_ALLOWLIST_FILENAME = "shell-hooks-allowlist.json";
|
|
8853
|
+
HERMES_HOOK_PLAN = [
|
|
8854
|
+
{ event: "pre_tool_call", subcmd: "check" },
|
|
8855
|
+
{ event: "post_tool_call", subcmd: "log" }
|
|
8856
|
+
];
|
|
8857
|
+
}
|
|
8858
|
+
});
|
|
8859
|
+
|
|
8860
|
+
// src/utils/hook-payload.ts
|
|
8861
|
+
function extractToolName(payload, defaultValue = "") {
|
|
8862
|
+
return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
|
|
8863
|
+
}
|
|
8864
|
+
function extractToolInput(payload) {
|
|
8865
|
+
return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
|
|
8866
|
+
}
|
|
8867
|
+
function canonicalToolName(name) {
|
|
8868
|
+
switch (name) {
|
|
8869
|
+
// Hermes Agent
|
|
8870
|
+
case "terminal":
|
|
8871
|
+
return "Bash";
|
|
8872
|
+
case "write_file":
|
|
8873
|
+
return "Write";
|
|
8874
|
+
case "patch":
|
|
8875
|
+
return "Edit";
|
|
8876
|
+
case "read_file":
|
|
8877
|
+
return "Read";
|
|
8878
|
+
case "search_files":
|
|
8879
|
+
return "Grep";
|
|
8880
|
+
// Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
|
|
8881
|
+
case "run_command":
|
|
8882
|
+
return "Bash";
|
|
8883
|
+
default:
|
|
8884
|
+
return name;
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
function agentLabelFromFlag(flag) {
|
|
8888
|
+
if (typeof flag !== "string") return void 0;
|
|
8889
|
+
switch (flag.toLowerCase()) {
|
|
8890
|
+
case "antigravity":
|
|
8891
|
+
case "agy":
|
|
8892
|
+
return "Antigravity";
|
|
8893
|
+
case "copilot":
|
|
8894
|
+
return "GitHub Copilot";
|
|
8895
|
+
default:
|
|
8896
|
+
return void 0;
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
function canonicalToolInput(rawToolName, input) {
|
|
8900
|
+
if (rawToolName !== "run_command") return input;
|
|
8901
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
8902
|
+
const args = input;
|
|
8903
|
+
if (typeof args.CommandLine !== "string") return input;
|
|
8904
|
+
const { CommandLine, Cwd, ...rest } = args;
|
|
8905
|
+
const canonical = { ...rest, command: CommandLine };
|
|
8906
|
+
if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
|
|
8907
|
+
return canonical;
|
|
8908
|
+
}
|
|
8909
|
+
var init_hook_payload = __esm({
|
|
8910
|
+
"src/utils/hook-payload.ts"() {
|
|
8911
|
+
"use strict";
|
|
8256
8912
|
}
|
|
8257
8913
|
});
|
|
8258
8914
|
|
|
8259
8915
|
// src/scan-summary.ts
|
|
8916
|
+
function agentDisplayName(agent) {
|
|
8917
|
+
return AGENT_LONG[agent] ?? "Claude Code";
|
|
8918
|
+
}
|
|
8919
|
+
function agentBadgeText(agent, width = 10) {
|
|
8920
|
+
return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
|
|
8921
|
+
}
|
|
8922
|
+
function agentColorName(agent) {
|
|
8923
|
+
switch (agent) {
|
|
8924
|
+
case "gemini":
|
|
8925
|
+
return "blue";
|
|
8926
|
+
case "codex":
|
|
8927
|
+
return "magenta";
|
|
8928
|
+
case "antigravity":
|
|
8929
|
+
return "yellow";
|
|
8930
|
+
case "copilot":
|
|
8931
|
+
return "green";
|
|
8932
|
+
case "shell":
|
|
8933
|
+
return "yellow";
|
|
8934
|
+
default:
|
|
8935
|
+
return "cyan";
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8260
8938
|
function buildScanSummary(agents) {
|
|
8261
8939
|
const stats = {
|
|
8262
8940
|
sessions: 0,
|
|
@@ -8433,12 +9111,29 @@ function fullCommandOf(input) {
|
|
|
8433
9111
|
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
8434
9112
|
return String(raw).replace(/\s+/g, " ").trim();
|
|
8435
9113
|
}
|
|
9114
|
+
var AGENT_SHORT, AGENT_LONG;
|
|
8436
9115
|
var init_scan_summary = __esm({
|
|
8437
9116
|
"src/scan-summary.ts"() {
|
|
8438
9117
|
"use strict";
|
|
8439
9118
|
init_shields();
|
|
8440
9119
|
init_dist();
|
|
8441
9120
|
init_dist();
|
|
9121
|
+
AGENT_SHORT = {
|
|
9122
|
+
claude: "Claude",
|
|
9123
|
+
gemini: "Gemini",
|
|
9124
|
+
codex: "Codex",
|
|
9125
|
+
antigravity: "Agy",
|
|
9126
|
+
copilot: "Copilot",
|
|
9127
|
+
shell: "Shell"
|
|
9128
|
+
};
|
|
9129
|
+
AGENT_LONG = {
|
|
9130
|
+
claude: "Claude Code",
|
|
9131
|
+
gemini: "Gemini CLI",
|
|
9132
|
+
codex: "Codex",
|
|
9133
|
+
antigravity: "Antigravity",
|
|
9134
|
+
copilot: "GitHub Copilot",
|
|
9135
|
+
shell: "Shell"
|
|
9136
|
+
};
|
|
8442
9137
|
}
|
|
8443
9138
|
});
|
|
8444
9139
|
|
|
@@ -10083,6 +10778,34 @@ function countScanFiles() {
|
|
|
10083
10778
|
} catch {
|
|
10084
10779
|
}
|
|
10085
10780
|
}
|
|
10781
|
+
for (const surface of ["antigravity-cli", "antigravity-ide"]) {
|
|
10782
|
+
const brainDir = path22.join(os19.homedir(), ".gemini", surface, "brain");
|
|
10783
|
+
if (!fs20.existsSync(brainDir)) continue;
|
|
10784
|
+
try {
|
|
10785
|
+
for (const conv of fs20.readdirSync(brainDir)) {
|
|
10786
|
+
const convPath = path22.join(brainDir, conv);
|
|
10787
|
+
try {
|
|
10788
|
+
if (!fs20.statSync(convPath).isDirectory()) continue;
|
|
10789
|
+
const logsDir = path22.join(convPath, ".system_generated", "logs");
|
|
10790
|
+
if (fs20.existsSync(path22.join(logsDir, "transcript_full.jsonl")) || fs20.existsSync(path22.join(logsDir, "transcript.jsonl"))) {
|
|
10791
|
+
total += 1;
|
|
10792
|
+
}
|
|
10793
|
+
} catch {
|
|
10794
|
+
continue;
|
|
10795
|
+
}
|
|
10796
|
+
}
|
|
10797
|
+
} catch {
|
|
10798
|
+
}
|
|
10799
|
+
}
|
|
10800
|
+
const copilotDir = path22.join(os19.homedir(), ".copilot", "session-state");
|
|
10801
|
+
if (fs20.existsSync(copilotDir)) {
|
|
10802
|
+
try {
|
|
10803
|
+
for (const sid of fs20.readdirSync(copilotDir)) {
|
|
10804
|
+
if (fs20.existsSync(path22.join(copilotDir, sid, "events.jsonl"))) total += 1;
|
|
10805
|
+
}
|
|
10806
|
+
} catch {
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10086
10809
|
const codexDir = path22.join(os19.homedir(), ".codex", "sessions");
|
|
10087
10810
|
if (fs20.existsSync(codexDir)) {
|
|
10088
10811
|
try {
|
|
@@ -10411,8 +11134,231 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
10411
11134
|
}
|
|
10412
11135
|
return result;
|
|
10413
11136
|
}
|
|
10414
|
-
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
10415
|
-
const tmpDir = path22.join(os19.homedir(), ".gemini", "tmp");
|
|
11137
|
+
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
11138
|
+
const tmpDir = path22.join(os19.homedir(), ".gemini", "tmp");
|
|
11139
|
+
const result = {
|
|
11140
|
+
filesScanned: 0,
|
|
11141
|
+
sessions: 0,
|
|
11142
|
+
totalToolCalls: 0,
|
|
11143
|
+
bashCalls: 0,
|
|
11144
|
+
findings: [],
|
|
11145
|
+
dlpFindings: [],
|
|
11146
|
+
loopFindings: [],
|
|
11147
|
+
totalCostUSD: 0,
|
|
11148
|
+
firstDate: null,
|
|
11149
|
+
lastDate: null,
|
|
11150
|
+
sessionsWithEarlySecrets: 0
|
|
11151
|
+
};
|
|
11152
|
+
const dedup = emptyScanDedup();
|
|
11153
|
+
if (!fs20.existsSync(tmpDir)) return result;
|
|
11154
|
+
let slugDirs;
|
|
11155
|
+
try {
|
|
11156
|
+
slugDirs = fs20.readdirSync(tmpDir);
|
|
11157
|
+
} catch {
|
|
11158
|
+
return result;
|
|
11159
|
+
}
|
|
11160
|
+
const ruleSources = buildRuleSources();
|
|
11161
|
+
for (const slug of slugDirs) {
|
|
11162
|
+
const slugPath = path22.join(tmpDir, slug);
|
|
11163
|
+
try {
|
|
11164
|
+
if (!fs20.statSync(slugPath).isDirectory()) continue;
|
|
11165
|
+
} catch {
|
|
11166
|
+
continue;
|
|
11167
|
+
}
|
|
11168
|
+
let projLabel = stripTerminalEscapes(slug).slice(0, 40);
|
|
11169
|
+
try {
|
|
11170
|
+
projLabel = stripTerminalEscapes(
|
|
11171
|
+
fs20.readFileSync(path22.join(slugPath, ".project_root"), "utf-8").trim()
|
|
11172
|
+
).replace(os19.homedir(), "~").slice(0, 40);
|
|
11173
|
+
} catch {
|
|
11174
|
+
}
|
|
11175
|
+
const chatsDir = path22.join(slugPath, "chats");
|
|
11176
|
+
if (!fs20.existsSync(chatsDir)) continue;
|
|
11177
|
+
let chatFiles;
|
|
11178
|
+
try {
|
|
11179
|
+
chatFiles = fs20.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
11180
|
+
} catch {
|
|
11181
|
+
continue;
|
|
11182
|
+
}
|
|
11183
|
+
for (const chatFile of chatFiles) {
|
|
11184
|
+
result.filesScanned++;
|
|
11185
|
+
onProgress?.(result.filesScanned);
|
|
11186
|
+
const sessionId = chatFile.replace(/\.json$/, "");
|
|
11187
|
+
let raw;
|
|
11188
|
+
try {
|
|
11189
|
+
raw = fs20.readFileSync(path22.join(chatsDir, chatFile), "utf-8");
|
|
11190
|
+
} catch {
|
|
11191
|
+
continue;
|
|
11192
|
+
}
|
|
11193
|
+
const sessionCalls = [];
|
|
11194
|
+
let session;
|
|
11195
|
+
try {
|
|
11196
|
+
session = JSON.parse(raw);
|
|
11197
|
+
} catch {
|
|
11198
|
+
continue;
|
|
11199
|
+
}
|
|
11200
|
+
result.sessions++;
|
|
11201
|
+
for (const msg of session.messages ?? []) {
|
|
11202
|
+
onLine?.();
|
|
11203
|
+
if (msg.type === "user") {
|
|
11204
|
+
const content = msg.content;
|
|
11205
|
+
const text = Array.isArray(content) ? content.map((c) => c.text ?? "").join("\n") : typeof content === "string" ? content : "";
|
|
11206
|
+
if (text) {
|
|
11207
|
+
const dlpMatch = scanArgs({ text });
|
|
11208
|
+
if (dlpMatch) {
|
|
11209
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
11210
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
11211
|
+
dedup.dlpKeys.add(k);
|
|
11212
|
+
result.dlpFindings.push({
|
|
11213
|
+
patternName: dlpMatch.patternName,
|
|
11214
|
+
redactedSample: dlpMatch.redactedSample,
|
|
11215
|
+
toolName: "user-prompt",
|
|
11216
|
+
timestamp: msg.timestamp ?? "",
|
|
11217
|
+
project: projLabel,
|
|
11218
|
+
sessionId,
|
|
11219
|
+
agent: "gemini"
|
|
11220
|
+
});
|
|
11221
|
+
}
|
|
11222
|
+
}
|
|
11223
|
+
}
|
|
11224
|
+
continue;
|
|
11225
|
+
}
|
|
11226
|
+
if (msg.type !== "gemini") continue;
|
|
11227
|
+
if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
|
|
11228
|
+
if (msg.timestamp) {
|
|
11229
|
+
if (!result.firstDate || msg.timestamp < result.firstDate)
|
|
11230
|
+
result.firstDate = msg.timestamp;
|
|
11231
|
+
if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
|
|
11232
|
+
}
|
|
11233
|
+
const tokens = msg.tokens;
|
|
11234
|
+
const model = msg.model;
|
|
11235
|
+
if (tokens && model) {
|
|
11236
|
+
const p = geminiModelPrice(model);
|
|
11237
|
+
if (p) {
|
|
11238
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
11239
|
+
result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
11243
|
+
result.totalToolCalls++;
|
|
11244
|
+
const toolName = tc.name ?? "";
|
|
11245
|
+
const toolNameLower = toolName.toLowerCase();
|
|
11246
|
+
const input = tc.args ?? {};
|
|
11247
|
+
sessionCalls.push({ toolName, input, timestamp: msg.timestamp ?? "" });
|
|
11248
|
+
if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
|
|
11249
|
+
result.bashCalls++;
|
|
11250
|
+
}
|
|
11251
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
11252
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
11253
|
+
continue;
|
|
11254
|
+
const dlpMatch = scanArgs(input);
|
|
11255
|
+
if (dlpMatch) {
|
|
11256
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
11257
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
11258
|
+
dedup.dlpKeys.add(k);
|
|
11259
|
+
result.dlpFindings.push({
|
|
11260
|
+
patternName: dlpMatch.patternName,
|
|
11261
|
+
redactedSample: dlpMatch.redactedSample,
|
|
11262
|
+
toolName,
|
|
11263
|
+
timestamp: msg.timestamp ?? "",
|
|
11264
|
+
project: projLabel,
|
|
11265
|
+
sessionId,
|
|
11266
|
+
agent: "gemini"
|
|
11267
|
+
});
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
let astFsMatched = false;
|
|
11271
|
+
const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
|
|
11272
|
+
if (astRanForBash) {
|
|
11273
|
+
astFsMatched = pushFsOpAstFinding(
|
|
11274
|
+
String(input.command ?? ""),
|
|
11275
|
+
toolName,
|
|
11276
|
+
input,
|
|
11277
|
+
msg.timestamp ?? "",
|
|
11278
|
+
projLabel,
|
|
11279
|
+
sessionId,
|
|
11280
|
+
"gemini",
|
|
11281
|
+
result,
|
|
11282
|
+
dedup
|
|
11283
|
+
);
|
|
11284
|
+
}
|
|
11285
|
+
let ruleMatched = astFsMatched;
|
|
11286
|
+
for (const source of ruleSources) {
|
|
11287
|
+
const { rule } = source;
|
|
11288
|
+
if (rule.verdict === "allow") continue;
|
|
11289
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
11290
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
11291
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
11292
|
+
const inputPreview = preview(input, 120);
|
|
11293
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
11294
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
11295
|
+
dedup.findingsKeys.add(k);
|
|
11296
|
+
result.findings.push({
|
|
11297
|
+
source,
|
|
11298
|
+
toolName,
|
|
11299
|
+
input,
|
|
11300
|
+
timestamp: msg.timestamp ?? "",
|
|
11301
|
+
project: projLabel,
|
|
11302
|
+
sessionId,
|
|
11303
|
+
agent: "gemini"
|
|
11304
|
+
});
|
|
11305
|
+
}
|
|
11306
|
+
ruleMatched = true;
|
|
11307
|
+
break;
|
|
11308
|
+
}
|
|
11309
|
+
const isShellTool = ["bash", "execute_bash", "run_shell_command", "shell"].includes(
|
|
11310
|
+
toolNameLower
|
|
11311
|
+
);
|
|
11312
|
+
if (!ruleMatched && isShellTool) {
|
|
11313
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
11314
|
+
if (shellVerdict) {
|
|
11315
|
+
const astRule = {
|
|
11316
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
11317
|
+
tool: "bash",
|
|
11318
|
+
conditions: [],
|
|
11319
|
+
verdict: shellVerdict,
|
|
11320
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
11321
|
+
};
|
|
11322
|
+
const inputPreview = preview(input, 120);
|
|
11323
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
11324
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
11325
|
+
dedup.findingsKeys.add(k);
|
|
11326
|
+
result.findings.push({
|
|
11327
|
+
source: {
|
|
11328
|
+
shieldName: "bash-safe",
|
|
11329
|
+
shieldLabel: "bash-safe (AST)",
|
|
11330
|
+
sourceType: "shield",
|
|
11331
|
+
rule: astRule
|
|
11332
|
+
},
|
|
11333
|
+
toolName,
|
|
11334
|
+
input,
|
|
11335
|
+
timestamp: msg.timestamp ?? "",
|
|
11336
|
+
project: projLabel,
|
|
11337
|
+
sessionId,
|
|
11338
|
+
agent: "gemini"
|
|
11339
|
+
});
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
}
|
|
11343
|
+
}
|
|
11344
|
+
}
|
|
11345
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "gemini"));
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
return result;
|
|
11349
|
+
}
|
|
11350
|
+
function antigravityBrainDirs() {
|
|
11351
|
+
return ["antigravity-cli", "antigravity-ide"].map((surface) => path22.join(os19.homedir(), ".gemini", surface, "brain")).filter((p) => fs20.existsSync(p));
|
|
11352
|
+
}
|
|
11353
|
+
function antigravityTranscriptPath(convPath) {
|
|
11354
|
+
const logsDir = path22.join(convPath, ".system_generated", "logs");
|
|
11355
|
+
for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
|
|
11356
|
+
const p = path22.join(logsDir, name);
|
|
11357
|
+
if (fs20.existsSync(p)) return p;
|
|
11358
|
+
}
|
|
11359
|
+
return null;
|
|
11360
|
+
}
|
|
11361
|
+
function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
10416
11362
|
const result = {
|
|
10417
11363
|
filesScanned: 0,
|
|
10418
11364
|
sessions: 0,
|
|
@@ -10422,64 +11368,56 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10422
11368
|
dlpFindings: [],
|
|
10423
11369
|
loopFindings: [],
|
|
10424
11370
|
totalCostUSD: 0,
|
|
11371
|
+
// transcripts carry no token/model data
|
|
10425
11372
|
firstDate: null,
|
|
10426
11373
|
lastDate: null,
|
|
10427
11374
|
sessionsWithEarlySecrets: 0
|
|
10428
11375
|
};
|
|
10429
11376
|
const dedup = emptyScanDedup();
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
try {
|
|
10433
|
-
slugDirs = fs20.readdirSync(tmpDir);
|
|
10434
|
-
} catch {
|
|
10435
|
-
return result;
|
|
10436
|
-
}
|
|
11377
|
+
const brainDirs = antigravityBrainDirs();
|
|
11378
|
+
if (brainDirs.length === 0) return result;
|
|
10437
11379
|
const ruleSources = buildRuleSources();
|
|
10438
|
-
for (const
|
|
10439
|
-
|
|
11380
|
+
for (const brainDir of brainDirs) {
|
|
11381
|
+
let convDirs;
|
|
10440
11382
|
try {
|
|
10441
|
-
|
|
10442
|
-
} catch {
|
|
10443
|
-
continue;
|
|
10444
|
-
}
|
|
10445
|
-
let projLabel = stripTerminalEscapes(slug).slice(0, 40);
|
|
10446
|
-
try {
|
|
10447
|
-
projLabel = stripTerminalEscapes(
|
|
10448
|
-
fs20.readFileSync(path22.join(slugPath, ".project_root"), "utf-8").trim()
|
|
10449
|
-
).replace(os19.homedir(), "~").slice(0, 40);
|
|
10450
|
-
} catch {
|
|
10451
|
-
}
|
|
10452
|
-
const chatsDir = path22.join(slugPath, "chats");
|
|
10453
|
-
if (!fs20.existsSync(chatsDir)) continue;
|
|
10454
|
-
let chatFiles;
|
|
10455
|
-
try {
|
|
10456
|
-
chatFiles = fs20.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
11383
|
+
convDirs = fs20.readdirSync(brainDir);
|
|
10457
11384
|
} catch {
|
|
10458
11385
|
continue;
|
|
10459
11386
|
}
|
|
10460
|
-
for (const
|
|
10461
|
-
|
|
10462
|
-
onProgress?.(result.filesScanned);
|
|
10463
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
10464
|
-
let raw;
|
|
11387
|
+
for (const conv of convDirs) {
|
|
11388
|
+
const convPath = path22.join(brainDir, conv);
|
|
10465
11389
|
try {
|
|
10466
|
-
|
|
11390
|
+
if (!fs20.statSync(convPath).isDirectory()) continue;
|
|
10467
11391
|
} catch {
|
|
10468
11392
|
continue;
|
|
10469
11393
|
}
|
|
10470
|
-
const
|
|
10471
|
-
|
|
11394
|
+
const transcriptFile = antigravityTranscriptPath(convPath);
|
|
11395
|
+
if (!transcriptFile) continue;
|
|
11396
|
+
result.filesScanned++;
|
|
11397
|
+
onProgress?.(result.filesScanned);
|
|
11398
|
+
let raw;
|
|
10472
11399
|
try {
|
|
10473
|
-
|
|
11400
|
+
raw = fs20.readFileSync(transcriptFile, "utf-8");
|
|
10474
11401
|
} catch {
|
|
10475
11402
|
continue;
|
|
10476
11403
|
}
|
|
11404
|
+
const sessionId = conv;
|
|
11405
|
+
let projLabel = conv.slice(0, 8);
|
|
11406
|
+
const sessionCalls = [];
|
|
10477
11407
|
result.sessions++;
|
|
10478
|
-
for (const
|
|
11408
|
+
for (const line of raw.split("\n")) {
|
|
11409
|
+
if (!line.trim()) continue;
|
|
10479
11410
|
onLine?.();
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
11411
|
+
let step;
|
|
11412
|
+
try {
|
|
11413
|
+
step = JSON.parse(line);
|
|
11414
|
+
} catch {
|
|
11415
|
+
continue;
|
|
11416
|
+
}
|
|
11417
|
+
const timestamp = step.created_at ?? "";
|
|
11418
|
+
if (startDate && timestamp && new Date(timestamp) < startDate) continue;
|
|
11419
|
+
if (step.type === "USER_INPUT") {
|
|
11420
|
+
const text = typeof step.content === "string" ? step.content : "";
|
|
10483
11421
|
if (text) {
|
|
10484
11422
|
const dlpMatch = scanArgs({ text });
|
|
10485
11423
|
if (dlpMatch) {
|
|
@@ -10490,40 +11428,34 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10490
11428
|
patternName: dlpMatch.patternName,
|
|
10491
11429
|
redactedSample: dlpMatch.redactedSample,
|
|
10492
11430
|
toolName: "user-prompt",
|
|
10493
|
-
timestamp
|
|
11431
|
+
timestamp,
|
|
10494
11432
|
project: projLabel,
|
|
10495
11433
|
sessionId,
|
|
10496
|
-
agent: "
|
|
11434
|
+
agent: "antigravity"
|
|
10497
11435
|
});
|
|
10498
11436
|
}
|
|
10499
11437
|
}
|
|
10500
11438
|
}
|
|
10501
11439
|
continue;
|
|
10502
11440
|
}
|
|
10503
|
-
if (
|
|
10504
|
-
if (
|
|
10505
|
-
|
|
10506
|
-
if (!result.
|
|
10507
|
-
result.firstDate = msg.timestamp;
|
|
10508
|
-
if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
|
|
10509
|
-
}
|
|
10510
|
-
const tokens = msg.tokens;
|
|
10511
|
-
const model = msg.model;
|
|
10512
|
-
if (tokens && model) {
|
|
10513
|
-
const p = geminiModelPrice(model);
|
|
10514
|
-
if (p) {
|
|
10515
|
-
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
10516
|
-
result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
10517
|
-
}
|
|
11441
|
+
if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
|
|
11442
|
+
if (timestamp) {
|
|
11443
|
+
if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
|
|
11444
|
+
if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
|
|
10518
11445
|
}
|
|
10519
|
-
for (const tc of
|
|
11446
|
+
for (const tc of step.tool_calls) {
|
|
10520
11447
|
result.totalToolCalls++;
|
|
10521
11448
|
const toolName = tc.name ?? "";
|
|
10522
11449
|
const toolNameLower = toolName.toLowerCase();
|
|
10523
|
-
const input = tc.args ?? {};
|
|
10524
|
-
sessionCalls.push({ toolName, input, timestamp
|
|
10525
|
-
|
|
11450
|
+
const input = canonicalToolInput(toolName, tc.args ?? {});
|
|
11451
|
+
sessionCalls.push({ toolName, input, timestamp });
|
|
11452
|
+
const isShellTool = toolNameLower === "run_command";
|
|
11453
|
+
if (isShellTool) {
|
|
10526
11454
|
result.bashCalls++;
|
|
11455
|
+
const cwd = String(input.cwd ?? "");
|
|
11456
|
+
if (cwd && projLabel === conv.slice(0, 8)) {
|
|
11457
|
+
projLabel = stripTerminalEscapes(cwd).replace(os19.homedir(), "~").slice(0, 40);
|
|
11458
|
+
}
|
|
10527
11459
|
}
|
|
10528
11460
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
10529
11461
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
@@ -10537,24 +11469,23 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10537
11469
|
patternName: dlpMatch.patternName,
|
|
10538
11470
|
redactedSample: dlpMatch.redactedSample,
|
|
10539
11471
|
toolName,
|
|
10540
|
-
timestamp
|
|
11472
|
+
timestamp,
|
|
10541
11473
|
project: projLabel,
|
|
10542
11474
|
sessionId,
|
|
10543
|
-
agent: "
|
|
11475
|
+
agent: "antigravity"
|
|
10544
11476
|
});
|
|
10545
11477
|
}
|
|
10546
11478
|
}
|
|
10547
11479
|
let astFsMatched = false;
|
|
10548
|
-
|
|
10549
|
-
if (astRanForBash) {
|
|
11480
|
+
if (isShellTool) {
|
|
10550
11481
|
astFsMatched = pushFsOpAstFinding(
|
|
10551
11482
|
String(input.command ?? ""),
|
|
10552
11483
|
toolName,
|
|
10553
11484
|
input,
|
|
10554
|
-
|
|
11485
|
+
timestamp,
|
|
10555
11486
|
projLabel,
|
|
10556
11487
|
sessionId,
|
|
10557
|
-
"
|
|
11488
|
+
"antigravity",
|
|
10558
11489
|
result,
|
|
10559
11490
|
dedup
|
|
10560
11491
|
);
|
|
@@ -10563,8 +11494,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10563
11494
|
for (const source of ruleSources) {
|
|
10564
11495
|
const { rule } = source;
|
|
10565
11496
|
if (rule.verdict === "allow") continue;
|
|
10566
|
-
|
|
10567
|
-
if (
|
|
11497
|
+
const ruleToolName = isShellTool ? "bash" : toolNameLower;
|
|
11498
|
+
if (rule.tool && !matchesPattern(ruleToolName, rule.tool)) continue;
|
|
11499
|
+
if (isShellTool && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
10568
11500
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
10569
11501
|
const inputPreview = preview(input, 120);
|
|
10570
11502
|
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
@@ -10574,18 +11506,15 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10574
11506
|
source,
|
|
10575
11507
|
toolName,
|
|
10576
11508
|
input,
|
|
10577
|
-
timestamp
|
|
11509
|
+
timestamp,
|
|
10578
11510
|
project: projLabel,
|
|
10579
11511
|
sessionId,
|
|
10580
|
-
agent: "
|
|
11512
|
+
agent: "antigravity"
|
|
10581
11513
|
});
|
|
10582
11514
|
}
|
|
10583
11515
|
ruleMatched = true;
|
|
10584
11516
|
break;
|
|
10585
11517
|
}
|
|
10586
|
-
const isShellTool = ["bash", "execute_bash", "run_shell_command", "shell"].includes(
|
|
10587
|
-
toolNameLower
|
|
10588
|
-
);
|
|
10589
11518
|
if (!ruleMatched && isShellTool) {
|
|
10590
11519
|
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
10591
11520
|
if (shellVerdict) {
|
|
@@ -10609,18 +11538,201 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
10609
11538
|
},
|
|
10610
11539
|
toolName,
|
|
10611
11540
|
input,
|
|
10612
|
-
timestamp
|
|
11541
|
+
timestamp,
|
|
10613
11542
|
project: projLabel,
|
|
10614
11543
|
sessionId,
|
|
10615
|
-
agent: "
|
|
11544
|
+
agent: "antigravity"
|
|
10616
11545
|
});
|
|
10617
11546
|
}
|
|
10618
11547
|
}
|
|
10619
11548
|
}
|
|
10620
11549
|
}
|
|
10621
11550
|
}
|
|
10622
|
-
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "
|
|
11551
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "antigravity"));
|
|
11552
|
+
}
|
|
11553
|
+
}
|
|
11554
|
+
return result;
|
|
11555
|
+
}
|
|
11556
|
+
function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
11557
|
+
const sessionDir = path22.join(os19.homedir(), ".copilot", "session-state");
|
|
11558
|
+
const result = {
|
|
11559
|
+
filesScanned: 0,
|
|
11560
|
+
sessions: 0,
|
|
11561
|
+
totalToolCalls: 0,
|
|
11562
|
+
bashCalls: 0,
|
|
11563
|
+
findings: [],
|
|
11564
|
+
dlpFindings: [],
|
|
11565
|
+
loopFindings: [],
|
|
11566
|
+
totalCostUSD: 0,
|
|
11567
|
+
// event logs carry no token/cost rollup
|
|
11568
|
+
firstDate: null,
|
|
11569
|
+
lastDate: null,
|
|
11570
|
+
sessionsWithEarlySecrets: 0
|
|
11571
|
+
};
|
|
11572
|
+
const dedup = emptyScanDedup();
|
|
11573
|
+
if (!fs20.existsSync(sessionDir)) return result;
|
|
11574
|
+
let sessionIds;
|
|
11575
|
+
try {
|
|
11576
|
+
sessionIds = fs20.readdirSync(sessionDir);
|
|
11577
|
+
} catch {
|
|
11578
|
+
return result;
|
|
11579
|
+
}
|
|
11580
|
+
const ruleSources = buildRuleSources();
|
|
11581
|
+
for (const sessionId of sessionIds) {
|
|
11582
|
+
const eventsPath = path22.join(sessionDir, sessionId, "events.jsonl");
|
|
11583
|
+
if (!fs20.existsSync(eventsPath)) continue;
|
|
11584
|
+
result.filesScanned++;
|
|
11585
|
+
onProgress?.(result.filesScanned);
|
|
11586
|
+
let raw;
|
|
11587
|
+
try {
|
|
11588
|
+
raw = fs20.readFileSync(eventsPath, "utf-8");
|
|
11589
|
+
} catch {
|
|
11590
|
+
continue;
|
|
11591
|
+
}
|
|
11592
|
+
let projLabel = sessionId.slice(0, 8);
|
|
11593
|
+
const sessionCalls = [];
|
|
11594
|
+
result.sessions++;
|
|
11595
|
+
for (const line of raw.split("\n")) {
|
|
11596
|
+
if (!line.trim()) continue;
|
|
11597
|
+
onLine?.();
|
|
11598
|
+
let ev;
|
|
11599
|
+
try {
|
|
11600
|
+
ev = JSON.parse(line);
|
|
11601
|
+
} catch {
|
|
11602
|
+
continue;
|
|
11603
|
+
}
|
|
11604
|
+
const timestamp = ev.timestamp ?? "";
|
|
11605
|
+
if (ev.type === "session.start") {
|
|
11606
|
+
const cwd = ev.data?.context?.cwd;
|
|
11607
|
+
if (typeof cwd === "string" && cwd) {
|
|
11608
|
+
projLabel = stripTerminalEscapes(cwd).replace(os19.homedir(), "~").slice(0, 40);
|
|
11609
|
+
}
|
|
11610
|
+
continue;
|
|
11611
|
+
}
|
|
11612
|
+
if (startDate && timestamp && new Date(timestamp) < startDate) continue;
|
|
11613
|
+
if (ev.type === "user.message") {
|
|
11614
|
+
const text = ev.data?.content ?? ev.data?.text ?? "";
|
|
11615
|
+
if (typeof text === "string" && text) {
|
|
11616
|
+
const dlpMatch2 = scanArgs({ text });
|
|
11617
|
+
if (dlpMatch2) {
|
|
11618
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
11619
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
11620
|
+
dedup.dlpKeys.add(k);
|
|
11621
|
+
result.dlpFindings.push({
|
|
11622
|
+
patternName: dlpMatch2.patternName,
|
|
11623
|
+
redactedSample: dlpMatch2.redactedSample,
|
|
11624
|
+
toolName: "user-prompt",
|
|
11625
|
+
timestamp,
|
|
11626
|
+
project: projLabel,
|
|
11627
|
+
sessionId,
|
|
11628
|
+
agent: "copilot"
|
|
11629
|
+
});
|
|
11630
|
+
}
|
|
11631
|
+
}
|
|
11632
|
+
}
|
|
11633
|
+
continue;
|
|
11634
|
+
}
|
|
11635
|
+
if (ev.type !== "tool.execution_start") continue;
|
|
11636
|
+
const toolName = ev.data?.toolName ?? "";
|
|
11637
|
+
const toolNameLower = toolName.toLowerCase();
|
|
11638
|
+
const input = ev.data?.arguments ?? {};
|
|
11639
|
+
result.totalToolCalls++;
|
|
11640
|
+
sessionCalls.push({ toolName, input, timestamp });
|
|
11641
|
+
const isShellTool = toolNameLower === "bash" || toolNameLower === "shell";
|
|
11642
|
+
if (isShellTool) result.bashCalls++;
|
|
11643
|
+
if (timestamp) {
|
|
11644
|
+
if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
|
|
11645
|
+
if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
|
|
11646
|
+
}
|
|
11647
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
11648
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
11649
|
+
const dlpMatch = scanArgs(input);
|
|
11650
|
+
if (dlpMatch) {
|
|
11651
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
11652
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
11653
|
+
dedup.dlpKeys.add(k);
|
|
11654
|
+
result.dlpFindings.push({
|
|
11655
|
+
patternName: dlpMatch.patternName,
|
|
11656
|
+
redactedSample: dlpMatch.redactedSample,
|
|
11657
|
+
toolName,
|
|
11658
|
+
timestamp,
|
|
11659
|
+
project: projLabel,
|
|
11660
|
+
sessionId,
|
|
11661
|
+
agent: "copilot"
|
|
11662
|
+
});
|
|
11663
|
+
}
|
|
11664
|
+
}
|
|
11665
|
+
let astFsMatched = false;
|
|
11666
|
+
if (isShellTool) {
|
|
11667
|
+
astFsMatched = pushFsOpAstFinding(
|
|
11668
|
+
String(input.command ?? ""),
|
|
11669
|
+
toolName,
|
|
11670
|
+
input,
|
|
11671
|
+
timestamp,
|
|
11672
|
+
projLabel,
|
|
11673
|
+
sessionId,
|
|
11674
|
+
"copilot",
|
|
11675
|
+
result,
|
|
11676
|
+
dedup
|
|
11677
|
+
);
|
|
11678
|
+
}
|
|
11679
|
+
let ruleMatched = astFsMatched;
|
|
11680
|
+
for (const source of ruleSources) {
|
|
11681
|
+
const { rule } = source;
|
|
11682
|
+
if (rule.verdict === "allow") continue;
|
|
11683
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
11684
|
+
if (isShellTool && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
11685
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
11686
|
+
const inputPreview = preview(input, 120);
|
|
11687
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
11688
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
11689
|
+
dedup.findingsKeys.add(k);
|
|
11690
|
+
result.findings.push({
|
|
11691
|
+
source,
|
|
11692
|
+
toolName,
|
|
11693
|
+
input,
|
|
11694
|
+
timestamp,
|
|
11695
|
+
project: projLabel,
|
|
11696
|
+
sessionId,
|
|
11697
|
+
agent: "copilot"
|
|
11698
|
+
});
|
|
11699
|
+
}
|
|
11700
|
+
ruleMatched = true;
|
|
11701
|
+
break;
|
|
11702
|
+
}
|
|
11703
|
+
if (!ruleMatched && isShellTool) {
|
|
11704
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
11705
|
+
if (shellVerdict) {
|
|
11706
|
+
const astRule = {
|
|
11707
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
11708
|
+
tool: "bash",
|
|
11709
|
+
conditions: [],
|
|
11710
|
+
verdict: shellVerdict,
|
|
11711
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
11712
|
+
};
|
|
11713
|
+
const inputPreview = preview(input, 120);
|
|
11714
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
11715
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
11716
|
+
dedup.findingsKeys.add(k);
|
|
11717
|
+
result.findings.push({
|
|
11718
|
+
source: {
|
|
11719
|
+
shieldName: "bash-safe",
|
|
11720
|
+
shieldLabel: "bash-safe (AST)",
|
|
11721
|
+
sourceType: "shield",
|
|
11722
|
+
rule: astRule
|
|
11723
|
+
},
|
|
11724
|
+
toolName,
|
|
11725
|
+
input,
|
|
11726
|
+
timestamp,
|
|
11727
|
+
project: projLabel,
|
|
11728
|
+
sessionId,
|
|
11729
|
+
agent: "copilot"
|
|
11730
|
+
});
|
|
11731
|
+
}
|
|
11732
|
+
}
|
|
11733
|
+
}
|
|
10623
11734
|
}
|
|
11735
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "copilot"));
|
|
10624
11736
|
}
|
|
10625
11737
|
return result;
|
|
10626
11738
|
}
|
|
@@ -10919,8 +12031,8 @@ function printFindingRow(f, drillDown, showSessionId, previewWidth) {
|
|
|
10919
12031
|
const stale = isStaleFinding(f.timestamp);
|
|
10920
12032
|
const ts = f.timestamp ? chalk5.dim(fmtTs(f.timestamp) + " ") : "";
|
|
10921
12033
|
const proj = chalk5.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
10922
|
-
const agentLabel2 = f.agent
|
|
10923
|
-
const agentBadge = stale ? chalk5.dim(agentLabel2) :
|
|
12034
|
+
const agentLabel2 = agentBadgeText(f.agent);
|
|
12035
|
+
const agentBadge = stale ? chalk5.dim(agentLabel2) : chalk5[agentColorName(f.agent)](agentLabel2);
|
|
10924
12036
|
let cmdText;
|
|
10925
12037
|
if (drillDown) {
|
|
10926
12038
|
cmdText = f.fullCommand;
|
|
@@ -11483,16 +12595,35 @@ function registerScanCommand(program2) {
|
|
|
11483
12595
|
(done) => onProgress(claudeScan.filesScanned + done),
|
|
11484
12596
|
onLine
|
|
11485
12597
|
);
|
|
11486
|
-
const
|
|
12598
|
+
const antigravityScan = scanAntigravityHistory(
|
|
11487
12599
|
startDate,
|
|
11488
12600
|
(done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done),
|
|
11489
12601
|
onLine
|
|
11490
12602
|
);
|
|
11491
|
-
const
|
|
12603
|
+
const copilotScan = scanCopilotHistory(
|
|
12604
|
+
startDate,
|
|
12605
|
+
(done) => onProgress(
|
|
12606
|
+
claudeScan.filesScanned + geminiScan.filesScanned + antigravityScan.filesScanned + done
|
|
12607
|
+
),
|
|
12608
|
+
onLine
|
|
12609
|
+
);
|
|
12610
|
+
const codexScan = scanCodexHistory(
|
|
12611
|
+
startDate,
|
|
12612
|
+
(done) => onProgress(
|
|
12613
|
+
claudeScan.filesScanned + geminiScan.filesScanned + antigravityScan.filesScanned + copilotScan.filesScanned + done
|
|
12614
|
+
),
|
|
12615
|
+
onLine
|
|
12616
|
+
);
|
|
12617
|
+
const scan = mergeScans(
|
|
12618
|
+
mergeScans(mergeScans(mergeScans(claudeScan, geminiScan), antigravityScan), copilotScan),
|
|
12619
|
+
codexScan
|
|
12620
|
+
);
|
|
11492
12621
|
scan.dlpFindings.push(...scanShellConfig());
|
|
11493
12622
|
const summary = buildScanSummary([
|
|
11494
12623
|
{ id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
|
|
11495
12624
|
{ id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
|
|
12625
|
+
{ id: "antigravity", label: "Antigravity", icon: "\u{1F680}", scan: antigravityScan },
|
|
12626
|
+
{ id: "copilot", label: "Copilot", icon: "\u{1F419}", scan: copilotScan },
|
|
11496
12627
|
{ id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
|
|
11497
12628
|
]);
|
|
11498
12629
|
if (useTTY) process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
@@ -11500,7 +12631,7 @@ function registerScanCommand(program2) {
|
|
|
11500
12631
|
console.log(chalk5.yellow(" No session history found."));
|
|
11501
12632
|
console.log(
|
|
11502
12633
|
chalk5.gray(
|
|
11503
|
-
" Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/)\n"
|
|
12634
|
+
" Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/) \xB7 Antigravity (~/.gemini/antigravity-*/brain/) \xB7 Copilot CLI (~/.copilot/session-state/)\n"
|
|
11504
12635
|
)
|
|
11505
12636
|
);
|
|
11506
12637
|
return;
|
|
@@ -11512,6 +12643,12 @@ function registerScanCommand(program2) {
|
|
|
11512
12643
|
breakdownParts.push(chalk5.cyan(String(claudeScan.sessions)) + chalk5.dim(" Claude"));
|
|
11513
12644
|
if (geminiScan.sessions > 0)
|
|
11514
12645
|
breakdownParts.push(chalk5.blue(String(geminiScan.sessions)) + chalk5.dim(" Gemini"));
|
|
12646
|
+
if (antigravityScan.sessions > 0)
|
|
12647
|
+
breakdownParts.push(
|
|
12648
|
+
chalk5.yellow(String(antigravityScan.sessions)) + chalk5.dim(" Antigravity")
|
|
12649
|
+
);
|
|
12650
|
+
if (copilotScan.sessions > 0)
|
|
12651
|
+
breakdownParts.push(chalk5.green(String(copilotScan.sessions)) + chalk5.dim(" Copilot"));
|
|
11515
12652
|
if (codexScan.sessions > 0)
|
|
11516
12653
|
breakdownParts.push(chalk5.magenta(String(codexScan.sessions)) + chalk5.dim(" Codex"));
|
|
11517
12654
|
const sessionBreakdown = breakdownParts.length > 1 ? chalk5.dim("(") + breakdownParts.join(chalk5.dim(" \xB7 ")) + chalk5.dim(")") : "";
|
|
@@ -11700,7 +12837,7 @@ function registerScanCommand(program2) {
|
|
|
11700
12837
|
const stale = isStaleFinding(f.timestamp);
|
|
11701
12838
|
const ts = f.timestamp ? chalk5.dim(fmtTs(f.timestamp) + " ") : "";
|
|
11702
12839
|
const proj = chalk5.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
11703
|
-
const agentBadge =
|
|
12840
|
+
const agentBadge = chalk5[agentColorName(f.agent)](agentBadgeText(f.agent));
|
|
11704
12841
|
const sessionSuffix = f.sessionId ? chalk5.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
|
|
11705
12842
|
const recurringBadge = recurringPatterns.has(f.patternName) ? chalk5.red.bold(" \u26A0\uFE0F recurring ") : "";
|
|
11706
12843
|
const patternDisplay = stale ? chalk5.dim(f.patternName) : chalk5.yellow(f.patternName);
|
|
@@ -11748,8 +12885,8 @@ function registerScanCommand(program2) {
|
|
|
11748
12885
|
const stale = isStaleFinding(f.timestamp);
|
|
11749
12886
|
const ts = f.timestamp ? chalk5.dim(fmtTs(f.timestamp) + " ") : "";
|
|
11750
12887
|
const proj = chalk5.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
11751
|
-
const agentLabel2 = f.agent
|
|
11752
|
-
const agentBadge = stale ? chalk5.dim(agentLabel2) :
|
|
12888
|
+
const agentLabel2 = agentBadgeText(f.agent);
|
|
12889
|
+
const agentBadge = stale ? chalk5.dim(agentLabel2) : chalk5[agentColorName(f.agent)](agentLabel2);
|
|
11753
12890
|
const toolDisplay = stale ? chalk5.dim(f.toolName) : chalk5.yellow(f.toolName);
|
|
11754
12891
|
const cmdDisplay = stale ? chalk5.dim(f.commandPreview) : chalk5.gray(f.commandPreview);
|
|
11755
12892
|
const sessionSuffix = f.sessionId ? chalk5.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
|
|
@@ -11883,6 +13020,7 @@ var init_scan = __esm({
|
|
|
11883
13020
|
init_policy();
|
|
11884
13021
|
init_dist();
|
|
11885
13022
|
init_dlp();
|
|
13023
|
+
init_hook_payload();
|
|
11886
13024
|
init_dist();
|
|
11887
13025
|
init_scan_summary();
|
|
11888
13026
|
init_setup();
|
|
@@ -11961,6 +13099,8 @@ var init_scan = __esm({
|
|
|
11961
13099
|
"exec_command",
|
|
11962
13100
|
"shell",
|
|
11963
13101
|
"run_shell_command",
|
|
13102
|
+
"run_command",
|
|
13103
|
+
// Antigravity (agy)
|
|
11964
13104
|
"write",
|
|
11965
13105
|
"edit",
|
|
11966
13106
|
"multiedit"
|
|
@@ -16547,6 +17687,7 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
16547
17687
|
// src/cli/commands/check.ts
|
|
16548
17688
|
init_dlp();
|
|
16549
17689
|
init_audit();
|
|
17690
|
+
init_hook_payload();
|
|
16550
17691
|
function sanitize2(value) {
|
|
16551
17692
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
16552
17693
|
}
|
|
@@ -16559,15 +17700,27 @@ function detectAiAgent(payload) {
|
|
|
16559
17700
|
if (payload.turn_id !== void 0) {
|
|
16560
17701
|
return "Codex";
|
|
16561
17702
|
}
|
|
17703
|
+
if (payload.toolCall !== void 0 || payload.conversationId !== void 0) {
|
|
17704
|
+
return "Antigravity";
|
|
17705
|
+
}
|
|
16562
17706
|
if (payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0) {
|
|
16563
17707
|
return "Claude Code";
|
|
16564
17708
|
}
|
|
16565
17709
|
if (payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0) {
|
|
16566
17710
|
return "Gemini CLI";
|
|
16567
17711
|
}
|
|
17712
|
+
if (payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call") {
|
|
17713
|
+
return "Hermes";
|
|
17714
|
+
}
|
|
16568
17715
|
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_SESSION_ID) {
|
|
16569
17716
|
return "Claude Code";
|
|
16570
17717
|
}
|
|
17718
|
+
if (process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE) {
|
|
17719
|
+
return "Hermes";
|
|
17720
|
+
}
|
|
17721
|
+
if (process.env.ANTIGRAVITY_CONVERSATION_ID) {
|
|
17722
|
+
return "Antigravity";
|
|
17723
|
+
}
|
|
16571
17724
|
if (process.env.GEMINI_CLI_VERSION || process.env.GEMINI_API_KEY) {
|
|
16572
17725
|
return "Gemini CLI";
|
|
16573
17726
|
}
|
|
@@ -16583,7 +17736,11 @@ function detectAiAgent(payload) {
|
|
|
16583
17736
|
return "Terminal";
|
|
16584
17737
|
}
|
|
16585
17738
|
function registerCheckCommand(program2) {
|
|
16586
|
-
program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").
|
|
17739
|
+
program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").option(
|
|
17740
|
+
"--agent <name>",
|
|
17741
|
+
"Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
|
|
17742
|
+
).action(async (data, opts) => {
|
|
17743
|
+
const agentOverride = agentLabelFromFlag(opts?.agent);
|
|
16587
17744
|
const processPayload = async (raw) => {
|
|
16588
17745
|
try {
|
|
16589
17746
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
@@ -16623,7 +17780,7 @@ RAW: ${raw}
|
|
|
16623
17780
|
if (!prompt) process.exit(0);
|
|
16624
17781
|
const dlpMatch = scanArgs({ prompt });
|
|
16625
17782
|
if (!dlpMatch) process.exit(0);
|
|
16626
|
-
const agent2 = detectAiAgent(payload);
|
|
17783
|
+
const agent2 = agentOverride ?? detectAiAgent(payload);
|
|
16627
17784
|
const sessionId2 = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
16628
17785
|
appendLocalAudit(
|
|
16629
17786
|
"UserPromptSubmit",
|
|
@@ -16664,7 +17821,8 @@ RAW: ${raw}
|
|
|
16664
17821
|
);
|
|
16665
17822
|
process.exit(2);
|
|
16666
17823
|
}
|
|
16667
|
-
const
|
|
17824
|
+
const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
|
|
17825
|
+
const safeCwdForConfig = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
16668
17826
|
const config = getConfig(safeCwdForConfig);
|
|
16669
17827
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
16670
17828
|
try {
|
|
@@ -16714,9 +17872,10 @@ RAW: ${raw}
|
|
|
16714
17872
|
fs32.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
16715
17873
|
`);
|
|
16716
17874
|
}
|
|
16717
|
-
const
|
|
16718
|
-
const
|
|
16719
|
-
const
|
|
17875
|
+
const rawToolName = sanitize2(extractToolName(payload));
|
|
17876
|
+
const toolName = canonicalToolName(rawToolName);
|
|
17877
|
+
const toolInput = canonicalToolInput(rawToolName, extractToolInput(payload));
|
|
17878
|
+
const agent = agentOverride ?? detectAiAgent(payload);
|
|
16720
17879
|
const mcpMatch = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
16721
17880
|
const mcpServer = mcpMatch?.[1];
|
|
16722
17881
|
const sendBlock = (msg, result2) => {
|
|
@@ -16754,6 +17913,21 @@ RAW: ${raw}
|
|
|
16754
17913
|
msg,
|
|
16755
17914
|
result2?.recoveryCommand
|
|
16756
17915
|
);
|
|
17916
|
+
if (agent === "Antigravity") {
|
|
17917
|
+
process.stdout.write(
|
|
17918
|
+
JSON.stringify({ decision: "deny", reason: aiFeedbackMessage }) + "\n"
|
|
17919
|
+
);
|
|
17920
|
+
process.exit(0);
|
|
17921
|
+
}
|
|
17922
|
+
if (agent === "GitHub Copilot") {
|
|
17923
|
+
process.stdout.write(
|
|
17924
|
+
JSON.stringify({
|
|
17925
|
+
permissionDecision: "deny",
|
|
17926
|
+
permissionDecisionReason: aiFeedbackMessage
|
|
17927
|
+
}) + "\n"
|
|
17928
|
+
);
|
|
17929
|
+
process.exit(0);
|
|
17930
|
+
}
|
|
16757
17931
|
process.stdout.write(
|
|
16758
17932
|
JSON.stringify({
|
|
16759
17933
|
decision: "block",
|
|
@@ -16772,11 +17946,11 @@ RAW: ${raw}
|
|
|
16772
17946
|
sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
|
|
16773
17947
|
return;
|
|
16774
17948
|
}
|
|
16775
|
-
const sessionId = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
16776
|
-
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
17949
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : typeof payload.conversationId === "string" ? payload.conversationId : void 0;
|
|
17950
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : typeof payload.transcriptPath === "string" ? payload.transcriptPath : void 0;
|
|
16777
17951
|
const meta = { agent, mcpServer, sessionId, transcriptPath };
|
|
16778
17952
|
const skillPinCfg = config.policy.skillPinning;
|
|
16779
|
-
const rawSessionId =
|
|
17953
|
+
const rawSessionId = sessionId ?? "";
|
|
16780
17954
|
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
16781
17955
|
if (skillPinCfg.enabled && safeSessionId) {
|
|
16782
17956
|
try {
|
|
@@ -16833,7 +18007,7 @@ RAW: ${raw}
|
|
|
16833
18007
|
return;
|
|
16834
18008
|
}
|
|
16835
18009
|
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
16836
|
-
const absoluteCwd = typeof
|
|
18010
|
+
const absoluteCwd = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
16837
18011
|
const extraRoots = skillPinCfg.roots;
|
|
16838
18012
|
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
16839
18013
|
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
@@ -16899,7 +18073,7 @@ RAW: ${raw}
|
|
|
16899
18073
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
16900
18074
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
16901
18075
|
}
|
|
16902
|
-
const safeCwdForAuth = typeof
|
|
18076
|
+
const safeCwdForAuth = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
16903
18077
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
16904
18078
|
cwd: safeCwdForAuth
|
|
16905
18079
|
});
|
|
@@ -17024,6 +18198,7 @@ function containsShellMetachar(token) {
|
|
|
17024
18198
|
}
|
|
17025
18199
|
|
|
17026
18200
|
// src/cli/commands/log.ts
|
|
18201
|
+
init_hook_payload();
|
|
17027
18202
|
var TEST_COMMAND_RE2 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
17028
18203
|
function detectTestResult(command, output) {
|
|
17029
18204
|
if (!TEST_COMMAND_RE2.test(command)) return null;
|
|
@@ -17042,13 +18217,19 @@ function sanitize3(value) {
|
|
|
17042
18217
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
17043
18218
|
}
|
|
17044
18219
|
function registerLogCommand(program2) {
|
|
17045
|
-
program2.command("log", { hidden: true }).description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").
|
|
18220
|
+
program2.command("log", { hidden: true }).description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").option(
|
|
18221
|
+
"--agent <name>",
|
|
18222
|
+
"Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
|
|
18223
|
+
).action(async (data, opts) => {
|
|
18224
|
+
const agentOverride = agentLabelFromFlag(opts?.agent);
|
|
17046
18225
|
const logPayload = async (raw) => {
|
|
17047
18226
|
try {
|
|
17048
18227
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
17049
18228
|
const payload = JSON.parse(raw);
|
|
17050
|
-
|
|
17051
|
-
const
|
|
18229
|
+
if (payload.toolCall === null) process.exit(0);
|
|
18230
|
+
const rawToolName = sanitize3(extractToolName(payload, "unknown"));
|
|
18231
|
+
const tool = canonicalToolName(rawToolName);
|
|
18232
|
+
const rawInput = canonicalToolInput(rawToolName, extractToolInput(payload));
|
|
17052
18233
|
const metaTag = (() => {
|
|
17053
18234
|
const m = payload.meta;
|
|
17054
18235
|
if (m && typeof m === "object") {
|
|
@@ -17057,7 +18238,7 @@ function registerLogCommand(program2) {
|
|
|
17057
18238
|
}
|
|
17058
18239
|
return void 0;
|
|
17059
18240
|
})();
|
|
17060
|
-
const agent = metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
18241
|
+
const agent = agentOverride !== void 0 ? agentOverride : metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.toolCall !== void 0 || payload.conversationId !== void 0 ? "Antigravity" : payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call" ? "Hermes" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE ? "Hermes" : process.env.ANTIGRAVITY_CONVERSATION_ID ? "Antigravity" : void 0;
|
|
17061
18242
|
const entry = {
|
|
17062
18243
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17063
18244
|
tool,
|
|
@@ -17066,7 +18247,9 @@ function registerLogCommand(program2) {
|
|
|
17066
18247
|
source: "post-hook"
|
|
17067
18248
|
};
|
|
17068
18249
|
if (agent) entry.agent = agent;
|
|
17069
|
-
if (
|
|
18250
|
+
if (rawToolName !== tool) entry.agentToolName = rawToolName;
|
|
18251
|
+
const payloadSessionId = payload.session_id ?? payload.conversationId;
|
|
18252
|
+
if (payloadSessionId) entry.sessionId = payloadSessionId;
|
|
17070
18253
|
const logPath = path34.join(os29.homedir(), ".node9", "audit.log");
|
|
17071
18254
|
if (!fs33.existsSync(path34.dirname(logPath)))
|
|
17072
18255
|
fs33.mkdirSync(path34.dirname(logPath), { recursive: true });
|
|
@@ -17103,7 +18286,8 @@ function registerLogCommand(program2) {
|
|
|
17103
18286
|
}
|
|
17104
18287
|
}
|
|
17105
18288
|
}
|
|
17106
|
-
const
|
|
18289
|
+
const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
|
|
18290
|
+
const safeCwd = typeof payloadCwd === "string" && path34.isAbsolute(payloadCwd) ? payloadCwd : void 0;
|
|
17107
18291
|
const config = getConfig(safeCwd);
|
|
17108
18292
|
if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
|
|
17109
18293
|
const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
@@ -19191,12 +20375,12 @@ function registerInitCommand(program2) {
|
|
|
19191
20375
|
if (found.length === 0) {
|
|
19192
20376
|
console.log(
|
|
19193
20377
|
chalk16.gray(
|
|
19194
|
-
"No AI agents detected. Install one of the supported agents (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, or
|
|
20378
|
+
"No AI agents detected. Install one of the supported agents (Claude Code, Codex, Antigravity, Gemini CLI, GitHub Copilot CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, Pi, or Hermes Agent)."
|
|
19195
20379
|
)
|
|
19196
20380
|
);
|
|
19197
20381
|
console.log(
|
|
19198
20382
|
chalk16.gray(
|
|
19199
|
-
"then run: node9 agents add <claude|codex|gemini|cursor|windsurf|vscode|claudeDesktop|opencode|pi>"
|
|
20383
|
+
"then run: node9 agents add <claude|codex|antigravity|gemini|copilot|cursor|windsurf|vscode|claudeDesktop|opencode|pi|hermes>"
|
|
19200
20384
|
)
|
|
19201
20385
|
);
|
|
19202
20386
|
return;
|
|
@@ -19210,6 +20394,8 @@ function registerInitCommand(program2) {
|
|
|
19210
20394
|
console.log(chalk16.bold(`Wiring ${agent}...`));
|
|
19211
20395
|
if (agent === "claude") await setupClaude();
|
|
19212
20396
|
else if (agent === "gemini") await setupGemini();
|
|
20397
|
+
else if (agent === "antigravity") await setupAntigravity();
|
|
20398
|
+
else if (agent === "copilot") await setupCopilot();
|
|
19213
20399
|
else if (agent === "cursor") await setupCursor();
|
|
19214
20400
|
else if (agent === "codex") await setupCodex();
|
|
19215
20401
|
else if (agent === "windsurf") await setupWindsurf();
|
|
@@ -19217,6 +20403,7 @@ function registerInitCommand(program2) {
|
|
|
19217
20403
|
else if (agent === "claudeDesktop") await setupClaudeDesktop();
|
|
19218
20404
|
else if (agent === "opencode") await setupOpencode();
|
|
19219
20405
|
else if (agent === "pi") await setupPi();
|
|
20406
|
+
else if (agent === "hermes") setupHermes();
|
|
19220
20407
|
console.log("");
|
|
19221
20408
|
}
|
|
19222
20409
|
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
@@ -20972,26 +22159,36 @@ import chalk23 from "chalk";
|
|
|
20972
22159
|
var SETUP_FN = {
|
|
20973
22160
|
claude: setupClaude,
|
|
20974
22161
|
gemini: setupGemini,
|
|
22162
|
+
antigravity: setupAntigravity,
|
|
22163
|
+
copilot: setupCopilot,
|
|
20975
22164
|
cursor: setupCursor,
|
|
20976
22165
|
codex: setupCodex,
|
|
20977
22166
|
windsurf: setupWindsurf,
|
|
20978
22167
|
vscode: setupVSCode,
|
|
20979
22168
|
claudeDesktop: setupClaudeDesktop,
|
|
20980
22169
|
opencode: setupOpencode,
|
|
20981
|
-
pi: setupPi
|
|
22170
|
+
pi: setupPi,
|
|
22171
|
+
hermes: setupHermes
|
|
20982
22172
|
};
|
|
20983
22173
|
var TEARDOWN_FN = {
|
|
20984
22174
|
claude: teardownClaude,
|
|
20985
22175
|
gemini: teardownGemini,
|
|
22176
|
+
antigravity: teardownAntigravity,
|
|
22177
|
+
copilot: teardownCopilot,
|
|
20986
22178
|
cursor: teardownCursor,
|
|
20987
22179
|
codex: teardownCodex,
|
|
20988
22180
|
windsurf: teardownWindsurf,
|
|
20989
22181
|
vscode: teardownVSCode,
|
|
20990
22182
|
claudeDesktop: teardownClaudeDesktop,
|
|
20991
22183
|
opencode: teardownOpencode,
|
|
20992
|
-
pi: teardownPi
|
|
22184
|
+
pi: teardownPi,
|
|
22185
|
+
hermes: teardownHermes
|
|
20993
22186
|
};
|
|
20994
22187
|
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
22188
|
+
function resolveAgentName(agent) {
|
|
22189
|
+
const lower = agent.toLowerCase();
|
|
22190
|
+
return lower === "agy" ? "antigravity" : lower;
|
|
22191
|
+
}
|
|
20995
22192
|
function registerAgentsCommand(program2) {
|
|
20996
22193
|
const agents = program2.command("agents").description("List and manage AI agent integrations");
|
|
20997
22194
|
agents.command("list").description("Show all supported agents and their Node9 status").action(() => {
|
|
@@ -21020,9 +22217,16 @@ function registerAgentsCommand(program2) {
|
|
|
21020
22217
|
chalk23.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk23.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
21021
22218
|
);
|
|
21022
22219
|
}
|
|
22220
|
+
if (statuses.some((s) => s.name === "gemini" && s.installed)) {
|
|
22221
|
+
console.log(
|
|
22222
|
+
chalk23.yellow(
|
|
22223
|
+
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
|
|
22224
|
+
) + chalk23.gray(" Migrate to Antigravity: ") + chalk23.white("node9 agents add antigravity") + "\n"
|
|
22225
|
+
);
|
|
22226
|
+
}
|
|
21023
22227
|
});
|
|
21024
22228
|
agents.command("add").description("Wire Node9 into an agent").argument("<agent>", `Agent to wire: ${AGENT_NAMES.join(" | ")}`).action(async (agent) => {
|
|
21025
|
-
const name = agent
|
|
22229
|
+
const name = resolveAgentName(agent);
|
|
21026
22230
|
const fn = SETUP_FN[name];
|
|
21027
22231
|
if (!fn) {
|
|
21028
22232
|
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
@@ -21031,7 +22235,7 @@ function registerAgentsCommand(program2) {
|
|
|
21031
22235
|
await fn();
|
|
21032
22236
|
});
|
|
21033
22237
|
agents.command("remove").description("Remove Node9 from an agent").argument("<agent>", `Agent to unwire: ${AGENT_NAMES.join(" | ")}`).action((agent) => {
|
|
21034
|
-
const name = agent
|
|
22238
|
+
const name = resolveAgentName(agent);
|
|
21035
22239
|
const fn = TEARDOWN_FN[name];
|
|
21036
22240
|
if (!fn) {
|
|
21037
22241
|
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
@@ -21049,6 +22253,7 @@ function registerAgentsCommand(program2) {
|
|
|
21049
22253
|
init_scan();
|
|
21050
22254
|
|
|
21051
22255
|
// src/cli/commands/sessions.ts
|
|
22256
|
+
init_scan_summary();
|
|
21052
22257
|
import chalk24 from "chalk";
|
|
21053
22258
|
import fs41 from "fs";
|
|
21054
22259
|
import path42 from "path";
|
|
@@ -21659,7 +22864,9 @@ function renderList(summaries, totalCost) {
|
|
|
21659
22864
|
const cost = s.costUSD > 0 ? chalk24.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
21660
22865
|
const blocked = s.blockedCalls.length > 0 ? chalk24.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
21661
22866
|
const snap = s.hasSnapshot ? chalk24.green(" \u{1F4F8}") : "";
|
|
21662
|
-
const agentBadge =
|
|
22867
|
+
const agentBadge = chalk24[agentColorName(s.agent ?? "claude")](
|
|
22868
|
+
" " + agentBadgeText(s.agent ?? "claude", 0)
|
|
22869
|
+
);
|
|
21663
22870
|
const sid = chalk24.dim(" " + s.sessionId.slice(0, 8));
|
|
21664
22871
|
console.log(
|
|
21665
22872
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
@@ -21679,7 +22886,7 @@ function renderDetail(s) {
|
|
|
21679
22886
|
);
|
|
21680
22887
|
console.log(chalk24.bold(" Project ") + chalk24.white(s.projectLabel));
|
|
21681
22888
|
if (s.agent) {
|
|
21682
|
-
const agentLabel2 = s.agent
|
|
22889
|
+
const agentLabel2 = chalk24[agentColorName(s.agent)](agentDisplayName(s.agent));
|
|
21683
22890
|
console.log(chalk24.bold(" Agent ") + agentLabel2);
|
|
21684
22891
|
}
|
|
21685
22892
|
console.log(chalk24.bold(" When ") + chalk24.white(fmtDateTime(s.startTime)));
|
|
@@ -22316,31 +23523,34 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
22316
23523
|
});
|
|
22317
23524
|
program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
|
|
22318
23525
|
"after",
|
|
22319
|
-
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
23526
|
+
"\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
|
|
22320
23527
|
).argument(
|
|
22321
23528
|
"<target>",
|
|
22322
|
-
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
23529
|
+
"The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
22323
23530
|
).action(async (target) => {
|
|
22324
23531
|
if (target === "gemini") return await setupGemini();
|
|
23532
|
+
if (target === "antigravity" || target === "agy") return await setupAntigravity();
|
|
23533
|
+
if (target === "copilot") return await setupCopilot();
|
|
22325
23534
|
if (target === "claude") return await setupClaude();
|
|
22326
23535
|
if (target === "cursor") return await setupCursor();
|
|
22327
23536
|
if (target === "codex") return await setupCodex();
|
|
22328
23537
|
if (target === "windsurf") return await setupWindsurf();
|
|
22329
23538
|
if (target === "vscode") return await setupVSCode();
|
|
23539
|
+
if (target === "hermes") return setupHermes();
|
|
22330
23540
|
if (target === "hud") return setupHud();
|
|
22331
23541
|
console.error(
|
|
22332
23542
|
chalk30.red(
|
|
22333
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
23543
|
+
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22334
23544
|
)
|
|
22335
23545
|
);
|
|
22336
23546
|
process.exit(1);
|
|
22337
23547
|
});
|
|
22338
23548
|
program.command("setup", { hidden: true }).description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
22339
23549
|
"after",
|
|
22340
|
-
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
23550
|
+
"\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
|
|
22341
23551
|
).argument(
|
|
22342
23552
|
"[target]",
|
|
22343
|
-
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
23553
|
+
"The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
22344
23554
|
).action(async (target) => {
|
|
22345
23555
|
if (!target) {
|
|
22346
23556
|
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
@@ -22348,10 +23558,13 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22348
23558
|
console.log(" Targets:");
|
|
22349
23559
|
console.log(" " + chalk30.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
22350
23560
|
console.log(" " + chalk30.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
23561
|
+
console.log(" " + chalk30.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
|
|
23562
|
+
console.log(" " + chalk30.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
|
|
22351
23563
|
console.log(" " + chalk30.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
22352
23564
|
console.log(" " + chalk30.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
22353
23565
|
console.log(" " + chalk30.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
22354
23566
|
console.log(" " + chalk30.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
23567
|
+
console.log(" " + chalk30.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
22355
23568
|
process.stdout.write(
|
|
22356
23569
|
" " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
22357
23570
|
);
|
|
@@ -22360,38 +23573,44 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22360
23573
|
}
|
|
22361
23574
|
const t = target.toLowerCase();
|
|
22362
23575
|
if (t === "gemini") return await setupGemini();
|
|
23576
|
+
if (t === "antigravity" || t === "agy") return await setupAntigravity();
|
|
23577
|
+
if (t === "copilot") return await setupCopilot();
|
|
22363
23578
|
if (t === "claude") return await setupClaude();
|
|
22364
23579
|
if (t === "cursor") return await setupCursor();
|
|
22365
23580
|
if (t === "codex") return await setupCodex();
|
|
22366
23581
|
if (t === "windsurf") return await setupWindsurf();
|
|
22367
23582
|
if (t === "vscode") return await setupVSCode();
|
|
23583
|
+
if (t === "hermes") return setupHermes();
|
|
22368
23584
|
if (t === "hud") return setupHud();
|
|
22369
23585
|
console.error(
|
|
22370
23586
|
chalk30.red(
|
|
22371
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
23587
|
+
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22372
23588
|
)
|
|
22373
23589
|
);
|
|
22374
23590
|
process.exit(1);
|
|
22375
23591
|
});
|
|
22376
23592
|
program.command("removefrom", { hidden: true }).description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
22377
23593
|
"after",
|
|
22378
|
-
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
23594
|
+
"\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
|
|
22379
23595
|
).argument(
|
|
22380
23596
|
"<target>",
|
|
22381
|
-
"The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
23597
|
+
"The agent to remove from: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
22382
23598
|
).action((target) => {
|
|
22383
23599
|
let fn;
|
|
22384
23600
|
if (target === "claude") fn = teardownClaude;
|
|
22385
23601
|
else if (target === "gemini") fn = teardownGemini;
|
|
23602
|
+
else if (target === "antigravity" || target === "agy") fn = teardownAntigravity;
|
|
23603
|
+
else if (target === "copilot") fn = teardownCopilot;
|
|
22386
23604
|
else if (target === "cursor") fn = teardownCursor;
|
|
22387
23605
|
else if (target === "codex") fn = teardownCodex;
|
|
22388
23606
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
22389
23607
|
else if (target === "vscode") fn = teardownVSCode;
|
|
23608
|
+
else if (target === "hermes") fn = teardownHermes;
|
|
22390
23609
|
else if (target === "hud") fn = teardownHud;
|
|
22391
23610
|
else {
|
|
22392
23611
|
console.error(
|
|
22393
23612
|
chalk30.red(
|
|
22394
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
23613
|
+
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22395
23614
|
)
|
|
22396
23615
|
);
|
|
22397
23616
|
process.exit(1);
|
|
@@ -22424,7 +23643,8 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
22424
23643
|
["Cursor", teardownCursor],
|
|
22425
23644
|
["Codex", teardownCodex],
|
|
22426
23645
|
["Windsurf", teardownWindsurf],
|
|
22427
|
-
["VSCode", teardownVSCode]
|
|
23646
|
+
["VSCode", teardownVSCode],
|
|
23647
|
+
["Hermes", teardownHermes]
|
|
22428
23648
|
]) {
|
|
22429
23649
|
try {
|
|
22430
23650
|
fn();
|
|
@@ -22647,6 +23867,9 @@ program.command("resume").description("Re-enable Node9 protection immediately").
|
|
|
22647
23867
|
var HOOK_BASED_AGENTS = {
|
|
22648
23868
|
claude: "claude",
|
|
22649
23869
|
gemini: "gemini",
|
|
23870
|
+
antigravity: "antigravity",
|
|
23871
|
+
agy: "antigravity",
|
|
23872
|
+
copilot: "copilot",
|
|
22650
23873
|
cursor: "cursor"
|
|
22651
23874
|
};
|
|
22652
23875
|
program.argument("[command...]", "The agent command to run (e.g., gemini)").action(async (commandArgs) => {
|