@solongate/proxy 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,29 +4,520 @@ var __esm = (fn, res) => function __init() {
4
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
5
  };
6
6
 
7
+ // src/config.ts
8
+ import { readFileSync, existsSync } from "fs";
9
+ import { resolve } from "path";
10
+ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
11
+ const url = `${apiUrl}/api/v1/policies/${policyId ?? "default"}`;
12
+ const res = await fetch(url, {
13
+ headers: { "Authorization": `Bearer ${apiKey}` }
14
+ });
15
+ if (!res.ok) {
16
+ const body = await res.text().catch(() => "");
17
+ throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
18
+ }
19
+ const data = await res.json();
20
+ return {
21
+ id: String(data.id ?? "cloud"),
22
+ name: String(data.name ?? "Cloud Policy"),
23
+ version: Number(data._version ?? 1),
24
+ rules: data.rules ?? [],
25
+ createdAt: String(data._created_at ?? ""),
26
+ updatedAt: ""
27
+ };
28
+ }
29
+ async function sendAuditLog(apiKey, apiUrl, entry) {
30
+ const url = `${apiUrl}/api/v1/audit-logs`;
31
+ try {
32
+ const res = await fetch(url, {
33
+ method: "POST",
34
+ headers: {
35
+ "Authorization": `Bearer ${apiKey}`,
36
+ "Content-Type": "application/json"
37
+ },
38
+ body: JSON.stringify(entry)
39
+ });
40
+ if (!res.ok) {
41
+ const body = await res.text().catch(() => "");
42
+ process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
43
+ `);
44
+ }
45
+ } catch (err) {
46
+ process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
47
+ `);
48
+ }
49
+ }
50
+ function loadPolicy(source) {
51
+ if (typeof source === "object") return source;
52
+ if (PRESETS[source]) return PRESETS[source];
53
+ const filePath = resolve(source);
54
+ if (existsSync(filePath)) {
55
+ const content = readFileSync(filePath, "utf-8");
56
+ return JSON.parse(content);
57
+ }
58
+ throw new Error(
59
+ `Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
60
+ );
61
+ }
62
+ function parseArgs(argv) {
63
+ const args = argv.slice(2);
64
+ let policySource = "restricted";
65
+ let name = "solongate-proxy";
66
+ let verbose = false;
67
+ let rateLimitPerTool;
68
+ let globalRateLimit;
69
+ let configFile;
70
+ let apiKey;
71
+ let apiUrl;
72
+ let upstreamUrl;
73
+ let upstreamTransport;
74
+ let port;
75
+ let separatorIndex = args.indexOf("--");
76
+ const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
77
+ const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
78
+ for (let i = 0; i < flags.length; i++) {
79
+ switch (flags[i]) {
80
+ case "--policy":
81
+ policySource = flags[++i];
82
+ break;
83
+ case "--name":
84
+ name = flags[++i];
85
+ break;
86
+ case "--verbose":
87
+ verbose = true;
88
+ break;
89
+ case "--rate-limit":
90
+ rateLimitPerTool = parseInt(flags[++i], 10);
91
+ break;
92
+ case "--global-rate-limit":
93
+ globalRateLimit = parseInt(flags[++i], 10);
94
+ break;
95
+ case "--config":
96
+ configFile = flags[++i];
97
+ break;
98
+ case "--api-key":
99
+ apiKey = flags[++i];
100
+ break;
101
+ case "--api-url":
102
+ apiUrl = flags[++i];
103
+ break;
104
+ case "--upstream-url":
105
+ upstreamUrl = flags[++i];
106
+ break;
107
+ case "--upstream-transport":
108
+ upstreamTransport = flags[++i];
109
+ break;
110
+ case "--port":
111
+ port = parseInt(flags[++i], 10);
112
+ break;
113
+ }
114
+ }
115
+ if (!apiKey) {
116
+ const envKey = process.env.SOLONGATE_API_KEY;
117
+ if (envKey) {
118
+ apiKey = envKey;
119
+ } else {
120
+ throw new Error(
121
+ "A valid SolonGate API key is required.\n\nUsage: solongate-proxy --api-key sg_live_xxx -- <command>\n or: set SOLONGATE_API_KEY=sg_live_xxx\n\nGet your API key at https://solongate.com\n"
122
+ );
123
+ }
124
+ }
125
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
126
+ throw new Error(
127
+ "Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
128
+ );
129
+ }
130
+ const resolvedPolicyPath = resolvePolicyPath(policySource);
131
+ if (configFile) {
132
+ const filePath = resolve(configFile);
133
+ const content = readFileSync(filePath, "utf-8");
134
+ const fileConfig = JSON.parse(content);
135
+ if (!fileConfig.upstream) {
136
+ throw new Error('Config file must include "upstream" with at least "command" or "url"');
137
+ }
138
+ const cfgPolicySource = fileConfig.policy ?? policySource;
139
+ return {
140
+ upstream: fileConfig.upstream,
141
+ policy: loadPolicy(cfgPolicySource),
142
+ name: fileConfig.name ?? name,
143
+ verbose: fileConfig.verbose ?? verbose,
144
+ rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
145
+ globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
146
+ apiKey: apiKey ?? fileConfig.apiKey,
147
+ apiUrl: apiUrl ?? fileConfig.apiUrl,
148
+ port: port ?? fileConfig.port,
149
+ policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0
150
+ };
151
+ }
152
+ if (upstreamUrl) {
153
+ const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
154
+ return {
155
+ upstream: {
156
+ transport,
157
+ command: "",
158
+ // not used for URL-based transports
159
+ url: upstreamUrl
160
+ },
161
+ policy: loadPolicy(policySource),
162
+ name,
163
+ verbose,
164
+ rateLimitPerTool,
165
+ globalRateLimit,
166
+ apiKey,
167
+ apiUrl,
168
+ port,
169
+ policyPath: resolvedPolicyPath ?? void 0
170
+ };
171
+ }
172
+ if (upstreamArgs.length === 0) {
173
+ throw new Error(
174
+ "No upstream server command provided.\n\nUsage: solongate-proxy [options] -- <command> [args...]\n\nExamples:\n solongate-proxy -- node my-server.js\n solongate-proxy --policy restricted -- npx @openclaw/server\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
175
+ );
176
+ }
177
+ const [command, ...commandArgs] = upstreamArgs;
178
+ return {
179
+ upstream: {
180
+ transport: upstreamTransport ?? "stdio",
181
+ command,
182
+ args: commandArgs,
183
+ env: { ...process.env }
184
+ },
185
+ policy: loadPolicy(policySource),
186
+ name,
187
+ verbose,
188
+ rateLimitPerTool,
189
+ globalRateLimit,
190
+ apiKey,
191
+ apiUrl,
192
+ port,
193
+ policyPath: resolvedPolicyPath ?? void 0
194
+ };
195
+ }
196
+ function resolvePolicyPath(source) {
197
+ if (PRESETS[source]) return null;
198
+ const filePath = resolve(source);
199
+ if (existsSync(filePath)) return filePath;
200
+ return null;
201
+ }
202
+ var PRESETS;
203
+ var init_config = __esm({
204
+ "src/config.ts"() {
205
+ "use strict";
206
+ PRESETS = {
207
+ restricted: {
208
+ id: "restricted",
209
+ name: "Restricted",
210
+ description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
211
+ version: 1,
212
+ rules: [
213
+ {
214
+ id: "deny-dangerous-commands",
215
+ description: "Block dangerous shell commands",
216
+ effect: "DENY",
217
+ priority: 50,
218
+ toolPattern: "*",
219
+ permission: "EXECUTE",
220
+ minimumTrustLevel: "UNTRUSTED",
221
+ enabled: true,
222
+ commandConstraints: {
223
+ denied: [
224
+ "rm -rf *",
225
+ "rm -r /*",
226
+ "mkfs*",
227
+ "dd if=*",
228
+ "curl*|*bash*",
229
+ "curl*|*sh*",
230
+ "wget*|*bash*",
231
+ "wget*|*sh*",
232
+ "shutdown*",
233
+ "reboot*",
234
+ "kill -9*",
235
+ "chmod*777*",
236
+ "iptables*",
237
+ "passwd*",
238
+ "useradd*",
239
+ "userdel*"
240
+ ]
241
+ },
242
+ createdAt: "",
243
+ updatedAt: ""
244
+ },
245
+ {
246
+ id: "deny-sensitive-paths",
247
+ description: "Block access to sensitive files",
248
+ effect: "DENY",
249
+ priority: 51,
250
+ toolPattern: "*",
251
+ permission: "EXECUTE",
252
+ minimumTrustLevel: "UNTRUSTED",
253
+ enabled: true,
254
+ pathConstraints: {
255
+ denied: [
256
+ "**/.env*",
257
+ "**/.ssh/**",
258
+ "**/.aws/**",
259
+ "**/.kube/**",
260
+ "**/credentials*",
261
+ "**/secrets*",
262
+ "**/*.pem",
263
+ "**/*.key",
264
+ "/etc/passwd",
265
+ "/etc/shadow",
266
+ "/proc/**",
267
+ "/dev/**"
268
+ ]
269
+ },
270
+ createdAt: "",
271
+ updatedAt: ""
272
+ },
273
+ {
274
+ id: "allow-rest",
275
+ description: "Allow all other tool calls",
276
+ effect: "ALLOW",
277
+ priority: 1e3,
278
+ toolPattern: "*",
279
+ permission: "EXECUTE",
280
+ minimumTrustLevel: "UNTRUSTED",
281
+ enabled: true,
282
+ createdAt: "",
283
+ updatedAt: ""
284
+ }
285
+ ],
286
+ createdAt: "",
287
+ updatedAt: ""
288
+ },
289
+ "read-only": {
290
+ id: "read-only",
291
+ name: "Read Only",
292
+ description: "Only allows read/list/get/search/query tools",
293
+ version: 1,
294
+ rules: [
295
+ {
296
+ id: "allow-read",
297
+ description: "Allow read tools",
298
+ effect: "ALLOW",
299
+ priority: 100,
300
+ toolPattern: "*read*",
301
+ permission: "EXECUTE",
302
+ minimumTrustLevel: "UNTRUSTED",
303
+ enabled: true,
304
+ createdAt: "",
305
+ updatedAt: ""
306
+ },
307
+ {
308
+ id: "allow-list",
309
+ description: "Allow list tools",
310
+ effect: "ALLOW",
311
+ priority: 101,
312
+ toolPattern: "*list*",
313
+ permission: "EXECUTE",
314
+ minimumTrustLevel: "UNTRUSTED",
315
+ enabled: true,
316
+ createdAt: "",
317
+ updatedAt: ""
318
+ },
319
+ {
320
+ id: "allow-get",
321
+ description: "Allow get tools",
322
+ effect: "ALLOW",
323
+ priority: 102,
324
+ toolPattern: "*get*",
325
+ permission: "EXECUTE",
326
+ minimumTrustLevel: "UNTRUSTED",
327
+ enabled: true,
328
+ createdAt: "",
329
+ updatedAt: ""
330
+ },
331
+ {
332
+ id: "allow-search",
333
+ description: "Allow search tools",
334
+ effect: "ALLOW",
335
+ priority: 103,
336
+ toolPattern: "*search*",
337
+ permission: "EXECUTE",
338
+ minimumTrustLevel: "UNTRUSTED",
339
+ enabled: true,
340
+ createdAt: "",
341
+ updatedAt: ""
342
+ },
343
+ {
344
+ id: "allow-query",
345
+ description: "Allow query tools",
346
+ effect: "ALLOW",
347
+ priority: 104,
348
+ toolPattern: "*query*",
349
+ permission: "EXECUTE",
350
+ minimumTrustLevel: "UNTRUSTED",
351
+ enabled: true,
352
+ createdAt: "",
353
+ updatedAt: ""
354
+ }
355
+ ],
356
+ createdAt: "",
357
+ updatedAt: ""
358
+ },
359
+ sandbox: {
360
+ id: "sandbox",
361
+ name: "Sandbox",
362
+ description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
363
+ version: 1,
364
+ rules: [
365
+ {
366
+ id: "deny-dangerous-commands",
367
+ description: "Block dangerous shell commands",
368
+ effect: "DENY",
369
+ priority: 50,
370
+ toolPattern: "*",
371
+ permission: "EXECUTE",
372
+ minimumTrustLevel: "UNTRUSTED",
373
+ enabled: true,
374
+ commandConstraints: {
375
+ denied: [
376
+ "rm -rf *",
377
+ "rm -r /*",
378
+ "mkfs*",
379
+ "dd if=*",
380
+ "curl*|*bash*",
381
+ "wget*|*sh*",
382
+ "shutdown*",
383
+ "reboot*",
384
+ "chmod*777*"
385
+ ]
386
+ },
387
+ createdAt: "",
388
+ updatedAt: ""
389
+ },
390
+ {
391
+ id: "allow-safe-commands",
392
+ description: "Allow safe shell commands only",
393
+ effect: "ALLOW",
394
+ priority: 100,
395
+ toolPattern: "*shell*",
396
+ permission: "EXECUTE",
397
+ minimumTrustLevel: "UNTRUSTED",
398
+ enabled: true,
399
+ commandConstraints: {
400
+ allowed: [
401
+ "ls*",
402
+ "cat*",
403
+ "head*",
404
+ "tail*",
405
+ "wc*",
406
+ "grep*",
407
+ "find*",
408
+ "echo*",
409
+ "pwd",
410
+ "whoami",
411
+ "date",
412
+ "env",
413
+ "git*",
414
+ "npm*",
415
+ "pnpm*",
416
+ "yarn*",
417
+ "node*",
418
+ "python*",
419
+ "pip*"
420
+ ]
421
+ },
422
+ createdAt: "",
423
+ updatedAt: ""
424
+ },
425
+ {
426
+ id: "deny-sensitive-paths",
427
+ description: "Block access to sensitive files",
428
+ effect: "DENY",
429
+ priority: 51,
430
+ toolPattern: "*",
431
+ permission: "EXECUTE",
432
+ minimumTrustLevel: "UNTRUSTED",
433
+ enabled: true,
434
+ pathConstraints: {
435
+ denied: [
436
+ "**/.env*",
437
+ "**/.ssh/**",
438
+ "**/.aws/**",
439
+ "**/credentials*",
440
+ "**/*.pem",
441
+ "**/*.key"
442
+ ]
443
+ },
444
+ createdAt: "",
445
+ updatedAt: ""
446
+ },
447
+ {
448
+ id: "allow-rest",
449
+ description: "Allow all other tools",
450
+ effect: "ALLOW",
451
+ priority: 1e3,
452
+ toolPattern: "*",
453
+ permission: "EXECUTE",
454
+ minimumTrustLevel: "UNTRUSTED",
455
+ enabled: true,
456
+ createdAt: "",
457
+ updatedAt: ""
458
+ }
459
+ ],
460
+ createdAt: "",
461
+ updatedAt: ""
462
+ },
463
+ permissive: {
464
+ id: "permissive",
465
+ name: "Permissive",
466
+ description: "Allows all tool calls (monitoring only)",
467
+ version: 1,
468
+ rules: [
469
+ {
470
+ id: "allow-all",
471
+ description: "Allow all",
472
+ effect: "ALLOW",
473
+ priority: 1e3,
474
+ toolPattern: "*",
475
+ permission: "EXECUTE",
476
+ minimumTrustLevel: "UNTRUSTED",
477
+ enabled: true,
478
+ createdAt: "",
479
+ updatedAt: ""
480
+ }
481
+ ],
482
+ createdAt: "",
483
+ updatedAt: ""
484
+ },
485
+ "deny-all": {
486
+ id: "deny-all",
487
+ name: "Deny All",
488
+ description: "Blocks all tool calls",
489
+ version: 1,
490
+ rules: [],
491
+ createdAt: "",
492
+ updatedAt: ""
493
+ }
494
+ };
495
+ }
496
+ });
497
+
7
498
  // src/init.ts
8
499
  var init_exports = {};
9
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, copyFileSync, mkdirSync } from "fs";
500
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
10
501
  import { resolve as resolve2, join } from "path";
11
502
  import { createInterface } from "readline";
12
503
  function findConfigFile(explicitPath) {
13
504
  if (explicitPath) {
14
- if (existsSync2(explicitPath)) {
505
+ if (existsSync3(explicitPath)) {
15
506
  return { path: resolve2(explicitPath), type: "mcp" };
16
507
  }
17
508
  return null;
18
509
  }
19
510
  for (const searchPath of SEARCH_PATHS) {
20
511
  const full = resolve2(searchPath);
21
- if (existsSync2(full)) return { path: full, type: "mcp" };
512
+ if (existsSync3(full)) return { path: full, type: "mcp" };
22
513
  }
23
514
  for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
24
- if (existsSync2(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
515
+ if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
25
516
  }
26
517
  return null;
27
518
  }
28
519
  function readConfig(filePath) {
29
- const content = readFileSync2(filePath, "utf-8");
520
+ const content = readFileSync3(filePath, "utf-8");
30
521
  const parsed = JSON.parse(content);
31
522
  if (parsed.mcpServers) return parsed;
32
523
  throw new Error(`Unrecognized config format in ${filePath}`);
@@ -97,7 +588,7 @@ function parseInitArgs(argv) {
97
588
  }
98
589
  }
99
590
  if (!POLICY_PRESETS.includes(options.policy)) {
100
- if (!existsSync2(resolve2(options.policy))) {
591
+ if (!existsSync3(resolve2(options.policy))) {
101
592
  console.error(`Unknown policy: ${options.policy}`);
102
593
  console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
103
594
  process.exit(1);
@@ -141,16 +632,16 @@ function installClaudeCodeHooks(apiKey) {
141
632
  const hooksDir = resolve2(".claude", "hooks");
142
633
  mkdirSync(hooksDir, { recursive: true });
143
634
  const guardPath = join(hooksDir, "guard.mjs");
144
- writeFileSync(guardPath, GUARD_SCRIPT);
635
+ writeFileSync2(guardPath, GUARD_SCRIPT);
145
636
  console.error(` Created ${guardPath}`);
146
637
  const auditPath = join(hooksDir, "audit.mjs");
147
- writeFileSync(auditPath, AUDIT_SCRIPT);
638
+ writeFileSync2(auditPath, AUDIT_SCRIPT);
148
639
  console.error(` Created ${auditPath}`);
149
640
  const settingsPath = resolve2(".claude", "settings.json");
150
641
  let settings = {};
151
- if (existsSync2(settingsPath)) {
642
+ if (existsSync3(settingsPath)) {
152
643
  try {
153
- settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
644
+ settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
154
645
  } catch {
155
646
  }
156
647
  }
@@ -184,7 +675,7 @@ function installClaudeCodeHooks(apiKey) {
184
675
  const envObj = settings.env || {};
185
676
  envObj.SOLONGATE_API_KEY = apiKey;
186
677
  settings.env = envObj;
187
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
678
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
188
679
  console.error(` Created ${settingsPath}`);
189
680
  console.error("");
190
681
  console.error(" Claude Code hooks installed!");
@@ -193,7 +684,7 @@ function installClaudeCodeHooks(apiKey) {
193
684
  }
194
685
  function ensureEnvFile() {
195
686
  const envPath = resolve2(".env");
196
- if (!existsSync2(envPath)) {
687
+ if (!existsSync3(envPath)) {
197
688
  const envContent = `# SolonGate API Keys
198
689
  # Get your keys from the dashboard: https://solongate.com
199
690
  # IMPORTANT: Never commit this file to git!
@@ -204,14 +695,14 @@ SOLONGATE_API_KEY=sg_live_your_key_here
204
695
  # Test key \u2014 local-only, no cloud connection (for development)
205
696
  # SOLONGATE_API_KEY=sg_test_your_key_here
206
697
  `;
207
- writeFileSync(envPath, envContent);
698
+ writeFileSync2(envPath, envContent);
208
699
  console.error(` Created .env with placeholder API keys`);
209
700
  console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
210
701
  console.error("");
211
702
  }
212
703
  const gitignorePath = resolve2(".gitignore");
213
- if (existsSync2(gitignorePath)) {
214
- let gitignore = readFileSync2(gitignorePath, "utf-8");
704
+ if (existsSync3(gitignorePath)) {
705
+ let gitignore = readFileSync3(gitignorePath, "utf-8");
215
706
  let updated = false;
216
707
  if (!gitignore.includes(".env")) {
217
708
  gitignore = gitignore.trimEnd() + "\n.env\n.env.local\n";
@@ -222,11 +713,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
222
713
  updated = true;
223
714
  }
224
715
  if (updated) {
225
- writeFileSync(gitignorePath, gitignore);
716
+ writeFileSync2(gitignorePath, gitignore);
226
717
  console.error(` Updated .gitignore (added .env + .mcp.json)`);
227
718
  }
228
719
  } else {
229
- writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
720
+ writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
230
721
  console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
231
722
  }
232
723
  }
@@ -274,7 +765,7 @@ async function main() {
274
765
  console.error("");
275
766
  const backupPath = configInfo.path + ".solongate-backup";
276
767
  if (options.restore) {
277
- if (!existsSync2(backupPath)) {
768
+ if (!existsSync3(backupPath)) {
278
769
  console.error(" No backup found. Nothing to restore.");
279
770
  process.exit(1);
280
771
  }
@@ -331,8 +822,8 @@ async function main() {
331
822
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
332
823
  if (!apiKey) {
333
824
  const envPath = resolve2(".env");
334
- if (existsSync2(envPath)) {
335
- const envContent = readFileSync2(envPath, "utf-8");
825
+ if (existsSync3(envPath)) {
826
+ const envContent = readFileSync3(envPath, "utf-8");
336
827
  const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
337
828
  if (match) apiKey = match[1];
338
829
  }
@@ -367,16 +858,16 @@ async function main() {
367
858
  console.error(JSON.stringify(newConfig, null, 2));
368
859
  process.exit(0);
369
860
  }
370
- if (!existsSync2(backupPath)) {
861
+ if (!existsSync3(backupPath)) {
371
862
  copyFileSync(configInfo.path, backupPath);
372
863
  console.error(` Backup: ${backupPath}`);
373
864
  }
374
865
  if (configInfo.type === "claude-desktop") {
375
- const original = JSON.parse(readFileSync2(configInfo.path, "utf-8"));
866
+ const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
376
867
  original.mcpServers = newConfig.mcpServers;
377
- writeFileSync(configInfo.path, JSON.stringify(original, null, 2) + "\n");
868
+ writeFileSync2(configInfo.path, JSON.stringify(original, null, 2) + "\n");
378
869
  } else {
379
- writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
870
+ writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
380
871
  }
381
872
  console.error(" Config updated!");
382
873
  console.error("");
@@ -635,7 +1126,7 @@ process.stdin.on('end', async () => {
635
1126
 
636
1127
  // src/inject.ts
637
1128
  var inject_exports = {};
638
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync as copyFileSync2 } from "fs";
1129
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync as copyFileSync2 } from "fs";
639
1130
  import { resolve as resolve3 } from "path";
640
1131
  import { execSync } from "child_process";
641
1132
  function parseInjectArgs(argv) {
@@ -668,7 +1159,7 @@ function parseInjectArgs(argv) {
668
1159
  return opts;
669
1160
  }
670
1161
  function printHelp2() {
671
- log2(`
1162
+ log3(`
672
1163
  SolonGate Inject \u2014 Add security to your MCP server in seconds
673
1164
 
674
1165
  USAGE
@@ -692,7 +1183,7 @@ WHAT IT DOES
692
1183
  All tool() calls are automatically protected by SolonGate.
693
1184
  `);
694
1185
  }
695
- function log2(msg) {
1186
+ function log3(msg) {
696
1187
  process.stderr.write(msg + "\n");
697
1188
  }
698
1189
  function printBanner(subtitle) {
@@ -705,18 +1196,18 @@ function printBanner(subtitle) {
705
1196
  " \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"
706
1197
  ];
707
1198
  const colors = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
708
- log2("");
1199
+ log3("");
709
1200
  for (let i = 0; i < lines.length; i++) {
710
- log2(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
1201
+ log3(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
711
1202
  }
712
- log2("");
713
- log2(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
714
- log2("");
1203
+ log3("");
1204
+ log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
1205
+ log3("");
715
1206
  }
716
1207
  function detectProject() {
717
- if (!existsSync3(resolve3("package.json"))) return false;
1208
+ if (!existsSync4(resolve3("package.json"))) return false;
718
1209
  try {
719
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1210
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
720
1211
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
721
1212
  return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
722
1213
  } catch {
@@ -725,18 +1216,18 @@ function detectProject() {
725
1216
  }
726
1217
  function findTsEntryFile() {
727
1218
  try {
728
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1219
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
729
1220
  if (pkg.bin) {
730
1221
  const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
731
1222
  if (typeof binPath === "string") {
732
1223
  const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
733
- if (existsSync3(resolve3(srcPath))) return resolve3(srcPath);
734
- if (existsSync3(resolve3(binPath))) return resolve3(binPath);
1224
+ if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
1225
+ if (existsSync4(resolve3(binPath))) return resolve3(binPath);
735
1226
  }
736
1227
  }
737
1228
  if (pkg.main) {
738
1229
  const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
739
- if (existsSync3(resolve3(srcPath))) return resolve3(srcPath);
1230
+ if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
740
1231
  }
741
1232
  } catch {
742
1233
  }
@@ -750,9 +1241,9 @@ function findTsEntryFile() {
750
1241
  ];
751
1242
  for (const c3 of candidates) {
752
1243
  const full = resolve3(c3);
753
- if (existsSync3(full)) {
1244
+ if (existsSync4(full)) {
754
1245
  try {
755
- const content = readFileSync3(full, "utf-8");
1246
+ const content = readFileSync4(full, "utf-8");
756
1247
  if (content.includes("McpServer") || content.includes("McpServer")) {
757
1248
  return full;
758
1249
  }
@@ -761,39 +1252,39 @@ function findTsEntryFile() {
761
1252
  }
762
1253
  }
763
1254
  for (const c3 of candidates) {
764
- if (existsSync3(resolve3(c3))) return resolve3(c3);
1255
+ if (existsSync4(resolve3(c3))) return resolve3(c3);
765
1256
  }
766
1257
  return null;
767
1258
  }
768
1259
  function detectPackageManager() {
769
- if (existsSync3(resolve3("pnpm-lock.yaml"))) return "pnpm";
770
- if (existsSync3(resolve3("yarn.lock"))) return "yarn";
1260
+ if (existsSync4(resolve3("pnpm-lock.yaml"))) return "pnpm";
1261
+ if (existsSync4(resolve3("yarn.lock"))) return "yarn";
771
1262
  return "npm";
772
1263
  }
773
1264
  function installSdk() {
774
1265
  try {
775
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1266
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
776
1267
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
777
1268
  if (allDeps["@solongate/sdk"]) {
778
- log2(" @solongate/sdk already installed");
1269
+ log3(" @solongate/sdk already installed");
779
1270
  return true;
780
1271
  }
781
1272
  } catch {
782
1273
  }
783
1274
  const pm = detectPackageManager();
784
1275
  const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
785
- log2(` Installing @solongate/sdk via ${pm}...`);
1276
+ log3(` Installing @solongate/sdk via ${pm}...`);
786
1277
  try {
787
1278
  execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
788
1279
  return true;
789
1280
  } catch (err) {
790
- log2(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
791
- log2(" You can install manually: npm install @solongate/sdk");
1281
+ log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
1282
+ log3(" You can install manually: npm install @solongate/sdk");
792
1283
  return false;
793
1284
  }
794
1285
  }
795
1286
  function injectTypeScript(filePath) {
796
- const original = readFileSync3(filePath, "utf-8");
1287
+ const original = readFileSync4(filePath, "utf-8");
797
1288
  const changes = [];
798
1289
  let modified = original;
799
1290
  if (modified.includes("SecureMcpServer")) {
@@ -871,14 +1362,14 @@ function findImportInsertPosition(content) {
871
1362
  }
872
1363
  function showDiff(result) {
873
1364
  if (result.original === result.modified) {
874
- log2(" No changes needed.");
1365
+ log3(" No changes needed.");
875
1366
  return;
876
1367
  }
877
1368
  const origLines = result.original.split("\n");
878
1369
  const modLines = result.modified.split("\n");
879
- log2("");
880
- log2(` File: ${result.file}`);
881
- log2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1370
+ log3("");
1371
+ log3(` File: ${result.file}`);
1372
+ log3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
882
1373
  const maxLines = Math.max(origLines.length, modLines.length);
883
1374
  let diffCount = 0;
884
1375
  for (let i = 0; i < maxLines; i++) {
@@ -886,99 +1377,99 @@ function showDiff(result) {
886
1377
  const mod = modLines[i];
887
1378
  if (orig !== mod) {
888
1379
  if (diffCount < 30) {
889
- if (orig !== void 0) log2(` - ${orig}`);
890
- if (mod !== void 0) log2(` + ${mod}`);
1380
+ if (orig !== void 0) log3(` - ${orig}`);
1381
+ if (mod !== void 0) log3(` + ${mod}`);
891
1382
  }
892
1383
  diffCount++;
893
1384
  }
894
1385
  }
895
1386
  if (diffCount > 30) {
896
- log2(` ... and ${diffCount - 30} more line changes`);
1387
+ log3(` ... and ${diffCount - 30} more line changes`);
897
1388
  }
898
- log2("");
1389
+ log3("");
899
1390
  }
900
1391
  async function main2() {
901
1392
  const opts = parseInjectArgs(process.argv);
902
1393
  printBanner("Inject SDK");
903
1394
  if (!detectProject()) {
904
- log2(" Could not detect a TypeScript MCP server project.");
905
- log2(" Make sure you are in a project directory with:");
906
- log2(" package.json + @modelcontextprotocol/sdk in dependencies");
907
- log2("");
908
- log2(" To create a new MCP server: npx @solongate/proxy create <name>");
1395
+ log3(" Could not detect a TypeScript MCP server project.");
1396
+ log3(" Make sure you are in a project directory with:");
1397
+ log3(" package.json + @modelcontextprotocol/sdk in dependencies");
1398
+ log3("");
1399
+ log3(" To create a new MCP server: npx @solongate/proxy create <name>");
909
1400
  process.exit(1);
910
1401
  }
911
- log2(" Language: TypeScript");
1402
+ log3(" Language: TypeScript");
912
1403
  const entryFile = opts.file ? resolve3(opts.file) : findTsEntryFile();
913
- if (!entryFile || !existsSync3(entryFile)) {
914
- log2(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
915
- log2("");
916
- log2(" Specify it manually: --file <path>");
917
- log2("");
918
- log2(" Common entry points:");
919
- log2(" src/index.ts, src/server.ts, index.ts");
1404
+ if (!entryFile || !existsSync4(entryFile)) {
1405
+ log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
1406
+ log3("");
1407
+ log3(" Specify it manually: --file <path>");
1408
+ log3("");
1409
+ log3(" Common entry points:");
1410
+ log3(" src/index.ts, src/server.ts, index.ts");
920
1411
  process.exit(1);
921
1412
  }
922
- log2(` Entry: ${entryFile}`);
923
- log2("");
1413
+ log3(` Entry: ${entryFile}`);
1414
+ log3("");
924
1415
  const backupPath = entryFile + ".solongate-backup";
925
1416
  if (opts.restore) {
926
- if (!existsSync3(backupPath)) {
927
- log2(" No backup found. Nothing to restore.");
1417
+ if (!existsSync4(backupPath)) {
1418
+ log3(" No backup found. Nothing to restore.");
928
1419
  process.exit(1);
929
1420
  }
930
1421
  copyFileSync2(backupPath, entryFile);
931
- log2(` Restored original file from backup.`);
932
- log2(` Backup: ${backupPath}`);
1422
+ log3(` Restored original file from backup.`);
1423
+ log3(` Backup: ${backupPath}`);
933
1424
  process.exit(0);
934
1425
  }
935
1426
  if (!opts.skipInstall && !opts.dryRun) {
936
1427
  installSdk();
937
- log2("");
1428
+ log3("");
938
1429
  }
939
1430
  const result = injectTypeScript(entryFile);
940
- log2(` Changes (${result.changes.length}):`);
1431
+ log3(` Changes (${result.changes.length}):`);
941
1432
  for (const change of result.changes) {
942
- log2(` - ${change}`);
1433
+ log3(` - ${change}`);
943
1434
  }
944
1435
  if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
945
- log2("");
946
- log2(" Your MCP server is already protected by SolonGate!");
1436
+ log3("");
1437
+ log3(" Your MCP server is already protected by SolonGate!");
947
1438
  process.exit(0);
948
1439
  }
949
1440
  if (result.original === result.modified) {
950
- log2("");
951
- log2(" No changes were made. The file may not contain recognizable MCP patterns.");
952
- log2(" See docs: https://solongate.com/docs/integration");
1441
+ log3("");
1442
+ log3(" No changes were made. The file may not contain recognizable MCP patterns.");
1443
+ log3(" See docs: https://solongate.com/docs/integration");
953
1444
  process.exit(0);
954
1445
  }
955
1446
  if (opts.dryRun) {
956
- log2("");
957
- log2(" --- DRY RUN (no changes written) ---");
1447
+ log3("");
1448
+ log3(" --- DRY RUN (no changes written) ---");
958
1449
  showDiff(result);
959
- log2(" To apply: npx @solongate/proxy inject");
1450
+ log3(" To apply: npx @solongate/proxy inject");
960
1451
  process.exit(0);
961
1452
  }
962
- if (!existsSync3(backupPath)) {
1453
+ if (!existsSync4(backupPath)) {
963
1454
  copyFileSync2(entryFile, backupPath);
964
- log2("");
965
- log2(` Backup: ${backupPath}`);
966
- }
967
- writeFileSync2(entryFile, result.modified);
968
- log2("");
969
- log2(" \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\u2510");
970
- log2(" \u2502 SolonGate SDK injected successfully! \u2502");
971
- log2(" \u2502 \u2502");
972
- log2(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
973
- log2(" \u2502 All tool() calls are now auto-protected. \u2502");
974
- log2(" \u2502 \u2502");
975
- log2(" \u2502 Set your API key: \u2502");
976
- log2(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
977
- log2(" \u2502 \u2502");
978
- log2(" \u2502 To undo: \u2502");
979
- log2(" \u2502 npx @solongate/proxy inject --restore \u2502");
980
- log2(" \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\u2518");
981
- log2("");
1455
+ log3("");
1456
+ log3(` Backup: ${backupPath}`);
1457
+ }
1458
+ writeFileSync3(entryFile, result.modified);
1459
+ log3("");
1460
+ log3(" \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\u2510");
1461
+ log3(" \u2502 SolonGate SDK injected successfully! \u2502");
1462
+ log3(" \u2502 \u2502");
1463
+ log3(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
1464
+ log3(" \u2502 All tool() calls are now auto-protected. \u2502");
1465
+ log3(" \u2502 \u2502");
1466
+ log3(" \u2502 Set your API key: \u2502");
1467
+ log3(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
1468
+ log3(" \u2502 \u2502");
1469
+ log3(" \u2502 To undo: \u2502");
1470
+ log3(" \u2502 npx @solongate/proxy inject --restore \u2502");
1471
+ log3(" \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\u2518");
1472
+ log3("");
982
1473
  }
983
1474
  var c;
984
1475
  var init_inject = __esm({
@@ -999,7 +1490,7 @@ var init_inject = __esm({
999
1490
  blue6: "\x1B[38;2;170;200;250m"
1000
1491
  };
1001
1492
  main2().catch((err) => {
1002
- log2(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1493
+ log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1003
1494
  process.exit(1);
1004
1495
  });
1005
1496
  }
@@ -1007,10 +1498,10 @@ var init_inject = __esm({
1007
1498
 
1008
1499
  // src/create.ts
1009
1500
  var create_exports = {};
1010
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
1501
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
1011
1502
  import { resolve as resolve4, join as join2 } from "path";
1012
1503
  import { execSync as execSync2 } from "child_process";
1013
- function log3(msg) {
1504
+ function log4(msg) {
1014
1505
  process.stderr.write(msg + "\n");
1015
1506
  }
1016
1507
  function printBanner2(subtitle) {
@@ -1023,13 +1514,13 @@ function printBanner2(subtitle) {
1023
1514
  " \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"
1024
1515
  ];
1025
1516
  const colors = [c2.blue1, c2.blue2, c2.blue3, c2.blue4, c2.blue5, c2.blue6];
1026
- log3("");
1517
+ log4("");
1027
1518
  for (let i = 0; i < lines.length; i++) {
1028
- log3(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
1519
+ log4(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
1029
1520
  }
1030
- log3("");
1031
- log3(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
1032
- log3("");
1521
+ log4("");
1522
+ log4(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
1523
+ log4("");
1033
1524
  }
1034
1525
  function withSpinner(message, fn) {
1035
1526
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -1078,20 +1569,20 @@ function parseCreateArgs(argv) {
1078
1569
  }
1079
1570
  }
1080
1571
  if (!opts.name) {
1081
- log3("");
1082
- log3(" Error: Project name required.");
1083
- log3("");
1084
- log3(" Usage: npx @solongate/proxy create <name>");
1085
- log3("");
1086
- log3(" Examples:");
1087
- log3(" npx @solongate/proxy create my-mcp-server");
1088
- log3(" npx @solongate/proxy create weather-api");
1572
+ log4("");
1573
+ log4(" Error: Project name required.");
1574
+ log4("");
1575
+ log4(" Usage: npx @solongate/proxy create <name>");
1576
+ log4("");
1577
+ log4(" Examples:");
1578
+ log4(" npx @solongate/proxy create my-mcp-server");
1579
+ log4(" npx @solongate/proxy create weather-api");
1089
1580
  process.exit(1);
1090
1581
  }
1091
1582
  return opts;
1092
1583
  }
1093
1584
  function printHelp3() {
1094
- log3(`
1585
+ log4(`
1095
1586
  SolonGate Create \u2014 Scaffold a secure MCP server in seconds
1096
1587
 
1097
1588
  USAGE
@@ -1108,7 +1599,7 @@ EXAMPLES
1108
1599
  `);
1109
1600
  }
1110
1601
  function createProject(dir, name, _policy) {
1111
- writeFileSync3(
1602
+ writeFileSync4(
1112
1603
  join2(dir, "package.json"),
1113
1604
  JSON.stringify(
1114
1605
  {
@@ -1138,7 +1629,7 @@ function createProject(dir, name, _policy) {
1138
1629
  2
1139
1630
  ) + "\n"
1140
1631
  );
1141
- writeFileSync3(
1632
+ writeFileSync4(
1142
1633
  join2(dir, "tsconfig.json"),
1143
1634
  JSON.stringify(
1144
1635
  {
@@ -1160,7 +1651,7 @@ function createProject(dir, name, _policy) {
1160
1651
  ) + "\n"
1161
1652
  );
1162
1653
  mkdirSync2(join2(dir, "src"), { recursive: true });
1163
- writeFileSync3(
1654
+ writeFileSync4(
1164
1655
  join2(dir, "src", "index.ts"),
1165
1656
  `#!/usr/bin/env node
1166
1657
 
@@ -1205,7 +1696,7 @@ console.log('');
1205
1696
  console.log('Press Ctrl+C to stop.');
1206
1697
  `
1207
1698
  );
1208
- writeFileSync3(
1699
+ writeFileSync4(
1209
1700
  join2(dir, ".mcp.json"),
1210
1701
  JSON.stringify(
1211
1702
  {
@@ -1223,12 +1714,12 @@ console.log('Press Ctrl+C to stop.');
1223
1714
  2
1224
1715
  ) + "\n"
1225
1716
  );
1226
- writeFileSync3(
1717
+ writeFileSync4(
1227
1718
  join2(dir, ".env"),
1228
1719
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
1229
1720
  `
1230
1721
  );
1231
- writeFileSync3(
1722
+ writeFileSync4(
1232
1723
  join2(dir, ".gitignore"),
1233
1724
  `node_modules/
1234
1725
  dist/
@@ -1243,8 +1734,8 @@ async function main3() {
1243
1734
  const opts = parseCreateArgs(process.argv);
1244
1735
  const dir = resolve4(opts.name);
1245
1736
  printBanner2("Create MCP Server");
1246
- if (existsSync4(dir)) {
1247
- log3(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
1737
+ if (existsSync5(dir)) {
1738
+ log4(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
1248
1739
  process.exit(1);
1249
1740
  }
1250
1741
  withSpinner(`Setting up ${opts.name}...`, () => {
@@ -1256,19 +1747,19 @@ async function main3() {
1256
1747
  execSync2("npm install", { cwd: dir, stdio: "pipe" });
1257
1748
  });
1258
1749
  }
1259
- log3("");
1750
+ log4("");
1260
1751
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
1261
1752
  const W = 52;
1262
1753
  const hr = "\u2500".repeat(W + 2);
1263
1754
  const bLine = (text) => {
1264
1755
  const visible = stripAnsi(text);
1265
1756
  const padding = W - visible.length;
1266
- log3(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
1757
+ log4(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
1267
1758
  };
1268
- const bEmpty = () => log3(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
1269
- log3(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
1270
- log3(` ${c2.dim}\u2502${c2.reset} ${c2.green}${c2.bold}\u2714 MCP Server created successfully!${c2.reset}${" ".repeat(W - 35)} ${c2.dim}\u2502${c2.reset}`);
1271
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1759
+ const bEmpty = () => log4(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
1760
+ log4(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
1761
+ log4(` ${c2.dim}\u2502${c2.reset} ${c2.green}${c2.bold}\u2714 MCP Server created successfully!${c2.reset}${" ".repeat(W - 35)} ${c2.dim}\u2502${c2.reset}`);
1762
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1272
1763
  bEmpty();
1273
1764
  bLine(`${c2.yellow}Next steps:${c2.reset}`);
1274
1765
  bEmpty();
@@ -1277,7 +1768,7 @@ async function main3() {
1277
1768
  bLine(` ${c2.cyan}$${c2.reset} npm run dev ${c2.dim}# dev mode${c2.reset}`);
1278
1769
  bLine(` ${c2.cyan}$${c2.reset} npm start ${c2.dim}# production${c2.reset}`);
1279
1770
  bEmpty();
1280
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1771
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1281
1772
  bEmpty();
1282
1773
  bLine(`${c2.yellow}Set your API key:${c2.reset}`);
1283
1774
  bEmpty();
@@ -1285,14 +1776,14 @@ async function main3() {
1285
1776
  bEmpty();
1286
1777
  bLine(`${c2.dim}https://dashboard.solongate.com/api-keys/${c2.reset}`);
1287
1778
  bEmpty();
1288
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1779
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1289
1780
  bEmpty();
1290
1781
  bLine(`${c2.yellow}Use with OpenClaw:${c2.reset}`);
1291
1782
  bEmpty();
1292
1783
  bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @openclaw/server`);
1293
1784
  bLine(` ${c2.dim}Wraps OpenClaw servers with SolonGate protection${c2.reset}`);
1294
1785
  bEmpty();
1295
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1786
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1296
1787
  bEmpty();
1297
1788
  bLine(`${c2.yellow}Use with Claude Code / Cursor / MCP client:${c2.reset}`);
1298
1789
  bEmpty();
@@ -1301,516 +1792,170 @@ async function main3() {
1301
1792
  bLine(` ${c2.dim}3.${c2.reset} .mcp.json is auto-detected on startup`);
1302
1793
  bEmpty();
1303
1794
  bLine(`${c2.yellow}Direct test (without MCP client):${c2.reset}`);
1304
- bEmpty();
1305
- bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
1306
- bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
1307
- bEmpty();
1308
- log3(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
1309
- log3("");
1310
- }
1311
- var c2;
1312
- var init_create = __esm({
1313
- "src/create.ts"() {
1314
- "use strict";
1315
- c2 = {
1316
- reset: "\x1B[0m",
1317
- bold: "\x1B[1m",
1318
- dim: "\x1B[2m",
1319
- italic: "\x1B[3m",
1320
- white: "\x1B[97m",
1321
- gray: "\x1B[90m",
1322
- blue1: "\x1B[38;2;20;50;160m",
1323
- blue2: "\x1B[38;2;40;80;190m",
1324
- blue3: "\x1B[38;2;60;110;215m",
1325
- blue4: "\x1B[38;2;90;140;230m",
1326
- blue5: "\x1B[38;2;130;170;240m",
1327
- blue6: "\x1B[38;2;170;200;250m",
1328
- green: "\x1B[38;2;80;200;120m",
1329
- red: "\x1B[38;2;220;80;80m",
1330
- cyan: "\x1B[38;2;100;200;220m",
1331
- yellow: "\x1B[38;2;220;200;80m",
1332
- bgBlue: "\x1B[48;2;20;50;160m"
1333
- };
1334
- main3().catch((err) => {
1335
- log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1336
- process.exit(1);
1337
- });
1338
- }
1339
- });
1340
-
1341
- // src/config.ts
1342
- import { readFileSync, existsSync } from "fs";
1343
- import { resolve } from "path";
1344
- async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
1345
- const url = `${apiUrl}/api/v1/policies/${policyId ?? "default"}`;
1346
- const res = await fetch(url, {
1347
- headers: { "Authorization": `Bearer ${apiKey}` }
1348
- });
1349
- if (!res.ok) {
1350
- const body = await res.text().catch(() => "");
1351
- throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
1352
- }
1353
- const data = await res.json();
1354
- return {
1355
- id: String(data.id ?? "cloud"),
1356
- name: String(data.name ?? "Cloud Policy"),
1357
- version: Number(data._version ?? 1),
1358
- rules: data.rules ?? [],
1359
- createdAt: String(data._created_at ?? ""),
1360
- updatedAt: ""
1361
- };
1362
- }
1363
- async function sendAuditLog(apiKey, apiUrl, entry) {
1364
- const url = `${apiUrl}/api/v1/audit-logs`;
1365
- try {
1366
- const res = await fetch(url, {
1367
- method: "POST",
1368
- headers: {
1369
- "Authorization": `Bearer ${apiKey}`,
1370
- "Content-Type": "application/json"
1371
- },
1372
- body: JSON.stringify(entry)
1373
- });
1374
- if (!res.ok) {
1375
- const body = await res.text().catch(() => "");
1376
- process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
1377
- `);
1378
- }
1379
- } catch (err) {
1380
- process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
1381
- `);
1382
- }
1383
- }
1384
- var PRESETS = {
1385
- restricted: {
1386
- id: "restricted",
1387
- name: "Restricted",
1388
- description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
1389
- version: 1,
1390
- rules: [
1391
- {
1392
- id: "deny-dangerous-commands",
1393
- description: "Block dangerous shell commands",
1394
- effect: "DENY",
1395
- priority: 50,
1396
- toolPattern: "*",
1397
- permission: "EXECUTE",
1398
- minimumTrustLevel: "UNTRUSTED",
1399
- enabled: true,
1400
- commandConstraints: {
1401
- denied: [
1402
- "rm -rf *",
1403
- "rm -r /*",
1404
- "mkfs*",
1405
- "dd if=*",
1406
- "curl*|*bash*",
1407
- "curl*|*sh*",
1408
- "wget*|*bash*",
1409
- "wget*|*sh*",
1410
- "shutdown*",
1411
- "reboot*",
1412
- "kill -9*",
1413
- "chmod*777*",
1414
- "iptables*",
1415
- "passwd*",
1416
- "useradd*",
1417
- "userdel*"
1418
- ]
1419
- },
1420
- createdAt: "",
1421
- updatedAt: ""
1422
- },
1423
- {
1424
- id: "deny-sensitive-paths",
1425
- description: "Block access to sensitive files",
1426
- effect: "DENY",
1427
- priority: 51,
1428
- toolPattern: "*",
1429
- permission: "EXECUTE",
1430
- minimumTrustLevel: "UNTRUSTED",
1431
- enabled: true,
1432
- pathConstraints: {
1433
- denied: [
1434
- "**/.env*",
1435
- "**/.ssh/**",
1436
- "**/.aws/**",
1437
- "**/.kube/**",
1438
- "**/credentials*",
1439
- "**/secrets*",
1440
- "**/*.pem",
1441
- "**/*.key",
1442
- "/etc/passwd",
1443
- "/etc/shadow",
1444
- "/proc/**",
1445
- "/dev/**"
1446
- ]
1447
- },
1448
- createdAt: "",
1449
- updatedAt: ""
1450
- },
1451
- {
1452
- id: "allow-rest",
1453
- description: "Allow all other tool calls",
1454
- effect: "ALLOW",
1455
- priority: 1e3,
1456
- toolPattern: "*",
1457
- permission: "EXECUTE",
1458
- minimumTrustLevel: "UNTRUSTED",
1459
- enabled: true,
1460
- createdAt: "",
1461
- updatedAt: ""
1462
- }
1463
- ],
1464
- createdAt: "",
1465
- updatedAt: ""
1466
- },
1467
- "read-only": {
1468
- id: "read-only",
1469
- name: "Read Only",
1470
- description: "Only allows read/list/get/search/query tools",
1471
- version: 1,
1472
- rules: [
1473
- {
1474
- id: "allow-read",
1475
- description: "Allow read tools",
1476
- effect: "ALLOW",
1477
- priority: 100,
1478
- toolPattern: "*read*",
1479
- permission: "EXECUTE",
1480
- minimumTrustLevel: "UNTRUSTED",
1481
- enabled: true,
1482
- createdAt: "",
1483
- updatedAt: ""
1484
- },
1485
- {
1486
- id: "allow-list",
1487
- description: "Allow list tools",
1488
- effect: "ALLOW",
1489
- priority: 101,
1490
- toolPattern: "*list*",
1491
- permission: "EXECUTE",
1492
- minimumTrustLevel: "UNTRUSTED",
1493
- enabled: true,
1494
- createdAt: "",
1495
- updatedAt: ""
1496
- },
1497
- {
1498
- id: "allow-get",
1499
- description: "Allow get tools",
1500
- effect: "ALLOW",
1501
- priority: 102,
1502
- toolPattern: "*get*",
1503
- permission: "EXECUTE",
1504
- minimumTrustLevel: "UNTRUSTED",
1505
- enabled: true,
1506
- createdAt: "",
1507
- updatedAt: ""
1508
- },
1509
- {
1510
- id: "allow-search",
1511
- description: "Allow search tools",
1512
- effect: "ALLOW",
1513
- priority: 103,
1514
- toolPattern: "*search*",
1515
- permission: "EXECUTE",
1516
- minimumTrustLevel: "UNTRUSTED",
1517
- enabled: true,
1518
- createdAt: "",
1519
- updatedAt: ""
1520
- },
1521
- {
1522
- id: "allow-query",
1523
- description: "Allow query tools",
1524
- effect: "ALLOW",
1525
- priority: 104,
1526
- toolPattern: "*query*",
1527
- permission: "EXECUTE",
1528
- minimumTrustLevel: "UNTRUSTED",
1529
- enabled: true,
1530
- createdAt: "",
1531
- updatedAt: ""
1532
- }
1533
- ],
1534
- createdAt: "",
1535
- updatedAt: ""
1536
- },
1537
- sandbox: {
1538
- id: "sandbox",
1539
- name: "Sandbox",
1540
- description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
1541
- version: 1,
1542
- rules: [
1543
- {
1544
- id: "deny-dangerous-commands",
1545
- description: "Block dangerous shell commands",
1546
- effect: "DENY",
1547
- priority: 50,
1548
- toolPattern: "*",
1549
- permission: "EXECUTE",
1550
- minimumTrustLevel: "UNTRUSTED",
1551
- enabled: true,
1552
- commandConstraints: {
1553
- denied: [
1554
- "rm -rf *",
1555
- "rm -r /*",
1556
- "mkfs*",
1557
- "dd if=*",
1558
- "curl*|*bash*",
1559
- "wget*|*sh*",
1560
- "shutdown*",
1561
- "reboot*",
1562
- "chmod*777*"
1563
- ]
1564
- },
1565
- createdAt: "",
1566
- updatedAt: ""
1567
- },
1568
- {
1569
- id: "allow-safe-commands",
1570
- description: "Allow safe shell commands only",
1571
- effect: "ALLOW",
1572
- priority: 100,
1573
- toolPattern: "*shell*",
1574
- permission: "EXECUTE",
1575
- minimumTrustLevel: "UNTRUSTED",
1576
- enabled: true,
1577
- commandConstraints: {
1578
- allowed: [
1579
- "ls*",
1580
- "cat*",
1581
- "head*",
1582
- "tail*",
1583
- "wc*",
1584
- "grep*",
1585
- "find*",
1586
- "echo*",
1587
- "pwd",
1588
- "whoami",
1589
- "date",
1590
- "env",
1591
- "git*",
1592
- "npm*",
1593
- "pnpm*",
1594
- "yarn*",
1595
- "node*",
1596
- "python*",
1597
- "pip*"
1598
- ]
1599
- },
1600
- createdAt: "",
1601
- updatedAt: ""
1602
- },
1603
- {
1604
- id: "deny-sensitive-paths",
1605
- description: "Block access to sensitive files",
1606
- effect: "DENY",
1607
- priority: 51,
1608
- toolPattern: "*",
1609
- permission: "EXECUTE",
1610
- minimumTrustLevel: "UNTRUSTED",
1611
- enabled: true,
1612
- pathConstraints: {
1613
- denied: [
1614
- "**/.env*",
1615
- "**/.ssh/**",
1616
- "**/.aws/**",
1617
- "**/credentials*",
1618
- "**/*.pem",
1619
- "**/*.key"
1620
- ]
1621
- },
1622
- createdAt: "",
1623
- updatedAt: ""
1624
- },
1625
- {
1626
- id: "allow-rest",
1627
- description: "Allow all other tools",
1628
- effect: "ALLOW",
1629
- priority: 1e3,
1630
- toolPattern: "*",
1631
- permission: "EXECUTE",
1632
- minimumTrustLevel: "UNTRUSTED",
1633
- enabled: true,
1634
- createdAt: "",
1635
- updatedAt: ""
1636
- }
1637
- ],
1638
- createdAt: "",
1639
- updatedAt: ""
1640
- },
1641
- permissive: {
1642
- id: "permissive",
1643
- name: "Permissive",
1644
- description: "Allows all tool calls (monitoring only)",
1645
- version: 1,
1646
- rules: [
1647
- {
1648
- id: "allow-all",
1649
- description: "Allow all",
1650
- effect: "ALLOW",
1651
- priority: 1e3,
1652
- toolPattern: "*",
1653
- permission: "EXECUTE",
1654
- minimumTrustLevel: "UNTRUSTED",
1655
- enabled: true,
1656
- createdAt: "",
1657
- updatedAt: ""
1658
- }
1659
- ],
1660
- createdAt: "",
1661
- updatedAt: ""
1662
- },
1663
- "deny-all": {
1664
- id: "deny-all",
1665
- name: "Deny All",
1666
- description: "Blocks all tool calls",
1667
- version: 1,
1668
- rules: [],
1669
- createdAt: "",
1670
- updatedAt: ""
1671
- }
1672
- };
1673
- function loadPolicy(source) {
1674
- if (typeof source === "object") return source;
1675
- if (PRESETS[source]) return PRESETS[source];
1676
- const filePath = resolve(source);
1677
- if (existsSync(filePath)) {
1678
- const content = readFileSync(filePath, "utf-8");
1679
- return JSON.parse(content);
1680
- }
1681
- throw new Error(
1682
- `Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
1683
- );
1684
- }
1685
- function parseArgs(argv) {
1686
- const args = argv.slice(2);
1687
- let policySource = "restricted";
1688
- let name = "solongate-proxy";
1689
- let verbose = false;
1690
- let rateLimitPerTool;
1691
- let globalRateLimit;
1692
- let configFile;
1693
- let apiKey;
1694
- let apiUrl;
1695
- let upstreamUrl;
1696
- let upstreamTransport;
1697
- let port;
1698
- let separatorIndex = args.indexOf("--");
1699
- const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
1700
- const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
1701
- for (let i = 0; i < flags.length; i++) {
1702
- switch (flags[i]) {
1703
- case "--policy":
1704
- policySource = flags[++i];
1705
- break;
1706
- case "--name":
1707
- name = flags[++i];
1708
- break;
1709
- case "--verbose":
1710
- verbose = true;
1711
- break;
1712
- case "--rate-limit":
1713
- rateLimitPerTool = parseInt(flags[++i], 10);
1714
- break;
1715
- case "--global-rate-limit":
1716
- globalRateLimit = parseInt(flags[++i], 10);
1717
- break;
1718
- case "--config":
1719
- configFile = flags[++i];
1720
- break;
1795
+ bEmpty();
1796
+ bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
1797
+ bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
1798
+ bEmpty();
1799
+ log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
1800
+ log4("");
1801
+ }
1802
+ var c2;
1803
+ var init_create = __esm({
1804
+ "src/create.ts"() {
1805
+ "use strict";
1806
+ c2 = {
1807
+ reset: "\x1B[0m",
1808
+ bold: "\x1B[1m",
1809
+ dim: "\x1B[2m",
1810
+ italic: "\x1B[3m",
1811
+ white: "\x1B[97m",
1812
+ gray: "\x1B[90m",
1813
+ blue1: "\x1B[38;2;20;50;160m",
1814
+ blue2: "\x1B[38;2;40;80;190m",
1815
+ blue3: "\x1B[38;2;60;110;215m",
1816
+ blue4: "\x1B[38;2;90;140;230m",
1817
+ blue5: "\x1B[38;2;130;170;240m",
1818
+ blue6: "\x1B[38;2;170;200;250m",
1819
+ green: "\x1B[38;2;80;200;120m",
1820
+ red: "\x1B[38;2;220;80;80m",
1821
+ cyan: "\x1B[38;2;100;200;220m",
1822
+ yellow: "\x1B[38;2;220;200;80m",
1823
+ bgBlue: "\x1B[48;2;20;50;160m"
1824
+ };
1825
+ main3().catch((err) => {
1826
+ log4(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1827
+ process.exit(1);
1828
+ });
1829
+ }
1830
+ });
1831
+
1832
+ // src/pull-push.ts
1833
+ var pull_push_exports = {};
1834
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
1835
+ import { resolve as resolve5 } from "path";
1836
+ function parseCliArgs() {
1837
+ const args = process.argv.slice(2);
1838
+ const command = args[0];
1839
+ let apiKey = process.env.SOLONGATE_API_KEY || "";
1840
+ let file = "policy.json";
1841
+ let policyId;
1842
+ for (let i = 1; i < args.length; i++) {
1843
+ switch (args[i]) {
1721
1844
  case "--api-key":
1722
- apiKey = flags[++i];
1723
- break;
1724
- case "--api-url":
1725
- apiUrl = flags[++i];
1726
- break;
1727
- case "--upstream-url":
1728
- upstreamUrl = flags[++i];
1845
+ apiKey = args[++i];
1729
1846
  break;
1730
- case "--upstream-transport":
1731
- upstreamTransport = flags[++i];
1847
+ case "--policy":
1848
+ case "--output":
1849
+ case "--file":
1850
+ case "-f":
1851
+ case "-o":
1852
+ file = args[++i];
1732
1853
  break;
1733
- case "--port":
1734
- port = parseInt(flags[++i], 10);
1854
+ case "--policy-id":
1855
+ case "--id":
1856
+ policyId = args[++i];
1735
1857
  break;
1736
1858
  }
1737
1859
  }
1738
1860
  if (!apiKey) {
1739
- const envKey = process.env.SOLONGATE_API_KEY;
1740
- if (envKey) {
1741
- apiKey = envKey;
1742
- } else {
1743
- throw new Error(
1744
- "A valid SolonGate API key is required.\n\nUsage: solongate-proxy --api-key sg_live_xxx -- <command>\n or: set SOLONGATE_API_KEY=sg_live_xxx\n\nGet your API key at https://solongate.com\n"
1745
- );
1746
- }
1747
- }
1748
- if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
1749
- throw new Error(
1750
- "Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
1751
- );
1861
+ log5("ERROR: API key required.");
1862
+ log5("");
1863
+ log5("Usage:");
1864
+ log5(` solongate-proxy ${command} --api-key sg_live_... --file policy.json`);
1865
+ log5(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
1866
+ process.exit(1);
1752
1867
  }
1753
- if (configFile) {
1754
- const filePath = resolve(configFile);
1755
- const content = readFileSync(filePath, "utf-8");
1756
- const fileConfig = JSON.parse(content);
1757
- if (!fileConfig.upstream) {
1758
- throw new Error('Config file must include "upstream" with at least "command" or "url"');
1759
- }
1760
- return {
1761
- upstream: fileConfig.upstream,
1762
- policy: loadPolicy(fileConfig.policy ?? policySource),
1763
- name: fileConfig.name ?? name,
1764
- verbose: fileConfig.verbose ?? verbose,
1765
- rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
1766
- globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
1767
- apiKey: apiKey ?? fileConfig.apiKey,
1768
- apiUrl: apiUrl ?? fileConfig.apiUrl,
1769
- port: port ?? fileConfig.port
1770
- };
1868
+ if (!apiKey.startsWith("sg_live_")) {
1869
+ log5("ERROR: Pull/push requires a live API key (sg_live_...).");
1870
+ process.exit(1);
1771
1871
  }
1772
- if (upstreamUrl) {
1773
- const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
1774
- return {
1775
- upstream: {
1776
- transport,
1777
- command: "",
1778
- // not used for URL-based transports
1779
- url: upstreamUrl
1780
- },
1781
- policy: loadPolicy(policySource),
1782
- name,
1783
- verbose,
1784
- rateLimitPerTool,
1785
- globalRateLimit,
1786
- apiKey,
1787
- apiUrl,
1788
- port
1789
- };
1872
+ return { command, apiKey, file: resolve5(file), policyId };
1873
+ }
1874
+ async function pull(apiKey, file, policyId) {
1875
+ const apiUrl = "https://api.solongate.com";
1876
+ log5(`Pulling policy from dashboard...`);
1877
+ const policy = await fetchCloudPolicy(apiKey, apiUrl, policyId);
1878
+ const json = JSON.stringify(policy, null, 2) + "\n";
1879
+ writeFileSync5(file, json, "utf-8");
1880
+ log5(`Saved: ${file}`);
1881
+ log5(` Name: ${policy.name}`);
1882
+ log5(` Version: ${policy.version}`);
1883
+ log5(` Rules: ${policy.rules.length}`);
1884
+ log5("");
1885
+ log5("Done. Policy pulled from dashboard to local file.");
1886
+ }
1887
+ async function push(apiKey, file) {
1888
+ const apiUrl = "https://api.solongate.com";
1889
+ if (!existsSync6(file)) {
1890
+ log5(`ERROR: File not found: ${file}`);
1891
+ process.exit(1);
1790
1892
  }
1791
- if (upstreamArgs.length === 0) {
1792
- throw new Error(
1793
- "No upstream server command provided.\n\nUsage: solongate-proxy [options] -- <command> [args...]\n\nExamples:\n solongate-proxy -- node my-server.js\n solongate-proxy --policy restricted -- npx @openclaw/server\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
1794
- );
1893
+ const content = readFileSync5(file, "utf-8");
1894
+ let policy;
1895
+ try {
1896
+ policy = JSON.parse(content);
1897
+ } catch {
1898
+ log5(`ERROR: Invalid JSON in ${file}`);
1899
+ process.exit(1);
1795
1900
  }
1796
- const [command, ...commandArgs] = upstreamArgs;
1797
- return {
1798
- upstream: {
1799
- transport: upstreamTransport ?? "stdio",
1800
- command,
1801
- args: commandArgs,
1802
- env: { ...process.env }
1901
+ log5(`Pushing policy to dashboard...`);
1902
+ log5(` File: ${file}`);
1903
+ log5(` Name: ${policy.name || "Unnamed"}`);
1904
+ log5(` Rules: ${(policy.rules || []).length}`);
1905
+ const res = await fetch(`${apiUrl}/api/v1/policies`, {
1906
+ method: "POST",
1907
+ headers: {
1908
+ "Authorization": `Bearer ${apiKey}`,
1909
+ "Content-Type": "application/json"
1803
1910
  },
1804
- policy: loadPolicy(policySource),
1805
- name,
1806
- verbose,
1807
- rateLimitPerTool,
1808
- globalRateLimit,
1809
- apiKey,
1810
- apiUrl,
1811
- port
1812
- };
1911
+ body: JSON.stringify({
1912
+ id: policy.id || "default",
1913
+ name: policy.name || "Local Policy",
1914
+ description: policy.description || "Pushed from local file",
1915
+ version: policy.version || 1,
1916
+ rules: policy.rules || []
1917
+ })
1918
+ });
1919
+ if (!res.ok) {
1920
+ const body = await res.text().catch(() => "");
1921
+ log5(`ERROR: Push failed (${res.status}): ${body}`);
1922
+ process.exit(1);
1923
+ }
1924
+ const data = await res.json();
1925
+ log5(` Cloud version: ${data._version ?? "created"}`);
1926
+ log5("");
1927
+ log5("Done. Policy pushed from local file to dashboard.");
1928
+ }
1929
+ async function main4() {
1930
+ const { command, apiKey, file, policyId } = parseCliArgs();
1931
+ try {
1932
+ if (command === "pull") {
1933
+ await pull(apiKey, file, policyId);
1934
+ } else if (command === "push") {
1935
+ await push(apiKey, file);
1936
+ } else {
1937
+ log5(`Unknown command: ${command}`);
1938
+ log5("Usage: solongate-proxy pull|push --api-key sg_live_... --file policy.json");
1939
+ process.exit(1);
1940
+ }
1941
+ } catch (err) {
1942
+ log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
1943
+ process.exit(1);
1944
+ }
1813
1945
  }
1946
+ var log5;
1947
+ var init_pull_push = __esm({
1948
+ "src/pull-push.ts"() {
1949
+ "use strict";
1950
+ init_config();
1951
+ log5 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
1952
+ `);
1953
+ main4();
1954
+ }
1955
+ });
1956
+
1957
+ // src/index.ts
1958
+ init_config();
1814
1959
 
1815
1960
  // src/proxy.ts
1816
1961
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -3348,7 +3493,202 @@ var SolonGate = class {
3348
3493
  };
3349
3494
 
3350
3495
  // src/proxy.ts
3351
- var log = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
3496
+ init_config();
3497
+
3498
+ // src/sync.ts
3499
+ init_config();
3500
+ import { readFileSync as readFileSync2, writeFileSync, watch, existsSync as existsSync2 } from "fs";
3501
+ var log = (...args) => process.stderr.write(`[SolonGate Sync] ${args.map(String).join(" ")}
3502
+ `);
3503
+ var PolicySyncManager = class {
3504
+ localPath;
3505
+ apiKey;
3506
+ apiUrl;
3507
+ pollIntervalMs;
3508
+ onPolicyUpdate;
3509
+ currentPolicy;
3510
+ localVersion;
3511
+ cloudVersion;
3512
+ skipNextWatch = false;
3513
+ debounceTimer = null;
3514
+ pollTimer = null;
3515
+ watcher = null;
3516
+ isLiveKey;
3517
+ constructor(opts) {
3518
+ this.localPath = opts.localPath;
3519
+ this.apiKey = opts.apiKey;
3520
+ this.apiUrl = opts.apiUrl;
3521
+ this.pollIntervalMs = opts.pollIntervalMs ?? 6e4;
3522
+ this.onPolicyUpdate = opts.onPolicyUpdate;
3523
+ this.currentPolicy = opts.initialPolicy;
3524
+ this.localVersion = opts.initialPolicy.version ?? 0;
3525
+ this.cloudVersion = 0;
3526
+ this.isLiveKey = opts.apiKey.startsWith("sg_live_");
3527
+ }
3528
+ /**
3529
+ * Start watching local file and polling cloud.
3530
+ */
3531
+ start() {
3532
+ if (this.localPath && existsSync2(this.localPath)) {
3533
+ this.startFileWatcher();
3534
+ }
3535
+ if (this.isLiveKey) {
3536
+ this.pushToCloud(this.currentPolicy).catch(() => {
3537
+ });
3538
+ this.startPolling();
3539
+ }
3540
+ }
3541
+ /**
3542
+ * Stop all watchers and timers.
3543
+ */
3544
+ stop() {
3545
+ if (this.watcher) {
3546
+ this.watcher.close();
3547
+ this.watcher = null;
3548
+ }
3549
+ if (this.pollTimer) {
3550
+ clearInterval(this.pollTimer);
3551
+ this.pollTimer = null;
3552
+ }
3553
+ if (this.debounceTimer) {
3554
+ clearTimeout(this.debounceTimer);
3555
+ this.debounceTimer = null;
3556
+ }
3557
+ }
3558
+ /**
3559
+ * Watch local file for changes (debounced).
3560
+ */
3561
+ startFileWatcher() {
3562
+ if (!this.localPath) return;
3563
+ const filePath = this.localPath;
3564
+ try {
3565
+ this.watcher = watch(filePath, () => {
3566
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
3567
+ this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
3568
+ });
3569
+ log(`Watching ${filePath} for changes`);
3570
+ } catch (err) {
3571
+ log(`File watch failed: ${err instanceof Error ? err.message : String(err)}`);
3572
+ }
3573
+ }
3574
+ /**
3575
+ * Handle local file change event.
3576
+ */
3577
+ async onFileChange(filePath) {
3578
+ if (this.skipNextWatch) {
3579
+ this.skipNextWatch = false;
3580
+ return;
3581
+ }
3582
+ try {
3583
+ if (!existsSync2(filePath)) {
3584
+ log("Policy file deleted \u2014 keeping current policy");
3585
+ return;
3586
+ }
3587
+ const content = readFileSync2(filePath, "utf-8");
3588
+ const newPolicy = JSON.parse(content);
3589
+ if (newPolicy.version <= this.localVersion) {
3590
+ newPolicy.version = Math.max(this.localVersion, this.cloudVersion) + 1;
3591
+ this.writeToFile(newPolicy);
3592
+ }
3593
+ if (this.policiesEqual(newPolicy, this.currentPolicy)) return;
3594
+ log(`File changed: ${newPolicy.name} v${newPolicy.version}`);
3595
+ this.localVersion = newPolicy.version;
3596
+ this.currentPolicy = newPolicy;
3597
+ this.onPolicyUpdate(newPolicy);
3598
+ if (this.isLiveKey) {
3599
+ try {
3600
+ const result = await this.pushToCloud(newPolicy);
3601
+ this.cloudVersion = result.version;
3602
+ log(`Pushed to cloud: v${result.version}`);
3603
+ } catch (err) {
3604
+ log(`Cloud push failed: ${err instanceof Error ? err.message : String(err)}`);
3605
+ }
3606
+ }
3607
+ } catch (err) {
3608
+ log(`File read error: ${err instanceof Error ? err.message : String(err)}`);
3609
+ }
3610
+ }
3611
+ /**
3612
+ * Poll cloud for policy changes.
3613
+ */
3614
+ startPolling() {
3615
+ this.pollTimer = setInterval(() => this.onPollTick(), this.pollIntervalMs);
3616
+ }
3617
+ /**
3618
+ * Handle poll tick — fetch cloud policy and compare.
3619
+ */
3620
+ async onPollTick() {
3621
+ try {
3622
+ const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl);
3623
+ const cloudVer = cloudPolicy.version ?? 0;
3624
+ if (cloudVer <= this.localVersion && this.policiesEqual(cloudPolicy, this.currentPolicy)) {
3625
+ return;
3626
+ }
3627
+ if (cloudVer > this.localVersion || !this.policiesEqual(cloudPolicy, this.currentPolicy)) {
3628
+ log(`Cloud update: ${cloudPolicy.name} v${cloudVer} (was v${this.localVersion})`);
3629
+ this.cloudVersion = cloudVer;
3630
+ this.localVersion = cloudVer;
3631
+ this.currentPolicy = cloudPolicy;
3632
+ this.onPolicyUpdate(cloudPolicy);
3633
+ if (this.localPath) {
3634
+ this.writeToFile(cloudPolicy);
3635
+ log(`Updated local file: ${this.localPath}`);
3636
+ }
3637
+ }
3638
+ } catch {
3639
+ }
3640
+ }
3641
+ /**
3642
+ * Push policy to cloud API.
3643
+ */
3644
+ async pushToCloud(policy) {
3645
+ const url = `${this.apiUrl}/api/v1/policies`;
3646
+ const res = await fetch(url, {
3647
+ method: "POST",
3648
+ headers: {
3649
+ "Authorization": `Bearer ${this.apiKey}`,
3650
+ "Content-Type": "application/json"
3651
+ },
3652
+ body: JSON.stringify({
3653
+ id: policy.id || "default",
3654
+ name: policy.name || "Default Policy",
3655
+ description: policy.description || "Synced from proxy",
3656
+ version: policy.version || 1,
3657
+ rules: policy.rules
3658
+ })
3659
+ });
3660
+ if (!res.ok) {
3661
+ const body = await res.text().catch(() => "");
3662
+ throw new Error(`Push failed (${res.status}): ${body}`);
3663
+ }
3664
+ const data = await res.json();
3665
+ return { version: Number(data._version ?? policy.version) };
3666
+ }
3667
+ /**
3668
+ * Write policy to local file (with loop prevention).
3669
+ */
3670
+ writeToFile(policy) {
3671
+ if (!this.localPath) return;
3672
+ this.skipNextWatch = true;
3673
+ try {
3674
+ const json = JSON.stringify(policy, null, 2) + "\n";
3675
+ writeFileSync(this.localPath, json, "utf-8");
3676
+ } catch (err) {
3677
+ log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
3678
+ this.skipNextWatch = false;
3679
+ }
3680
+ }
3681
+ /**
3682
+ * Compare two policies by rules content (ignoring timestamps).
3683
+ */
3684
+ policiesEqual(a, b) {
3685
+ if (a.id !== b.id || a.name !== b.name || a.rules.length !== b.rules.length) return false;
3686
+ return JSON.stringify(a.rules) === JSON.stringify(b.rules);
3687
+ }
3688
+ };
3689
+
3690
+ // src/proxy.ts
3691
+ var log2 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
3352
3692
  `);
3353
3693
  var Mutex = class {
3354
3694
  queue = [];
@@ -3358,8 +3698,8 @@ var Mutex = class {
3358
3698
  this.locked = true;
3359
3699
  return;
3360
3700
  }
3361
- return new Promise((resolve5) => {
3362
- this.queue.push(resolve5);
3701
+ return new Promise((resolve6) => {
3702
+ this.queue.push(resolve6);
3363
3703
  });
3364
3704
  }
3365
3705
  release() {
@@ -3377,6 +3717,7 @@ var SolonGateProxy = class {
3377
3717
  client = null;
3378
3718
  server = null;
3379
3719
  callMutex = new Mutex();
3720
+ syncManager = null;
3380
3721
  upstreamTools = [];
3381
3722
  constructor(config) {
3382
3723
  this.config = config;
@@ -3393,20 +3734,20 @@ var SolonGateProxy = class {
3393
3734
  });
3394
3735
  const warnings = this.gate.getWarnings();
3395
3736
  for (const w of warnings) {
3396
- log("WARNING:", w);
3737
+ log2("WARNING:", w);
3397
3738
  }
3398
3739
  }
3399
3740
  /**
3400
3741
  * Start the proxy: connect to upstream, then serve downstream.
3401
3742
  */
3402
3743
  async start() {
3403
- log("Starting SolonGate Proxy...");
3744
+ log2("Starting SolonGate Proxy...");
3404
3745
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3405
3746
  if (this.config.apiKey) {
3406
3747
  if (this.config.apiKey.startsWith("sg_test_")) {
3407
- log("Using test API key \u2014 skipping online validation.");
3748
+ log2("Using test API key \u2014 skipping online validation.");
3408
3749
  } else {
3409
- log(`Validating license with ${apiUrl}...`);
3750
+ log2(`Validating license with ${apiUrl}...`);
3410
3751
  try {
3411
3752
  const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
3412
3753
  headers: {
@@ -3416,17 +3757,17 @@ var SolonGateProxy = class {
3416
3757
  signal: AbortSignal.timeout(1e4)
3417
3758
  });
3418
3759
  if (res.status === 401) {
3419
- log("ERROR: Invalid or expired API key.");
3760
+ log2("ERROR: Invalid or expired API key.");
3420
3761
  process.exit(1);
3421
3762
  }
3422
3763
  if (res.status === 403) {
3423
- log("ERROR: Your subscription is inactive. Renew at https://solongate.com");
3764
+ log2("ERROR: Your subscription is inactive. Renew at https://solongate.com");
3424
3765
  process.exit(1);
3425
3766
  }
3426
- log("License validated.");
3767
+ log2("License validated.");
3427
3768
  } catch (err) {
3428
- log(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
3429
- log(`Details: ${err instanceof Error ? err.message : String(err)}`);
3769
+ log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
3770
+ log2(`Details: ${err instanceof Error ? err.message : String(err)}`);
3430
3771
  process.exit(1);
3431
3772
  }
3432
3773
  }
@@ -3434,28 +3775,27 @@ var SolonGateProxy = class {
3434
3775
  try {
3435
3776
  const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
3436
3777
  this.config.policy = cloudPolicy;
3437
- log(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
3778
+ log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
3438
3779
  } catch (err) {
3439
- log(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
3780
+ log2(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
3440
3781
  }
3441
3782
  }
3442
3783
  }
3443
3784
  this.gate.loadPolicy(this.config.policy);
3444
- log(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
3785
+ log2(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
3445
3786
  const transport = this.config.upstream.transport ?? "stdio";
3446
3787
  if (transport === "stdio") {
3447
- log(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3788
+ log2(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3448
3789
  } else {
3449
- log(`Upstream: [${transport}] ${this.config.upstream.url}`);
3790
+ log2(`Upstream: [${transport}] ${this.config.upstream.url}`);
3450
3791
  }
3451
3792
  await this.connectUpstream();
3452
3793
  await this.discoverTools();
3453
3794
  this.registerToolsToCloud();
3454
3795
  this.registerServerToCloud();
3455
- this.syncPolicyToCloud();
3796
+ this.startPolicySync();
3456
3797
  this.createServer();
3457
3798
  await this.serve();
3458
- this.startPolicyPolling();
3459
3799
  }
3460
3800
  /**
3461
3801
  * Connect to the upstream MCP server.
@@ -3493,7 +3833,7 @@ var SolonGateProxy = class {
3493
3833
  break;
3494
3834
  }
3495
3835
  }
3496
- log(`Connected to upstream server (${upstreamTransport})`);
3836
+ log2(`Connected to upstream server (${upstreamTransport})`);
3497
3837
  }
3498
3838
  /**
3499
3839
  * Discover tools from the upstream server.
@@ -3506,9 +3846,9 @@ var SolonGateProxy = class {
3506
3846
  description: t.description,
3507
3847
  inputSchema: t.inputSchema
3508
3848
  }));
3509
- log(`Discovered ${this.upstreamTools.length} tools from upstream:`);
3849
+ log2(`Discovered ${this.upstreamTools.length} tools from upstream:`);
3510
3850
  for (const tool of this.upstreamTools) {
3511
- log(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
3851
+ log2(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
3512
3852
  }
3513
3853
  }
3514
3854
  /**
@@ -3537,13 +3877,13 @@ var SolonGateProxy = class {
3537
3877
  const { name, arguments: args } = request.params;
3538
3878
  const argsSize = JSON.stringify(args ?? {}).length;
3539
3879
  if (argsSize > MAX_ARGUMENT_SIZE) {
3540
- log(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
3880
+ log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
3541
3881
  return {
3542
3882
  content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
3543
3883
  isError: true
3544
3884
  };
3545
3885
  }
3546
- log(`Tool call: ${name}`);
3886
+ log2(`Tool call: ${name}`);
3547
3887
  await this.callMutex.acquire();
3548
3888
  const startTime = Date.now();
3549
3889
  try {
@@ -3560,10 +3900,10 @@ var SolonGateProxy = class {
3560
3900
  );
3561
3901
  const decision = result.isError ? "DENY" : "ALLOW";
3562
3902
  const evaluationTimeMs = Date.now() - startTime;
3563
- log(`Result: ${decision} (${evaluationTimeMs}ms)`);
3903
+ log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
3564
3904
  if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
3565
3905
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3566
- log(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
3906
+ log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
3567
3907
  let reason = "allowed";
3568
3908
  let matchedRule;
3569
3909
  if (result.isError) {
@@ -3586,7 +3926,7 @@ var SolonGateProxy = class {
3586
3926
  evaluationTimeMs
3587
3927
  });
3588
3928
  } else {
3589
- log(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
3929
+ log2(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
3590
3930
  }
3591
3931
  return {
3592
3932
  content: [...result.content],
@@ -3660,13 +4000,13 @@ var SolonGateProxy = class {
3660
4000
  registered++;
3661
4001
  } else {
3662
4002
  const body = await res.text().catch(() => "");
3663
- log(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
4003
+ log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
3664
4004
  }
3665
4005
  }).catch((err) => {
3666
- log(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
4006
+ log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
3667
4007
  });
3668
4008
  }
3669
- log(`Registering ${total} tools to dashboard...`);
4009
+ log2(`Registering ${total} tools to dashboard...`);
3670
4010
  }
3671
4011
  /**
3672
4012
  * Guess tool permissions from tool name.
@@ -3720,68 +4060,42 @@ var SolonGateProxy = class {
3720
4060
  })
3721
4061
  }).then(async (res) => {
3722
4062
  if (res.ok) {
3723
- log(`Registered MCP server "${serverName}" to dashboard.`);
4063
+ log2(`Registered MCP server "${serverName}" to dashboard.`);
3724
4064
  } else if (res.status === 409) {
3725
- log(`MCP server "${serverName}" already registered.`);
4065
+ log2(`MCP server "${serverName}" already registered.`);
3726
4066
  } else {
3727
4067
  const body = await res.text().catch(() => "");
3728
- log(`MCP server registration failed (${res.status}): ${body}`);
4068
+ log2(`MCP server registration failed (${res.status}): ${body}`);
3729
4069
  }
3730
4070
  }).catch((err) => {
3731
- log(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
4071
+ log2(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
3732
4072
  });
3733
4073
  }
3734
4074
  /**
3735
- * Sync the active policy to the SolonGate Cloud API.
3736
- * This makes it visible on the Dashboard Policies page.
4075
+ * Start bidirectional policy sync between local JSON file and cloud dashboard.
4076
+ *
4077
+ * - Watches local policy.json for changes → pushes to cloud API
4078
+ * - Polls cloud API for dashboard changes → writes to local policy.json
4079
+ * - Version number determines which is newer (higher wins, cloud wins on tie)
3737
4080
  */
3738
- syncPolicyToCloud() {
3739
- if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
4081
+ startPolicySync() {
4082
+ const apiKey = this.config.apiKey;
4083
+ if (!apiKey) return;
3740
4084
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3741
- const policy = this.config.policy;
3742
- fetch(`${apiUrl}/api/v1/policies`, {
3743
- method: "POST",
3744
- headers: {
3745
- "Authorization": `Bearer ${this.config.apiKey}`,
3746
- "Content-Type": "application/json"
3747
- },
3748
- body: JSON.stringify({
3749
- id: policy.id || "default",
3750
- name: policy.name || "Default Policy",
3751
- description: policy.description || `Policy synced from proxy`,
3752
- version: policy.version || 1,
3753
- rules: policy.rules
3754
- })
3755
- }).then(async (res) => {
3756
- if (res.ok) {
3757
- log(`Synced policy "${policy.name}" to dashboard.`);
3758
- } else {
3759
- const body = await res.text().catch(() => "");
3760
- log(`Policy sync failed (${res.status}): ${body}`);
4085
+ this.syncManager = new PolicySyncManager({
4086
+ localPath: this.config.policyPath ?? null,
4087
+ apiKey,
4088
+ apiUrl,
4089
+ pollIntervalMs: 6e4,
4090
+ initialPolicy: this.config.policy,
4091
+ onPolicyUpdate: (policy) => {
4092
+ this.config.policy = policy;
4093
+ this.gate.loadPolicy(policy);
4094
+ log2(`Policy hot-reloaded: ${policy.name} v${policy.version} (${policy.rules.length} rules)`);
3761
4095
  }
3762
- }).catch((err) => {
3763
- log(`Policy sync error: ${err instanceof Error ? err.message : String(err)}`);
3764
4096
  });
3765
- }
3766
- /**
3767
- * Poll for policy updates from dashboard every 60 seconds.
3768
- * When user changes policy in dashboard, proxy picks it up automatically.
3769
- */
3770
- startPolicyPolling() {
3771
- if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
3772
- const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3773
- const POLL_INTERVAL = 6e4;
3774
- setInterval(async () => {
3775
- try {
3776
- const newPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
3777
- if (newPolicy.version !== this.config.policy.version || newPolicy.rules.length !== this.config.policy.rules.length) {
3778
- this.config.policy = newPolicy;
3779
- this.gate.loadPolicy(newPolicy);
3780
- log(`Policy updated from dashboard: ${newPolicy.name} v${newPolicy.version} (${newPolicy.rules.length} rules)`);
3781
- }
3782
- } catch {
3783
- }
3784
- }, POLL_INTERVAL);
4097
+ this.syncManager.start();
4098
+ log2("Bidirectional policy sync started.");
3785
4099
  }
3786
4100
  /**
3787
4101
  * Start serving downstream.
@@ -3807,14 +4121,14 @@ var SolonGateProxy = class {
3807
4121
  }
3808
4122
  });
3809
4123
  httpServer.listen(this.config.port, () => {
3810
- log(`Proxy is live on http://localhost:${this.config.port}/mcp`);
3811
- log("All tool calls are now protected by SolonGate.");
4124
+ log2(`Proxy is live on http://localhost:${this.config.port}/mcp`);
4125
+ log2("All tool calls are now protected by SolonGate.");
3812
4126
  });
3813
4127
  } else {
3814
4128
  const transport = new StdioServerTransport();
3815
4129
  await this.server.connect(transport);
3816
- log("Proxy is live. All tool calls are now protected by SolonGate.");
3817
- log("Waiting for requests...");
4130
+ log2("Proxy is live. All tool calls are now protected by SolonGate.");
4131
+ log2("Waiting for requests...");
3818
4132
  }
3819
4133
  }
3820
4134
  };
@@ -3832,7 +4146,7 @@ console.error = (...args) => {
3832
4146
  process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
3833
4147
  `);
3834
4148
  };
3835
- async function main4() {
4149
+ async function main5() {
3836
4150
  const subcommand = process.argv[2];
3837
4151
  if (subcommand === "init") {
3838
4152
  process.argv.splice(2, 1);
@@ -3849,6 +4163,10 @@ async function main4() {
3849
4163
  await Promise.resolve().then(() => (init_create(), create_exports));
3850
4164
  return;
3851
4165
  }
4166
+ if (subcommand === "pull" || subcommand === "push") {
4167
+ await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
4168
+ return;
4169
+ }
3852
4170
  try {
3853
4171
  const config = parseArgs(process.argv);
3854
4172
  const proxy = new SolonGateProxy(config);
@@ -3860,4 +4178,4 @@ async function main4() {
3860
4178
  process.exit(1);
3861
4179
  }
3862
4180
  }
3863
- main4();
4181
+ main5();