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