@solongate/proxy 0.28.9 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +60 -39
  2. package/dist/init.js +59 -38
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -464,6 +464,22 @@ import { resolve as resolve3, join, dirname as dirname2 } from "path";
464
464
  import { fileURLToPath } from "url";
465
465
  import { execFileSync } from "child_process";
466
466
  import { createInterface } from "readline";
467
+ function startSpinner(message) {
468
+ spinnerFrame = 0;
469
+ process.stderr.write(` ${SPINNER_FRAMES[0]} ${message}`);
470
+ spinnerInterval = setInterval(() => {
471
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
472
+ process.stderr.write(`\r ${SPINNER_FRAMES[spinnerFrame]} ${message}`);
473
+ }, 80);
474
+ }
475
+ function stopSpinner(result) {
476
+ if (spinnerInterval) {
477
+ clearInterval(spinnerInterval);
478
+ spinnerInterval = null;
479
+ }
480
+ process.stderr.write(`\r ${result}
481
+ `);
482
+ }
467
483
  function findConfigFile(explicitPath, createIfMissing = false) {
468
484
  if (explicitPath) {
469
485
  if (existsSync4(explicitPath)) {
@@ -786,33 +802,31 @@ SOLONGATE_API_KEY=sg_live_your_key_here
786
802
  }
787
803
  }
788
804
  const gitignorePath = resolve3(".gitignore");
789
- const requiredEntries = [
790
- { pattern: ".env", lines: ".env\n.env.local" },
791
- { pattern: ".mcp.json", lines: ".mcp.json" },
792
- { pattern: ".solongate", lines: ".solongate/" },
793
- { pattern: ".claude", lines: ".claude/" },
794
- { pattern: ".cursor", lines: ".cursor/" },
795
- { pattern: ".gemini", lines: ".gemini/" },
796
- { pattern: ".antigravity", lines: ".antigravity/" },
797
- { pattern: ".openclaw", lines: ".openclaw/" },
798
- { pattern: ".perplexity", lines: ".perplexity/" }
805
+ const requiredLines = [
806
+ ".env",
807
+ ".env.local",
808
+ ".mcp.json",
809
+ ".solongate/**",
810
+ ".claude/**",
811
+ ".cursor/**",
812
+ ".gemini/**",
813
+ ".antigravity/**",
814
+ ".openclaw/**",
815
+ ".perplexity/**"
799
816
  ];
800
817
  if (existsSync4(gitignorePath)) {
801
818
  let gitignore = readFileSync4(gitignorePath, "utf-8");
802
- const added = [];
803
- for (const entry of requiredEntries) {
804
- if (!gitignore.includes(entry.pattern)) {
805
- gitignore = gitignore.trimEnd() + "\n" + entry.lines + "\n";
806
- added.push(entry.pattern);
807
- }
808
- }
809
- if (added.length > 0) {
819
+ const existingLines = new Set(gitignore.split("\n").map((l) => l.trim()));
820
+ const missing = requiredLines.filter((line) => !existingLines.has(line));
821
+ if (missing.length > 0) {
822
+ gitignore = gitignore.trimEnd() + "\n\n# SolonGate\n" + missing.join("\n") + "\n";
810
823
  writeFileSync2(gitignorePath, gitignore);
811
- console.log(` Updated .gitignore (added ${added.join(", ")})`);
824
+ console.log(` Updated .gitignore (+${missing.length} entries)`);
812
825
  gitignoreChanged = true;
813
826
  }
814
827
  } else {
815
- writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\n.solongate/\n.claude/\n.cursor/\n.gemini/\n.antigravity/\n.openclaw/\n.perplexity/\nnode_modules/\n");
828
+ const content = "# SolonGate\n" + requiredLines.join("\n") + "\nnode_modules/\n";
829
+ writeFileSync2(gitignorePath, content);
816
830
  console.log(` Created .gitignore`);
817
831
  gitignoreChanged = true;
818
832
  }
@@ -858,13 +872,14 @@ async function main() {
858
872
  console.log("");
859
873
  console.log(` ${c.dim}${c.italic}Init Setup${c.reset}`);
860
874
  console.log("");
875
+ startSpinner("Scanning for MCP config...");
861
876
  await sleep(400);
862
877
  const configInfo = findConfigFile(options.configPath, true);
863
878
  if (!configInfo) {
864
- console.log(" No MCP config found and could not create one.");
879
+ stopSpinner("\u2717 No MCP config found and could not create one.");
865
880
  process.exit(1);
866
881
  }
867
- console.log(` Config: ${configInfo.path}`);
882
+ stopSpinner(`\u2713 Found config: ${configInfo.path}`);
868
883
  console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
869
884
  console.log("");
870
885
  const config = readConfig(configInfo.path);
@@ -873,25 +888,31 @@ async function main() {
873
888
  console.log(" No MCP servers found in config.");
874
889
  process.exit(1);
875
890
  }
891
+ startSpinner("Analyzing servers...");
876
892
  await sleep(300);
877
- console.log(` Found ${serverNames.length} MCP server(s):`);
878
- console.log("");
879
893
  const toProtect = [];
880
894
  const alreadyProtected = [];
881
895
  const skipped = [];
882
896
  for (const name of serverNames) {
883
897
  const server = config.mcpServers[name];
884
- await sleep(200);
885
898
  if (isAlreadyProtected(server)) {
886
899
  alreadyProtected.push(name);
887
- console.log(` [protected] ${name}`);
888
900
  } else {
889
- console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
890
901
  if (options.all) {
891
902
  toProtect.push(name);
892
903
  }
893
904
  }
894
905
  }
906
+ stopSpinner(`\u2713 Found ${serverNames.length} MCP server(s)`);
907
+ console.log("");
908
+ for (const name of serverNames) {
909
+ const server = config.mcpServers[name];
910
+ if (alreadyProtected.includes(name)) {
911
+ console.log(` [protected] ${name}`);
912
+ } else {
913
+ console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
914
+ }
915
+ }
895
916
  console.log("");
896
917
  if (alreadyProtected.length === serverNames.length) {
897
918
  console.log(" All servers are already protected by SolonGate!");
@@ -917,7 +938,9 @@ async function main() {
917
938
  process.exit(0);
918
939
  }
919
940
  console.log("");
941
+ startSpinner("Setting up environment...");
920
942
  await sleep(300);
943
+ stopSpinner("\u2713 Environment");
921
944
  ensureEnvFile();
922
945
  console.log("");
923
946
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
@@ -966,12 +989,10 @@ async function main() {
966
989
  console.log(` Policy: cloud-managed (fetched via API key)`);
967
990
  }
968
991
  }
969
- await sleep(300);
970
- await sleep(150);
971
992
  console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
972
- await sleep(150);
973
993
  console.log(` Protecting: ${toProtect.join(", ")}`);
974
994
  console.log("");
995
+ startSpinner("Wrapping MCP servers with proxy...");
975
996
  const newConfig = { mcpServers: {} };
976
997
  for (const name of serverNames) {
977
998
  if (toProtect.includes(name)) {
@@ -991,33 +1012,30 @@ async function main() {
991
1012
  }
992
1013
  await sleep(400);
993
1014
  if (newContent === currentContent) {
994
- console.log(" Config already up to date");
1015
+ stopSpinner("\u2713 Config already up to date");
995
1016
  } else {
996
1017
  writeFileSync2(configInfo.path, newContent);
997
- console.log(" Config updated!");
1018
+ stopSpinner("\u2713 Config updated");
998
1019
  }
999
1020
  console.log("");
1000
- await sleep(500);
1021
+ startSpinner("Installing hooks...");
1022
+ await sleep(300);
1023
+ stopSpinner("\u2713 Hooks");
1001
1024
  installHooks(options.tools);
1002
1025
  console.log("");
1003
- await sleep(400);
1004
1026
  const allUpToDate = toProtect.length === 0 && alreadyProtected.length === serverNames.length;
1005
1027
  console.log(" \u2500\u2500 Summary \u2500\u2500");
1006
1028
  console.log("");
1007
1029
  for (const name of toProtect) {
1008
- await sleep(200);
1009
1030
  console.log(` \u2713 ${name} \u2014 protected`);
1010
1031
  }
1011
1032
  for (const name of alreadyProtected) {
1012
- await sleep(200);
1013
1033
  console.log(` \u25CF ${name} \u2014 already protected`);
1014
1034
  }
1015
1035
  for (const name of skipped) {
1016
- await sleep(200);
1017
1036
  console.log(` \u25CB ${name} \u2014 skipped`);
1018
1037
  }
1019
1038
  console.log("");
1020
- await sleep(500);
1021
1039
  if (allUpToDate) {
1022
1040
  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");
1023
1041
  console.log(" \u2502 Everything is already up to date! \u2502");
@@ -1042,7 +1060,7 @@ async function main() {
1042
1060
  }
1043
1061
  console.log("");
1044
1062
  }
1045
- var SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, sleep, __dirname, HOOKS_DIR;
1063
+ var SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, sleep, SPINNER_FRAMES, spinnerInterval, spinnerFrame, __dirname, HOOKS_DIR;
1046
1064
  var init_init = __esm({
1047
1065
  "src/init.ts"() {
1048
1066
  "use strict";
@@ -1054,6 +1072,9 @@ var init_init = __esm({
1054
1072
  ];
1055
1073
  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")];
1056
1074
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1075
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1076
+ spinnerInterval = null;
1077
+ spinnerFrame = 0;
1057
1078
  __dirname = dirname2(fileURLToPath(import.meta.url));
1058
1079
  HOOKS_DIR = resolve3(__dirname, "..", "hooks");
1059
1080
  main().catch((err) => {
package/dist/init.js CHANGED
@@ -45,6 +45,25 @@ var SEARCH_PATHS = [
45
45
  ];
46
46
  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")];
47
47
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
48
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
49
+ var spinnerInterval = null;
50
+ var spinnerFrame = 0;
51
+ function startSpinner(message) {
52
+ spinnerFrame = 0;
53
+ process.stderr.write(` ${SPINNER_FRAMES[0]} ${message}`);
54
+ spinnerInterval = setInterval(() => {
55
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
56
+ process.stderr.write(`\r ${SPINNER_FRAMES[spinnerFrame]} ${message}`);
57
+ }, 80);
58
+ }
59
+ function stopSpinner(result) {
60
+ if (spinnerInterval) {
61
+ clearInterval(spinnerInterval);
62
+ spinnerInterval = null;
63
+ }
64
+ process.stderr.write(`\r ${result}
65
+ `);
66
+ }
48
67
  function findConfigFile(explicitPath, createIfMissing = false) {
49
68
  if (explicitPath) {
50
69
  if (existsSync(explicitPath)) {
@@ -369,33 +388,31 @@ SOLONGATE_API_KEY=sg_live_your_key_here
369
388
  }
370
389
  }
371
390
  const gitignorePath = resolve(".gitignore");
372
- const requiredEntries = [
373
- { pattern: ".env", lines: ".env\n.env.local" },
374
- { pattern: ".mcp.json", lines: ".mcp.json" },
375
- { pattern: ".solongate", lines: ".solongate/" },
376
- { pattern: ".claude", lines: ".claude/" },
377
- { pattern: ".cursor", lines: ".cursor/" },
378
- { pattern: ".gemini", lines: ".gemini/" },
379
- { pattern: ".antigravity", lines: ".antigravity/" },
380
- { pattern: ".openclaw", lines: ".openclaw/" },
381
- { pattern: ".perplexity", lines: ".perplexity/" }
391
+ const requiredLines = [
392
+ ".env",
393
+ ".env.local",
394
+ ".mcp.json",
395
+ ".solongate/**",
396
+ ".claude/**",
397
+ ".cursor/**",
398
+ ".gemini/**",
399
+ ".antigravity/**",
400
+ ".openclaw/**",
401
+ ".perplexity/**"
382
402
  ];
383
403
  if (existsSync(gitignorePath)) {
384
404
  let gitignore = readFileSync(gitignorePath, "utf-8");
385
- const added = [];
386
- for (const entry of requiredEntries) {
387
- if (!gitignore.includes(entry.pattern)) {
388
- gitignore = gitignore.trimEnd() + "\n" + entry.lines + "\n";
389
- added.push(entry.pattern);
390
- }
391
- }
392
- if (added.length > 0) {
405
+ const existingLines = new Set(gitignore.split("\n").map((l) => l.trim()));
406
+ const missing = requiredLines.filter((line) => !existingLines.has(line));
407
+ if (missing.length > 0) {
408
+ gitignore = gitignore.trimEnd() + "\n\n# SolonGate\n" + missing.join("\n") + "\n";
393
409
  writeFileSync(gitignorePath, gitignore);
394
- console.log(` Updated .gitignore (added ${added.join(", ")})`);
410
+ console.log(` Updated .gitignore (+${missing.length} entries)`);
395
411
  gitignoreChanged = true;
396
412
  }
397
413
  } else {
398
- writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\n.solongate/\n.claude/\n.cursor/\n.gemini/\n.antigravity/\n.openclaw/\n.perplexity/\nnode_modules/\n");
414
+ const content = "# SolonGate\n" + requiredLines.join("\n") + "\nnode_modules/\n";
415
+ writeFileSync(gitignorePath, content);
399
416
  console.log(` Created .gitignore`);
400
417
  gitignoreChanged = true;
401
418
  }
@@ -441,13 +458,14 @@ async function main() {
441
458
  console.log("");
442
459
  console.log(` ${c.dim}${c.italic}Init Setup${c.reset}`);
443
460
  console.log("");
461
+ startSpinner("Scanning for MCP config...");
444
462
  await sleep(400);
445
463
  const configInfo = findConfigFile(options.configPath, true);
446
464
  if (!configInfo) {
447
- console.log(" No MCP config found and could not create one.");
465
+ stopSpinner("\u2717 No MCP config found and could not create one.");
448
466
  process.exit(1);
449
467
  }
450
- console.log(` Config: ${configInfo.path}`);
468
+ stopSpinner(`\u2713 Found config: ${configInfo.path}`);
451
469
  console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
452
470
  console.log("");
453
471
  const config = readConfig(configInfo.path);
@@ -456,25 +474,31 @@ async function main() {
456
474
  console.log(" No MCP servers found in config.");
457
475
  process.exit(1);
458
476
  }
477
+ startSpinner("Analyzing servers...");
459
478
  await sleep(300);
460
- console.log(` Found ${serverNames.length} MCP server(s):`);
461
- console.log("");
462
479
  const toProtect = [];
463
480
  const alreadyProtected = [];
464
481
  const skipped = [];
465
482
  for (const name of serverNames) {
466
483
  const server = config.mcpServers[name];
467
- await sleep(200);
468
484
  if (isAlreadyProtected(server)) {
469
485
  alreadyProtected.push(name);
470
- console.log(` [protected] ${name}`);
471
486
  } else {
472
- console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
473
487
  if (options.all) {
474
488
  toProtect.push(name);
475
489
  }
476
490
  }
477
491
  }
492
+ stopSpinner(`\u2713 Found ${serverNames.length} MCP server(s)`);
493
+ console.log("");
494
+ for (const name of serverNames) {
495
+ const server = config.mcpServers[name];
496
+ if (alreadyProtected.includes(name)) {
497
+ console.log(` [protected] ${name}`);
498
+ } else {
499
+ console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
500
+ }
501
+ }
478
502
  console.log("");
479
503
  if (alreadyProtected.length === serverNames.length) {
480
504
  console.log(" All servers are already protected by SolonGate!");
@@ -500,7 +524,9 @@ async function main() {
500
524
  process.exit(0);
501
525
  }
502
526
  console.log("");
527
+ startSpinner("Setting up environment...");
503
528
  await sleep(300);
529
+ stopSpinner("\u2713 Environment");
504
530
  ensureEnvFile();
505
531
  console.log("");
506
532
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
@@ -549,12 +575,10 @@ async function main() {
549
575
  console.log(` Policy: cloud-managed (fetched via API key)`);
550
576
  }
551
577
  }
552
- await sleep(300);
553
- await sleep(150);
554
578
  console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
555
- await sleep(150);
556
579
  console.log(` Protecting: ${toProtect.join(", ")}`);
557
580
  console.log("");
581
+ startSpinner("Wrapping MCP servers with proxy...");
558
582
  const newConfig = { mcpServers: {} };
559
583
  for (const name of serverNames) {
560
584
  if (toProtect.includes(name)) {
@@ -574,33 +598,30 @@ async function main() {
574
598
  }
575
599
  await sleep(400);
576
600
  if (newContent === currentContent) {
577
- console.log(" Config already up to date");
601
+ stopSpinner("\u2713 Config already up to date");
578
602
  } else {
579
603
  writeFileSync(configInfo.path, newContent);
580
- console.log(" Config updated!");
604
+ stopSpinner("\u2713 Config updated");
581
605
  }
582
606
  console.log("");
583
- await sleep(500);
607
+ startSpinner("Installing hooks...");
608
+ await sleep(300);
609
+ stopSpinner("\u2713 Hooks");
584
610
  installHooks(options.tools);
585
611
  console.log("");
586
- await sleep(400);
587
612
  const allUpToDate = toProtect.length === 0 && alreadyProtected.length === serverNames.length;
588
613
  console.log(" \u2500\u2500 Summary \u2500\u2500");
589
614
  console.log("");
590
615
  for (const name of toProtect) {
591
- await sleep(200);
592
616
  console.log(` \u2713 ${name} \u2014 protected`);
593
617
  }
594
618
  for (const name of alreadyProtected) {
595
- await sleep(200);
596
619
  console.log(` \u25CF ${name} \u2014 already protected`);
597
620
  }
598
621
  for (const name of skipped) {
599
- await sleep(200);
600
622
  console.log(` \u25CB ${name} \u2014 skipped`);
601
623
  }
602
624
  console.log("");
603
- await sleep(500);
604
625
  if (allUpToDate) {
605
626
  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");
606
627
  console.log(" \u2502 Everything is already up to date! \u2502");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.28.9",
3
+ "version": "0.29.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": {