@solongate/proxy 0.30.2 → 0.32.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/index.js CHANGED
@@ -1,11 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
3
  var __esm = (fn, res) => function __init() {
10
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
5
  };
@@ -465,8 +459,8 @@ var init_cli_utils = __esm({
465
459
 
466
460
  // src/init.ts
467
461
  var init_exports = {};
468
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
469
- import { resolve as resolve3, join, dirname as dirname2 } from "path";
462
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
463
+ import { resolve as resolve3, join as join2, dirname as dirname2 } from "path";
470
464
  import { homedir } from "os";
471
465
  import { fileURLToPath } from "url";
472
466
  import { execFileSync } from "child_process";
@@ -540,13 +534,16 @@ function isAlreadyProtected(server) {
540
534
  }
541
535
  return false;
542
536
  }
543
- function wrapServer(serverName, server, policy) {
537
+ function wrapServer(serverName, server, policy, agentName) {
544
538
  const env = { ...server.env ?? {} };
545
539
  env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
546
540
  const proxyArgs = ["-y", "@solongate/proxy@latest"];
547
541
  if (policy) {
548
542
  proxyArgs.push("--policy", policy);
549
543
  }
544
+ if (agentName) {
545
+ proxyArgs.push("--agent-name", agentName);
546
+ }
550
547
  proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
551
548
  return {
552
549
  command: "npx",
@@ -639,7 +636,7 @@ EXAMPLES
639
636
  console.log(help);
640
637
  }
641
638
  function readHookScript(filename) {
642
- return readFileSync4(join(HOOKS_DIR, filename), "utf-8");
639
+ return readFileSync4(join2(HOOKS_DIR, filename), "utf-8");
643
640
  }
644
641
  function unlockProtectedDirs() {
645
642
  const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
@@ -694,12 +691,12 @@ function unlockProtectedDirs() {
694
691
  function installHooks(selectedTools = [], wrappedMcpConfig) {
695
692
  unlockProtectedDirs();
696
693
  const hooksDir = resolve3(".solongate", "hooks");
697
- mkdirSync2(hooksDir, { recursive: true });
694
+ mkdirSync3(hooksDir, { recursive: true });
698
695
  const hookFiles = ["guard.mjs", "audit.mjs", "stop.mjs"];
699
696
  let hooksUpdated = 0;
700
697
  let hooksSkipped = 0;
701
698
  for (const filename of hookFiles) {
702
- const hookPath = join(hooksDir, filename);
699
+ const hookPath = join2(hooksDir, filename);
703
700
  const latest = readHookScript(filename);
704
701
  let needsWrite = true;
705
702
  if (existsSync4(hookPath)) {
@@ -731,7 +728,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
731
728
  const skippedNames = [];
732
729
  for (const client of clients) {
733
730
  const clientDir = resolve3(client.dir);
734
- mkdirSync2(clientDir, { recursive: true });
731
+ mkdirSync3(clientDir, { recursive: true });
735
732
  const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
736
733
  const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
737
734
  const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
@@ -766,7 +763,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
766
763
  }
767
764
  }
768
765
  function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, stopCmd) {
769
- const settingsPath = join(clientDir, "settings.json");
766
+ const settingsPath = join2(clientDir, "settings.json");
770
767
  const hookSettings = {
771
768
  PreToolUse: [
772
769
  { matcher: "", hooks: [{ type: "command", command: guardCmd }] }
@@ -799,12 +796,30 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
799
796
  ],
800
797
  postToolUse: [
801
798
  { matcher: "", command: auditCmd }
799
+ ],
800
+ beforeShellExecution: [
801
+ { matcher: "", command: guardCmd, failClosed: true }
802
+ ],
803
+ afterShellExecution: [
804
+ { matcher: "", command: auditCmd }
805
+ ],
806
+ beforeReadFile: [
807
+ { matcher: "", command: guardCmd, failClosed: true }
808
+ ],
809
+ afterFileEdit: [
810
+ { matcher: "", command: auditCmd }
811
+ ],
812
+ beforeMCPExecution: [
813
+ { matcher: "", command: guardCmd, failClosed: true }
814
+ ],
815
+ afterMCPExecution: [
816
+ { matcher: "", command: auditCmd }
802
817
  ]
803
818
  };
804
819
  const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
805
- const userCursorDir = join(homedir(), ".cursor");
820
+ const userCursorDir = join2(homedir(), ".cursor");
806
821
  if (existsSync4(userCursorDir)) {
807
- const userHooksPath = join(userCursorDir, "hooks.json");
822
+ const userHooksPath = join2(userCursorDir, "hooks.json");
808
823
  const existingUserHooks = existsSync4(userHooksPath) ? readFileSync4(userHooksPath, "utf-8") : "";
809
824
  if (hooksContent !== existingUserHooks) {
810
825
  writeFileSync2(userHooksPath, hooksContent);
@@ -812,7 +827,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
812
827
  changed = true;
813
828
  }
814
829
  }
815
- const hooksPath = join(clientDir, "hooks.json");
830
+ const hooksPath = join2(clientDir, "hooks.json");
816
831
  const existingHooks = existsSync4(hooksPath) ? readFileSync4(hooksPath, "utf-8") : "";
817
832
  if (hooksContent !== existingHooks) {
818
833
  writeFileSync2(hooksPath, hooksContent);
@@ -820,8 +835,22 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
820
835
  changed = true;
821
836
  }
822
837
  if (wrappedMcpConfig) {
823
- const mcpPath = join(clientDir, "mcp.json");
824
- const mcpStr = JSON.stringify(wrappedMcpConfig, null, 2) + "\n";
838
+ const cursorMcpConfig = { mcpServers: {} };
839
+ for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
840
+ const args = [...server.args ?? []];
841
+ const agentIdx = args.indexOf("--agent-name");
842
+ if (agentIdx >= 0 && agentIdx + 1 < args.length) {
843
+ args[agentIdx + 1] = "cursor";
844
+ } else {
845
+ const sepIdx = args.indexOf("--");
846
+ if (sepIdx >= 0) {
847
+ args.splice(sepIdx, 0, "--agent-name", "cursor");
848
+ }
849
+ }
850
+ cursorMcpConfig.mcpServers[name] = { ...server, args };
851
+ }
852
+ const mcpPath = join2(clientDir, "mcp.json");
853
+ const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
825
854
  const existingMcp = existsSync4(mcpPath) ? readFileSync4(mcpPath, "utf-8") : "";
826
855
  if (mcpStr !== existingMcp) {
827
856
  writeFileSync2(mcpPath, mcpStr);
@@ -829,7 +858,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
829
858
  changed = true;
830
859
  }
831
860
  }
832
- const staleSettings = join(clientDir, "settings.json");
861
+ const staleSettings = join2(clientDir, "settings.json");
833
862
  if (existsSync4(staleSettings)) {
834
863
  try {
835
864
  const content = JSON.parse(readFileSync4(staleSettings, "utf-8"));
@@ -844,7 +873,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
844
873
  return changed ? "installed" : "skipped";
845
874
  }
846
875
  function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
847
- const settingsPath = join(clientDir, "settings.json");
876
+ const settingsPath = join2(clientDir, "settings.json");
848
877
  let existing = {};
849
878
  try {
850
879
  existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
@@ -852,7 +881,19 @@ function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
852
881
  }
853
882
  const merged = { ...existing };
854
883
  if (wrappedMcpConfig) {
855
- merged.mcpServers = wrappedMcpConfig.mcpServers;
884
+ const geminiMcp = {};
885
+ for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
886
+ const args = [...server.args ?? []];
887
+ const agentIdx = args.indexOf("--agent-name");
888
+ if (agentIdx >= 0 && agentIdx + 1 < args.length) {
889
+ args[agentIdx + 1] = "gemini-cli";
890
+ } else {
891
+ const sepIdx = args.indexOf("--");
892
+ if (sepIdx >= 0) args.splice(sepIdx, 0, "--agent-name", "gemini-cli");
893
+ }
894
+ geminiMcp[name] = { ...server, args };
895
+ }
896
+ merged.mcpServers = geminiMcp;
856
897
  }
857
898
  merged.hooks = {
858
899
  BeforeTool: [
@@ -1186,7 +1227,7 @@ var init_init = __esm({
1186
1227
  "mcp.json",
1187
1228
  ".claude/mcp.json"
1188
1229
  ];
1189
- CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
1230
+ CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join2(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join2(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join2(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
1190
1231
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1191
1232
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1192
1233
  spinnerInterval = null;
@@ -1539,8 +1580,8 @@ var init_inject = __esm({
1539
1580
 
1540
1581
  // src/create.ts
1541
1582
  var create_exports = {};
1542
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
1543
- import { resolve as resolve5, join as join2 } from "path";
1583
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
1584
+ import { resolve as resolve5, join as join3 } from "path";
1544
1585
  import { execSync as execSync2 } from "child_process";
1545
1586
  function withSpinner(message, fn) {
1546
1587
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -1620,7 +1661,7 @@ EXAMPLES
1620
1661
  }
1621
1662
  function createProject(dir, name, _policy) {
1622
1663
  writeFileSync4(
1623
- join2(dir, "package.json"),
1664
+ join3(dir, "package.json"),
1624
1665
  JSON.stringify(
1625
1666
  {
1626
1667
  name,
@@ -1650,7 +1691,7 @@ function createProject(dir, name, _policy) {
1650
1691
  ) + "\n"
1651
1692
  );
1652
1693
  writeFileSync4(
1653
- join2(dir, "tsconfig.json"),
1694
+ join3(dir, "tsconfig.json"),
1654
1695
  JSON.stringify(
1655
1696
  {
1656
1697
  compilerOptions: {
@@ -1670,9 +1711,9 @@ function createProject(dir, name, _policy) {
1670
1711
  2
1671
1712
  ) + "\n"
1672
1713
  );
1673
- mkdirSync3(join2(dir, "src"), { recursive: true });
1714
+ mkdirSync4(join3(dir, "src"), { recursive: true });
1674
1715
  writeFileSync4(
1675
- join2(dir, "src", "index.ts"),
1716
+ join3(dir, "src", "index.ts"),
1676
1717
  `#!/usr/bin/env node
1677
1718
 
1678
1719
  console.log = (...args: unknown[]) => {
@@ -1717,7 +1758,7 @@ console.log('Press Ctrl+C to stop.');
1717
1758
  `
1718
1759
  );
1719
1760
  writeFileSync4(
1720
- join2(dir, ".mcp.json"),
1761
+ join3(dir, ".mcp.json"),
1721
1762
  JSON.stringify(
1722
1763
  {
1723
1764
  mcpServers: {
@@ -1735,12 +1776,12 @@ console.log('Press Ctrl+C to stop.');
1735
1776
  ) + "\n"
1736
1777
  );
1737
1778
  writeFileSync4(
1738
- join2(dir, ".env"),
1779
+ join3(dir, ".env"),
1739
1780
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
1740
1781
  `
1741
1782
  );
1742
1783
  writeFileSync4(
1743
- join2(dir, ".gitignore"),
1784
+ join3(dir, ".gitignore"),
1744
1785
  `node_modules/
1745
1786
  dist/
1746
1787
  *.solongate-backup
@@ -1759,7 +1800,7 @@ async function main3() {
1759
1800
  process.exit(1);
1760
1801
  }
1761
1802
  withSpinner(`Setting up ${opts.name}...`, () => {
1762
- mkdirSync3(dir, { recursive: true });
1803
+ mkdirSync4(dir, { recursive: true });
1763
1804
  createProject(dir, opts.name, opts.policy);
1764
1805
  });
1765
1806
  if (!opts.noInstall) {
@@ -2170,8 +2211,8 @@ import {
2170
2211
  ListResourceTemplatesRequestSchema
2171
2212
  } from "@modelcontextprotocol/sdk/types.js";
2172
2213
  import { createServer as createHttpServer } from "http";
2173
- import { resolve as resolve2 } from "path";
2174
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2214
+ import { resolve as resolve2, join } from "path";
2215
+ import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
2175
2216
 
2176
2217
  // ../core/dist/index.js
2177
2218
  import { z } from "zod";
@@ -6227,23 +6268,26 @@ var SolonGateProxy = class {
6227
6268
  const clientVersion = this.server.getClientVersion();
6228
6269
  log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
6229
6270
  try {
6230
- const fs = __require("fs");
6231
- const path = __require("path");
6232
- const debugDir = path.resolve(".solongate");
6233
- fs.mkdirSync(debugDir, { recursive: true });
6234
- fs.writeFileSync(path.join(debugDir, ".debug-clientinfo"), JSON.stringify({
6235
- clientVersion,
6236
- agentIdBefore: this.agentId,
6237
- agentNameBefore: this.agentName,
6238
- ts: Date.now()
6239
- }, null, 2));
6271
+ const debugInfo = {
6272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6273
+ clientInfo: clientVersion,
6274
+ agentNameFlag: this.config.agentName ?? null,
6275
+ resolvedAgentId: this.agentId,
6276
+ resolvedAgentName: this.agentName,
6277
+ pid: process.pid
6278
+ };
6279
+ const debugDir = resolve2(".solongate");
6280
+ mkdirSync2(debugDir, { recursive: true });
6281
+ appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
6240
6282
  } catch {
6241
6283
  }
6242
- if (clientVersion?.name) {
6284
+ if (clientVersion?.name && !this.config.agentName) {
6243
6285
  const normalized = this.normalizeAgentName(clientVersion.name);
6244
6286
  this.agentId = normalized.id;
6245
6287
  this.agentName = normalized.name;
6246
6288
  log2(`Agent identified from MCP clientInfo: ${this.agentName} (raw: ${clientVersion.name})`);
6289
+ } else if (this.config.agentName) {
6290
+ log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
6247
6291
  }
6248
6292
  }
6249
6293
  };
package/dist/init.js CHANGED
@@ -118,13 +118,16 @@ function isAlreadyProtected(server) {
118
118
  }
119
119
  return false;
120
120
  }
121
- function wrapServer(serverName, server, policy) {
121
+ function wrapServer(serverName, server, policy, agentName) {
122
122
  const env = { ...server.env ?? {} };
123
123
  env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
124
124
  const proxyArgs = ["-y", "@solongate/proxy@latest"];
125
125
  if (policy) {
126
126
  proxyArgs.push("--policy", policy);
127
127
  }
128
+ if (agentName) {
129
+ proxyArgs.push("--agent-name", agentName);
130
+ }
128
131
  proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
129
132
  return {
130
133
  command: "npx",
@@ -379,6 +382,24 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
379
382
  ],
380
383
  postToolUse: [
381
384
  { matcher: "", command: auditCmd }
385
+ ],
386
+ beforeShellExecution: [
387
+ { matcher: "", command: guardCmd, failClosed: true }
388
+ ],
389
+ afterShellExecution: [
390
+ { matcher: "", command: auditCmd }
391
+ ],
392
+ beforeReadFile: [
393
+ { matcher: "", command: guardCmd, failClosed: true }
394
+ ],
395
+ afterFileEdit: [
396
+ { matcher: "", command: auditCmd }
397
+ ],
398
+ beforeMCPExecution: [
399
+ { matcher: "", command: guardCmd, failClosed: true }
400
+ ],
401
+ afterMCPExecution: [
402
+ { matcher: "", command: auditCmd }
382
403
  ]
383
404
  };
384
405
  const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
@@ -400,8 +421,22 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
400
421
  changed = true;
401
422
  }
402
423
  if (wrappedMcpConfig) {
424
+ const cursorMcpConfig = { mcpServers: {} };
425
+ for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
426
+ const args = [...server.args ?? []];
427
+ const agentIdx = args.indexOf("--agent-name");
428
+ if (agentIdx >= 0 && agentIdx + 1 < args.length) {
429
+ args[agentIdx + 1] = "cursor";
430
+ } else {
431
+ const sepIdx = args.indexOf("--");
432
+ if (sepIdx >= 0) {
433
+ args.splice(sepIdx, 0, "--agent-name", "cursor");
434
+ }
435
+ }
436
+ cursorMcpConfig.mcpServers[name] = { ...server, args };
437
+ }
403
438
  const mcpPath = join(clientDir, "mcp.json");
404
- const mcpStr = JSON.stringify(wrappedMcpConfig, null, 2) + "\n";
439
+ const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
405
440
  const existingMcp = existsSync(mcpPath) ? readFileSync(mcpPath, "utf-8") : "";
406
441
  if (mcpStr !== existingMcp) {
407
442
  writeFileSync(mcpPath, mcpStr);
@@ -432,7 +467,19 @@ function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
432
467
  }
433
468
  const merged = { ...existing };
434
469
  if (wrappedMcpConfig) {
435
- merged.mcpServers = wrappedMcpConfig.mcpServers;
470
+ const geminiMcp = {};
471
+ for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
472
+ const args = [...server.args ?? []];
473
+ const agentIdx = args.indexOf("--agent-name");
474
+ if (agentIdx >= 0 && agentIdx + 1 < args.length) {
475
+ args[agentIdx + 1] = "gemini-cli";
476
+ } else {
477
+ const sepIdx = args.indexOf("--");
478
+ if (sepIdx >= 0) args.splice(sepIdx, 0, "--agent-name", "gemini-cli");
479
+ }
480
+ geminiMcp[name] = { ...server, args };
481
+ }
482
+ merged.mcpServers = geminiMcp;
436
483
  }
437
484
  merged.hooks = {
438
485
  BeforeTool: [
package/dist/lib.js CHANGED
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // ../core/dist/index.js
9
2
  import { z } from "zod";
10
3
  var __defProp = Object.defineProperty;
@@ -3619,8 +3612,8 @@ import {
3619
3612
  ListResourceTemplatesRequestSchema
3620
3613
  } from "@modelcontextprotocol/sdk/types.js";
3621
3614
  import { createServer as createHttpServer } from "http";
3622
- import { resolve as resolve2 } from "path";
3623
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
3615
+ import { resolve as resolve2, join } from "path";
3616
+ import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
3624
3617
 
3625
3618
  // src/config.ts
3626
3619
  import { readFileSync, existsSync } from "fs";
@@ -4468,23 +4461,26 @@ var SolonGateProxy = class {
4468
4461
  const clientVersion = this.server.getClientVersion();
4469
4462
  log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
4470
4463
  try {
4471
- const fs = __require("fs");
4472
- const path = __require("path");
4473
- const debugDir = path.resolve(".solongate");
4474
- fs.mkdirSync(debugDir, { recursive: true });
4475
- fs.writeFileSync(path.join(debugDir, ".debug-clientinfo"), JSON.stringify({
4476
- clientVersion,
4477
- agentIdBefore: this.agentId,
4478
- agentNameBefore: this.agentName,
4479
- ts: Date.now()
4480
- }, null, 2));
4464
+ const debugInfo = {
4465
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4466
+ clientInfo: clientVersion,
4467
+ agentNameFlag: this.config.agentName ?? null,
4468
+ resolvedAgentId: this.agentId,
4469
+ resolvedAgentName: this.agentName,
4470
+ pid: process.pid
4471
+ };
4472
+ const debugDir = resolve2(".solongate");
4473
+ mkdirSync2(debugDir, { recursive: true });
4474
+ appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
4481
4475
  } catch {
4482
4476
  }
4483
- if (clientVersion?.name) {
4477
+ if (clientVersion?.name && !this.config.agentName) {
4484
4478
  const normalized = this.normalizeAgentName(clientVersion.name);
4485
4479
  this.agentId = normalized.id;
4486
4480
  this.agentName = normalized.name;
4487
4481
  log2(`Agent identified from MCP clientInfo: ${this.agentName} (raw: ${clientVersion.name})`);
4482
+ } else if (this.config.agentName) {
4483
+ log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
4488
4484
  }
4489
4485
  }
4490
4486
  };
package/hooks/audit.mjs CHANGED
@@ -38,8 +38,12 @@ process.stdin.on('end', async () => {
38
38
  try {
39
39
  const data = JSON.parse(input);
40
40
 
41
- // Debug: dump raw stdin to file for agent detection troubleshooting
42
- try { writeFileSync(resolve('.solongate', '.debug-stdin'), JSON.stringify(data, null, 2)); } catch {}
41
+ // Debug: append raw stdin to file for agent detection troubleshooting
42
+ try {
43
+ const debugLine = JSON.stringify({ ts: new Date().toISOString(), argv: process.argv.slice(2), cursor_version: data.cursor_version || null, tool_name: data.tool_name || data.toolName, agent_id: AGENT_ID, hook_event_name: data.hook_event_name || null }) + '\n';
44
+ const { appendFileSync: afs } = await import('node:fs');
45
+ afs(resolve('.solongate', '.debug-audit-log'), debugLine);
46
+ } catch {}
43
47
 
44
48
  // Auto-detect agent from stdin data (overrides CLI args if detected)
45
49
  if (data.cursor_version) {
@@ -50,9 +54,35 @@ process.stdin.on('end', async () => {
50
54
  AGENT_NAME = 'Gemini CLI';
51
55
  }
52
56
 
53
- // Normalize field names across tools (Claude: tool_name, others may use toolName)
54
- const toolName = data.tool_name || data.toolName || 'unknown';
55
- const toolInput = data.tool_input || data.toolInput || data.params || {};
57
+ const hookEvent = data.hook_event_name || '';
58
+
59
+ // Map Cursor-specific hook events to standard tool_name/tool_input
60
+ let toolName = data.tool_name || data.toolName || '';
61
+ let toolInput = data.tool_input || data.toolInput || data.params || {};
62
+
63
+ if (!toolName && hookEvent) {
64
+ switch (hookEvent) {
65
+ case 'afterShellExecution':
66
+ toolName = 'Shell';
67
+ toolInput = { command: data.command || '' };
68
+ break;
69
+ case 'afterFileEdit':
70
+ toolName = 'Edit';
71
+ toolInput = { file_path: data.file_path || '' };
72
+ break;
73
+ case 'afterMCPExecution':
74
+ toolName = data.tool_name || data.toolName || `mcp:${data.server_name || 'unknown'}`;
75
+ toolInput = data.arguments || {};
76
+ break;
77
+ case 'postToolUse':
78
+ case 'postToolUseFailure':
79
+ toolName = data.tool_name || data.toolName || 'unknown';
80
+ break;
81
+ default:
82
+ break;
83
+ }
84
+ }
85
+ if (!toolName) toolName = 'unknown';
56
86
 
57
87
  if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
58
88
  process.exit(0);
@@ -71,14 +101,16 @@ process.stdin.on('end', async () => {
71
101
  }
72
102
  } catch {}
73
103
 
74
- // Cursor uses result_json, Claude uses tool_response
104
+ // Cursor uses tool_output, Claude uses tool_response
75
105
  const toolResponse = data.tool_response || data.toolResponse || {};
106
+ const toolOutput = data.tool_output || data.toolOutput || '';
76
107
  const resultJson = data.result_json ? (typeof data.result_json === 'string' ? data.result_json : JSON.stringify(data.result_json)) : '';
77
108
 
78
109
  const hasError = guardDenied ||
79
110
  toolResponse.error ||
80
111
  toolResponse.exitCode > 0 ||
81
112
  toolResponse.isError ||
113
+ (toolOutput && typeof toolOutput === 'string' && toolOutput.includes('"error"')) ||
82
114
  (resultJson && resultJson.includes('"error"'));
83
115
 
84
116
  const argsSummary = {};
package/hooks/guard.mjs CHANGED
@@ -46,17 +46,33 @@ let AGENT_ID = process.argv[2] || 'claude-code';
46
46
  let AGENT_NAME = process.argv[3] || 'Claude Code';
47
47
 
48
48
  // ── Per-tool block/allow output ──
49
- // Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
50
- // Cursor: JSON stdout {"permission": "deny", "user_message": "..."} = BLOCK
51
- // Gemini CLI: JSON stdout {"decision": "deny", "reason": "..."} = BLOCK
49
+ // Response format depends on BOTH the agent AND the hook event type:
50
+ // Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
51
+ // Cursor preToolUse: {"permission": "deny/allow", "user_message": "..."}
52
+ // Cursor before*: {"continue": false/true, "userMessage": "..."}
53
+ // Gemini CLI: {"decision": "deny/allow", "reason": "..."}
54
+ let HOOK_EVENT = ''; // set from stdin hook_event_name
55
+ const CURSOR_BEFORE_EVENTS = new Set([
56
+ 'beforeShellExecution', 'beforeReadFile', 'beforeMCPExecution',
57
+ ]);
58
+
52
59
  function blockTool(reason) {
53
60
  if (AGENT_ID === 'cursor') {
54
- // Cursor: JSON stdout with permission: "deny", exit 0
55
- process.stdout.write(JSON.stringify({
56
- permission: 'deny',
57
- user_message: `[SolonGate] ${reason}`,
58
- agent_message: `BLOCKED by SolonGate security policy: ${reason}`,
59
- }));
61
+ if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
62
+ // Cursor before* events use continue: false
63
+ process.stdout.write(JSON.stringify({
64
+ continue: false,
65
+ userMessage: `[SolonGate] ${reason}`,
66
+ agentMessage: `BLOCKED by SolonGate security policy: ${reason}`,
67
+ }));
68
+ } else {
69
+ // Cursor preToolUse uses permission: deny
70
+ process.stdout.write(JSON.stringify({
71
+ permission: 'deny',
72
+ user_message: `[SolonGate] ${reason}`,
73
+ agent_message: `BLOCKED by SolonGate security policy: ${reason}`,
74
+ }));
75
+ }
60
76
  process.exit(0);
61
77
  } else if (AGENT_ID === 'gemini-cli') {
62
78
  process.stdout.write(JSON.stringify({
@@ -73,7 +89,11 @@ function blockTool(reason) {
73
89
 
74
90
  function allowTool() {
75
91
  if (AGENT_ID === 'cursor') {
76
- process.stdout.write(JSON.stringify({ permission: 'allow' }));
92
+ if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
93
+ process.stdout.write(JSON.stringify({ continue: true }));
94
+ } else {
95
+ process.stdout.write(JSON.stringify({ permission: 'allow' }));
96
+ }
77
97
  } else if (AGENT_ID === 'gemini-cli') {
78
98
  process.stdout.write(JSON.stringify({ decision: 'allow' }));
79
99
  }
@@ -384,14 +404,62 @@ process.stdin.on('end', async () => {
384
404
  AGENT_NAME = 'Gemini CLI';
385
405
  }
386
406
 
387
- // Normalize field names across tools (Claude: tool_name/tool_input, others may use toolName/toolInput/params)
407
+ // Track hook event name for response format selection
408
+ HOOK_EVENT = raw.hook_event_name || '';
409
+
410
+ // Debug: append guard invocation to debug log
411
+ try {
412
+ const { appendFileSync: afs, mkdirSync: mds } = await import('node:fs');
413
+ mds(resolve('.solongate'), { recursive: true });
414
+ const debugLine = JSON.stringify({ ts: new Date().toISOString(), hook: 'guard', argv: process.argv.slice(2), cursor_version: raw.cursor_version || null, tool_name: raw.tool_name || raw.toolName || raw.command, agent_id: AGENT_ID, hook_event_name: HOOK_EVENT }) + '\n';
415
+ afs(resolve('.solongate', '.debug-guard-log'), debugLine);
416
+ } catch {}
417
+
418
+ // Map Cursor-specific hook events to standard tool_name/tool_input format
419
+ // beforeShellExecution: {command, cwd} → tool_name=Shell, tool_input={command}
420
+ // beforeReadFile: {file_path} → tool_name=Read, tool_input={file_path}
421
+ // afterFileEdit: {file_path, new_content} → tool_name=Edit, tool_input={file_path}
422
+ // beforeMCPExecution: {server_name, tool_name, arguments} → tool_name=<mcp_tool>, tool_input=arguments
423
+ let mappedToolName = raw.tool_name || raw.toolName || '';
424
+ let mappedToolInput = raw.tool_input || raw.toolInput || raw.params || {};
425
+
426
+ if (!mappedToolName && HOOK_EVENT) {
427
+ switch (HOOK_EVENT) {
428
+ case 'beforeShellExecution':
429
+ mappedToolName = 'Shell';
430
+ mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
431
+ break;
432
+ case 'afterShellExecution':
433
+ mappedToolName = 'Shell';
434
+ mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
435
+ break;
436
+ case 'beforeReadFile':
437
+ mappedToolName = 'Read';
438
+ mappedToolInput = { file_path: raw.file_path || '' };
439
+ break;
440
+ case 'afterFileEdit':
441
+ mappedToolName = 'Edit';
442
+ mappedToolInput = { file_path: raw.file_path || '', new_content: raw.new_content || '' };
443
+ break;
444
+ case 'beforeMCPExecution':
445
+ mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
446
+ mappedToolInput = raw.arguments || raw.tool_input || {};
447
+ break;
448
+ case 'afterMCPExecution':
449
+ mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
450
+ mappedToolInput = raw.arguments || raw.tool_input || {};
451
+ break;
452
+ }
453
+ }
454
+
455
+ // Normalize field names across tools
388
456
  const data = {
389
457
  ...raw,
390
- tool_name: raw.tool_name || raw.toolName || '',
391
- tool_input: raw.tool_input || raw.toolInput || raw.params || {},
458
+ tool_name: mappedToolName,
459
+ tool_input: mappedToolInput,
392
460
  tool_response: raw.tool_response || raw.toolResponse || {},
393
461
  cwd: raw.cwd || process.cwd(),
394
- session_id: raw.session_id || raw.sessionId || '',
462
+ session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
395
463
  };
396
464
  const args = data.tool_input;
397
465
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.30.2",
3
+ "version": "0.32.0",
4
4
  "description": "AI tool security proxy — protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {