@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/dist/cli.js CHANGED
@@ -119,9 +119,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
119
119
  const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
120
120
  const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
121
121
  const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
122
+ const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
122
123
  appendToLog(LOCAL_AUDIT_LOG, {
123
124
  ts: (/* @__PURE__ */ new Date()).toISOString(),
124
125
  tool: toolName,
126
+ ...agentToolNameField,
125
127
  ...argsField,
126
128
  decision,
127
129
  checkedBy,
@@ -3945,7 +3947,14 @@ var init_config = __esm({
3945
3947
  "edit_file",
3946
3948
  "create_file",
3947
3949
  "edit",
3948
- "replace"
3950
+ "replace",
3951
+ // Claude / canonicalised Hermes — shouldSnapshot lowercases the
3952
+ // incoming name before set-membership, so we list the lowercase
3953
+ // forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
3954
+ // post-canonicalisation Hermes `patch` / `write_file` (which now
3955
+ // arrive as `Edit` / `Write`) silently skipped snapshotting.
3956
+ "write",
3957
+ "multiedit"
3949
3958
  ],
3950
3959
  onlyPaths: [],
3951
3960
  ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
@@ -7181,6 +7190,12 @@ async function setupClaude() {
7181
7190
  }
7182
7191
  }
7183
7192
  async function setupGemini() {
7193
+ console.log(
7194
+ import_chalk.default.yellow(
7195
+ " \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"
7196
+ )
7197
+ );
7198
+ console.log("");
7184
7199
  seedMcpPinsIfMissing();
7185
7200
  const homeDir2 = import_os12.default.homedir();
7186
7201
  const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
@@ -7278,6 +7293,355 @@ async function setupGemini() {
7278
7293
  printDaemonTip();
7279
7294
  }
7280
7295
  }
7296
+ async function setupAntigravity() {
7297
+ seedMcpPinsIfMissing();
7298
+ const homeDir2 = import_os12.default.homedir();
7299
+ const hooksPath = import_path15.default.join(homeDir2, ".gemini", "config", "hooks.json");
7300
+ const mcpPath = import_path15.default.join(homeDir2, ".gemini", "config", "mcp_config.json");
7301
+ const hooksFile = readJson(hooksPath) ?? {};
7302
+ const mcpConfig = readJson(mcpPath) ?? {};
7303
+ const servers = mcpConfig.mcpServers ?? {};
7304
+ let hooksChanged = false;
7305
+ let anythingChanged = false;
7306
+ if (!hooksFile.hooks) hooksFile.hooks = {};
7307
+ const hasPreHook = hooksFile.hooks.PreToolUse?.some(
7308
+ (m) => m.hooks.some((h) => isNode9Hook(h.command))
7309
+ );
7310
+ if (!hasPreHook) {
7311
+ if (!Array.isArray(hooksFile.hooks.PreToolUse)) hooksFile.hooks.PreToolUse = [];
7312
+ hooksFile.hooks.PreToolUse.push({
7313
+ matcher: ".*",
7314
+ hooks: [
7315
+ {
7316
+ name: "node9-check",
7317
+ type: "command",
7318
+ command: fullPathCommand("check --agent antigravity"),
7319
+ timeout: 600
7320
+ }
7321
+ ]
7322
+ });
7323
+ console.log(import_chalk.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
7324
+ hooksChanged = true;
7325
+ anythingChanged = true;
7326
+ } else if (hooksFile.hooks.PreToolUse) {
7327
+ for (const matcher of hooksFile.hooks.PreToolUse) {
7328
+ for (const h of matcher.hooks) {
7329
+ const cmd = h.command ?? "";
7330
+ if (isNode9Hook(cmd) && needsRewrite(cmd)) {
7331
+ h.command = fullPathCommand("check --agent antigravity");
7332
+ console.log(import_chalk.default.yellow(" \u{1F527} PreToolUse hook repaired (stale path \u2192 current binary)"));
7333
+ hooksChanged = true;
7334
+ anythingChanged = true;
7335
+ }
7336
+ }
7337
+ }
7338
+ }
7339
+ const hasPostHook = hooksFile.hooks.PostToolUse?.some(
7340
+ (m) => m.hooks.some((h) => isNode9Hook(h.command))
7341
+ );
7342
+ if (!hasPostHook) {
7343
+ if (!Array.isArray(hooksFile.hooks.PostToolUse)) hooksFile.hooks.PostToolUse = [];
7344
+ hooksFile.hooks.PostToolUse.push({
7345
+ matcher: ".*",
7346
+ hooks: [
7347
+ {
7348
+ name: "node9-log",
7349
+ type: "command",
7350
+ command: fullPathCommand("log --agent antigravity"),
7351
+ timeout: 600
7352
+ }
7353
+ ]
7354
+ });
7355
+ console.log(import_chalk.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
7356
+ hooksChanged = true;
7357
+ anythingChanged = true;
7358
+ } else if (hooksFile.hooks.PostToolUse) {
7359
+ for (const matcher of hooksFile.hooks.PostToolUse) {
7360
+ for (const h of matcher.hooks) {
7361
+ const cmd = h.command ?? "";
7362
+ if (isNode9Hook(cmd) && needsRewrite(cmd)) {
7363
+ h.command = fullPathCommand("log --agent antigravity");
7364
+ console.log(import_chalk.default.yellow(" \u{1F527} PostToolUse hook repaired (stale path \u2192 current binary)"));
7365
+ hooksChanged = true;
7366
+ anythingChanged = true;
7367
+ }
7368
+ }
7369
+ }
7370
+ }
7371
+ if (hooksChanged) {
7372
+ writeJson(hooksPath, hooksFile);
7373
+ console.log("");
7374
+ }
7375
+ if (!hasNode9McpServer(servers)) {
7376
+ servers["node9"] = NODE9_MCP_SERVER_ENTRY;
7377
+ mcpConfig.mcpServers = servers;
7378
+ writeJson(mcpPath, mcpConfig);
7379
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
7380
+ anythingChanged = true;
7381
+ }
7382
+ const legacySettings = readJson(import_path15.default.join(homeDir2, ".gemini", "settings.json"));
7383
+ const legacyHasNode9 = ["BeforeTool", "AfterTool"].some(
7384
+ (ev) => legacySettings?.hooks?.[ev]?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)))
7385
+ );
7386
+ if (legacyHasNode9) {
7387
+ console.log(
7388
+ import_chalk.default.yellow(
7389
+ " \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)."
7390
+ )
7391
+ );
7392
+ const clean = await (0, import_prompts.confirm)({
7393
+ message: "Remove the legacy Gemini CLI hooks?",
7394
+ default: false
7395
+ });
7396
+ if (clean) {
7397
+ teardownGemini();
7398
+ anythingChanged = true;
7399
+ }
7400
+ console.log("");
7401
+ }
7402
+ const serversToWrap = [];
7403
+ for (const [name, server] of Object.entries(servers)) {
7404
+ if (!server.command || server.command === "node9") continue;
7405
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
7406
+ }
7407
+ if (serversToWrap.length > 0) {
7408
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
7409
+ console.log(import_chalk.default.white(` ${mcpPath}`));
7410
+ for (const { name, upstream } of serversToWrap) {
7411
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
7412
+ }
7413
+ console.log("");
7414
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
7415
+ if (proceed) {
7416
+ for (const { name, upstream } of serversToWrap) {
7417
+ servers[name] = {
7418
+ ...servers[name],
7419
+ command: "node9",
7420
+ args: ["mcp", "--upstream", upstream]
7421
+ };
7422
+ }
7423
+ mcpConfig.mcpServers = servers;
7424
+ writeJson(mcpPath, mcpConfig);
7425
+ console.log(import_chalk.default.green(`
7426
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
7427
+ anythingChanged = true;
7428
+ } else {
7429
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
7430
+ }
7431
+ console.log("");
7432
+ }
7433
+ if (!anythingChanged && serversToWrap.length === 0) {
7434
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Antigravity."));
7435
+ printDaemonTip();
7436
+ return;
7437
+ }
7438
+ if (anythingChanged) {
7439
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Antigravity!"));
7440
+ console.log(
7441
+ import_chalk.default.gray(
7442
+ " Covers both the agy CLI and the Antigravity IDE (shared ~/.gemini/config).\n Restart agy / the IDE for changes to take effect."
7443
+ )
7444
+ );
7445
+ printDaemonTip();
7446
+ }
7447
+ }
7448
+ function teardownAntigravity() {
7449
+ const homeDir2 = import_os12.default.homedir();
7450
+ const hooksPath = import_path15.default.join(homeDir2, ".gemini", "config", "hooks.json");
7451
+ const mcpPath = import_path15.default.join(homeDir2, ".gemini", "config", "mcp_config.json");
7452
+ let changed = false;
7453
+ const hooksFile = readJson(hooksPath);
7454
+ if (hooksFile?.hooks) {
7455
+ for (const event of ["PreToolUse", "PostToolUse"]) {
7456
+ const before = hooksFile.hooks[event]?.length ?? 0;
7457
+ hooksFile.hooks[event] = hooksFile.hooks[event]?.filter(
7458
+ (m) => !m.hooks.some((h) => isNode9Hook(h.command))
7459
+ );
7460
+ if ((hooksFile.hooks[event]?.length ?? 0) < before) changed = true;
7461
+ if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
7462
+ }
7463
+ if (changed) {
7464
+ writeJson(hooksPath, hooksFile);
7465
+ console.log(
7466
+ import_chalk.default.green(" \u2705 Removed PreToolUse / PostToolUse hooks from ~/.gemini/config/hooks.json")
7467
+ );
7468
+ } else {
7469
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.gemini/config/hooks.json"));
7470
+ }
7471
+ } else {
7472
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/config/hooks.json not found \u2014 nothing to remove"));
7473
+ }
7474
+ const mcpConfig = readJson(mcpPath);
7475
+ if (mcpConfig?.mcpServers) {
7476
+ let mcpChanged = false;
7477
+ if (removeNode9McpServer(mcpConfig.mcpServers)) {
7478
+ mcpChanged = true;
7479
+ console.log(
7480
+ import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/config/mcp_config.json")
7481
+ );
7482
+ }
7483
+ for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
7484
+ const args = server.args;
7485
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
7486
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
7487
+ mcpConfig.mcpServers[name] = {
7488
+ ...server,
7489
+ command: originalCmd,
7490
+ args: originalArgs.length ? originalArgs : void 0
7491
+ };
7492
+ mcpChanged = true;
7493
+ }
7494
+ }
7495
+ if (mcpChanged) {
7496
+ writeJson(mcpPath, mcpConfig);
7497
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.gemini/config/mcp_config.json"));
7498
+ }
7499
+ }
7500
+ }
7501
+ async function setupCopilot() {
7502
+ seedMcpPinsIfMissing();
7503
+ const homeDir2 = import_os12.default.homedir();
7504
+ const hooksPath = import_path15.default.join(homeDir2, ".copilot", "hooks", "node9.json");
7505
+ const mcpPath = import_path15.default.join(homeDir2, ".copilot", "mcp-config.json");
7506
+ const hooksFile = readJson(hooksPath) ?? { version: 1 };
7507
+ if (!hooksFile.version) hooksFile.version = 1;
7508
+ if (!hooksFile.hooks) hooksFile.hooks = {};
7509
+ const mcpConfig = readJson(mcpPath) ?? {};
7510
+ const servers = mcpConfig.mcpServers ?? {};
7511
+ let hooksChanged = false;
7512
+ let anythingChanged = false;
7513
+ const addHook = (event, subcommand) => {
7514
+ const arr = Array.isArray(hooksFile.hooks[event]) ? hooksFile.hooks[event] : [];
7515
+ const present = arr.some((h) => isNode9Hook(h.command));
7516
+ if (!present) {
7517
+ arr.push({ type: "command", command: fullPathCommand(subcommand), timeoutSec: 600 });
7518
+ hooksFile.hooks[event] = arr;
7519
+ console.log(import_chalk.default.green(` \u2705 ${event} hook added \u2192 node9 ${subcommand.split(" ")[0]}`));
7520
+ hooksChanged = true;
7521
+ anythingChanged = true;
7522
+ } else {
7523
+ for (const h of arr) {
7524
+ if (h.command && isNode9Hook(h.command) && needsRewrite(h.command)) {
7525
+ h.command = fullPathCommand(subcommand);
7526
+ console.log(import_chalk.default.yellow(` \u{1F527} ${event} hook repaired (stale path \u2192 current binary)`));
7527
+ hooksChanged = true;
7528
+ anythingChanged = true;
7529
+ }
7530
+ }
7531
+ }
7532
+ };
7533
+ addHook("PreToolUse", "check --agent copilot");
7534
+ addHook("PostToolUse", "log --agent copilot");
7535
+ addHook("UserPromptSubmit", "check --agent copilot");
7536
+ if (hooksChanged) {
7537
+ writeJson(hooksPath, hooksFile);
7538
+ console.log("");
7539
+ }
7540
+ if (!hasNode9McpServer(servers)) {
7541
+ servers["node9"] = NODE9_MCP_SERVER_ENTRY;
7542
+ mcpConfig.mcpServers = servers;
7543
+ writeJson(mcpPath, mcpConfig);
7544
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
7545
+ anythingChanged = true;
7546
+ }
7547
+ const serversToWrap = [];
7548
+ for (const [name, server] of Object.entries(servers)) {
7549
+ if (!server.command || server.command === "node9") continue;
7550
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
7551
+ }
7552
+ if (serversToWrap.length > 0) {
7553
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
7554
+ console.log(import_chalk.default.white(` ${mcpPath}`));
7555
+ for (const { name, upstream } of serversToWrap) {
7556
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
7557
+ }
7558
+ console.log("");
7559
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
7560
+ if (proceed) {
7561
+ for (const { name, upstream } of serversToWrap) {
7562
+ servers[name] = {
7563
+ ...servers[name],
7564
+ command: "node9",
7565
+ args: ["mcp", "--upstream", upstream]
7566
+ };
7567
+ }
7568
+ mcpConfig.mcpServers = servers;
7569
+ writeJson(mcpPath, mcpConfig);
7570
+ console.log(import_chalk.default.green(`
7571
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
7572
+ anythingChanged = true;
7573
+ } else {
7574
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
7575
+ }
7576
+ console.log("");
7577
+ }
7578
+ if (!anythingChanged) {
7579
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for GitHub Copilot CLI."));
7580
+ printDaemonTip();
7581
+ return;
7582
+ }
7583
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting GitHub Copilot CLI!"));
7584
+ console.log(import_chalk.default.gray(" Restart Copilot CLI for changes to take effect."));
7585
+ printDaemonTip();
7586
+ }
7587
+ function teardownCopilot() {
7588
+ const homeDir2 = import_os12.default.homedir();
7589
+ const hooksPath = import_path15.default.join(homeDir2, ".copilot", "hooks", "node9.json");
7590
+ const mcpPath = import_path15.default.join(homeDir2, ".copilot", "mcp-config.json");
7591
+ const hooksFile = readJson(hooksPath);
7592
+ let changed = false;
7593
+ if (hooksFile?.hooks) {
7594
+ for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
7595
+ const before = hooksFile.hooks[event]?.length ?? 0;
7596
+ hooksFile.hooks[event] = hooksFile.hooks[event]?.filter((h) => !isNode9Hook(h.command));
7597
+ if ((hooksFile.hooks[event]?.length ?? 0) < before) changed = true;
7598
+ if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
7599
+ }
7600
+ if (changed) {
7601
+ if (Object.keys(hooksFile.hooks).length === 0) {
7602
+ try {
7603
+ import_fs13.default.unlinkSync(hooksPath);
7604
+ console.log(import_chalk.default.green(" \u2705 Removed ~/.copilot/hooks/node9.json"));
7605
+ } catch {
7606
+ writeJson(hooksPath, hooksFile);
7607
+ }
7608
+ } else {
7609
+ writeJson(hooksPath, hooksFile);
7610
+ console.log(import_chalk.default.green(" \u2705 Removed Node9 hooks from ~/.copilot/hooks/node9.json"));
7611
+ }
7612
+ } else {
7613
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.copilot/hooks/node9.json"));
7614
+ }
7615
+ } else {
7616
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.copilot/hooks/node9.json not found \u2014 nothing to remove"));
7617
+ }
7618
+ const mcpConfig = readJson(mcpPath);
7619
+ if (mcpConfig?.mcpServers) {
7620
+ let mcpChanged = false;
7621
+ if (removeNode9McpServer(mcpConfig.mcpServers)) {
7622
+ mcpChanged = true;
7623
+ console.log(
7624
+ import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.copilot/mcp-config.json")
7625
+ );
7626
+ }
7627
+ for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
7628
+ const args = server.args;
7629
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
7630
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
7631
+ mcpConfig.mcpServers[name] = {
7632
+ ...server,
7633
+ command: originalCmd,
7634
+ args: originalArgs.length ? originalArgs : void 0
7635
+ };
7636
+ mcpChanged = true;
7637
+ }
7638
+ }
7639
+ if (mcpChanged) {
7640
+ writeJson(mcpPath, mcpConfig);
7641
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.copilot/mcp-config.json"));
7642
+ }
7643
+ }
7644
+ }
7281
7645
  function claudeDesktopConfigPath(homeDir2 = import_os12.default.homedir()) {
7282
7646
  if (process.platform === "darwin") {
7283
7647
  return import_path15.default.join(
@@ -7325,7 +7689,23 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
7325
7689
  const desktopPath = claudeDesktopConfigPath(homeDir2);
7326
7690
  return {
7327
7691
  claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
7328
- gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
7692
+ // Antigravity (agy) shares the ~/.gemini root, so a bare
7693
+ // `exists(~/.gemini)` would report the (EOL'd) Gemini CLI as
7694
+ // installed on every agy machine — and `node9 init` would then
7695
+ // write BeforeTool hooks into settings.json, a file agy ignores,
7696
+ // while reporting the machine protected (silent protection gap,
7697
+ // verified live on a machine with both installed). Gemini CLI
7698
+ // creates ~/.gemini/settings.json on first run; agy does not touch
7699
+ // it (it uses antigravity-cli/settings.json) — that file is the
7700
+ // legacy-CLI discriminator.
7701
+ gemini: exists(import_path15.default.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
7702
+ // agy creates ~/.gemini/antigravity-cli/ on first launch; the IDE
7703
+ // creates antigravity-ide/. PATH fallback covers installed-but-
7704
+ // never-launched (same class as opencode #186).
7705
+ antigravity: exists(import_path15.default.join(homeDir2, ".gemini", "antigravity-cli")) || exists(import_path15.default.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
7706
+ // GitHub Copilot CLI creates ~/.copilot on first launch; PATH
7707
+ // fallback covers installed-but-never-launched (same as opencode #186).
7708
+ copilot: exists(import_path15.default.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
7329
7709
  cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
7330
7710
  codex: exists(import_path15.default.join(homeDir2, ".codex")),
7331
7711
  windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
@@ -7340,7 +7720,13 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
7340
7720
  // dir lazily on first launch — same class of bug as opencode's #186
7341
7721
  // (design R6) — so fall back to PATH lookup for installed-but-never-
7342
7722
  // launched pi.
7343
- pi: exists(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi")
7723
+ pi: exists(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
7724
+ // Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
7725
+ // is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
7726
+ // appears after `hermes setup` has run; the directory alone exists from
7727
+ // install time. PATH fallback covers the rare case where the user blew
7728
+ // away ~/.hermes but kept the binary.
7729
+ hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
7344
7730
  };
7345
7731
  }
7346
7732
  async function setupCursor() {
@@ -8147,6 +8533,169 @@ function teardownPi() {
8147
8533
  console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not remove ${extensionPath}: ${String(err2)}`));
8148
8534
  }
8149
8535
  }
8536
+ function hermesHomeDir(homeDir2 = import_os12.default.homedir()) {
8537
+ const env = process.env.HERMES_HOME?.trim();
8538
+ if (env && import_path15.default.isAbsolute(env)) return env;
8539
+ return import_path15.default.join(homeDir2, ".hermes");
8540
+ }
8541
+ function hermesConfigPath(homeDir2 = import_os12.default.homedir()) {
8542
+ return import_path15.default.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
8543
+ }
8544
+ function hermesAllowlistPath(homeDir2 = import_os12.default.homedir()) {
8545
+ return import_path15.default.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
8546
+ }
8547
+ function setupHermes() {
8548
+ const homeDir2 = import_os12.default.homedir();
8549
+ const configPath = hermesConfigPath(homeDir2);
8550
+ const allowlistPath = hermesAllowlistPath(homeDir2);
8551
+ if (!import_fs13.default.existsSync(configPath)) {
8552
+ console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
8553
+ console.log(import_chalk.default.gray(" Run `hermes setup` first, then re-run node9 setup hermes."));
8554
+ return;
8555
+ }
8556
+ let anythingChanged = false;
8557
+ const raw = import_fs13.default.readFileSync(configPath, "utf-8");
8558
+ const doc = yaml.parseDocument(raw);
8559
+ if (doc.errors.length > 0) {
8560
+ console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
8561
+ for (const err2 of doc.errors.slice(0, 3)) {
8562
+ console.log(import_chalk.default.gray(` \u2022 ${err2.message}`));
8563
+ }
8564
+ console.log(
8565
+ import_chalk.default.gray(" Fix the file (or run `hermes config edit`), then re-run node9 setup hermes.")
8566
+ );
8567
+ return;
8568
+ }
8569
+ const current = doc.toJS() ?? {};
8570
+ for (const { event, subcmd } of HERMES_HOOK_PLAN) {
8571
+ const command = fullPathCommand(subcmd);
8572
+ const existing = current.hooks?.[event] ?? [];
8573
+ const node9Idx = existing.findIndex(
8574
+ (e) => typeof e?.command === "string" && isNode9Hook(e.command)
8575
+ );
8576
+ if (node9Idx === -1) {
8577
+ const newEntries = [...existing, { command, timeout: 10 }];
8578
+ doc.setIn(["hooks", event], newEntries);
8579
+ console.log(import_chalk.default.green(` \u2705 Hermes ${event} hook added \u2192 node9 ${subcmd}`));
8580
+ anythingChanged = true;
8581
+ } else if (existing[node9Idx].command !== command || isStaleHookCommand(existing[node9Idx].command ?? "")) {
8582
+ const newEntries = [...existing];
8583
+ newEntries[node9Idx] = { ...newEntries[node9Idx], command };
8584
+ doc.setIn(["hooks", event], newEntries);
8585
+ console.log(import_chalk.default.yellow(` \u{1F527} Hermes ${event} hook repaired (stale path \u2192 current binary)`));
8586
+ anythingChanged = true;
8587
+ }
8588
+ }
8589
+ if (current.hooks_auto_accept !== true) {
8590
+ doc.set("hooks_auto_accept", true);
8591
+ console.log(import_chalk.default.green(" \u2705 hooks_auto_accept set to true"));
8592
+ anythingChanged = true;
8593
+ }
8594
+ if (anythingChanged) {
8595
+ import_fs13.default.writeFileSync(configPath, doc.toString());
8596
+ }
8597
+ let allowlist = {};
8598
+ if (import_fs13.default.existsSync(allowlistPath)) {
8599
+ try {
8600
+ allowlist = JSON.parse(import_fs13.default.readFileSync(allowlistPath, "utf-8"));
8601
+ } catch {
8602
+ allowlist = {};
8603
+ }
8604
+ }
8605
+ if (!Array.isArray(allowlist.approvals)) allowlist.approvals = [];
8606
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
8607
+ let allowlistChanged = false;
8608
+ for (const { event, subcmd } of HERMES_HOOK_PLAN) {
8609
+ const command = fullPathCommand(subcmd);
8610
+ const existingIdx = allowlist.approvals.findIndex(
8611
+ (e) => e?.event === event && typeof e?.command === "string" && isNode9Hook(e.command)
8612
+ );
8613
+ if (existingIdx === -1) {
8614
+ allowlist.approvals.push({ event, command, approved_at: nowIso });
8615
+ allowlistChanged = true;
8616
+ } else if (allowlist.approvals[existingIdx].command !== command) {
8617
+ allowlist.approvals[existingIdx] = { event, command, approved_at: nowIso };
8618
+ allowlistChanged = true;
8619
+ }
8620
+ }
8621
+ if (allowlistChanged) {
8622
+ import_fs13.default.mkdirSync(import_path15.default.dirname(allowlistPath), { recursive: true });
8623
+ import_fs13.default.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
8624
+ console.log(import_chalk.default.green(" \u2705 Hermes shell-hooks allowlist populated"));
8625
+ anythingChanged = true;
8626
+ }
8627
+ if (anythingChanged) {
8628
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Hermes Agent!"));
8629
+ console.log(import_chalk.default.gray(" Restart Hermes for changes to take effect."));
8630
+ printDaemonTip();
8631
+ } else {
8632
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Hermes Agent."));
8633
+ printDaemonTip();
8634
+ }
8635
+ }
8636
+ function teardownHermes() {
8637
+ const homeDir2 = import_os12.default.homedir();
8638
+ const configPath = hermesConfigPath(homeDir2);
8639
+ const allowlistPath = hermesAllowlistPath(homeDir2);
8640
+ if (!import_fs13.default.existsSync(configPath)) {
8641
+ console.log(import_chalk.default.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
8642
+ return;
8643
+ }
8644
+ const raw = import_fs13.default.readFileSync(configPath, "utf-8");
8645
+ const doc = yaml.parseDocument(raw);
8646
+ if (doc.errors.length > 0) {
8647
+ console.log(
8648
+ import_chalk.default.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
8649
+ );
8650
+ } else {
8651
+ teardownHermesConfigDoc(doc, configPath);
8652
+ }
8653
+ teardownHermesAllowlist(allowlistPath);
8654
+ }
8655
+ function teardownHermesConfigDoc(doc, configPath) {
8656
+ let anythingChanged = false;
8657
+ const current = doc.toJS() ?? {};
8658
+ for (const { event } of HERMES_HOOK_PLAN) {
8659
+ const existing = current.hooks?.[event] ?? [];
8660
+ const filtered = existing.filter(
8661
+ (e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
8662
+ );
8663
+ if (filtered.length === existing.length) continue;
8664
+ if (filtered.length === 0) {
8665
+ doc.deleteIn(["hooks", event]);
8666
+ } else {
8667
+ doc.setIn(["hooks", event], filtered);
8668
+ }
8669
+ anythingChanged = true;
8670
+ }
8671
+ const afterHooks = doc.toJS()?.hooks;
8672
+ if (afterHooks && typeof afterHooks === "object" && Object.keys(afterHooks).length === 0) {
8673
+ doc.delete("hooks");
8674
+ anythingChanged = true;
8675
+ }
8676
+ if (anythingChanged) {
8677
+ import_fs13.default.writeFileSync(configPath, doc.toString());
8678
+ console.log(import_chalk.default.green(` \u2705 Removed Node9 hooks from ${configPath}`));
8679
+ } else {
8680
+ console.log(import_chalk.default.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
8681
+ }
8682
+ }
8683
+ function teardownHermesAllowlist(allowlistPath) {
8684
+ if (!import_fs13.default.existsSync(allowlistPath)) return;
8685
+ try {
8686
+ const raw = import_fs13.default.readFileSync(allowlistPath, "utf-8");
8687
+ const allowlist = JSON.parse(raw);
8688
+ if (!Array.isArray(allowlist.approvals)) return;
8689
+ const before = allowlist.approvals.length;
8690
+ allowlist.approvals = allowlist.approvals.filter(
8691
+ (e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
8692
+ );
8693
+ if (allowlist.approvals.length === before) return;
8694
+ import_fs13.default.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
8695
+ console.log(import_chalk.default.green(` \u2705 Removed Node9 entries from ${allowlistPath}`));
8696
+ } catch {
8697
+ }
8698
+ }
8150
8699
  function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
8151
8700
  const detected = detectAgents(homeDir2);
8152
8701
  const claudeWired = (() => {
@@ -8157,6 +8706,18 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
8157
8706
  const settings = readJson(import_path15.default.join(homeDir2, ".gemini", "settings.json"));
8158
8707
  return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
8159
8708
  })();
8709
+ const antigravityWired = (() => {
8710
+ const hooksFile = readJson(
8711
+ import_path15.default.join(homeDir2, ".gemini", "config", "hooks.json")
8712
+ );
8713
+ return !!hooksFile?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
8714
+ })();
8715
+ const copilotWired = (() => {
8716
+ const hooksFile = readJson(
8717
+ import_path15.default.join(homeDir2, ".copilot", "hooks", "node9.json")
8718
+ );
8719
+ return !!hooksFile?.hooks?.PreToolUse?.some((h) => isNode9Hook(h.command));
8720
+ })();
8160
8721
  const cursorWired = (() => {
8161
8722
  const cfg = readJson(import_path15.default.join(homeDir2, ".cursor", "mcp.json"));
8162
8723
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
@@ -8190,6 +8751,20 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
8190
8751
  wired: geminiWired,
8191
8752
  mode: detected.gemini ? "hooks" : null
8192
8753
  },
8754
+ {
8755
+ name: "antigravity",
8756
+ label: "Antigravity",
8757
+ installed: detected.antigravity,
8758
+ wired: antigravityWired,
8759
+ mode: detected.antigravity ? "hooks" : null
8760
+ },
8761
+ {
8762
+ name: "copilot",
8763
+ label: "GitHub Copilot",
8764
+ installed: detected.copilot,
8765
+ wired: copilotWired,
8766
+ mode: detected.copilot ? "hooks" : null
8767
+ },
8193
8768
  {
8194
8769
  name: "cursor",
8195
8770
  label: "Cursor",
@@ -8258,10 +8833,29 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
8258
8833
  // simple existence check on the canonical install location.
8259
8834
  wired: import_fs13.default.existsSync(import_path15.default.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
8260
8835
  mode: detected.pi ? "hooks" : null
8836
+ },
8837
+ {
8838
+ name: "hermes",
8839
+ label: "Hermes Agent",
8840
+ installed: detected.hermes,
8841
+ // Wired = node9 hook entry exists in the parsed config.yaml.
8842
+ // Reading the YAML cheaply via yaml.parse — we don't need the
8843
+ // Document API for a boolean status check.
8844
+ wired: (() => {
8845
+ try {
8846
+ const raw = import_fs13.default.readFileSync(hermesConfigPath(homeDir2), "utf-8");
8847
+ const cfg = yaml.parse(raw);
8848
+ const pre = cfg?.hooks?.["pre_tool_call"] ?? [];
8849
+ return pre.some((e) => typeof e?.command === "string" && isNode9Hook(e.command));
8850
+ } catch {
8851
+ return false;
8852
+ }
8853
+ })(),
8854
+ mode: detected.hermes ? "hooks" : null
8261
8855
  }
8262
8856
  ];
8263
8857
  }
8264
- var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME;
8858
+ var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, yaml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME, HERMES_CONFIG_FILENAME, HERMES_ALLOWLIST_FILENAME, HERMES_HOOK_PLAN;
8265
8859
  var init_setup = __esm({
8266
8860
  "src/setup.ts"() {
8267
8861
  "use strict";
@@ -8271,6 +8865,7 @@ var init_setup = __esm({
8271
8865
  import_chalk = __toESM(require("chalk"));
8272
8866
  import_prompts = require("@inquirer/prompts");
8273
8867
  import_smol_toml = require("smol-toml");
8868
+ yaml = __toESM(require("yaml"));
8274
8869
  init_mcp_pin();
8275
8870
  init_setup_opencode_shim();
8276
8871
  init_setup_pi_shim();
@@ -8278,10 +8873,93 @@ var init_setup = __esm({
8278
8873
  CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
8279
8874
  OPENCODE_PLUGIN_NAME = "node9.js";
8280
8875
  PI_EXTENSION_NAME = "node9.js";
8876
+ HERMES_CONFIG_FILENAME = "config.yaml";
8877
+ HERMES_ALLOWLIST_FILENAME = "shell-hooks-allowlist.json";
8878
+ HERMES_HOOK_PLAN = [
8879
+ { event: "pre_tool_call", subcmd: "check" },
8880
+ { event: "post_tool_call", subcmd: "log" }
8881
+ ];
8882
+ }
8883
+ });
8884
+
8885
+ // src/utils/hook-payload.ts
8886
+ function extractToolName(payload, defaultValue = "") {
8887
+ return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
8888
+ }
8889
+ function extractToolInput(payload) {
8890
+ return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
8891
+ }
8892
+ function canonicalToolName(name) {
8893
+ switch (name) {
8894
+ // Hermes Agent
8895
+ case "terminal":
8896
+ return "Bash";
8897
+ case "write_file":
8898
+ return "Write";
8899
+ case "patch":
8900
+ return "Edit";
8901
+ case "read_file":
8902
+ return "Read";
8903
+ case "search_files":
8904
+ return "Grep";
8905
+ // Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
8906
+ case "run_command":
8907
+ return "Bash";
8908
+ default:
8909
+ return name;
8910
+ }
8911
+ }
8912
+ function agentLabelFromFlag(flag) {
8913
+ if (typeof flag !== "string") return void 0;
8914
+ switch (flag.toLowerCase()) {
8915
+ case "antigravity":
8916
+ case "agy":
8917
+ return "Antigravity";
8918
+ case "copilot":
8919
+ return "GitHub Copilot";
8920
+ default:
8921
+ return void 0;
8922
+ }
8923
+ }
8924
+ function canonicalToolInput(rawToolName, input) {
8925
+ if (rawToolName !== "run_command") return input;
8926
+ if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
8927
+ const args = input;
8928
+ if (typeof args.CommandLine !== "string") return input;
8929
+ const { CommandLine, Cwd, ...rest } = args;
8930
+ const canonical = { ...rest, command: CommandLine };
8931
+ if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
8932
+ return canonical;
8933
+ }
8934
+ var init_hook_payload = __esm({
8935
+ "src/utils/hook-payload.ts"() {
8936
+ "use strict";
8281
8937
  }
8282
8938
  });
8283
8939
 
8284
8940
  // src/scan-summary.ts
8941
+ function agentDisplayName(agent) {
8942
+ return AGENT_LONG[agent] ?? "Claude Code";
8943
+ }
8944
+ function agentBadgeText(agent, width = 10) {
8945
+ return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
8946
+ }
8947
+ function agentColorName(agent) {
8948
+ switch (agent) {
8949
+ case "gemini":
8950
+ return "blue";
8951
+ case "codex":
8952
+ return "magenta";
8953
+ case "antigravity":
8954
+ return "yellow";
8955
+ case "copilot":
8956
+ return "green";
8957
+ case "shell":
8958
+ return "yellow";
8959
+ default:
8960
+ return "cyan";
8961
+ }
8962
+ }
8285
8963
  function buildScanSummary(agents) {
8286
8964
  const stats = {
8287
8965
  sessions: 0,
@@ -8458,12 +9136,29 @@ function fullCommandOf(input) {
8458
9136
  const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8459
9137
  return String(raw).replace(/\s+/g, " ").trim();
8460
9138
  }
9139
+ var AGENT_SHORT, AGENT_LONG;
8461
9140
  var init_scan_summary = __esm({
8462
9141
  "src/scan-summary.ts"() {
8463
9142
  "use strict";
8464
9143
  init_shields();
8465
9144
  init_dist();
8466
9145
  init_dist();
9146
+ AGENT_SHORT = {
9147
+ claude: "Claude",
9148
+ gemini: "Gemini",
9149
+ codex: "Codex",
9150
+ antigravity: "Agy",
9151
+ copilot: "Copilot",
9152
+ shell: "Shell"
9153
+ };
9154
+ AGENT_LONG = {
9155
+ claude: "Claude Code",
9156
+ gemini: "Gemini CLI",
9157
+ codex: "Codex",
9158
+ antigravity: "Antigravity",
9159
+ copilot: "GitHub Copilot",
9160
+ shell: "Shell"
9161
+ };
8467
9162
  }
8468
9163
  });
8469
9164
 
@@ -10104,6 +10799,34 @@ function countScanFiles() {
10104
10799
  } catch {
10105
10800
  }
10106
10801
  }
10802
+ for (const surface of ["antigravity-cli", "antigravity-ide"]) {
10803
+ const brainDir = import_path22.default.join(import_os19.default.homedir(), ".gemini", surface, "brain");
10804
+ if (!import_fs20.default.existsSync(brainDir)) continue;
10805
+ try {
10806
+ for (const conv of import_fs20.default.readdirSync(brainDir)) {
10807
+ const convPath = import_path22.default.join(brainDir, conv);
10808
+ try {
10809
+ if (!import_fs20.default.statSync(convPath).isDirectory()) continue;
10810
+ const logsDir = import_path22.default.join(convPath, ".system_generated", "logs");
10811
+ if (import_fs20.default.existsSync(import_path22.default.join(logsDir, "transcript_full.jsonl")) || import_fs20.default.existsSync(import_path22.default.join(logsDir, "transcript.jsonl"))) {
10812
+ total += 1;
10813
+ }
10814
+ } catch {
10815
+ continue;
10816
+ }
10817
+ }
10818
+ } catch {
10819
+ }
10820
+ }
10821
+ const copilotDir = import_path22.default.join(import_os19.default.homedir(), ".copilot", "session-state");
10822
+ if (import_fs20.default.existsSync(copilotDir)) {
10823
+ try {
10824
+ for (const sid of import_fs20.default.readdirSync(copilotDir)) {
10825
+ if (import_fs20.default.existsSync(import_path22.default.join(copilotDir, sid, "events.jsonl"))) total += 1;
10826
+ }
10827
+ } catch {
10828
+ }
10829
+ }
10107
10830
  const codexDir = import_path22.default.join(import_os19.default.homedir(), ".codex", "sessions");
10108
10831
  if (import_fs20.default.existsSync(codexDir)) {
10109
10832
  try {
@@ -10432,8 +11155,231 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
10432
11155
  }
10433
11156
  return result;
10434
11157
  }
10435
- function scanGeminiHistory(startDate, onProgress, onLine) {
10436
- const tmpDir = import_path22.default.join(import_os19.default.homedir(), ".gemini", "tmp");
11158
+ function scanGeminiHistory(startDate, onProgress, onLine) {
11159
+ const tmpDir = import_path22.default.join(import_os19.default.homedir(), ".gemini", "tmp");
11160
+ const result = {
11161
+ filesScanned: 0,
11162
+ sessions: 0,
11163
+ totalToolCalls: 0,
11164
+ bashCalls: 0,
11165
+ findings: [],
11166
+ dlpFindings: [],
11167
+ loopFindings: [],
11168
+ totalCostUSD: 0,
11169
+ firstDate: null,
11170
+ lastDate: null,
11171
+ sessionsWithEarlySecrets: 0
11172
+ };
11173
+ const dedup = emptyScanDedup();
11174
+ if (!import_fs20.default.existsSync(tmpDir)) return result;
11175
+ let slugDirs;
11176
+ try {
11177
+ slugDirs = import_fs20.default.readdirSync(tmpDir);
11178
+ } catch {
11179
+ return result;
11180
+ }
11181
+ const ruleSources = buildRuleSources();
11182
+ for (const slug of slugDirs) {
11183
+ const slugPath = import_path22.default.join(tmpDir, slug);
11184
+ try {
11185
+ if (!import_fs20.default.statSync(slugPath).isDirectory()) continue;
11186
+ } catch {
11187
+ continue;
11188
+ }
11189
+ let projLabel = stripTerminalEscapes(slug).slice(0, 40);
11190
+ try {
11191
+ projLabel = stripTerminalEscapes(
11192
+ import_fs20.default.readFileSync(import_path22.default.join(slugPath, ".project_root"), "utf-8").trim()
11193
+ ).replace(import_os19.default.homedir(), "~").slice(0, 40);
11194
+ } catch {
11195
+ }
11196
+ const chatsDir = import_path22.default.join(slugPath, "chats");
11197
+ if (!import_fs20.default.existsSync(chatsDir)) continue;
11198
+ let chatFiles;
11199
+ try {
11200
+ chatFiles = import_fs20.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
11201
+ } catch {
11202
+ continue;
11203
+ }
11204
+ for (const chatFile of chatFiles) {
11205
+ result.filesScanned++;
11206
+ onProgress?.(result.filesScanned);
11207
+ const sessionId = chatFile.replace(/\.json$/, "");
11208
+ let raw;
11209
+ try {
11210
+ raw = import_fs20.default.readFileSync(import_path22.default.join(chatsDir, chatFile), "utf-8");
11211
+ } catch {
11212
+ continue;
11213
+ }
11214
+ const sessionCalls = [];
11215
+ let session;
11216
+ try {
11217
+ session = JSON.parse(raw);
11218
+ } catch {
11219
+ continue;
11220
+ }
11221
+ result.sessions++;
11222
+ for (const msg of session.messages ?? []) {
11223
+ onLine?.();
11224
+ if (msg.type === "user") {
11225
+ const content = msg.content;
11226
+ const text = Array.isArray(content) ? content.map((c) => c.text ?? "").join("\n") : typeof content === "string" ? content : "";
11227
+ if (text) {
11228
+ const dlpMatch = scanArgs({ text });
11229
+ if (dlpMatch) {
11230
+ const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
11231
+ if (!dedup.dlpKeys.has(k)) {
11232
+ dedup.dlpKeys.add(k);
11233
+ result.dlpFindings.push({
11234
+ patternName: dlpMatch.patternName,
11235
+ redactedSample: dlpMatch.redactedSample,
11236
+ toolName: "user-prompt",
11237
+ timestamp: msg.timestamp ?? "",
11238
+ project: projLabel,
11239
+ sessionId,
11240
+ agent: "gemini"
11241
+ });
11242
+ }
11243
+ }
11244
+ }
11245
+ continue;
11246
+ }
11247
+ if (msg.type !== "gemini") continue;
11248
+ if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
11249
+ if (msg.timestamp) {
11250
+ if (!result.firstDate || msg.timestamp < result.firstDate)
11251
+ result.firstDate = msg.timestamp;
11252
+ if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
11253
+ }
11254
+ const tokens = msg.tokens;
11255
+ const model = msg.model;
11256
+ if (tokens && model) {
11257
+ const p = geminiModelPrice(model);
11258
+ if (p) {
11259
+ const nonCached = Math.max(0, tokens.input - tokens.cached);
11260
+ result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
11261
+ }
11262
+ }
11263
+ for (const tc of msg.toolCalls ?? []) {
11264
+ result.totalToolCalls++;
11265
+ const toolName = tc.name ?? "";
11266
+ const toolNameLower = toolName.toLowerCase();
11267
+ const input = tc.args ?? {};
11268
+ sessionCalls.push({ toolName, input, timestamp: msg.timestamp ?? "" });
11269
+ if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
11270
+ result.bashCalls++;
11271
+ }
11272
+ const rawCmd = String(input.command ?? "").trimStart();
11273
+ if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
11274
+ continue;
11275
+ const dlpMatch = scanArgs(input);
11276
+ if (dlpMatch) {
11277
+ const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
11278
+ if (!dedup.dlpKeys.has(k)) {
11279
+ dedup.dlpKeys.add(k);
11280
+ result.dlpFindings.push({
11281
+ patternName: dlpMatch.patternName,
11282
+ redactedSample: dlpMatch.redactedSample,
11283
+ toolName,
11284
+ timestamp: msg.timestamp ?? "",
11285
+ project: projLabel,
11286
+ sessionId,
11287
+ agent: "gemini"
11288
+ });
11289
+ }
11290
+ }
11291
+ let astFsMatched = false;
11292
+ const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
11293
+ if (astRanForBash) {
11294
+ astFsMatched = pushFsOpAstFinding(
11295
+ String(input.command ?? ""),
11296
+ toolName,
11297
+ input,
11298
+ msg.timestamp ?? "",
11299
+ projLabel,
11300
+ sessionId,
11301
+ "gemini",
11302
+ result,
11303
+ dedup
11304
+ );
11305
+ }
11306
+ let ruleMatched = astFsMatched;
11307
+ for (const source of ruleSources) {
11308
+ const { rule } = source;
11309
+ if (rule.verdict === "allow") continue;
11310
+ if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
11311
+ if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
11312
+ if (!evaluateSmartConditions(input, rule)) continue;
11313
+ const inputPreview = preview(input, 120);
11314
+ const k = findingKey(rule.name, inputPreview, projLabel);
11315
+ if (!dedup.findingsKeys.has(k)) {
11316
+ dedup.findingsKeys.add(k);
11317
+ result.findings.push({
11318
+ source,
11319
+ toolName,
11320
+ input,
11321
+ timestamp: msg.timestamp ?? "",
11322
+ project: projLabel,
11323
+ sessionId,
11324
+ agent: "gemini"
11325
+ });
11326
+ }
11327
+ ruleMatched = true;
11328
+ break;
11329
+ }
11330
+ const isShellTool = ["bash", "execute_bash", "run_shell_command", "shell"].includes(
11331
+ toolNameLower
11332
+ );
11333
+ if (!ruleMatched && isShellTool) {
11334
+ const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
11335
+ if (shellVerdict) {
11336
+ const astRule = {
11337
+ name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
11338
+ tool: "bash",
11339
+ conditions: [],
11340
+ verdict: shellVerdict,
11341
+ reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
11342
+ };
11343
+ const inputPreview = preview(input, 120);
11344
+ const k = findingKey(astRule.name, inputPreview, projLabel);
11345
+ if (!dedup.findingsKeys.has(k)) {
11346
+ dedup.findingsKeys.add(k);
11347
+ result.findings.push({
11348
+ source: {
11349
+ shieldName: "bash-safe",
11350
+ shieldLabel: "bash-safe (AST)",
11351
+ sourceType: "shield",
11352
+ rule: astRule
11353
+ },
11354
+ toolName,
11355
+ input,
11356
+ timestamp: msg.timestamp ?? "",
11357
+ project: projLabel,
11358
+ sessionId,
11359
+ agent: "gemini"
11360
+ });
11361
+ }
11362
+ }
11363
+ }
11364
+ }
11365
+ }
11366
+ result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "gemini"));
11367
+ }
11368
+ }
11369
+ return result;
11370
+ }
11371
+ function antigravityBrainDirs() {
11372
+ return ["antigravity-cli", "antigravity-ide"].map((surface) => import_path22.default.join(import_os19.default.homedir(), ".gemini", surface, "brain")).filter((p) => import_fs20.default.existsSync(p));
11373
+ }
11374
+ function antigravityTranscriptPath(convPath) {
11375
+ const logsDir = import_path22.default.join(convPath, ".system_generated", "logs");
11376
+ for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
11377
+ const p = import_path22.default.join(logsDir, name);
11378
+ if (import_fs20.default.existsSync(p)) return p;
11379
+ }
11380
+ return null;
11381
+ }
11382
+ function scanAntigravityHistory(startDate, onProgress, onLine) {
10437
11383
  const result = {
10438
11384
  filesScanned: 0,
10439
11385
  sessions: 0,
@@ -10443,64 +11389,56 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10443
11389
  dlpFindings: [],
10444
11390
  loopFindings: [],
10445
11391
  totalCostUSD: 0,
11392
+ // transcripts carry no token/model data
10446
11393
  firstDate: null,
10447
11394
  lastDate: null,
10448
11395
  sessionsWithEarlySecrets: 0
10449
11396
  };
10450
11397
  const dedup = emptyScanDedup();
10451
- if (!import_fs20.default.existsSync(tmpDir)) return result;
10452
- let slugDirs;
10453
- try {
10454
- slugDirs = import_fs20.default.readdirSync(tmpDir);
10455
- } catch {
10456
- return result;
10457
- }
11398
+ const brainDirs = antigravityBrainDirs();
11399
+ if (brainDirs.length === 0) return result;
10458
11400
  const ruleSources = buildRuleSources();
10459
- for (const slug of slugDirs) {
10460
- const slugPath = import_path22.default.join(tmpDir, slug);
11401
+ for (const brainDir of brainDirs) {
11402
+ let convDirs;
10461
11403
  try {
10462
- if (!import_fs20.default.statSync(slugPath).isDirectory()) continue;
10463
- } catch {
10464
- continue;
10465
- }
10466
- let projLabel = stripTerminalEscapes(slug).slice(0, 40);
10467
- try {
10468
- projLabel = stripTerminalEscapes(
10469
- import_fs20.default.readFileSync(import_path22.default.join(slugPath, ".project_root"), "utf-8").trim()
10470
- ).replace(import_os19.default.homedir(), "~").slice(0, 40);
10471
- } catch {
10472
- }
10473
- const chatsDir = import_path22.default.join(slugPath, "chats");
10474
- if (!import_fs20.default.existsSync(chatsDir)) continue;
10475
- let chatFiles;
10476
- try {
10477
- chatFiles = import_fs20.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
11404
+ convDirs = import_fs20.default.readdirSync(brainDir);
10478
11405
  } catch {
10479
11406
  continue;
10480
11407
  }
10481
- for (const chatFile of chatFiles) {
10482
- result.filesScanned++;
10483
- onProgress?.(result.filesScanned);
10484
- const sessionId = chatFile.replace(/\.json$/, "");
10485
- let raw;
11408
+ for (const conv of convDirs) {
11409
+ const convPath = import_path22.default.join(brainDir, conv);
10486
11410
  try {
10487
- raw = import_fs20.default.readFileSync(import_path22.default.join(chatsDir, chatFile), "utf-8");
11411
+ if (!import_fs20.default.statSync(convPath).isDirectory()) continue;
10488
11412
  } catch {
10489
11413
  continue;
10490
11414
  }
10491
- const sessionCalls = [];
10492
- let session;
11415
+ const transcriptFile = antigravityTranscriptPath(convPath);
11416
+ if (!transcriptFile) continue;
11417
+ result.filesScanned++;
11418
+ onProgress?.(result.filesScanned);
11419
+ let raw;
10493
11420
  try {
10494
- session = JSON.parse(raw);
11421
+ raw = import_fs20.default.readFileSync(transcriptFile, "utf-8");
10495
11422
  } catch {
10496
11423
  continue;
10497
11424
  }
11425
+ const sessionId = conv;
11426
+ let projLabel = conv.slice(0, 8);
11427
+ const sessionCalls = [];
10498
11428
  result.sessions++;
10499
- for (const msg of session.messages ?? []) {
11429
+ for (const line of raw.split("\n")) {
11430
+ if (!line.trim()) continue;
10500
11431
  onLine?.();
10501
- if (msg.type === "user") {
10502
- const content = msg.content;
10503
- const text = Array.isArray(content) ? content.map((c) => c.text ?? "").join("\n") : typeof content === "string" ? content : "";
11432
+ let step;
11433
+ try {
11434
+ step = JSON.parse(line);
11435
+ } catch {
11436
+ continue;
11437
+ }
11438
+ const timestamp = step.created_at ?? "";
11439
+ if (startDate && timestamp && new Date(timestamp) < startDate) continue;
11440
+ if (step.type === "USER_INPUT") {
11441
+ const text = typeof step.content === "string" ? step.content : "";
10504
11442
  if (text) {
10505
11443
  const dlpMatch = scanArgs({ text });
10506
11444
  if (dlpMatch) {
@@ -10511,40 +11449,34 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10511
11449
  patternName: dlpMatch.patternName,
10512
11450
  redactedSample: dlpMatch.redactedSample,
10513
11451
  toolName: "user-prompt",
10514
- timestamp: msg.timestamp ?? "",
11452
+ timestamp,
10515
11453
  project: projLabel,
10516
11454
  sessionId,
10517
- agent: "gemini"
11455
+ agent: "antigravity"
10518
11456
  });
10519
11457
  }
10520
11458
  }
10521
11459
  }
10522
11460
  continue;
10523
11461
  }
10524
- if (msg.type !== "gemini") continue;
10525
- if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
10526
- if (msg.timestamp) {
10527
- if (!result.firstDate || msg.timestamp < result.firstDate)
10528
- result.firstDate = msg.timestamp;
10529
- if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
10530
- }
10531
- const tokens = msg.tokens;
10532
- const model = msg.model;
10533
- if (tokens && model) {
10534
- const p = geminiModelPrice(model);
10535
- if (p) {
10536
- const nonCached = Math.max(0, tokens.input - tokens.cached);
10537
- result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
10538
- }
11462
+ if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
11463
+ if (timestamp) {
11464
+ if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
11465
+ if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
10539
11466
  }
10540
- for (const tc of msg.toolCalls ?? []) {
11467
+ for (const tc of step.tool_calls) {
10541
11468
  result.totalToolCalls++;
10542
11469
  const toolName = tc.name ?? "";
10543
11470
  const toolNameLower = toolName.toLowerCase();
10544
- const input = tc.args ?? {};
10545
- sessionCalls.push({ toolName, input, timestamp: msg.timestamp ?? "" });
10546
- if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
11471
+ const input = canonicalToolInput(toolName, tc.args ?? {});
11472
+ sessionCalls.push({ toolName, input, timestamp });
11473
+ const isShellTool = toolNameLower === "run_command";
11474
+ if (isShellTool) {
10547
11475
  result.bashCalls++;
11476
+ const cwd = String(input.cwd ?? "");
11477
+ if (cwd && projLabel === conv.slice(0, 8)) {
11478
+ projLabel = stripTerminalEscapes(cwd).replace(import_os19.default.homedir(), "~").slice(0, 40);
11479
+ }
10548
11480
  }
10549
11481
  const rawCmd = String(input.command ?? "").trimStart();
10550
11482
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
@@ -10558,24 +11490,23 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10558
11490
  patternName: dlpMatch.patternName,
10559
11491
  redactedSample: dlpMatch.redactedSample,
10560
11492
  toolName,
10561
- timestamp: msg.timestamp ?? "",
11493
+ timestamp,
10562
11494
  project: projLabel,
10563
11495
  sessionId,
10564
- agent: "gemini"
11496
+ agent: "antigravity"
10565
11497
  });
10566
11498
  }
10567
11499
  }
10568
11500
  let astFsMatched = false;
10569
- const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
10570
- if (astRanForBash) {
11501
+ if (isShellTool) {
10571
11502
  astFsMatched = pushFsOpAstFinding(
10572
11503
  String(input.command ?? ""),
10573
11504
  toolName,
10574
11505
  input,
10575
- msg.timestamp ?? "",
11506
+ timestamp,
10576
11507
  projLabel,
10577
11508
  sessionId,
10578
- "gemini",
11509
+ "antigravity",
10579
11510
  result,
10580
11511
  dedup
10581
11512
  );
@@ -10584,8 +11515,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10584
11515
  for (const source of ruleSources) {
10585
11516
  const { rule } = source;
10586
11517
  if (rule.verdict === "allow") continue;
10587
- if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
10588
- if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
11518
+ const ruleToolName = isShellTool ? "bash" : toolNameLower;
11519
+ if (rule.tool && !matchesPattern(ruleToolName, rule.tool)) continue;
11520
+ if (isShellTool && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
10589
11521
  if (!evaluateSmartConditions(input, rule)) continue;
10590
11522
  const inputPreview = preview(input, 120);
10591
11523
  const k = findingKey(rule.name, inputPreview, projLabel);
@@ -10595,18 +11527,15 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10595
11527
  source,
10596
11528
  toolName,
10597
11529
  input,
10598
- timestamp: msg.timestamp ?? "",
11530
+ timestamp,
10599
11531
  project: projLabel,
10600
11532
  sessionId,
10601
- agent: "gemini"
11533
+ agent: "antigravity"
10602
11534
  });
10603
11535
  }
10604
11536
  ruleMatched = true;
10605
11537
  break;
10606
11538
  }
10607
- const isShellTool = ["bash", "execute_bash", "run_shell_command", "shell"].includes(
10608
- toolNameLower
10609
- );
10610
11539
  if (!ruleMatched && isShellTool) {
10611
11540
  const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
10612
11541
  if (shellVerdict) {
@@ -10630,18 +11559,201 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
10630
11559
  },
10631
11560
  toolName,
10632
11561
  input,
10633
- timestamp: msg.timestamp ?? "",
11562
+ timestamp,
10634
11563
  project: projLabel,
10635
11564
  sessionId,
10636
- agent: "gemini"
11565
+ agent: "antigravity"
10637
11566
  });
10638
11567
  }
10639
11568
  }
10640
11569
  }
10641
11570
  }
10642
11571
  }
10643
- result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "gemini"));
11572
+ result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "antigravity"));
11573
+ }
11574
+ }
11575
+ return result;
11576
+ }
11577
+ function scanCopilotHistory(startDate, onProgress, onLine) {
11578
+ const sessionDir = import_path22.default.join(import_os19.default.homedir(), ".copilot", "session-state");
11579
+ const result = {
11580
+ filesScanned: 0,
11581
+ sessions: 0,
11582
+ totalToolCalls: 0,
11583
+ bashCalls: 0,
11584
+ findings: [],
11585
+ dlpFindings: [],
11586
+ loopFindings: [],
11587
+ totalCostUSD: 0,
11588
+ // event logs carry no token/cost rollup
11589
+ firstDate: null,
11590
+ lastDate: null,
11591
+ sessionsWithEarlySecrets: 0
11592
+ };
11593
+ const dedup = emptyScanDedup();
11594
+ if (!import_fs20.default.existsSync(sessionDir)) return result;
11595
+ let sessionIds;
11596
+ try {
11597
+ sessionIds = import_fs20.default.readdirSync(sessionDir);
11598
+ } catch {
11599
+ return result;
11600
+ }
11601
+ const ruleSources = buildRuleSources();
11602
+ for (const sessionId of sessionIds) {
11603
+ const eventsPath = import_path22.default.join(sessionDir, sessionId, "events.jsonl");
11604
+ if (!import_fs20.default.existsSync(eventsPath)) continue;
11605
+ result.filesScanned++;
11606
+ onProgress?.(result.filesScanned);
11607
+ let raw;
11608
+ try {
11609
+ raw = import_fs20.default.readFileSync(eventsPath, "utf-8");
11610
+ } catch {
11611
+ continue;
11612
+ }
11613
+ let projLabel = sessionId.slice(0, 8);
11614
+ const sessionCalls = [];
11615
+ result.sessions++;
11616
+ for (const line of raw.split("\n")) {
11617
+ if (!line.trim()) continue;
11618
+ onLine?.();
11619
+ let ev;
11620
+ try {
11621
+ ev = JSON.parse(line);
11622
+ } catch {
11623
+ continue;
11624
+ }
11625
+ const timestamp = ev.timestamp ?? "";
11626
+ if (ev.type === "session.start") {
11627
+ const cwd = ev.data?.context?.cwd;
11628
+ if (typeof cwd === "string" && cwd) {
11629
+ projLabel = stripTerminalEscapes(cwd).replace(import_os19.default.homedir(), "~").slice(0, 40);
11630
+ }
11631
+ continue;
11632
+ }
11633
+ if (startDate && timestamp && new Date(timestamp) < startDate) continue;
11634
+ if (ev.type === "user.message") {
11635
+ const text = ev.data?.content ?? ev.data?.text ?? "";
11636
+ if (typeof text === "string" && text) {
11637
+ const dlpMatch2 = scanArgs({ text });
11638
+ if (dlpMatch2) {
11639
+ const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
11640
+ if (!dedup.dlpKeys.has(k)) {
11641
+ dedup.dlpKeys.add(k);
11642
+ result.dlpFindings.push({
11643
+ patternName: dlpMatch2.patternName,
11644
+ redactedSample: dlpMatch2.redactedSample,
11645
+ toolName: "user-prompt",
11646
+ timestamp,
11647
+ project: projLabel,
11648
+ sessionId,
11649
+ agent: "copilot"
11650
+ });
11651
+ }
11652
+ }
11653
+ }
11654
+ continue;
11655
+ }
11656
+ if (ev.type !== "tool.execution_start") continue;
11657
+ const toolName = ev.data?.toolName ?? "";
11658
+ const toolNameLower = toolName.toLowerCase();
11659
+ const input = ev.data?.arguments ?? {};
11660
+ result.totalToolCalls++;
11661
+ sessionCalls.push({ toolName, input, timestamp });
11662
+ const isShellTool = toolNameLower === "bash" || toolNameLower === "shell";
11663
+ if (isShellTool) result.bashCalls++;
11664
+ if (timestamp) {
11665
+ if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
11666
+ if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
11667
+ }
11668
+ const rawCmd = String(input.command ?? "").trimStart();
11669
+ if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
11670
+ const dlpMatch = scanArgs(input);
11671
+ if (dlpMatch) {
11672
+ const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
11673
+ if (!dedup.dlpKeys.has(k)) {
11674
+ dedup.dlpKeys.add(k);
11675
+ result.dlpFindings.push({
11676
+ patternName: dlpMatch.patternName,
11677
+ redactedSample: dlpMatch.redactedSample,
11678
+ toolName,
11679
+ timestamp,
11680
+ project: projLabel,
11681
+ sessionId,
11682
+ agent: "copilot"
11683
+ });
11684
+ }
11685
+ }
11686
+ let astFsMatched = false;
11687
+ if (isShellTool) {
11688
+ astFsMatched = pushFsOpAstFinding(
11689
+ String(input.command ?? ""),
11690
+ toolName,
11691
+ input,
11692
+ timestamp,
11693
+ projLabel,
11694
+ sessionId,
11695
+ "copilot",
11696
+ result,
11697
+ dedup
11698
+ );
11699
+ }
11700
+ let ruleMatched = astFsMatched;
11701
+ for (const source of ruleSources) {
11702
+ const { rule } = source;
11703
+ if (rule.verdict === "allow") continue;
11704
+ if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
11705
+ if (isShellTool && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
11706
+ if (!evaluateSmartConditions(input, rule)) continue;
11707
+ const inputPreview = preview(input, 120);
11708
+ const k = findingKey(rule.name, inputPreview, projLabel);
11709
+ if (!dedup.findingsKeys.has(k)) {
11710
+ dedup.findingsKeys.add(k);
11711
+ result.findings.push({
11712
+ source,
11713
+ toolName,
11714
+ input,
11715
+ timestamp,
11716
+ project: projLabel,
11717
+ sessionId,
11718
+ agent: "copilot"
11719
+ });
11720
+ }
11721
+ ruleMatched = true;
11722
+ break;
11723
+ }
11724
+ if (!ruleMatched && isShellTool) {
11725
+ const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
11726
+ if (shellVerdict) {
11727
+ const astRule = {
11728
+ name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
11729
+ tool: "bash",
11730
+ conditions: [],
11731
+ verdict: shellVerdict,
11732
+ reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
11733
+ };
11734
+ const inputPreview = preview(input, 120);
11735
+ const k = findingKey(astRule.name, inputPreview, projLabel);
11736
+ if (!dedup.findingsKeys.has(k)) {
11737
+ dedup.findingsKeys.add(k);
11738
+ result.findings.push({
11739
+ source: {
11740
+ shieldName: "bash-safe",
11741
+ shieldLabel: "bash-safe (AST)",
11742
+ sourceType: "shield",
11743
+ rule: astRule
11744
+ },
11745
+ toolName,
11746
+ input,
11747
+ timestamp,
11748
+ project: projLabel,
11749
+ sessionId,
11750
+ agent: "copilot"
11751
+ });
11752
+ }
11753
+ }
11754
+ }
10644
11755
  }
11756
+ result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "copilot"));
10645
11757
  }
10646
11758
  return result;
10647
11759
  }
@@ -10940,8 +12052,8 @@ function printFindingRow(f, drillDown, showSessionId, previewWidth) {
10940
12052
  const stale = isStaleFinding(f.timestamp);
10941
12053
  const ts = f.timestamp ? import_chalk5.default.dim(fmtTs(f.timestamp) + " ") : "";
10942
12054
  const proj = import_chalk5.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
10943
- const agentLabel2 = f.agent === "gemini" ? "[Gemini] " : f.agent === "codex" ? "[Codex] " : "[Claude] ";
10944
- const agentBadge = stale ? import_chalk5.default.dim(agentLabel2) : f.agent === "gemini" ? import_chalk5.default.blue(agentLabel2) : f.agent === "codex" ? import_chalk5.default.magenta(agentLabel2) : import_chalk5.default.cyan(agentLabel2);
12055
+ const agentLabel2 = agentBadgeText(f.agent);
12056
+ const agentBadge = stale ? import_chalk5.default.dim(agentLabel2) : import_chalk5.default[agentColorName(f.agent)](agentLabel2);
10945
12057
  let cmdText;
10946
12058
  if (drillDown) {
10947
12059
  cmdText = f.fullCommand;
@@ -11504,16 +12616,35 @@ function registerScanCommand(program2) {
11504
12616
  (done) => onProgress(claudeScan.filesScanned + done),
11505
12617
  onLine
11506
12618
  );
11507
- const codexScan = scanCodexHistory(
12619
+ const antigravityScan = scanAntigravityHistory(
11508
12620
  startDate,
11509
12621
  (done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done),
11510
12622
  onLine
11511
12623
  );
11512
- const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
12624
+ const copilotScan = scanCopilotHistory(
12625
+ startDate,
12626
+ (done) => onProgress(
12627
+ claudeScan.filesScanned + geminiScan.filesScanned + antigravityScan.filesScanned + done
12628
+ ),
12629
+ onLine
12630
+ );
12631
+ const codexScan = scanCodexHistory(
12632
+ startDate,
12633
+ (done) => onProgress(
12634
+ claudeScan.filesScanned + geminiScan.filesScanned + antigravityScan.filesScanned + copilotScan.filesScanned + done
12635
+ ),
12636
+ onLine
12637
+ );
12638
+ const scan = mergeScans(
12639
+ mergeScans(mergeScans(mergeScans(claudeScan, geminiScan), antigravityScan), copilotScan),
12640
+ codexScan
12641
+ );
11513
12642
  scan.dlpFindings.push(...scanShellConfig());
11514
12643
  const summary = buildScanSummary([
11515
12644
  { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
11516
12645
  { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
12646
+ { id: "antigravity", label: "Antigravity", icon: "\u{1F680}", scan: antigravityScan },
12647
+ { id: "copilot", label: "Copilot", icon: "\u{1F419}", scan: copilotScan },
11517
12648
  { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
11518
12649
  ]);
11519
12650
  if (useTTY) process.stdout.write("\r" + " ".repeat(60) + "\r");
@@ -11521,7 +12652,7 @@ function registerScanCommand(program2) {
11521
12652
  console.log(import_chalk5.default.yellow(" No session history found."));
11522
12653
  console.log(
11523
12654
  import_chalk5.default.gray(
11524
- " Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/)\n"
12655
+ " Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/) \xB7 Antigravity (~/.gemini/antigravity-*/brain/) \xB7 Copilot CLI (~/.copilot/session-state/)\n"
11525
12656
  )
11526
12657
  );
11527
12658
  return;
@@ -11533,6 +12664,12 @@ function registerScanCommand(program2) {
11533
12664
  breakdownParts.push(import_chalk5.default.cyan(String(claudeScan.sessions)) + import_chalk5.default.dim(" Claude"));
11534
12665
  if (geminiScan.sessions > 0)
11535
12666
  breakdownParts.push(import_chalk5.default.blue(String(geminiScan.sessions)) + import_chalk5.default.dim(" Gemini"));
12667
+ if (antigravityScan.sessions > 0)
12668
+ breakdownParts.push(
12669
+ import_chalk5.default.yellow(String(antigravityScan.sessions)) + import_chalk5.default.dim(" Antigravity")
12670
+ );
12671
+ if (copilotScan.sessions > 0)
12672
+ breakdownParts.push(import_chalk5.default.green(String(copilotScan.sessions)) + import_chalk5.default.dim(" Copilot"));
11536
12673
  if (codexScan.sessions > 0)
11537
12674
  breakdownParts.push(import_chalk5.default.magenta(String(codexScan.sessions)) + import_chalk5.default.dim(" Codex"));
11538
12675
  const sessionBreakdown = breakdownParts.length > 1 ? import_chalk5.default.dim("(") + breakdownParts.join(import_chalk5.default.dim(" \xB7 ")) + import_chalk5.default.dim(")") : "";
@@ -11721,7 +12858,7 @@ function registerScanCommand(program2) {
11721
12858
  const stale = isStaleFinding(f.timestamp);
11722
12859
  const ts = f.timestamp ? import_chalk5.default.dim(fmtTs(f.timestamp) + " ") : "";
11723
12860
  const proj = import_chalk5.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
11724
- const agentBadge = f.agent === "gemini" ? import_chalk5.default.blue("[Gemini] ") : f.agent === "codex" ? import_chalk5.default.magenta("[Codex] ") : f.agent === "shell" ? import_chalk5.default.yellow("[Shell] ") : import_chalk5.default.cyan("[Claude] ");
12861
+ const agentBadge = import_chalk5.default[agentColorName(f.agent)](agentBadgeText(f.agent));
11725
12862
  const sessionSuffix = f.sessionId ? import_chalk5.default.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
11726
12863
  const recurringBadge = recurringPatterns.has(f.patternName) ? import_chalk5.default.red.bold(" \u26A0\uFE0F recurring ") : "";
11727
12864
  const patternDisplay = stale ? import_chalk5.default.dim(f.patternName) : import_chalk5.default.yellow(f.patternName);
@@ -11769,8 +12906,8 @@ function registerScanCommand(program2) {
11769
12906
  const stale = isStaleFinding(f.timestamp);
11770
12907
  const ts = f.timestamp ? import_chalk5.default.dim(fmtTs(f.timestamp) + " ") : "";
11771
12908
  const proj = import_chalk5.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
11772
- const agentLabel2 = f.agent === "gemini" ? "[Gemini] " : f.agent === "codex" ? "[Codex] " : "[Claude] ";
11773
- const agentBadge = stale ? import_chalk5.default.dim(agentLabel2) : f.agent === "gemini" ? import_chalk5.default.blue(agentLabel2) : f.agent === "codex" ? import_chalk5.default.magenta(agentLabel2) : import_chalk5.default.cyan(agentLabel2);
12909
+ const agentLabel2 = agentBadgeText(f.agent);
12910
+ const agentBadge = stale ? import_chalk5.default.dim(agentLabel2) : import_chalk5.default[agentColorName(f.agent)](agentLabel2);
11774
12911
  const toolDisplay = stale ? import_chalk5.default.dim(f.toolName) : import_chalk5.default.yellow(f.toolName);
11775
12912
  const cmdDisplay = stale ? import_chalk5.default.dim(f.commandPreview) : import_chalk5.default.gray(f.commandPreview);
11776
12913
  const sessionSuffix = f.sessionId ? import_chalk5.default.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
@@ -11908,6 +13045,7 @@ var init_scan = __esm({
11908
13045
  init_policy();
11909
13046
  init_dist();
11910
13047
  init_dlp();
13048
+ init_hook_payload();
11911
13049
  init_dist();
11912
13050
  init_scan_summary();
11913
13051
  init_setup();
@@ -11987,6 +13125,8 @@ var init_scan = __esm({
11987
13125
  "exec_command",
11988
13126
  "shell",
11989
13127
  "run_shell_command",
13128
+ "run_command",
13129
+ // Antigravity (agy)
11990
13130
  "write",
11991
13131
  "edit",
11992
13132
  "multiedit"
@@ -16575,6 +17715,7 @@ function resolveUserSkillRoot(entry, cwd) {
16575
17715
  // src/cli/commands/check.ts
16576
17716
  init_dlp();
16577
17717
  init_audit();
17718
+ init_hook_payload();
16578
17719
  function sanitize2(value) {
16579
17720
  return value.replace(/[\x00-\x1F\x7F]/g, "");
16580
17721
  }
@@ -16587,15 +17728,27 @@ function detectAiAgent(payload) {
16587
17728
  if (payload.turn_id !== void 0) {
16588
17729
  return "Codex";
16589
17730
  }
17731
+ if (payload.toolCall !== void 0 || payload.conversationId !== void 0) {
17732
+ return "Antigravity";
17733
+ }
16590
17734
  if (payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0) {
16591
17735
  return "Claude Code";
16592
17736
  }
16593
17737
  if (payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0) {
16594
17738
  return "Gemini CLI";
16595
17739
  }
17740
+ if (payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call") {
17741
+ return "Hermes";
17742
+ }
16596
17743
  if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_SESSION_ID) {
16597
17744
  return "Claude Code";
16598
17745
  }
17746
+ if (process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE) {
17747
+ return "Hermes";
17748
+ }
17749
+ if (process.env.ANTIGRAVITY_CONVERSATION_ID) {
17750
+ return "Antigravity";
17751
+ }
16599
17752
  if (process.env.GEMINI_CLI_VERSION || process.env.GEMINI_API_KEY) {
16600
17753
  return "Gemini CLI";
16601
17754
  }
@@ -16611,7 +17764,11 @@ function detectAiAgent(payload) {
16611
17764
  return "Terminal";
16612
17765
  }
16613
17766
  function registerCheckCommand(program2) {
16614
- program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
17767
+ program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").option(
17768
+ "--agent <name>",
17769
+ "Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
17770
+ ).action(async (data, opts) => {
17771
+ const agentOverride = agentLabelFromFlag(opts?.agent);
16615
17772
  const processPayload = async (raw) => {
16616
17773
  try {
16617
17774
  if (!raw || raw.trim() === "") process.exit(0);
@@ -16651,7 +17808,7 @@ RAW: ${raw}
16651
17808
  if (!prompt) process.exit(0);
16652
17809
  const dlpMatch = scanArgs({ prompt });
16653
17810
  if (!dlpMatch) process.exit(0);
16654
- const agent2 = detectAiAgent(payload);
17811
+ const agent2 = agentOverride ?? detectAiAgent(payload);
16655
17812
  const sessionId2 = typeof payload.session_id === "string" ? payload.session_id : void 0;
16656
17813
  appendLocalAudit(
16657
17814
  "UserPromptSubmit",
@@ -16692,7 +17849,8 @@ RAW: ${raw}
16692
17849
  );
16693
17850
  process.exit(2);
16694
17851
  }
16695
- const safeCwdForConfig = typeof payload.cwd === "string" && import_path33.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
17852
+ const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
17853
+ const safeCwdForConfig = typeof payloadCwd === "string" && import_path33.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
16696
17854
  const config = getConfig(safeCwdForConfig);
16697
17855
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
16698
17856
  try {
@@ -16742,9 +17900,10 @@ RAW: ${raw}
16742
17900
  import_fs32.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
16743
17901
  `);
16744
17902
  }
16745
- const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
16746
- const toolInput = payload.tool_input ?? payload.args ?? {};
16747
- const agent = detectAiAgent(payload);
17903
+ const rawToolName = sanitize2(extractToolName(payload));
17904
+ const toolName = canonicalToolName(rawToolName);
17905
+ const toolInput = canonicalToolInput(rawToolName, extractToolInput(payload));
17906
+ const agent = agentOverride ?? detectAiAgent(payload);
16748
17907
  const mcpMatch = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
16749
17908
  const mcpServer = mcpMatch?.[1];
16750
17909
  const sendBlock = (msg, result2) => {
@@ -16782,6 +17941,21 @@ RAW: ${raw}
16782
17941
  msg,
16783
17942
  result2?.recoveryCommand
16784
17943
  );
17944
+ if (agent === "Antigravity") {
17945
+ process.stdout.write(
17946
+ JSON.stringify({ decision: "deny", reason: aiFeedbackMessage }) + "\n"
17947
+ );
17948
+ process.exit(0);
17949
+ }
17950
+ if (agent === "GitHub Copilot") {
17951
+ process.stdout.write(
17952
+ JSON.stringify({
17953
+ permissionDecision: "deny",
17954
+ permissionDecisionReason: aiFeedbackMessage
17955
+ }) + "\n"
17956
+ );
17957
+ process.exit(0);
17958
+ }
16785
17959
  process.stdout.write(
16786
17960
  JSON.stringify({
16787
17961
  decision: "block",
@@ -16800,11 +17974,11 @@ RAW: ${raw}
16800
17974
  sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
16801
17975
  return;
16802
17976
  }
16803
- const sessionId = typeof payload.session_id === "string" ? payload.session_id : void 0;
16804
- const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
17977
+ const sessionId = typeof payload.session_id === "string" ? payload.session_id : typeof payload.conversationId === "string" ? payload.conversationId : void 0;
17978
+ const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : typeof payload.transcriptPath === "string" ? payload.transcriptPath : void 0;
16805
17979
  const meta = { agent, mcpServer, sessionId, transcriptPath };
16806
17980
  const skillPinCfg = config.policy.skillPinning;
16807
- const rawSessionId = typeof payload.session_id === "string" ? payload.session_id : "";
17981
+ const rawSessionId = sessionId ?? "";
16808
17982
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
16809
17983
  if (skillPinCfg.enabled && safeSessionId) {
16810
17984
  try {
@@ -16861,7 +18035,7 @@ RAW: ${raw}
16861
18035
  return;
16862
18036
  }
16863
18037
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
16864
- const absoluteCwd = typeof payload.cwd === "string" && import_path33.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
18038
+ const absoluteCwd = typeof payloadCwd === "string" && import_path33.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
16865
18039
  const extraRoots = skillPinCfg.roots;
16866
18040
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
16867
18041
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -16927,7 +18101,7 @@ RAW: ${raw}
16927
18101
  if (shouldSnapshot(toolName, toolInput, config)) {
16928
18102
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
16929
18103
  }
16930
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path33.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
18104
+ const safeCwdForAuth = typeof payloadCwd === "string" && import_path33.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
16931
18105
  const result = await authorizeHeadless(toolName, toolInput, meta, {
16932
18106
  cwd: safeCwdForAuth
16933
18107
  });
@@ -17052,6 +18226,7 @@ function containsShellMetachar(token) {
17052
18226
  }
17053
18227
 
17054
18228
  // src/cli/commands/log.ts
18229
+ init_hook_payload();
17055
18230
  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;
17056
18231
  function detectTestResult(command, output) {
17057
18232
  if (!TEST_COMMAND_RE2.test(command)) return null;
@@ -17070,13 +18245,19 @@ function sanitize3(value) {
17070
18245
  return value.replace(/[\x00-\x1F\x7F]/g, "");
17071
18246
  }
17072
18247
  function registerLogCommand(program2) {
17073
- program2.command("log", { hidden: true }).description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").action(async (data) => {
18248
+ program2.command("log", { hidden: true }).description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").option(
18249
+ "--agent <name>",
18250
+ "Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
18251
+ ).action(async (data, opts) => {
18252
+ const agentOverride = agentLabelFromFlag(opts?.agent);
17074
18253
  const logPayload = async (raw) => {
17075
18254
  try {
17076
18255
  if (!raw || raw.trim() === "") process.exit(0);
17077
18256
  const payload = JSON.parse(raw);
17078
- const tool = sanitize3(payload.tool_name ?? payload.name ?? "unknown");
17079
- const rawInput = payload.tool_input ?? payload.args ?? {};
18257
+ if (payload.toolCall === null) process.exit(0);
18258
+ const rawToolName = sanitize3(extractToolName(payload, "unknown"));
18259
+ const tool = canonicalToolName(rawToolName);
18260
+ const rawInput = canonicalToolInput(rawToolName, extractToolInput(payload));
17080
18261
  const metaTag = (() => {
17081
18262
  const m = payload.meta;
17082
18263
  if (m && typeof m === "object") {
@@ -17085,7 +18266,7 @@ function registerLogCommand(program2) {
17085
18266
  }
17086
18267
  return void 0;
17087
18268
  })();
17088
- 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;
18269
+ 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;
17089
18270
  const entry = {
17090
18271
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17091
18272
  tool,
@@ -17094,7 +18275,9 @@ function registerLogCommand(program2) {
17094
18275
  source: "post-hook"
17095
18276
  };
17096
18277
  if (agent) entry.agent = agent;
17097
- if (payload.session_id) entry.sessionId = payload.session_id;
18278
+ if (rawToolName !== tool) entry.agentToolName = rawToolName;
18279
+ const payloadSessionId = payload.session_id ?? payload.conversationId;
18280
+ if (payloadSessionId) entry.sessionId = payloadSessionId;
17098
18281
  const logPath = import_path34.default.join(import_os29.default.homedir(), ".node9", "audit.log");
17099
18282
  if (!import_fs33.default.existsSync(import_path34.default.dirname(logPath)))
17100
18283
  import_fs33.default.mkdirSync(import_path34.default.dirname(logPath), { recursive: true });
@@ -17131,7 +18314,8 @@ function registerLogCommand(program2) {
17131
18314
  }
17132
18315
  }
17133
18316
  }
17134
- const safeCwd = typeof payload.cwd === "string" && import_path34.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
18317
+ const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
18318
+ const safeCwd = typeof payloadCwd === "string" && import_path34.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
17135
18319
  const config = getConfig(safeCwd);
17136
18320
  if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
17137
18321
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -19219,12 +20403,12 @@ function registerInitCommand(program2) {
19219
20403
  if (found.length === 0) {
19220
20404
  console.log(
19221
20405
  import_chalk16.default.gray(
19222
- "No AI agents detected. Install one of the supported agents (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, or Pi)."
20406
+ "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)."
19223
20407
  )
19224
20408
  );
19225
20409
  console.log(
19226
20410
  import_chalk16.default.gray(
19227
- "then run: node9 agents add <claude|codex|gemini|cursor|windsurf|vscode|claudeDesktop|opencode|pi>"
20411
+ "then run: node9 agents add <claude|codex|antigravity|gemini|copilot|cursor|windsurf|vscode|claudeDesktop|opencode|pi|hermes>"
19228
20412
  )
19229
20413
  );
19230
20414
  return;
@@ -19238,6 +20422,8 @@ function registerInitCommand(program2) {
19238
20422
  console.log(import_chalk16.default.bold(`Wiring ${agent}...`));
19239
20423
  if (agent === "claude") await setupClaude();
19240
20424
  else if (agent === "gemini") await setupGemini();
20425
+ else if (agent === "antigravity") await setupAntigravity();
20426
+ else if (agent === "copilot") await setupCopilot();
19241
20427
  else if (agent === "cursor") await setupCursor();
19242
20428
  else if (agent === "codex") await setupCodex();
19243
20429
  else if (agent === "windsurf") await setupWindsurf();
@@ -19245,6 +20431,7 @@ function registerInitCommand(program2) {
19245
20431
  else if (agent === "claudeDesktop") await setupClaudeDesktop();
19246
20432
  else if (agent === "opencode") await setupOpencode();
19247
20433
  else if (agent === "pi") await setupPi();
20434
+ else if (agent === "hermes") setupHermes();
19248
20435
  console.log("");
19249
20436
  }
19250
20437
  if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
@@ -21000,26 +22187,36 @@ init_setup();
21000
22187
  var SETUP_FN = {
21001
22188
  claude: setupClaude,
21002
22189
  gemini: setupGemini,
22190
+ antigravity: setupAntigravity,
22191
+ copilot: setupCopilot,
21003
22192
  cursor: setupCursor,
21004
22193
  codex: setupCodex,
21005
22194
  windsurf: setupWindsurf,
21006
22195
  vscode: setupVSCode,
21007
22196
  claudeDesktop: setupClaudeDesktop,
21008
22197
  opencode: setupOpencode,
21009
- pi: setupPi
22198
+ pi: setupPi,
22199
+ hermes: setupHermes
21010
22200
  };
21011
22201
  var TEARDOWN_FN = {
21012
22202
  claude: teardownClaude,
21013
22203
  gemini: teardownGemini,
22204
+ antigravity: teardownAntigravity,
22205
+ copilot: teardownCopilot,
21014
22206
  cursor: teardownCursor,
21015
22207
  codex: teardownCodex,
21016
22208
  windsurf: teardownWindsurf,
21017
22209
  vscode: teardownVSCode,
21018
22210
  claudeDesktop: teardownClaudeDesktop,
21019
22211
  opencode: teardownOpencode,
21020
- pi: teardownPi
22212
+ pi: teardownPi,
22213
+ hermes: teardownHermes
21021
22214
  };
21022
22215
  var AGENT_NAMES = Object.keys(SETUP_FN);
22216
+ function resolveAgentName(agent) {
22217
+ const lower = agent.toLowerCase();
22218
+ return lower === "agy" ? "antigravity" : lower;
22219
+ }
21023
22220
  function registerAgentsCommand(program2) {
21024
22221
  const agents = program2.command("agents").description("List and manage AI agent integrations");
21025
22222
  agents.command("list").description("Show all supported agents and their Node9 status").action(() => {
@@ -21048,9 +22245,16 @@ function registerAgentsCommand(program2) {
21048
22245
  import_chalk23.default.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + import_chalk23.default.white(`node9 agents add ${unwired[0].name}`) + "\n"
21049
22246
  );
21050
22247
  }
22248
+ if (statuses.some((s) => s.name === "gemini" && s.installed)) {
22249
+ console.log(
22250
+ import_chalk23.default.yellow(
22251
+ " \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
22252
+ ) + import_chalk23.default.gray(" Migrate to Antigravity: ") + import_chalk23.default.white("node9 agents add antigravity") + "\n"
22253
+ );
22254
+ }
21051
22255
  });
21052
22256
  agents.command("add").description("Wire Node9 into an agent").argument("<agent>", `Agent to wire: ${AGENT_NAMES.join(" | ")}`).action(async (agent) => {
21053
- const name = agent.toLowerCase();
22257
+ const name = resolveAgentName(agent);
21054
22258
  const fn = SETUP_FN[name];
21055
22259
  if (!fn) {
21056
22260
  console.error(import_chalk23.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
@@ -21059,7 +22263,7 @@ function registerAgentsCommand(program2) {
21059
22263
  await fn();
21060
22264
  });
21061
22265
  agents.command("remove").description("Remove Node9 from an agent").argument("<agent>", `Agent to unwire: ${AGENT_NAMES.join(" | ")}`).action((agent) => {
21062
- const name = agent.toLowerCase();
22266
+ const name = resolveAgentName(agent);
21063
22267
  const fn = TEARDOWN_FN[name];
21064
22268
  if (!fn) {
21065
22269
  console.error(import_chalk23.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
@@ -21081,6 +22285,7 @@ var import_chalk24 = __toESM(require("chalk"));
21081
22285
  var import_fs41 = __toESM(require("fs"));
21082
22286
  var import_path42 = __toESM(require("path"));
21083
22287
  var import_os36 = __toESM(require("os"));
22288
+ init_scan_summary();
21084
22289
  var CLAUDE_PRICING3 = {
21085
22290
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
21086
22291
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -21687,7 +22892,9 @@ function renderList(summaries, totalCost) {
21687
22892
  const cost = s.costUSD > 0 ? import_chalk24.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
21688
22893
  const blocked = s.blockedCalls.length > 0 ? import_chalk24.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
21689
22894
  const snap = s.hasSnapshot ? import_chalk24.default.green(" \u{1F4F8}") : "";
21690
- const agentBadge = s.agent === "gemini" ? import_chalk24.default.blue(" [Gemini]") : s.agent === "codex" ? import_chalk24.default.magenta(" [Codex]") : import_chalk24.default.cyan(" [Claude]");
22895
+ const agentBadge = import_chalk24.default[agentColorName(s.agent ?? "claude")](
22896
+ " " + agentBadgeText(s.agent ?? "claude", 0)
22897
+ );
21691
22898
  const sid = import_chalk24.default.dim(" " + s.sessionId.slice(0, 8));
21692
22899
  console.log(
21693
22900
  ` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
@@ -21707,7 +22914,7 @@ function renderDetail(s) {
21707
22914
  );
21708
22915
  console.log(import_chalk24.default.bold(" Project ") + import_chalk24.default.white(s.projectLabel));
21709
22916
  if (s.agent) {
21710
- const agentLabel2 = s.agent === "gemini" ? import_chalk24.default.blue("Gemini CLI") : s.agent === "codex" ? import_chalk24.default.magenta("Codex") : import_chalk24.default.cyan("Claude Code");
22917
+ const agentLabel2 = import_chalk24.default[agentColorName(s.agent)](agentDisplayName(s.agent));
21711
22918
  console.log(import_chalk24.default.bold(" Agent ") + agentLabel2);
21712
22919
  }
21713
22920
  console.log(import_chalk24.default.bold(" When ") + import_chalk24.default.white(fmtDateTime(s.startTime)));
@@ -22344,31 +23551,34 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
22344
23551
  });
22345
23552
  program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
22346
23553
  "after",
22347
- "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
23554
+ "\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
22348
23555
  ).argument(
22349
23556
  "<target>",
22350
- "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
23557
+ "The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
22351
23558
  ).action(async (target) => {
22352
23559
  if (target === "gemini") return await setupGemini();
23560
+ if (target === "antigravity" || target === "agy") return await setupAntigravity();
23561
+ if (target === "copilot") return await setupCopilot();
22353
23562
  if (target === "claude") return await setupClaude();
22354
23563
  if (target === "cursor") return await setupCursor();
22355
23564
  if (target === "codex") return await setupCodex();
22356
23565
  if (target === "windsurf") return await setupWindsurf();
22357
23566
  if (target === "vscode") return await setupVSCode();
23567
+ if (target === "hermes") return setupHermes();
22358
23568
  if (target === "hud") return setupHud();
22359
23569
  console.error(
22360
23570
  import_chalk30.default.red(
22361
- `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
23571
+ `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
22362
23572
  )
22363
23573
  );
22364
23574
  process.exit(1);
22365
23575
  });
22366
23576
  program.command("setup", { hidden: true }).description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
22367
23577
  "after",
22368
- "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
23578
+ "\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
22369
23579
  ).argument(
22370
23580
  "[target]",
22371
- "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
23581
+ "The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
22372
23582
  ).action(async (target) => {
22373
23583
  if (!target) {
22374
23584
  console.log(import_chalk30.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
@@ -22376,10 +23586,13 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
22376
23586
  console.log(" Targets:");
22377
23587
  console.log(" " + import_chalk30.default.green("claude") + " \u2014 Claude Code (hook mode)");
22378
23588
  console.log(" " + import_chalk30.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
23589
+ console.log(" " + import_chalk30.default.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
23590
+ console.log(" " + import_chalk30.default.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
22379
23591
  console.log(" " + import_chalk30.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
22380
23592
  console.log(" " + import_chalk30.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
22381
23593
  console.log(" " + import_chalk30.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
22382
23594
  console.log(" " + import_chalk30.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
23595
+ console.log(" " + import_chalk30.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
22383
23596
  process.stdout.write(
22384
23597
  " " + import_chalk30.default.green("hud") + " \u2014 Claude Code security statusline\n"
22385
23598
  );
@@ -22388,38 +23601,44 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
22388
23601
  }
22389
23602
  const t = target.toLowerCase();
22390
23603
  if (t === "gemini") return await setupGemini();
23604
+ if (t === "antigravity" || t === "agy") return await setupAntigravity();
23605
+ if (t === "copilot") return await setupCopilot();
22391
23606
  if (t === "claude") return await setupClaude();
22392
23607
  if (t === "cursor") return await setupCursor();
22393
23608
  if (t === "codex") return await setupCodex();
22394
23609
  if (t === "windsurf") return await setupWindsurf();
22395
23610
  if (t === "vscode") return await setupVSCode();
23611
+ if (t === "hermes") return setupHermes();
22396
23612
  if (t === "hud") return setupHud();
22397
23613
  console.error(
22398
23614
  import_chalk30.default.red(
22399
- `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
23615
+ `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
22400
23616
  )
22401
23617
  );
22402
23618
  process.exit(1);
22403
23619
  });
22404
23620
  program.command("removefrom", { hidden: true }).description("Remove Node9 hooks from an AI agent configuration").addHelpText(
22405
23621
  "after",
22406
- "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
23622
+ "\n Supported targets: claude antigravity copilot gemini cursor codex windsurf vscode hud"
22407
23623
  ).argument(
22408
23624
  "<target>",
22409
- "The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
23625
+ "The agent to remove from: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
22410
23626
  ).action((target) => {
22411
23627
  let fn;
22412
23628
  if (target === "claude") fn = teardownClaude;
22413
23629
  else if (target === "gemini") fn = teardownGemini;
23630
+ else if (target === "antigravity" || target === "agy") fn = teardownAntigravity;
23631
+ else if (target === "copilot") fn = teardownCopilot;
22414
23632
  else if (target === "cursor") fn = teardownCursor;
22415
23633
  else if (target === "codex") fn = teardownCodex;
22416
23634
  else if (target === "windsurf") fn = teardownWindsurf;
22417
23635
  else if (target === "vscode") fn = teardownVSCode;
23636
+ else if (target === "hermes") fn = teardownHermes;
22418
23637
  else if (target === "hud") fn = teardownHud;
22419
23638
  else {
22420
23639
  console.error(
22421
23640
  import_chalk30.default.red(
22422
- `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
23641
+ `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
22423
23642
  )
22424
23643
  );
22425
23644
  process.exit(1);
@@ -22452,7 +23671,8 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
22452
23671
  ["Cursor", teardownCursor],
22453
23672
  ["Codex", teardownCodex],
22454
23673
  ["Windsurf", teardownWindsurf],
22455
- ["VSCode", teardownVSCode]
23674
+ ["VSCode", teardownVSCode],
23675
+ ["Hermes", teardownHermes]
22456
23676
  ]) {
22457
23677
  try {
22458
23678
  fn();
@@ -22675,6 +23895,9 @@ program.command("resume").description("Re-enable Node9 protection immediately").
22675
23895
  var HOOK_BASED_AGENTS = {
22676
23896
  claude: "claude",
22677
23897
  gemini: "gemini",
23898
+ antigravity: "antigravity",
23899
+ agy: "antigravity",
23900
+ copilot: "copilot",
22678
23901
  cursor: "cursor"
22679
23902
  };
22680
23903
  program.argument("[command...]", "The agent command to run (e.g., gemini)").action(async (commandArgs) => {