@solongate/proxy 0.4.8 → 0.5.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.
Files changed (3) hide show
  1. package/dist/index.js +108 -106
  2. package/dist/init.js +107 -105
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -516,7 +516,7 @@ var init_exports = {};
516
516
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
517
517
  import { resolve as resolve2, join } from "path";
518
518
  import { createInterface } from "readline";
519
- function findConfigFile(explicitPath) {
519
+ function findConfigFile(explicitPath, createIfMissing = false) {
520
520
  if (explicitPath) {
521
521
  if (existsSync3(explicitPath)) {
522
522
  return { path: resolve2(explicitPath), type: "mcp" };
@@ -527,6 +527,25 @@ function findConfigFile(explicitPath) {
527
527
  const full = resolve2(searchPath);
528
528
  if (existsSync3(full)) return { path: full, type: "mcp" };
529
529
  }
530
+ if (createIfMissing) {
531
+ const starterConfig = {
532
+ mcpServers: {
533
+ "filesystem": {
534
+ command: "npx",
535
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
536
+ },
537
+ "playwright": {
538
+ command: "npx",
539
+ args: ["-y", "@playwright/mcp@latest"]
540
+ }
541
+ }
542
+ };
543
+ const starterPath = resolve2(".mcp.json");
544
+ writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
545
+ console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
546
+ console.log("");
547
+ return { path: starterPath, type: "mcp", created: true };
548
+ }
530
549
  for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
531
550
  if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
532
551
  }
@@ -550,10 +569,8 @@ function isAlreadyProtected(server) {
550
569
  }
551
570
  return false;
552
571
  }
553
- function wrapServer(server, policy, apiKey) {
554
- const env = { ...server.env ?? {} };
555
- env.SOLONGATE_API_KEY = apiKey;
556
- return {
572
+ function wrapServer(server, policy) {
573
+ const result = {
557
574
  command: "npx",
558
575
  args: [
559
576
  "-y",
@@ -564,9 +581,12 @@ function wrapServer(server, policy, apiKey) {
564
581
  "--",
565
582
  server.command,
566
583
  ...server.args ?? []
567
- ],
568
- env
584
+ ]
569
585
  };
586
+ if (server.env && Object.keys(server.env).length > 0) {
587
+ result.env = { ...server.env };
588
+ }
589
+ return result;
570
590
  }
571
591
  async function prompt(question) {
572
592
  const rl = createInterface({ input: process.stdin, output: process.stderr });
@@ -613,8 +633,8 @@ function parseInitArgs(argv) {
613
633
  }
614
634
  if (!POLICY_PRESETS.includes(options.policy)) {
615
635
  if (!existsSync3(resolve2(options.policy))) {
616
- console.error(`Unknown policy: ${options.policy}`);
617
- console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
636
+ console.log(`Unknown policy: ${options.policy}`);
637
+ console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
618
638
  process.exit(1);
619
639
  }
620
640
  }
@@ -650,17 +670,17 @@ POLICY PRESETS
650
670
  permissive Allow everything (monitoring + audit only)
651
671
  deny-all Block all tool calls
652
672
  `;
653
- console.error(help);
673
+ console.log(help);
654
674
  }
655
675
  function installClaudeCodeHooks(apiKey) {
656
676
  const hooksDir = resolve2(".claude", "hooks");
657
677
  mkdirSync(hooksDir, { recursive: true });
658
678
  const guardPath = join(hooksDir, "guard.mjs");
659
679
  writeFileSync2(guardPath, GUARD_SCRIPT);
660
- console.error(` Created ${guardPath}`);
680
+ console.log(` Created ${guardPath}`);
661
681
  const auditPath = join(hooksDir, "audit.mjs");
662
682
  writeFileSync2(auditPath, AUDIT_SCRIPT);
663
- console.error(` Created ${auditPath}`);
683
+ console.log(` Created ${auditPath}`);
664
684
  const settingsPath = resolve2(".claude", "settings.json");
665
685
  let settings = {};
666
686
  if (existsSync3(settingsPath)) {
@@ -700,29 +720,26 @@ function installClaudeCodeHooks(apiKey) {
700
720
  envObj.SOLONGATE_API_KEY = apiKey;
701
721
  settings.env = envObj;
702
722
  writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
703
- console.error(` Created ${settingsPath}`);
704
- console.error("");
705
- console.error(" Claude Code hooks installed!");
706
- console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
707
- console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
723
+ console.log(` Created ${settingsPath}`);
724
+ console.log("");
725
+ console.log(" Claude Code hooks installed!");
726
+ console.log(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
727
+ console.log(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
708
728
  }
709
729
  function ensureEnvFile() {
710
730
  const envPath = resolve2(".env");
711
731
  if (!existsSync3(envPath)) {
712
- const envContent = `# SolonGate API Keys
713
- # Get your keys from the dashboard: https://solongate.com
732
+ const envContent = `# SolonGate Configuration
733
+ # Get your API key from: https://dashboard.solongate.com/api-keys
714
734
  # IMPORTANT: Never commit this file to git!
715
735
 
716
- # Live key \u2014 enables cloud policy sync + audit logging to dashboard
736
+ # Your SolonGate API key \u2014 the proxy reads this automatically
717
737
  SOLONGATE_API_KEY=sg_live_your_key_here
718
-
719
- # Test key \u2014 local-only, no cloud connection (for development)
720
- # SOLONGATE_API_KEY=sg_test_your_key_here
721
738
  `;
722
739
  writeFileSync2(envPath, envContent);
723
- console.error(` Created .env with placeholder API keys`);
724
- console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
725
- console.error("");
740
+ console.log(` Created .env`);
741
+ console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
742
+ console.log("");
726
743
  }
727
744
  const gitignorePath = resolve2(".gitignore");
728
745
  if (existsSync3(gitignorePath)) {
@@ -738,11 +755,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
738
755
  }
739
756
  if (updated) {
740
757
  writeFileSync2(gitignorePath, gitignore);
741
- console.error(` Updated .gitignore (added .env + .mcp.json)`);
758
+ console.log(` Updated .gitignore (added .env + .mcp.json)`);
742
759
  }
743
760
  } else {
744
761
  writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
745
- console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
762
+ console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
746
763
  }
747
764
  }
748
765
  async function main() {
@@ -768,54 +785,39 @@ async function main() {
768
785
  " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
769
786
  ];
770
787
  const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
771
- console.error("");
788
+ console.log("");
772
789
  for (let i = 0; i < bannerLines.length; i++) {
773
- console.error(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
790
+ console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
774
791
  }
775
- console.error("");
776
- console.error(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
777
- console.error("");
778
- let configInfo = findConfigFile(options.configPath);
792
+ console.log("");
793
+ console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
794
+ console.log("");
795
+ const configInfo = findConfigFile(options.configPath, true);
779
796
  if (!configInfo) {
780
- const starterConfig = {
781
- mcpServers: {
782
- "filesystem": {
783
- command: "npx",
784
- args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
785
- },
786
- "playwright": {
787
- command: "npx",
788
- args: ["-y", "@playwright/mcp@latest"]
789
- }
790
- }
791
- };
792
- const starterPath = resolve2(".mcp.json");
793
- writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
794
- console.error(" No MCP config found \u2014 created .mcp.json with starter servers.");
795
- console.error("");
796
- configInfo = { path: starterPath, type: "mcp" };
797
+ console.log(" No MCP config found and could not create one.");
798
+ process.exit(1);
797
799
  }
798
- console.error(` Config: ${configInfo.path}`);
799
- console.error(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
800
- console.error("");
800
+ console.log(` Config: ${configInfo.path}`);
801
+ console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
802
+ console.log("");
801
803
  const backupPath = configInfo.path + ".solongate-backup";
802
804
  if (options.restore) {
803
805
  if (!existsSync3(backupPath)) {
804
- console.error(" No backup found. Nothing to restore.");
806
+ console.log(" No backup found. Nothing to restore.");
805
807
  process.exit(1);
806
808
  }
807
809
  copyFileSync(backupPath, configInfo.path);
808
- console.error(" Restored original config from backup.");
810
+ console.log(" Restored original config from backup.");
809
811
  process.exit(0);
810
812
  }
811
813
  const config = readConfig(configInfo.path);
812
814
  const serverNames = Object.keys(config.mcpServers);
813
815
  if (serverNames.length === 0) {
814
- console.error(" No MCP servers found in config.");
816
+ console.log(" No MCP servers found in config.");
815
817
  process.exit(1);
816
818
  }
817
- console.error(` Found ${serverNames.length} MCP server(s):`);
818
- console.error("");
819
+ console.log(` Found ${serverNames.length} MCP server(s):`);
820
+ console.log("");
819
821
  const toProtect = [];
820
822
  const alreadyProtected = [];
821
823
  const skipped = [];
@@ -823,17 +825,17 @@ async function main() {
823
825
  const server = config.mcpServers[name];
824
826
  if (isAlreadyProtected(server)) {
825
827
  alreadyProtected.push(name);
826
- console.error(` [protected] ${name}`);
828
+ console.log(` [protected] ${name}`);
827
829
  } else {
828
- console.error(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
830
+ console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
829
831
  if (options.all) {
830
832
  toProtect.push(name);
831
833
  }
832
834
  }
833
835
  }
834
- console.error("");
836
+ console.log("");
835
837
  if (alreadyProtected.length === serverNames.length) {
836
- console.error(" All servers are already protected by SolonGate!");
838
+ console.log(" All servers are already protected by SolonGate!");
837
839
  ensureEnvFile();
838
840
  process.exit(0);
839
841
  }
@@ -849,11 +851,13 @@ async function main() {
849
851
  }
850
852
  }
851
853
  if (toProtect.length === 0) {
852
- console.error(" No servers selected for protection.");
854
+ console.log(" No servers selected for protection.");
853
855
  ensureEnvFile();
854
856
  process.exit(0);
855
857
  }
856
- console.error("");
858
+ console.log("");
859
+ ensureEnvFile();
860
+ console.log("");
857
861
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
858
862
  if (!apiKey) {
859
863
  const envPath = resolve2(".env");
@@ -866,43 +870,43 @@ async function main() {
866
870
  if (!apiKey || apiKey === "sg_live_your_key_here") {
867
871
  if (options.all) {
868
872
  apiKey = "sg_test_demo_init_key";
869
- console.error(" No API key found \u2014 using demo test key.");
870
- console.error(" For cloud features, set your key in .env or pass --api-key");
871
- console.error("");
873
+ console.log(" No API key found \u2014 using demo test key.");
874
+ console.log(" Set your real key in .env for cloud features.");
875
+ console.log("");
872
876
  } else {
873
877
  apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
874
878
  }
875
879
  if (!apiKey) {
876
- console.error(" API key is required. Get one at https://dashboard.solongate.com");
880
+ console.log(" API key is required. Get one at https://dashboard.solongate.com");
877
881
  process.exit(1);
878
882
  }
879
883
  }
880
884
  if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
881
- console.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
885
+ console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
882
886
  process.exit(1);
883
887
  }
884
- console.error(` Policy: ${options.policy}`);
885
- console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
886
- console.error(` Protecting: ${toProtect.join(", ")}`);
887
- console.error("");
888
+ console.log(` Policy: ${options.policy}`);
889
+ console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
890
+ console.log(` Protecting: ${toProtect.join(", ")}`);
891
+ console.log("");
888
892
  const newConfig = { mcpServers: {} };
889
893
  for (const name of serverNames) {
890
894
  if (toProtect.includes(name)) {
891
- newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, apiKey);
895
+ newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
892
896
  } else {
893
897
  newConfig.mcpServers[name] = config.mcpServers[name];
894
898
  }
895
899
  }
896
900
  if (options.dryRun) {
897
- console.error(" --- DRY RUN (no changes written) ---");
898
- console.error("");
899
- console.error(" New config:");
900
- console.error(JSON.stringify(newConfig, null, 2));
901
+ console.log(" --- DRY RUN (no changes written) ---");
902
+ console.log("");
903
+ console.log(" New config:");
904
+ console.log(JSON.stringify(newConfig, null, 2));
901
905
  process.exit(0);
902
906
  }
903
- if (!existsSync3(backupPath)) {
907
+ if (!configInfo.created && !existsSync3(backupPath)) {
904
908
  copyFileSync(configInfo.path, backupPath);
905
- console.error(` Backup: ${backupPath}`);
909
+ console.log(` Backup: ${backupPath}`);
906
910
  }
907
911
  if (configInfo.type === "claude-desktop") {
908
912
  const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
@@ -911,36 +915,34 @@ async function main() {
911
915
  } else {
912
916
  writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
913
917
  }
914
- console.error(" Config updated!");
915
- console.error("");
918
+ console.log(" Config updated!");
919
+ console.log("");
916
920
  installClaudeCodeHooks(apiKey);
917
- console.error("");
918
- ensureEnvFile();
919
- console.error("");
920
- console.error(" \u2500\u2500 Summary \u2500\u2500");
921
- console.error("");
921
+ console.log("");
922
+ console.log(" \u2500\u2500 Summary \u2500\u2500");
923
+ console.log("");
922
924
  for (const name of toProtect) {
923
- console.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
925
+ console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
924
926
  }
925
927
  for (const name of alreadyProtected) {
926
- console.error(` \u25CF ${name} \u2014 already protected`);
928
+ console.log(` \u25CF ${name} \u2014 already protected`);
927
929
  }
928
930
  for (const name of skipped) {
929
- console.error(` \u25CB ${name} \u2014 skipped`);
930
- }
931
- console.error("");
932
- console.error(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
933
- console.error(" \u2502 Setup complete! \u2502");
934
- console.error(" \u2502 \u2502");
935
- console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
936
- console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
937
- console.error(" \u2502 \u2502");
938
- console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
939
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
940
- console.error(" \u2502 \u2502");
941
- console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
942
- console.error(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
943
- console.error("");
931
+ console.log(` \u25CB ${name} \u2014 skipped`);
932
+ }
933
+ console.log("");
934
+ console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
935
+ console.log(" \u2502 Setup complete! \u2502");
936
+ console.log(" \u2502 \u2502");
937
+ console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
938
+ console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
939
+ console.log(" \u2502 \u2502");
940
+ console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
941
+ console.log(" \u2502 To undo: solongate-init --restore \u2502");
942
+ console.log(" \u2502 \u2502");
943
+ console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
944
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
945
+ console.log("");
944
946
  }
945
947
  var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, GUARD_SCRIPT, AUDIT_SCRIPT;
946
948
  var init_init = __esm({
@@ -1160,7 +1162,7 @@ process.stdin.on('end', async () => {
1160
1162
  });
1161
1163
  `;
1162
1164
  main().catch((err) => {
1163
- console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1165
+ console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1164
1166
  process.exit(1);
1165
1167
  });
1166
1168
  }
package/dist/init.js CHANGED
@@ -11,7 +11,7 @@ var SEARCH_PATHS = [
11
11
  ".claude/mcp.json"
12
12
  ];
13
13
  var 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")];
14
- function findConfigFile(explicitPath) {
14
+ function findConfigFile(explicitPath, createIfMissing = false) {
15
15
  if (explicitPath) {
16
16
  if (existsSync(explicitPath)) {
17
17
  return { path: resolve(explicitPath), type: "mcp" };
@@ -22,6 +22,25 @@ function findConfigFile(explicitPath) {
22
22
  const full = resolve(searchPath);
23
23
  if (existsSync(full)) return { path: full, type: "mcp" };
24
24
  }
25
+ if (createIfMissing) {
26
+ const starterConfig = {
27
+ mcpServers: {
28
+ "filesystem": {
29
+ command: "npx",
30
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
31
+ },
32
+ "playwright": {
33
+ command: "npx",
34
+ args: ["-y", "@playwright/mcp@latest"]
35
+ }
36
+ }
37
+ };
38
+ const starterPath = resolve(".mcp.json");
39
+ writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
40
+ console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
41
+ console.log("");
42
+ return { path: starterPath, type: "mcp", created: true };
43
+ }
25
44
  for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
26
45
  if (existsSync(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
27
46
  }
@@ -45,10 +64,8 @@ function isAlreadyProtected(server) {
45
64
  }
46
65
  return false;
47
66
  }
48
- function wrapServer(server, policy, apiKey) {
49
- const env = { ...server.env ?? {} };
50
- env.SOLONGATE_API_KEY = apiKey;
51
- return {
67
+ function wrapServer(server, policy) {
68
+ const result = {
52
69
  command: "npx",
53
70
  args: [
54
71
  "-y",
@@ -59,9 +76,12 @@ function wrapServer(server, policy, apiKey) {
59
76
  "--",
60
77
  server.command,
61
78
  ...server.args ?? []
62
- ],
63
- env
79
+ ]
64
80
  };
81
+ if (server.env && Object.keys(server.env).length > 0) {
82
+ result.env = { ...server.env };
83
+ }
84
+ return result;
65
85
  }
66
86
  async function prompt(question) {
67
87
  const rl = createInterface({ input: process.stdin, output: process.stderr });
@@ -108,8 +128,8 @@ function parseInitArgs(argv) {
108
128
  }
109
129
  if (!POLICY_PRESETS.includes(options.policy)) {
110
130
  if (!existsSync(resolve(options.policy))) {
111
- console.error(`Unknown policy: ${options.policy}`);
112
- console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
131
+ console.log(`Unknown policy: ${options.policy}`);
132
+ console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
113
133
  process.exit(1);
114
134
  }
115
135
  }
@@ -145,7 +165,7 @@ POLICY PRESETS
145
165
  permissive Allow everything (monitoring + audit only)
146
166
  deny-all Block all tool calls
147
167
  `;
148
- console.error(help);
168
+ console.log(help);
149
169
  }
150
170
  var GUARD_SCRIPT = `#!/usr/bin/env node
151
171
  /**
@@ -358,10 +378,10 @@ function installClaudeCodeHooks(apiKey) {
358
378
  mkdirSync(hooksDir, { recursive: true });
359
379
  const guardPath = join(hooksDir, "guard.mjs");
360
380
  writeFileSync(guardPath, GUARD_SCRIPT);
361
- console.error(` Created ${guardPath}`);
381
+ console.log(` Created ${guardPath}`);
362
382
  const auditPath = join(hooksDir, "audit.mjs");
363
383
  writeFileSync(auditPath, AUDIT_SCRIPT);
364
- console.error(` Created ${auditPath}`);
384
+ console.log(` Created ${auditPath}`);
365
385
  const settingsPath = resolve(".claude", "settings.json");
366
386
  let settings = {};
367
387
  if (existsSync(settingsPath)) {
@@ -401,29 +421,26 @@ function installClaudeCodeHooks(apiKey) {
401
421
  envObj.SOLONGATE_API_KEY = apiKey;
402
422
  settings.env = envObj;
403
423
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
404
- console.error(` Created ${settingsPath}`);
405
- console.error("");
406
- console.error(" Claude Code hooks installed!");
407
- console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
408
- console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
424
+ console.log(` Created ${settingsPath}`);
425
+ console.log("");
426
+ console.log(" Claude Code hooks installed!");
427
+ console.log(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
428
+ console.log(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
409
429
  }
410
430
  function ensureEnvFile() {
411
431
  const envPath = resolve(".env");
412
432
  if (!existsSync(envPath)) {
413
- const envContent = `# SolonGate API Keys
414
- # Get your keys from the dashboard: https://solongate.com
433
+ const envContent = `# SolonGate Configuration
434
+ # Get your API key from: https://dashboard.solongate.com/api-keys
415
435
  # IMPORTANT: Never commit this file to git!
416
436
 
417
- # Live key \u2014 enables cloud policy sync + audit logging to dashboard
437
+ # Your SolonGate API key \u2014 the proxy reads this automatically
418
438
  SOLONGATE_API_KEY=sg_live_your_key_here
419
-
420
- # Test key \u2014 local-only, no cloud connection (for development)
421
- # SOLONGATE_API_KEY=sg_test_your_key_here
422
439
  `;
423
440
  writeFileSync(envPath, envContent);
424
- console.error(` Created .env with placeholder API keys`);
425
- console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
426
- console.error("");
441
+ console.log(` Created .env`);
442
+ console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
443
+ console.log("");
427
444
  }
428
445
  const gitignorePath = resolve(".gitignore");
429
446
  if (existsSync(gitignorePath)) {
@@ -439,11 +456,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
439
456
  }
440
457
  if (updated) {
441
458
  writeFileSync(gitignorePath, gitignore);
442
- console.error(` Updated .gitignore (added .env + .mcp.json)`);
459
+ console.log(` Updated .gitignore (added .env + .mcp.json)`);
443
460
  }
444
461
  } else {
445
462
  writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
446
- console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
463
+ console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
447
464
  }
448
465
  }
449
466
  async function main() {
@@ -469,54 +486,39 @@ async function main() {
469
486
  " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
470
487
  ];
471
488
  const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
472
- console.error("");
489
+ console.log("");
473
490
  for (let i = 0; i < bannerLines.length; i++) {
474
- console.error(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
491
+ console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
475
492
  }
476
- console.error("");
477
- console.error(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
478
- console.error("");
479
- let configInfo = findConfigFile(options.configPath);
493
+ console.log("");
494
+ console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
495
+ console.log("");
496
+ const configInfo = findConfigFile(options.configPath, true);
480
497
  if (!configInfo) {
481
- const starterConfig = {
482
- mcpServers: {
483
- "filesystem": {
484
- command: "npx",
485
- args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
486
- },
487
- "playwright": {
488
- command: "npx",
489
- args: ["-y", "@playwright/mcp@latest"]
490
- }
491
- }
492
- };
493
- const starterPath = resolve(".mcp.json");
494
- writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
495
- console.error(" No MCP config found \u2014 created .mcp.json with starter servers.");
496
- console.error("");
497
- configInfo = { path: starterPath, type: "mcp" };
498
+ console.log(" No MCP config found and could not create one.");
499
+ process.exit(1);
498
500
  }
499
- console.error(` Config: ${configInfo.path}`);
500
- console.error(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
501
- console.error("");
501
+ console.log(` Config: ${configInfo.path}`);
502
+ console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
503
+ console.log("");
502
504
  const backupPath = configInfo.path + ".solongate-backup";
503
505
  if (options.restore) {
504
506
  if (!existsSync(backupPath)) {
505
- console.error(" No backup found. Nothing to restore.");
507
+ console.log(" No backup found. Nothing to restore.");
506
508
  process.exit(1);
507
509
  }
508
510
  copyFileSync(backupPath, configInfo.path);
509
- console.error(" Restored original config from backup.");
511
+ console.log(" Restored original config from backup.");
510
512
  process.exit(0);
511
513
  }
512
514
  const config = readConfig(configInfo.path);
513
515
  const serverNames = Object.keys(config.mcpServers);
514
516
  if (serverNames.length === 0) {
515
- console.error(" No MCP servers found in config.");
517
+ console.log(" No MCP servers found in config.");
516
518
  process.exit(1);
517
519
  }
518
- console.error(` Found ${serverNames.length} MCP server(s):`);
519
- console.error("");
520
+ console.log(` Found ${serverNames.length} MCP server(s):`);
521
+ console.log("");
520
522
  const toProtect = [];
521
523
  const alreadyProtected = [];
522
524
  const skipped = [];
@@ -524,17 +526,17 @@ async function main() {
524
526
  const server = config.mcpServers[name];
525
527
  if (isAlreadyProtected(server)) {
526
528
  alreadyProtected.push(name);
527
- console.error(` [protected] ${name}`);
529
+ console.log(` [protected] ${name}`);
528
530
  } else {
529
- console.error(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
531
+ console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
530
532
  if (options.all) {
531
533
  toProtect.push(name);
532
534
  }
533
535
  }
534
536
  }
535
- console.error("");
537
+ console.log("");
536
538
  if (alreadyProtected.length === serverNames.length) {
537
- console.error(" All servers are already protected by SolonGate!");
539
+ console.log(" All servers are already protected by SolonGate!");
538
540
  ensureEnvFile();
539
541
  process.exit(0);
540
542
  }
@@ -550,11 +552,13 @@ async function main() {
550
552
  }
551
553
  }
552
554
  if (toProtect.length === 0) {
553
- console.error(" No servers selected for protection.");
555
+ console.log(" No servers selected for protection.");
554
556
  ensureEnvFile();
555
557
  process.exit(0);
556
558
  }
557
- console.error("");
559
+ console.log("");
560
+ ensureEnvFile();
561
+ console.log("");
558
562
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
559
563
  if (!apiKey) {
560
564
  const envPath = resolve(".env");
@@ -567,43 +571,43 @@ async function main() {
567
571
  if (!apiKey || apiKey === "sg_live_your_key_here") {
568
572
  if (options.all) {
569
573
  apiKey = "sg_test_demo_init_key";
570
- console.error(" No API key found \u2014 using demo test key.");
571
- console.error(" For cloud features, set your key in .env or pass --api-key");
572
- console.error("");
574
+ console.log(" No API key found \u2014 using demo test key.");
575
+ console.log(" Set your real key in .env for cloud features.");
576
+ console.log("");
573
577
  } else {
574
578
  apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
575
579
  }
576
580
  if (!apiKey) {
577
- console.error(" API key is required. Get one at https://dashboard.solongate.com");
581
+ console.log(" API key is required. Get one at https://dashboard.solongate.com");
578
582
  process.exit(1);
579
583
  }
580
584
  }
581
585
  if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
582
- console.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
586
+ console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
583
587
  process.exit(1);
584
588
  }
585
- console.error(` Policy: ${options.policy}`);
586
- console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
587
- console.error(` Protecting: ${toProtect.join(", ")}`);
588
- console.error("");
589
+ console.log(` Policy: ${options.policy}`);
590
+ console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
591
+ console.log(` Protecting: ${toProtect.join(", ")}`);
592
+ console.log("");
589
593
  const newConfig = { mcpServers: {} };
590
594
  for (const name of serverNames) {
591
595
  if (toProtect.includes(name)) {
592
- newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, apiKey);
596
+ newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
593
597
  } else {
594
598
  newConfig.mcpServers[name] = config.mcpServers[name];
595
599
  }
596
600
  }
597
601
  if (options.dryRun) {
598
- console.error(" --- DRY RUN (no changes written) ---");
599
- console.error("");
600
- console.error(" New config:");
601
- console.error(JSON.stringify(newConfig, null, 2));
602
+ console.log(" --- DRY RUN (no changes written) ---");
603
+ console.log("");
604
+ console.log(" New config:");
605
+ console.log(JSON.stringify(newConfig, null, 2));
602
606
  process.exit(0);
603
607
  }
604
- if (!existsSync(backupPath)) {
608
+ if (!configInfo.created && !existsSync(backupPath)) {
605
609
  copyFileSync(configInfo.path, backupPath);
606
- console.error(` Backup: ${backupPath}`);
610
+ console.log(` Backup: ${backupPath}`);
607
611
  }
608
612
  if (configInfo.type === "claude-desktop") {
609
613
  const original = JSON.parse(readFileSync(configInfo.path, "utf-8"));
@@ -612,38 +616,36 @@ async function main() {
612
616
  } else {
613
617
  writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
614
618
  }
615
- console.error(" Config updated!");
616
- console.error("");
619
+ console.log(" Config updated!");
620
+ console.log("");
617
621
  installClaudeCodeHooks(apiKey);
618
- console.error("");
619
- ensureEnvFile();
620
- console.error("");
621
- console.error(" \u2500\u2500 Summary \u2500\u2500");
622
- console.error("");
622
+ console.log("");
623
+ console.log(" \u2500\u2500 Summary \u2500\u2500");
624
+ console.log("");
623
625
  for (const name of toProtect) {
624
- console.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
626
+ console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
625
627
  }
626
628
  for (const name of alreadyProtected) {
627
- console.error(` \u25CF ${name} \u2014 already protected`);
629
+ console.log(` \u25CF ${name} \u2014 already protected`);
628
630
  }
629
631
  for (const name of skipped) {
630
- console.error(` \u25CB ${name} \u2014 skipped`);
632
+ console.log(` \u25CB ${name} \u2014 skipped`);
631
633
  }
632
- console.error("");
633
- console.error(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
634
- console.error(" \u2502 Setup complete! \u2502");
635
- console.error(" \u2502 \u2502");
636
- console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
637
- console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
638
- console.error(" \u2502 \u2502");
639
- console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
640
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
641
- console.error(" \u2502 \u2502");
642
- console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
643
- console.error(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
644
- console.error("");
634
+ console.log("");
635
+ console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
636
+ console.log(" \u2502 Setup complete! \u2502");
637
+ console.log(" \u2502 \u2502");
638
+ console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
639
+ console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
640
+ console.log(" \u2502 \u2502");
641
+ console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
642
+ console.log(" \u2502 To undo: solongate-init --restore \u2502");
643
+ console.log(" \u2502 \u2502");
644
+ console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
645
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
646
+ console.log("");
645
647
  }
646
648
  main().catch((err) => {
647
- console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
649
+ console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
648
650
  process.exit(1);
649
651
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "MCP security proxy — protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {