@solongate/proxy 0.31.0 → 0.33.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/create.js +1 -2
- package/dist/index.js +47 -127
- package/dist/init.js +10 -100
- package/dist/lib.js +17 -6
- package/hooks/audit.mjs +13 -11
- package/hooks/guard.mjs +25 -26
- package/hooks/stop.mjs +2 -3
- package/package.json +1 -1
package/dist/create.js
CHANGED
|
@@ -214,7 +214,6 @@ console.log('You need an MCP client to connect:');
|
|
|
214
214
|
console.log('');
|
|
215
215
|
console.log(' Claude Code Open this folder, .mcp.json is auto-detected');
|
|
216
216
|
console.log(' Claude Desktop Add to Settings > MCP Servers');
|
|
217
|
-
console.log(' Cursor Open this folder, .mcp.json is auto-detected');
|
|
218
217
|
console.log(' Windsurf Open this folder, .mcp.json is auto-detected');
|
|
219
218
|
console.log(' Cline VS Code extension, add server in settings');
|
|
220
219
|
console.log(' Zed Add to settings.json under mcp_servers');
|
|
@@ -312,7 +311,7 @@ async function main() {
|
|
|
312
311
|
bEmpty();
|
|
313
312
|
log(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
314
313
|
bEmpty();
|
|
315
|
-
bLine(`${c.yellow}Use with Claude Code /
|
|
314
|
+
bLine(`${c.yellow}Use with Claude Code / MCP client:${c.reset}`);
|
|
316
315
|
bEmpty();
|
|
317
316
|
bLine(` ${c.dim}1.${c.reset} Replace ${c.blue3}sg_live_YOUR_KEY_HERE${c.reset} in .mcp.json`);
|
|
318
317
|
bLine(` ${c.dim}2.${c.reset} Open this folder in your MCP client`);
|
package/dist/index.js
CHANGED
|
@@ -459,9 +459,8 @@ var init_cli_utils = __esm({
|
|
|
459
459
|
|
|
460
460
|
// src/init.ts
|
|
461
461
|
var init_exports = {};
|
|
462
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as
|
|
463
|
-
import { resolve as resolve3, join, dirname as dirname2 } from "path";
|
|
464
|
-
import { homedir } from "os";
|
|
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";
|
|
465
464
|
import { fileURLToPath } from "url";
|
|
466
465
|
import { execFileSync } from "child_process";
|
|
467
466
|
import { createInterface } from "readline";
|
|
@@ -580,24 +579,15 @@ function parseInitArgs(argv) {
|
|
|
580
579
|
case "--all":
|
|
581
580
|
options.all = true;
|
|
582
581
|
break;
|
|
583
|
-
case "--claude":
|
|
584
|
-
options.tools.push("claude");
|
|
585
|
-
break;
|
|
586
|
-
case "--cursor":
|
|
587
|
-
options.tools.push("cursor");
|
|
582
|
+
case "--claude-code":
|
|
583
|
+
options.tools.push("claude-code");
|
|
588
584
|
break;
|
|
589
585
|
case "--gemini":
|
|
590
586
|
options.tools.push("gemini");
|
|
591
587
|
break;
|
|
592
|
-
case "--antigravity":
|
|
593
|
-
options.tools.push("antigravity");
|
|
594
|
-
break;
|
|
595
588
|
case "--openclaw":
|
|
596
589
|
options.tools.push("openclaw");
|
|
597
590
|
break;
|
|
598
|
-
case "--perplexity":
|
|
599
|
-
options.tools.push("perplexity");
|
|
600
|
-
break;
|
|
601
591
|
case "--help":
|
|
602
592
|
case "-h":
|
|
603
593
|
printHelp();
|
|
@@ -621,25 +611,22 @@ OPTIONS
|
|
|
621
611
|
-h, --help Show this help message
|
|
622
612
|
|
|
623
613
|
AI TOOL HOOKS (default: all)
|
|
624
|
-
--claude
|
|
625
|
-
--cursor Install hooks for Cursor
|
|
614
|
+
--claude-code Install hooks for Claude Code
|
|
626
615
|
--gemini Install hooks for Gemini CLI
|
|
627
|
-
--antigravity Install hooks for Antigravity
|
|
628
616
|
--openclaw Install hooks for OpenClaw
|
|
629
|
-
--perplexity Install hooks for Perplexity
|
|
630
617
|
|
|
631
618
|
EXAMPLES
|
|
632
619
|
npx @solongate/proxy init --all # Protect everything, all tools
|
|
633
|
-
npx @solongate/proxy init --all --claude --
|
|
620
|
+
npx @solongate/proxy init --all --claude-code --gemini # Only Claude Code + Gemini hooks
|
|
634
621
|
npx @solongate/proxy init --all --policy policy.json # With custom policy
|
|
635
622
|
`;
|
|
636
623
|
console.log(help);
|
|
637
624
|
}
|
|
638
625
|
function readHookScript(filename) {
|
|
639
|
-
return readFileSync4(
|
|
626
|
+
return readFileSync4(join2(HOOKS_DIR, filename), "utf-8");
|
|
640
627
|
}
|
|
641
628
|
function unlockProtectedDirs() {
|
|
642
|
-
const dirs = [".solongate", ".claude", ".
|
|
629
|
+
const dirs = [".solongate", ".claude", ".gemini", ".openclaw"];
|
|
643
630
|
for (const dir of dirs) {
|
|
644
631
|
const fullDir = resolve3(dir);
|
|
645
632
|
if (!existsSync4(fullDir)) continue;
|
|
@@ -691,12 +678,12 @@ function unlockProtectedDirs() {
|
|
|
691
678
|
function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
692
679
|
unlockProtectedDirs();
|
|
693
680
|
const hooksDir = resolve3(".solongate", "hooks");
|
|
694
|
-
|
|
681
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
695
682
|
const hookFiles = ["guard.mjs", "audit.mjs", "stop.mjs"];
|
|
696
683
|
let hooksUpdated = 0;
|
|
697
684
|
let hooksSkipped = 0;
|
|
698
685
|
for (const filename of hookFiles) {
|
|
699
|
-
const hookPath =
|
|
686
|
+
const hookPath = join2(hooksDir, filename);
|
|
700
687
|
const latest = readHookScript(filename);
|
|
701
688
|
let needsWrite = true;
|
|
702
689
|
if (existsSync4(hookPath)) {
|
|
@@ -716,27 +703,20 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
716
703
|
console.log(` Hook files already up to date`);
|
|
717
704
|
}
|
|
718
705
|
const allClients = [
|
|
719
|
-
{ name: "Claude Code", dir: ".claude", key: "claude", agentId: "claude-code", agentName: "Claude Code" },
|
|
720
|
-
{ name: "Cursor", dir: ".cursor", key: "cursor", agentId: "cursor", agentName: "Cursor" },
|
|
706
|
+
{ name: "Claude Code", dir: ".claude", key: "claude-code", agentId: "claude-code", agentName: "Claude Code" },
|
|
721
707
|
{ name: "Gemini CLI", dir: ".gemini", key: "gemini", agentId: "gemini-cli", agentName: "Gemini CLI" },
|
|
722
|
-
{ name: "
|
|
723
|
-
{ name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" },
|
|
724
|
-
{ name: "Perplexity", dir: ".perplexity", key: "perplexity", agentId: "perplexity", agentName: "Perplexity" }
|
|
708
|
+
{ name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" }
|
|
725
709
|
];
|
|
726
710
|
const clients = selectedTools.length > 0 ? allClients.filter((c3) => selectedTools.includes(c3.key)) : allClients;
|
|
727
711
|
const activatedNames = [];
|
|
728
712
|
const skippedNames = [];
|
|
729
713
|
for (const client of clients) {
|
|
730
714
|
const clientDir = resolve3(client.dir);
|
|
731
|
-
|
|
715
|
+
mkdirSync3(clientDir, { recursive: true });
|
|
732
716
|
const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
|
|
733
717
|
const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
|
|
734
718
|
const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
|
|
735
|
-
if (client.key === "
|
|
736
|
-
const result = installCursorConfig(clientDir, guardCmd, auditCmd, stopCmd, wrappedMcpConfig);
|
|
737
|
-
if (result === "skipped") skippedNames.push(client.name);
|
|
738
|
-
else activatedNames.push(client.name);
|
|
739
|
-
} else if (client.key === "gemini") {
|
|
719
|
+
if (client.key === "gemini") {
|
|
740
720
|
const result = installGeminiConfig(clientDir, guardCmd, auditCmd, stopCmd, wrappedMcpConfig);
|
|
741
721
|
if (result === "skipped") skippedNames.push(client.name);
|
|
742
722
|
else activatedNames.push(client.name);
|
|
@@ -763,7 +743,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
763
743
|
}
|
|
764
744
|
}
|
|
765
745
|
function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, stopCmd) {
|
|
766
|
-
const settingsPath =
|
|
746
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
767
747
|
const hookSettings = {
|
|
768
748
|
PreToolUse: [
|
|
769
749
|
{ matcher: "", hooks: [{ type: "command", command: guardCmd }] }
|
|
@@ -788,74 +768,8 @@ function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, st
|
|
|
788
768
|
console.log(` ${existingStr ? "Updated" : "Created"} ${settingsPath}`);
|
|
789
769
|
return "installed";
|
|
790
770
|
}
|
|
791
|
-
function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
792
|
-
let changed = false;
|
|
793
|
-
const hookConfig = {
|
|
794
|
-
preToolUse: [
|
|
795
|
-
{ matcher: "", command: guardCmd, failClosed: true }
|
|
796
|
-
],
|
|
797
|
-
postToolUse: [
|
|
798
|
-
{ matcher: "", command: auditCmd }
|
|
799
|
-
]
|
|
800
|
-
};
|
|
801
|
-
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
|
802
|
-
const userCursorDir = join(homedir(), ".cursor");
|
|
803
|
-
if (existsSync4(userCursorDir)) {
|
|
804
|
-
const userHooksPath = join(userCursorDir, "hooks.json");
|
|
805
|
-
const existingUserHooks = existsSync4(userHooksPath) ? readFileSync4(userHooksPath, "utf-8") : "";
|
|
806
|
-
if (hooksContent !== existingUserHooks) {
|
|
807
|
-
writeFileSync2(userHooksPath, hooksContent);
|
|
808
|
-
console.log(` ${existingUserHooks ? "Updated" : "Created"} ${userHooksPath} (user-level)`);
|
|
809
|
-
changed = true;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
const hooksPath = join(clientDir, "hooks.json");
|
|
813
|
-
const existingHooks = existsSync4(hooksPath) ? readFileSync4(hooksPath, "utf-8") : "";
|
|
814
|
-
if (hooksContent !== existingHooks) {
|
|
815
|
-
writeFileSync2(hooksPath, hooksContent);
|
|
816
|
-
console.log(` ${existingHooks ? "Updated" : "Created"} ${hooksPath}`);
|
|
817
|
-
changed = true;
|
|
818
|
-
}
|
|
819
|
-
if (wrappedMcpConfig) {
|
|
820
|
-
const cursorMcpConfig = { mcpServers: {} };
|
|
821
|
-
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
822
|
-
const args = [...server.args ?? []];
|
|
823
|
-
const agentIdx = args.indexOf("--agent-name");
|
|
824
|
-
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
825
|
-
args[agentIdx + 1] = "cursor";
|
|
826
|
-
} else {
|
|
827
|
-
const sepIdx = args.indexOf("--");
|
|
828
|
-
if (sepIdx >= 0) {
|
|
829
|
-
args.splice(sepIdx, 0, "--agent-name", "cursor");
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
cursorMcpConfig.mcpServers[name] = { ...server, args };
|
|
833
|
-
}
|
|
834
|
-
const mcpPath = join(clientDir, "mcp.json");
|
|
835
|
-
const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
|
|
836
|
-
const existingMcp = existsSync4(mcpPath) ? readFileSync4(mcpPath, "utf-8") : "";
|
|
837
|
-
if (mcpStr !== existingMcp) {
|
|
838
|
-
writeFileSync2(mcpPath, mcpStr);
|
|
839
|
-
console.log(` ${existingMcp ? "Updated" : "Created"} ${mcpPath}`);
|
|
840
|
-
changed = true;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
const staleSettings = join(clientDir, "settings.json");
|
|
844
|
-
if (existsSync4(staleSettings)) {
|
|
845
|
-
try {
|
|
846
|
-
const content = JSON.parse(readFileSync4(staleSettings, "utf-8"));
|
|
847
|
-
if (content.hooks?.PreToolUse || content.hooks?.PostToolUse) {
|
|
848
|
-
writeFileSync2(staleSettings, JSON.stringify({}, null, 2) + "\n");
|
|
849
|
-
console.log(` Cleaned stale ${staleSettings} (had wrong hook format)`);
|
|
850
|
-
changed = true;
|
|
851
|
-
}
|
|
852
|
-
} catch {
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return changed ? "installed" : "skipped";
|
|
856
|
-
}
|
|
857
771
|
function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
858
|
-
const settingsPath =
|
|
772
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
859
773
|
let existing = {};
|
|
860
774
|
try {
|
|
861
775
|
existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
@@ -947,11 +861,8 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
947
861
|
".mcp.json",
|
|
948
862
|
".solongate/**",
|
|
949
863
|
".claude/**",
|
|
950
|
-
".cursor/**",
|
|
951
864
|
".gemini/**",
|
|
952
|
-
".
|
|
953
|
-
".openclaw/**",
|
|
954
|
-
".perplexity/**"
|
|
865
|
+
".openclaw/**"
|
|
955
866
|
];
|
|
956
867
|
if (existsSync4(gitignorePath)) {
|
|
957
868
|
let gitignore = readFileSync4(gitignorePath, "utf-8");
|
|
@@ -1188,8 +1099,7 @@ async function main() {
|
|
|
1188
1099
|
console.log(" \u2502 \u2502");
|
|
1189
1100
|
console.log(" \u2502 MCP servers \u2192 Protected via proxy \u2502");
|
|
1190
1101
|
console.log(" \u2502 AI tools \u2192 Guarded via hooks \u2502");
|
|
1191
|
-
console.log(" \u2502 Claude
|
|
1192
|
-
console.log(" \u2502 OpenClaw, Perplexity \u2502");
|
|
1102
|
+
console.log(" \u2502 Claude Code, Gemini, OpenClaw \u2502");
|
|
1193
1103
|
console.log(" \u2502 API key \u2192 Set in .env \u2502");
|
|
1194
1104
|
console.log(" \u2502 \u2502");
|
|
1195
1105
|
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
|
@@ -1209,7 +1119,7 @@ var init_init = __esm({
|
|
|
1209
1119
|
"mcp.json",
|
|
1210
1120
|
".claude/mcp.json"
|
|
1211
1121
|
];
|
|
1212
|
-
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [
|
|
1122
|
+
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")];
|
|
1213
1123
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1214
1124
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1215
1125
|
spinnerInterval = null;
|
|
@@ -1562,8 +1472,8 @@ var init_inject = __esm({
|
|
|
1562
1472
|
|
|
1563
1473
|
// src/create.ts
|
|
1564
1474
|
var create_exports = {};
|
|
1565
|
-
import { mkdirSync as
|
|
1566
|
-
import { resolve as resolve5, join as
|
|
1475
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
1476
|
+
import { resolve as resolve5, join as join3 } from "path";
|
|
1567
1477
|
import { execSync as execSync2 } from "child_process";
|
|
1568
1478
|
function withSpinner(message, fn) {
|
|
1569
1479
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -1643,7 +1553,7 @@ EXAMPLES
|
|
|
1643
1553
|
}
|
|
1644
1554
|
function createProject(dir, name, _policy) {
|
|
1645
1555
|
writeFileSync4(
|
|
1646
|
-
|
|
1556
|
+
join3(dir, "package.json"),
|
|
1647
1557
|
JSON.stringify(
|
|
1648
1558
|
{
|
|
1649
1559
|
name,
|
|
@@ -1673,7 +1583,7 @@ function createProject(dir, name, _policy) {
|
|
|
1673
1583
|
) + "\n"
|
|
1674
1584
|
);
|
|
1675
1585
|
writeFileSync4(
|
|
1676
|
-
|
|
1586
|
+
join3(dir, "tsconfig.json"),
|
|
1677
1587
|
JSON.stringify(
|
|
1678
1588
|
{
|
|
1679
1589
|
compilerOptions: {
|
|
@@ -1693,9 +1603,9 @@ function createProject(dir, name, _policy) {
|
|
|
1693
1603
|
2
|
|
1694
1604
|
) + "\n"
|
|
1695
1605
|
);
|
|
1696
|
-
|
|
1606
|
+
mkdirSync4(join3(dir, "src"), { recursive: true });
|
|
1697
1607
|
writeFileSync4(
|
|
1698
|
-
|
|
1608
|
+
join3(dir, "src", "index.ts"),
|
|
1699
1609
|
`#!/usr/bin/env node
|
|
1700
1610
|
|
|
1701
1611
|
console.log = (...args: unknown[]) => {
|
|
@@ -1731,7 +1641,6 @@ console.log('You need an MCP client to connect:');
|
|
|
1731
1641
|
console.log('');
|
|
1732
1642
|
console.log(' Claude Code Open this folder, .mcp.json is auto-detected');
|
|
1733
1643
|
console.log(' Claude Desktop Add to Settings > MCP Servers');
|
|
1734
|
-
console.log(' Cursor Open this folder, .mcp.json is auto-detected');
|
|
1735
1644
|
console.log(' Windsurf Open this folder, .mcp.json is auto-detected');
|
|
1736
1645
|
console.log(' Cline VS Code extension, add server in settings');
|
|
1737
1646
|
console.log(' Zed Add to settings.json under mcp_servers');
|
|
@@ -1740,7 +1649,7 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1740
1649
|
`
|
|
1741
1650
|
);
|
|
1742
1651
|
writeFileSync4(
|
|
1743
|
-
|
|
1652
|
+
join3(dir, ".mcp.json"),
|
|
1744
1653
|
JSON.stringify(
|
|
1745
1654
|
{
|
|
1746
1655
|
mcpServers: {
|
|
@@ -1758,12 +1667,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1758
1667
|
) + "\n"
|
|
1759
1668
|
);
|
|
1760
1669
|
writeFileSync4(
|
|
1761
|
-
|
|
1670
|
+
join3(dir, ".env"),
|
|
1762
1671
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
1763
1672
|
`
|
|
1764
1673
|
);
|
|
1765
1674
|
writeFileSync4(
|
|
1766
|
-
|
|
1675
|
+
join3(dir, ".gitignore"),
|
|
1767
1676
|
`node_modules/
|
|
1768
1677
|
dist/
|
|
1769
1678
|
*.solongate-backup
|
|
@@ -1782,7 +1691,7 @@ async function main3() {
|
|
|
1782
1691
|
process.exit(1);
|
|
1783
1692
|
}
|
|
1784
1693
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
1785
|
-
|
|
1694
|
+
mkdirSync4(dir, { recursive: true });
|
|
1786
1695
|
createProject(dir, opts.name, opts.policy);
|
|
1787
1696
|
});
|
|
1788
1697
|
if (!opts.noInstall) {
|
|
@@ -1829,7 +1738,7 @@ async function main3() {
|
|
|
1829
1738
|
bEmpty();
|
|
1830
1739
|
log3(` ${c.dim}\u251C${hr}\u2524${c.reset}`);
|
|
1831
1740
|
bEmpty();
|
|
1832
|
-
bLine(`${c.yellow}Use with Claude Code /
|
|
1741
|
+
bLine(`${c.yellow}Use with Claude Code / MCP client:${c.reset}`);
|
|
1833
1742
|
bEmpty();
|
|
1834
1743
|
bLine(` ${c.dim}1.${c.reset} Replace ${c.blue3}sg_live_YOUR_KEY_HERE${c.reset} in .mcp.json`);
|
|
1835
1744
|
bLine(` ${c.dim}2.${c.reset} Open this folder in your MCP client`);
|
|
@@ -2193,8 +2102,8 @@ import {
|
|
|
2193
2102
|
ListResourceTemplatesRequestSchema
|
|
2194
2103
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2195
2104
|
import { createServer as createHttpServer } from "http";
|
|
2196
|
-
import { resolve as resolve2 } from "path";
|
|
2197
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2105
|
+
import { resolve as resolve2, join } from "path";
|
|
2106
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
2198
2107
|
|
|
2199
2108
|
// ../core/dist/index.js
|
|
2200
2109
|
import { z } from "zod";
|
|
@@ -6044,12 +5953,9 @@ var SolonGateProxy = class {
|
|
|
6044
5953
|
/** Normalize well-known MCP client names to display-friendly agent identities */
|
|
6045
5954
|
normalizeAgentName(raw) {
|
|
6046
5955
|
const lower = raw.toLowerCase();
|
|
6047
|
-
if (lower.includes("cursor")) return { id: "cursor", name: "Cursor" };
|
|
6048
5956
|
if (lower.includes("claude-code") || lower === "claude code") return { id: "claude-code", name: "Claude Code" };
|
|
6049
5957
|
if (lower.includes("claude")) return { id: "claude-desktop", name: "Claude Desktop" };
|
|
6050
5958
|
if (lower.includes("gemini")) return { id: "gemini-cli", name: "Gemini CLI" };
|
|
6051
|
-
if (lower.includes("antigravity")) return { id: "antigravity", name: "Antigravity" };
|
|
6052
|
-
if (lower.includes("perplexity")) return { id: "perplexity", name: "Perplexity" };
|
|
6053
5959
|
return { id: raw.toLowerCase().replace(/\s+/g, "-"), name: raw };
|
|
6054
5960
|
}
|
|
6055
5961
|
/** Extract sub-agent identity from MCP _meta field */
|
|
@@ -6249,6 +6155,20 @@ var SolonGateProxy = class {
|
|
|
6249
6155
|
if (this.server) {
|
|
6250
6156
|
const clientVersion = this.server.getClientVersion();
|
|
6251
6157
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
6158
|
+
try {
|
|
6159
|
+
const debugInfo = {
|
|
6160
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6161
|
+
clientInfo: clientVersion,
|
|
6162
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
6163
|
+
resolvedAgentId: this.agentId,
|
|
6164
|
+
resolvedAgentName: this.agentName,
|
|
6165
|
+
pid: process.pid
|
|
6166
|
+
};
|
|
6167
|
+
const debugDir = resolve2(".solongate");
|
|
6168
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
6169
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
6170
|
+
} catch {
|
|
6171
|
+
}
|
|
6252
6172
|
if (clientVersion?.name && !this.config.agentName) {
|
|
6253
6173
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
6254
6174
|
this.agentId = normalized.id;
|
|
@@ -6678,7 +6598,7 @@ ${msg.content.text}`;
|
|
|
6678
6598
|
/**
|
|
6679
6599
|
* Start serving downstream.
|
|
6680
6600
|
* If --port is set, serves via StreamableHTTP on that port.
|
|
6681
|
-
* Otherwise, serves on stdio (default for Claude Code /
|
|
6601
|
+
* Otherwise, serves on stdio (default for Claude Code / etc).
|
|
6682
6602
|
*/
|
|
6683
6603
|
async serve() {
|
|
6684
6604
|
if (!this.server) throw new Error("Server not created");
|
package/dist/init.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// src/init.ts
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
5
5
|
import { resolve, join, dirname } from "path";
|
|
6
|
-
import { homedir } from "os";
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
8
7
|
import { execFileSync } from "child_process";
|
|
9
8
|
import { createInterface } from "readline";
|
|
@@ -164,24 +163,15 @@ function parseInitArgs(argv) {
|
|
|
164
163
|
case "--all":
|
|
165
164
|
options.all = true;
|
|
166
165
|
break;
|
|
167
|
-
case "--claude":
|
|
168
|
-
options.tools.push("claude");
|
|
169
|
-
break;
|
|
170
|
-
case "--cursor":
|
|
171
|
-
options.tools.push("cursor");
|
|
166
|
+
case "--claude-code":
|
|
167
|
+
options.tools.push("claude-code");
|
|
172
168
|
break;
|
|
173
169
|
case "--gemini":
|
|
174
170
|
options.tools.push("gemini");
|
|
175
171
|
break;
|
|
176
|
-
case "--antigravity":
|
|
177
|
-
options.tools.push("antigravity");
|
|
178
|
-
break;
|
|
179
172
|
case "--openclaw":
|
|
180
173
|
options.tools.push("openclaw");
|
|
181
174
|
break;
|
|
182
|
-
case "--perplexity":
|
|
183
|
-
options.tools.push("perplexity");
|
|
184
|
-
break;
|
|
185
175
|
case "--help":
|
|
186
176
|
case "-h":
|
|
187
177
|
printHelp();
|
|
@@ -205,16 +195,13 @@ OPTIONS
|
|
|
205
195
|
-h, --help Show this help message
|
|
206
196
|
|
|
207
197
|
AI TOOL HOOKS (default: all)
|
|
208
|
-
--claude
|
|
209
|
-
--cursor Install hooks for Cursor
|
|
198
|
+
--claude-code Install hooks for Claude Code
|
|
210
199
|
--gemini Install hooks for Gemini CLI
|
|
211
|
-
--antigravity Install hooks for Antigravity
|
|
212
200
|
--openclaw Install hooks for OpenClaw
|
|
213
|
-
--perplexity Install hooks for Perplexity
|
|
214
201
|
|
|
215
202
|
EXAMPLES
|
|
216
203
|
npx @solongate/proxy init --all # Protect everything, all tools
|
|
217
|
-
npx @solongate/proxy init --all --claude --
|
|
204
|
+
npx @solongate/proxy init --all --claude-code --gemini # Only Claude Code + Gemini hooks
|
|
218
205
|
npx @solongate/proxy init --all --policy policy.json # With custom policy
|
|
219
206
|
`;
|
|
220
207
|
console.log(help);
|
|
@@ -225,7 +212,7 @@ function readHookScript(filename) {
|
|
|
225
212
|
return readFileSync(join(HOOKS_DIR, filename), "utf-8");
|
|
226
213
|
}
|
|
227
214
|
function unlockProtectedDirs() {
|
|
228
|
-
const dirs = [".solongate", ".claude", ".
|
|
215
|
+
const dirs = [".solongate", ".claude", ".gemini", ".openclaw"];
|
|
229
216
|
for (const dir of dirs) {
|
|
230
217
|
const fullDir = resolve(dir);
|
|
231
218
|
if (!existsSync(fullDir)) continue;
|
|
@@ -302,12 +289,9 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
302
289
|
console.log(` Hook files already up to date`);
|
|
303
290
|
}
|
|
304
291
|
const allClients = [
|
|
305
|
-
{ name: "Claude Code", dir: ".claude", key: "claude", agentId: "claude-code", agentName: "Claude Code" },
|
|
306
|
-
{ name: "Cursor", dir: ".cursor", key: "cursor", agentId: "cursor", agentName: "Cursor" },
|
|
292
|
+
{ name: "Claude Code", dir: ".claude", key: "claude-code", agentId: "claude-code", agentName: "Claude Code" },
|
|
307
293
|
{ name: "Gemini CLI", dir: ".gemini", key: "gemini", agentId: "gemini-cli", agentName: "Gemini CLI" },
|
|
308
|
-
{ name: "
|
|
309
|
-
{ name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" },
|
|
310
|
-
{ name: "Perplexity", dir: ".perplexity", key: "perplexity", agentId: "perplexity", agentName: "Perplexity" }
|
|
294
|
+
{ name: "OpenClaw", dir: ".openclaw", key: "openclaw", agentId: "openclaw", agentName: "OpenClaw" }
|
|
311
295
|
];
|
|
312
296
|
const clients = selectedTools.length > 0 ? allClients.filter((c2) => selectedTools.includes(c2.key)) : allClients;
|
|
313
297
|
const activatedNames = [];
|
|
@@ -318,11 +302,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
318
302
|
const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
|
|
319
303
|
const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
|
|
320
304
|
const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
|
|
321
|
-
if (client.key === "
|
|
322
|
-
const result = installCursorConfig(clientDir, guardCmd, auditCmd, stopCmd, wrappedMcpConfig);
|
|
323
|
-
if (result === "skipped") skippedNames.push(client.name);
|
|
324
|
-
else activatedNames.push(client.name);
|
|
325
|
-
} else if (client.key === "gemini") {
|
|
305
|
+
if (client.key === "gemini") {
|
|
326
306
|
const result = installGeminiConfig(clientDir, guardCmd, auditCmd, stopCmd, wrappedMcpConfig);
|
|
327
307
|
if (result === "skipped") skippedNames.push(client.name);
|
|
328
308
|
else activatedNames.push(client.name);
|
|
@@ -374,72 +354,6 @@ function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, st
|
|
|
374
354
|
console.log(` ${existingStr ? "Updated" : "Created"} ${settingsPath}`);
|
|
375
355
|
return "installed";
|
|
376
356
|
}
|
|
377
|
-
function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
378
|
-
let changed = false;
|
|
379
|
-
const hookConfig = {
|
|
380
|
-
preToolUse: [
|
|
381
|
-
{ matcher: "", command: guardCmd, failClosed: true }
|
|
382
|
-
],
|
|
383
|
-
postToolUse: [
|
|
384
|
-
{ matcher: "", command: auditCmd }
|
|
385
|
-
]
|
|
386
|
-
};
|
|
387
|
-
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
|
388
|
-
const userCursorDir = join(homedir(), ".cursor");
|
|
389
|
-
if (existsSync(userCursorDir)) {
|
|
390
|
-
const userHooksPath = join(userCursorDir, "hooks.json");
|
|
391
|
-
const existingUserHooks = existsSync(userHooksPath) ? readFileSync(userHooksPath, "utf-8") : "";
|
|
392
|
-
if (hooksContent !== existingUserHooks) {
|
|
393
|
-
writeFileSync(userHooksPath, hooksContent);
|
|
394
|
-
console.log(` ${existingUserHooks ? "Updated" : "Created"} ${userHooksPath} (user-level)`);
|
|
395
|
-
changed = true;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const hooksPath = join(clientDir, "hooks.json");
|
|
399
|
-
const existingHooks = existsSync(hooksPath) ? readFileSync(hooksPath, "utf-8") : "";
|
|
400
|
-
if (hooksContent !== existingHooks) {
|
|
401
|
-
writeFileSync(hooksPath, hooksContent);
|
|
402
|
-
console.log(` ${existingHooks ? "Updated" : "Created"} ${hooksPath}`);
|
|
403
|
-
changed = true;
|
|
404
|
-
}
|
|
405
|
-
if (wrappedMcpConfig) {
|
|
406
|
-
const cursorMcpConfig = { mcpServers: {} };
|
|
407
|
-
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
408
|
-
const args = [...server.args ?? []];
|
|
409
|
-
const agentIdx = args.indexOf("--agent-name");
|
|
410
|
-
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
411
|
-
args[agentIdx + 1] = "cursor";
|
|
412
|
-
} else {
|
|
413
|
-
const sepIdx = args.indexOf("--");
|
|
414
|
-
if (sepIdx >= 0) {
|
|
415
|
-
args.splice(sepIdx, 0, "--agent-name", "cursor");
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
cursorMcpConfig.mcpServers[name] = { ...server, args };
|
|
419
|
-
}
|
|
420
|
-
const mcpPath = join(clientDir, "mcp.json");
|
|
421
|
-
const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
|
|
422
|
-
const existingMcp = existsSync(mcpPath) ? readFileSync(mcpPath, "utf-8") : "";
|
|
423
|
-
if (mcpStr !== existingMcp) {
|
|
424
|
-
writeFileSync(mcpPath, mcpStr);
|
|
425
|
-
console.log(` ${existingMcp ? "Updated" : "Created"} ${mcpPath}`);
|
|
426
|
-
changed = true;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
const staleSettings = join(clientDir, "settings.json");
|
|
430
|
-
if (existsSync(staleSettings)) {
|
|
431
|
-
try {
|
|
432
|
-
const content = JSON.parse(readFileSync(staleSettings, "utf-8"));
|
|
433
|
-
if (content.hooks?.PreToolUse || content.hooks?.PostToolUse) {
|
|
434
|
-
writeFileSync(staleSettings, JSON.stringify({}, null, 2) + "\n");
|
|
435
|
-
console.log(` Cleaned stale ${staleSettings} (had wrong hook format)`);
|
|
436
|
-
changed = true;
|
|
437
|
-
}
|
|
438
|
-
} catch {
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return changed ? "installed" : "skipped";
|
|
442
|
-
}
|
|
443
357
|
function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
444
358
|
const settingsPath = join(clientDir, "settings.json");
|
|
445
359
|
let existing = {};
|
|
@@ -533,11 +447,8 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
533
447
|
".mcp.json",
|
|
534
448
|
".solongate/**",
|
|
535
449
|
".claude/**",
|
|
536
|
-
".cursor/**",
|
|
537
450
|
".gemini/**",
|
|
538
|
-
".
|
|
539
|
-
".openclaw/**",
|
|
540
|
-
".perplexity/**"
|
|
451
|
+
".openclaw/**"
|
|
541
452
|
];
|
|
542
453
|
if (existsSync(gitignorePath)) {
|
|
543
454
|
let gitignore = readFileSync(gitignorePath, "utf-8");
|
|
@@ -774,8 +685,7 @@ async function main() {
|
|
|
774
685
|
console.log(" \u2502 \u2502");
|
|
775
686
|
console.log(" \u2502 MCP servers \u2192 Protected via proxy \u2502");
|
|
776
687
|
console.log(" \u2502 AI tools \u2192 Guarded via hooks \u2502");
|
|
777
|
-
console.log(" \u2502 Claude
|
|
778
|
-
console.log(" \u2502 OpenClaw, Perplexity \u2502");
|
|
688
|
+
console.log(" \u2502 Claude Code, Gemini, OpenClaw \u2502");
|
|
779
689
|
console.log(" \u2502 API key \u2192 Set in .env \u2502");
|
|
780
690
|
console.log(" \u2502 \u2502");
|
|
781
691
|
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
package/dist/lib.js
CHANGED
|
@@ -3612,8 +3612,8 @@ import {
|
|
|
3612
3612
|
ListResourceTemplatesRequestSchema
|
|
3613
3613
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
3614
3614
|
import { createServer as createHttpServer } from "http";
|
|
3615
|
-
import { resolve as resolve2 } from "path";
|
|
3616
|
-
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";
|
|
3617
3617
|
|
|
3618
3618
|
// src/config.ts
|
|
3619
3619
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -4255,12 +4255,9 @@ var SolonGateProxy = class {
|
|
|
4255
4255
|
/** Normalize well-known MCP client names to display-friendly agent identities */
|
|
4256
4256
|
normalizeAgentName(raw) {
|
|
4257
4257
|
const lower = raw.toLowerCase();
|
|
4258
|
-
if (lower.includes("cursor")) return { id: "cursor", name: "Cursor" };
|
|
4259
4258
|
if (lower.includes("claude-code") || lower === "claude code") return { id: "claude-code", name: "Claude Code" };
|
|
4260
4259
|
if (lower.includes("claude")) return { id: "claude-desktop", name: "Claude Desktop" };
|
|
4261
4260
|
if (lower.includes("gemini")) return { id: "gemini-cli", name: "Gemini CLI" };
|
|
4262
|
-
if (lower.includes("antigravity")) return { id: "antigravity", name: "Antigravity" };
|
|
4263
|
-
if (lower.includes("perplexity")) return { id: "perplexity", name: "Perplexity" };
|
|
4264
4261
|
return { id: raw.toLowerCase().replace(/\s+/g, "-"), name: raw };
|
|
4265
4262
|
}
|
|
4266
4263
|
/** Extract sub-agent identity from MCP _meta field */
|
|
@@ -4460,6 +4457,20 @@ var SolonGateProxy = class {
|
|
|
4460
4457
|
if (this.server) {
|
|
4461
4458
|
const clientVersion = this.server.getClientVersion();
|
|
4462
4459
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
4460
|
+
try {
|
|
4461
|
+
const debugInfo = {
|
|
4462
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4463
|
+
clientInfo: clientVersion,
|
|
4464
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
4465
|
+
resolvedAgentId: this.agentId,
|
|
4466
|
+
resolvedAgentName: this.agentName,
|
|
4467
|
+
pid: process.pid
|
|
4468
|
+
};
|
|
4469
|
+
const debugDir = resolve2(".solongate");
|
|
4470
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
4471
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
4472
|
+
} catch {
|
|
4473
|
+
}
|
|
4463
4474
|
if (clientVersion?.name && !this.config.agentName) {
|
|
4464
4475
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
4465
4476
|
this.agentId = normalized.id;
|
|
@@ -4889,7 +4900,7 @@ ${msg.content.text}`;
|
|
|
4889
4900
|
/**
|
|
4890
4901
|
* Start serving downstream.
|
|
4891
4902
|
* If --port is set, serves via StreamableHTTP on that port.
|
|
4892
|
-
* Otherwise, serves on stdio (default for Claude Code /
|
|
4903
|
+
* Otherwise, serves on stdio (default for Claude Code / etc).
|
|
4893
4904
|
*/
|
|
4894
4905
|
async serve() {
|
|
4895
4906
|
if (!this.server) throw new Error("Server not created");
|
package/hooks/audit.mjs
CHANGED
|
@@ -26,7 +26,7 @@ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
|
|
|
26
26
|
const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
27
27
|
|
|
28
28
|
// Agent identity from CLI args: node audit.mjs <agent_id> <agent_name>
|
|
29
|
-
// Can be overridden at runtime when stdin contains
|
|
29
|
+
// Can be overridden at runtime when stdin contains gemini_version
|
|
30
30
|
let AGENT_ID = process.argv[2] || 'claude-code';
|
|
31
31
|
let AGENT_NAME = process.argv[3] || 'Claude Code';
|
|
32
32
|
|
|
@@ -38,21 +38,22 @@ process.stdin.on('end', async () => {
|
|
|
38
38
|
try {
|
|
39
39
|
const data = JSON.parse(input);
|
|
40
40
|
|
|
41
|
-
// Debug:
|
|
42
|
-
try {
|
|
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), tool_name: data.tool_name || data.toolName, agent_id: AGENT_ID }) + '\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
|
-
if (data.
|
|
46
|
-
AGENT_ID = 'cursor';
|
|
47
|
-
AGENT_NAME = 'Cursor';
|
|
48
|
-
} else if (data.gemini_version) {
|
|
49
|
+
if (data.gemini_version) {
|
|
49
50
|
AGENT_ID = 'gemini-cli';
|
|
50
51
|
AGENT_NAME = 'Gemini CLI';
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
let toolName = data.tool_name || data.toolName || '';
|
|
55
|
+
let toolInput = data.tool_input || data.toolInput || data.params || {};
|
|
56
|
+
if (!toolName) toolName = 'unknown';
|
|
56
57
|
|
|
57
58
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
58
59
|
process.exit(0);
|
|
@@ -71,14 +72,15 @@ process.stdin.on('end', async () => {
|
|
|
71
72
|
}
|
|
72
73
|
} catch {}
|
|
73
74
|
|
|
74
|
-
// Cursor uses result_json, Claude uses tool_response
|
|
75
75
|
const toolResponse = data.tool_response || data.toolResponse || {};
|
|
76
|
+
const toolOutput = data.tool_output || data.toolOutput || '';
|
|
76
77
|
const resultJson = data.result_json ? (typeof data.result_json === 'string' ? data.result_json : JSON.stringify(data.result_json)) : '';
|
|
77
78
|
|
|
78
79
|
const hasError = guardDenied ||
|
|
79
80
|
toolResponse.error ||
|
|
80
81
|
toolResponse.exitCode > 0 ||
|
|
81
82
|
toolResponse.isError ||
|
|
83
|
+
(toolOutput && typeof toolOutput === 'string' && toolOutput.includes('"error"')) ||
|
|
82
84
|
(resultJson && resultJson.includes('"error"'));
|
|
83
85
|
|
|
84
86
|
const argsSummary = {};
|
package/hooks/guard.mjs
CHANGED
|
@@ -41,40 +41,31 @@ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
|
|
|
41
41
|
const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
42
42
|
|
|
43
43
|
// Agent identity from CLI args: node guard.mjs <agent_id> <agent_name>
|
|
44
|
-
// Can be overridden at runtime when stdin contains
|
|
44
|
+
// Can be overridden at runtime when stdin contains gemini_version
|
|
45
45
|
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
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// Gemini CLI:
|
|
49
|
+
// Response format depends on the agent:
|
|
50
|
+
// Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
|
|
51
|
+
// Gemini CLI: {"decision": "deny/allow", "reason": "..."}
|
|
52
|
+
|
|
52
53
|
function blockTool(reason) {
|
|
53
|
-
if (AGENT_ID === '
|
|
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
|
-
}));
|
|
60
|
-
process.exit(0);
|
|
61
|
-
} else if (AGENT_ID === 'gemini-cli') {
|
|
54
|
+
if (AGENT_ID === 'gemini-cli') {
|
|
62
55
|
process.stdout.write(JSON.stringify({
|
|
63
56
|
decision: 'deny',
|
|
64
57
|
reason: `[SolonGate] ${reason}`,
|
|
65
58
|
}));
|
|
66
59
|
process.exit(0);
|
|
67
60
|
} else {
|
|
68
|
-
// Claude Code
|
|
61
|
+
// Claude Code — exit code 2
|
|
69
62
|
process.stderr.write(reason);
|
|
70
63
|
process.exit(2);
|
|
71
64
|
}
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
function allowTool() {
|
|
75
|
-
if (AGENT_ID === '
|
|
76
|
-
process.stdout.write(JSON.stringify({ permission: 'allow' }));
|
|
77
|
-
} else if (AGENT_ID === 'gemini-cli') {
|
|
68
|
+
if (AGENT_ID === 'gemini-cli') {
|
|
78
69
|
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
79
70
|
}
|
|
80
71
|
process.exit(0);
|
|
@@ -376,22 +367,30 @@ process.stdin.on('end', async () => {
|
|
|
376
367
|
const raw = JSON.parse(input);
|
|
377
368
|
|
|
378
369
|
// Auto-detect agent from stdin data (overrides CLI args if detected)
|
|
379
|
-
if (raw.
|
|
380
|
-
AGENT_ID = 'cursor';
|
|
381
|
-
AGENT_NAME = 'Cursor';
|
|
382
|
-
} else if (raw.gemini_version) {
|
|
370
|
+
if (raw.gemini_version) {
|
|
383
371
|
AGENT_ID = 'gemini-cli';
|
|
384
372
|
AGENT_NAME = 'Gemini CLI';
|
|
385
373
|
}
|
|
386
374
|
|
|
387
|
-
//
|
|
375
|
+
// Debug: append guard invocation to debug log
|
|
376
|
+
try {
|
|
377
|
+
const { appendFileSync: afs, mkdirSync: mds } = await import('node:fs');
|
|
378
|
+
mds(resolve('.solongate'), { recursive: true });
|
|
379
|
+
const debugLine = JSON.stringify({ ts: new Date().toISOString(), hook: 'guard', argv: process.argv.slice(2), tool_name: raw.tool_name || raw.toolName || raw.command, agent_id: AGENT_ID }) + '\n';
|
|
380
|
+
afs(resolve('.solongate', '.debug-guard-log'), debugLine);
|
|
381
|
+
} catch {}
|
|
382
|
+
|
|
383
|
+
let mappedToolName = raw.tool_name || raw.toolName || '';
|
|
384
|
+
let mappedToolInput = raw.tool_input || raw.toolInput || raw.params || {};
|
|
385
|
+
|
|
386
|
+
// Normalize field names across tools
|
|
388
387
|
const data = {
|
|
389
388
|
...raw,
|
|
390
|
-
tool_name:
|
|
391
|
-
tool_input:
|
|
389
|
+
tool_name: mappedToolName,
|
|
390
|
+
tool_input: mappedToolInput,
|
|
392
391
|
tool_response: raw.tool_response || raw.toolResponse || {},
|
|
393
392
|
cwd: raw.cwd || process.cwd(),
|
|
394
|
-
session_id: raw.session_id || raw.sessionId || '',
|
|
393
|
+
session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
|
|
395
394
|
};
|
|
396
395
|
const args = data.tool_input;
|
|
397
396
|
|
|
@@ -399,7 +398,7 @@ process.stdin.on('end', async () => {
|
|
|
399
398
|
// Hardcoded, no bypass possible — runs before policy/PI config
|
|
400
399
|
// Fully protected: block ALL access (read, write, delete, move)
|
|
401
400
|
const protectedPaths = [
|
|
402
|
-
'.solongate', '.claude', '.
|
|
401
|
+
'.solongate', '.claude', '.gemini', '.openclaw',
|
|
403
402
|
'policy.json', '.mcp.json',
|
|
404
403
|
];
|
|
405
404
|
// Write-protected: block write/delete/modify, allow read (cat, grep, head, etc.)
|
package/hooks/stop.mjs
CHANGED
|
@@ -27,7 +27,7 @@ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
|
|
|
27
27
|
const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
28
28
|
|
|
29
29
|
// Agent identity from CLI args: node stop.mjs <agent_id> <agent_name>
|
|
30
|
-
// Can be overridden at runtime when stdin contains
|
|
30
|
+
// Can be overridden at runtime when stdin contains gemini_version
|
|
31
31
|
let AGENT_ID = process.argv[2] || 'claude-code';
|
|
32
32
|
let AGENT_NAME = process.argv[3] || 'Claude Code';
|
|
33
33
|
|
|
@@ -46,8 +46,7 @@ process.stdin.on('end', async () => {
|
|
|
46
46
|
// Auto-detect agent from stdin data
|
|
47
47
|
try {
|
|
48
48
|
const raw = JSON.parse(input);
|
|
49
|
-
if (raw.
|
|
50
|
-
else if (raw.gemini_version) { AGENT_ID = 'gemini-cli'; AGENT_NAME = 'Gemini CLI'; }
|
|
49
|
+
if (raw.gemini_version) { AGENT_ID = 'gemini-cli'; AGENT_NAME = 'Gemini CLI'; }
|
|
51
50
|
} catch {}
|
|
52
51
|
|
|
53
52
|
// Check if tool calls were made in this turn
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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": {
|