@solongate/proxy 0.28.8 → 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 +61 -34
  2. package/dist/init.js +60 -33
  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,28 +802,32 @@ 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/" }
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/**"
793
816
  ];
794
817
  if (existsSync4(gitignorePath)) {
795
818
  let gitignore = readFileSync4(gitignorePath, "utf-8");
796
- const added = [];
797
- for (const entry of requiredEntries) {
798
- if (!gitignore.includes(entry.pattern)) {
799
- gitignore = gitignore.trimEnd() + "\n" + entry.lines + "\n";
800
- added.push(entry.pattern);
801
- }
802
- }
803
- 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";
804
823
  writeFileSync2(gitignorePath, gitignore);
805
- console.log(` Updated .gitignore (added ${added.join(", ")})`);
824
+ console.log(` Updated .gitignore (+${missing.length} entries)`);
806
825
  gitignoreChanged = true;
807
826
  }
808
827
  } else {
809
- writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\n.solongate/\nnode_modules/\n");
810
- console.log(` Created .gitignore (with .env, .mcp.json, .solongate/ excluded)`);
828
+ const content = "# SolonGate\n" + requiredLines.join("\n") + "\nnode_modules/\n";
829
+ writeFileSync2(gitignorePath, content);
830
+ console.log(` Created .gitignore`);
811
831
  gitignoreChanged = true;
812
832
  }
813
833
  if (!envChanged && !gitignoreChanged) {
@@ -852,13 +872,14 @@ async function main() {
852
872
  console.log("");
853
873
  console.log(` ${c.dim}${c.italic}Init Setup${c.reset}`);
854
874
  console.log("");
875
+ startSpinner("Scanning for MCP config...");
855
876
  await sleep(400);
856
877
  const configInfo = findConfigFile(options.configPath, true);
857
878
  if (!configInfo) {
858
- console.log(" No MCP config found and could not create one.");
879
+ stopSpinner("\u2717 No MCP config found and could not create one.");
859
880
  process.exit(1);
860
881
  }
861
- console.log(` Config: ${configInfo.path}`);
882
+ stopSpinner(`\u2713 Found config: ${configInfo.path}`);
862
883
  console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
863
884
  console.log("");
864
885
  const config = readConfig(configInfo.path);
@@ -867,25 +888,31 @@ async function main() {
867
888
  console.log(" No MCP servers found in config.");
868
889
  process.exit(1);
869
890
  }
891
+ startSpinner("Analyzing servers...");
870
892
  await sleep(300);
871
- console.log(` Found ${serverNames.length} MCP server(s):`);
872
- console.log("");
873
893
  const toProtect = [];
874
894
  const alreadyProtected = [];
875
895
  const skipped = [];
876
896
  for (const name of serverNames) {
877
897
  const server = config.mcpServers[name];
878
- await sleep(200);
879
898
  if (isAlreadyProtected(server)) {
880
899
  alreadyProtected.push(name);
881
- console.log(` [protected] ${name}`);
882
900
  } else {
883
- console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
884
901
  if (options.all) {
885
902
  toProtect.push(name);
886
903
  }
887
904
  }
888
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
+ }
889
916
  console.log("");
890
917
  if (alreadyProtected.length === serverNames.length) {
891
918
  console.log(" All servers are already protected by SolonGate!");
@@ -911,7 +938,9 @@ async function main() {
911
938
  process.exit(0);
912
939
  }
913
940
  console.log("");
941
+ startSpinner("Setting up environment...");
914
942
  await sleep(300);
943
+ stopSpinner("\u2713 Environment");
915
944
  ensureEnvFile();
916
945
  console.log("");
917
946
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
@@ -960,12 +989,10 @@ async function main() {
960
989
  console.log(` Policy: cloud-managed (fetched via API key)`);
961
990
  }
962
991
  }
963
- await sleep(300);
964
- await sleep(150);
965
992
  console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
966
- await sleep(150);
967
993
  console.log(` Protecting: ${toProtect.join(", ")}`);
968
994
  console.log("");
995
+ startSpinner("Wrapping MCP servers with proxy...");
969
996
  const newConfig = { mcpServers: {} };
970
997
  for (const name of serverNames) {
971
998
  if (toProtect.includes(name)) {
@@ -985,33 +1012,30 @@ async function main() {
985
1012
  }
986
1013
  await sleep(400);
987
1014
  if (newContent === currentContent) {
988
- console.log(" Config already up to date");
1015
+ stopSpinner("\u2713 Config already up to date");
989
1016
  } else {
990
1017
  writeFileSync2(configInfo.path, newContent);
991
- console.log(" Config updated!");
1018
+ stopSpinner("\u2713 Config updated");
992
1019
  }
993
1020
  console.log("");
994
- await sleep(500);
1021
+ startSpinner("Installing hooks...");
1022
+ await sleep(300);
1023
+ stopSpinner("\u2713 Hooks");
995
1024
  installHooks(options.tools);
996
1025
  console.log("");
997
- await sleep(400);
998
1026
  const allUpToDate = toProtect.length === 0 && alreadyProtected.length === serverNames.length;
999
1027
  console.log(" \u2500\u2500 Summary \u2500\u2500");
1000
1028
  console.log("");
1001
1029
  for (const name of toProtect) {
1002
- await sleep(200);
1003
1030
  console.log(` \u2713 ${name} \u2014 protected`);
1004
1031
  }
1005
1032
  for (const name of alreadyProtected) {
1006
- await sleep(200);
1007
1033
  console.log(` \u25CF ${name} \u2014 already protected`);
1008
1034
  }
1009
1035
  for (const name of skipped) {
1010
- await sleep(200);
1011
1036
  console.log(` \u25CB ${name} \u2014 skipped`);
1012
1037
  }
1013
1038
  console.log("");
1014
- await sleep(500);
1015
1039
  if (allUpToDate) {
1016
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");
1017
1041
  console.log(" \u2502 Everything is already up to date! \u2502");
@@ -1036,7 +1060,7 @@ async function main() {
1036
1060
  }
1037
1061
  console.log("");
1038
1062
  }
1039
- 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;
1040
1064
  var init_init = __esm({
1041
1065
  "src/init.ts"() {
1042
1066
  "use strict";
@@ -1048,6 +1072,9 @@ var init_init = __esm({
1048
1072
  ];
1049
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")];
1050
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;
1051
1078
  __dirname = dirname2(fileURLToPath(import.meta.url));
1052
1079
  HOOKS_DIR = resolve3(__dirname, "..", "hooks");
1053
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,28 +388,32 @@ 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/" }
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/**"
376
402
  ];
377
403
  if (existsSync(gitignorePath)) {
378
404
  let gitignore = readFileSync(gitignorePath, "utf-8");
379
- const added = [];
380
- for (const entry of requiredEntries) {
381
- if (!gitignore.includes(entry.pattern)) {
382
- gitignore = gitignore.trimEnd() + "\n" + entry.lines + "\n";
383
- added.push(entry.pattern);
384
- }
385
- }
386
- 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";
387
409
  writeFileSync(gitignorePath, gitignore);
388
- console.log(` Updated .gitignore (added ${added.join(", ")})`);
410
+ console.log(` Updated .gitignore (+${missing.length} entries)`);
389
411
  gitignoreChanged = true;
390
412
  }
391
413
  } else {
392
- writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\n.solongate/\nnode_modules/\n");
393
- console.log(` Created .gitignore (with .env, .mcp.json, .solongate/ excluded)`);
414
+ const content = "# SolonGate\n" + requiredLines.join("\n") + "\nnode_modules/\n";
415
+ writeFileSync(gitignorePath, content);
416
+ console.log(` Created .gitignore`);
394
417
  gitignoreChanged = true;
395
418
  }
396
419
  if (!envChanged && !gitignoreChanged) {
@@ -435,13 +458,14 @@ async function main() {
435
458
  console.log("");
436
459
  console.log(` ${c.dim}${c.italic}Init Setup${c.reset}`);
437
460
  console.log("");
461
+ startSpinner("Scanning for MCP config...");
438
462
  await sleep(400);
439
463
  const configInfo = findConfigFile(options.configPath, true);
440
464
  if (!configInfo) {
441
- console.log(" No MCP config found and could not create one.");
465
+ stopSpinner("\u2717 No MCP config found and could not create one.");
442
466
  process.exit(1);
443
467
  }
444
- console.log(` Config: ${configInfo.path}`);
468
+ stopSpinner(`\u2713 Found config: ${configInfo.path}`);
445
469
  console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
446
470
  console.log("");
447
471
  const config = readConfig(configInfo.path);
@@ -450,25 +474,31 @@ async function main() {
450
474
  console.log(" No MCP servers found in config.");
451
475
  process.exit(1);
452
476
  }
477
+ startSpinner("Analyzing servers...");
453
478
  await sleep(300);
454
- console.log(` Found ${serverNames.length} MCP server(s):`);
455
- console.log("");
456
479
  const toProtect = [];
457
480
  const alreadyProtected = [];
458
481
  const skipped = [];
459
482
  for (const name of serverNames) {
460
483
  const server = config.mcpServers[name];
461
- await sleep(200);
462
484
  if (isAlreadyProtected(server)) {
463
485
  alreadyProtected.push(name);
464
- console.log(` [protected] ${name}`);
465
486
  } else {
466
- console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
467
487
  if (options.all) {
468
488
  toProtect.push(name);
469
489
  }
470
490
  }
471
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
+ }
472
502
  console.log("");
473
503
  if (alreadyProtected.length === serverNames.length) {
474
504
  console.log(" All servers are already protected by SolonGate!");
@@ -494,7 +524,9 @@ async function main() {
494
524
  process.exit(0);
495
525
  }
496
526
  console.log("");
527
+ startSpinner("Setting up environment...");
497
528
  await sleep(300);
529
+ stopSpinner("\u2713 Environment");
498
530
  ensureEnvFile();
499
531
  console.log("");
500
532
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
@@ -543,12 +575,10 @@ async function main() {
543
575
  console.log(` Policy: cloud-managed (fetched via API key)`);
544
576
  }
545
577
  }
546
- await sleep(300);
547
- await sleep(150);
548
578
  console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
549
- await sleep(150);
550
579
  console.log(` Protecting: ${toProtect.join(", ")}`);
551
580
  console.log("");
581
+ startSpinner("Wrapping MCP servers with proxy...");
552
582
  const newConfig = { mcpServers: {} };
553
583
  for (const name of serverNames) {
554
584
  if (toProtect.includes(name)) {
@@ -568,33 +598,30 @@ async function main() {
568
598
  }
569
599
  await sleep(400);
570
600
  if (newContent === currentContent) {
571
- console.log(" Config already up to date");
601
+ stopSpinner("\u2713 Config already up to date");
572
602
  } else {
573
603
  writeFileSync(configInfo.path, newContent);
574
- console.log(" Config updated!");
604
+ stopSpinner("\u2713 Config updated");
575
605
  }
576
606
  console.log("");
577
- await sleep(500);
607
+ startSpinner("Installing hooks...");
608
+ await sleep(300);
609
+ stopSpinner("\u2713 Hooks");
578
610
  installHooks(options.tools);
579
611
  console.log("");
580
- await sleep(400);
581
612
  const allUpToDate = toProtect.length === 0 && alreadyProtected.length === serverNames.length;
582
613
  console.log(" \u2500\u2500 Summary \u2500\u2500");
583
614
  console.log("");
584
615
  for (const name of toProtect) {
585
- await sleep(200);
586
616
  console.log(` \u2713 ${name} \u2014 protected`);
587
617
  }
588
618
  for (const name of alreadyProtected) {
589
- await sleep(200);
590
619
  console.log(` \u25CF ${name} \u2014 already protected`);
591
620
  }
592
621
  for (const name of skipped) {
593
- await sleep(200);
594
622
  console.log(` \u25CB ${name} \u2014 skipped`);
595
623
  }
596
624
  console.log("");
597
- await sleep(500);
598
625
  if (allUpToDate) {
599
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");
600
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.8",
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": {