@solongate/proxy 0.3.1 → 0.4.1

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,536 @@ 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
+ let resolvedId = policyId;
12
+ if (!resolvedId) {
13
+ const listRes = await fetch(`${apiUrl}/api/v1/policies`, {
14
+ headers: { "Authorization": `Bearer ${apiKey}` }
15
+ });
16
+ if (!listRes.ok) {
17
+ const body = await listRes.text().catch(() => "");
18
+ throw new Error(`Failed to list policies from cloud (${listRes.status}): ${body}`);
19
+ }
20
+ const listData = await listRes.json();
21
+ const policies = listData.policies ?? [];
22
+ if (policies.length === 0) {
23
+ throw new Error("No policies found in cloud. Create one in the dashboard first.");
24
+ }
25
+ resolvedId = policies[0].id;
26
+ }
27
+ const url = `${apiUrl}/api/v1/policies/${resolvedId}`;
28
+ const res = await fetch(url, {
29
+ headers: { "Authorization": `Bearer ${apiKey}` }
30
+ });
31
+ if (!res.ok) {
32
+ const body = await res.text().catch(() => "");
33
+ throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
34
+ }
35
+ const data = await res.json();
36
+ return {
37
+ id: String(data.id ?? "cloud"),
38
+ name: String(data.name ?? "Cloud Policy"),
39
+ version: Number(data._version ?? 1),
40
+ rules: data.rules ?? [],
41
+ createdAt: String(data._created_at ?? ""),
42
+ updatedAt: ""
43
+ };
44
+ }
45
+ async function sendAuditLog(apiKey, apiUrl, entry) {
46
+ const url = `${apiUrl}/api/v1/audit-logs`;
47
+ try {
48
+ const res = await fetch(url, {
49
+ method: "POST",
50
+ headers: {
51
+ "Authorization": `Bearer ${apiKey}`,
52
+ "Content-Type": "application/json"
53
+ },
54
+ body: JSON.stringify(entry)
55
+ });
56
+ if (!res.ok) {
57
+ const body = await res.text().catch(() => "");
58
+ process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
59
+ `);
60
+ }
61
+ } catch (err) {
62
+ process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
63
+ `);
64
+ }
65
+ }
66
+ function loadPolicy(source) {
67
+ if (typeof source === "object") return source;
68
+ if (PRESETS[source]) return PRESETS[source];
69
+ const filePath = resolve(source);
70
+ if (existsSync(filePath)) {
71
+ const content = readFileSync(filePath, "utf-8");
72
+ return JSON.parse(content);
73
+ }
74
+ throw new Error(
75
+ `Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
76
+ );
77
+ }
78
+ function parseArgs(argv) {
79
+ const args = argv.slice(2);
80
+ let policySource = "restricted";
81
+ let name = "solongate-proxy";
82
+ let verbose = false;
83
+ let rateLimitPerTool;
84
+ let globalRateLimit;
85
+ let configFile;
86
+ let apiKey;
87
+ let apiUrl;
88
+ let upstreamUrl;
89
+ let upstreamTransport;
90
+ let port;
91
+ let separatorIndex = args.indexOf("--");
92
+ const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
93
+ const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
94
+ for (let i = 0; i < flags.length; i++) {
95
+ switch (flags[i]) {
96
+ case "--policy":
97
+ policySource = flags[++i];
98
+ break;
99
+ case "--name":
100
+ name = flags[++i];
101
+ break;
102
+ case "--verbose":
103
+ verbose = true;
104
+ break;
105
+ case "--rate-limit":
106
+ rateLimitPerTool = parseInt(flags[++i], 10);
107
+ break;
108
+ case "--global-rate-limit":
109
+ globalRateLimit = parseInt(flags[++i], 10);
110
+ break;
111
+ case "--config":
112
+ configFile = flags[++i];
113
+ break;
114
+ case "--api-key":
115
+ apiKey = flags[++i];
116
+ break;
117
+ case "--api-url":
118
+ apiUrl = flags[++i];
119
+ break;
120
+ case "--upstream-url":
121
+ upstreamUrl = flags[++i];
122
+ break;
123
+ case "--upstream-transport":
124
+ upstreamTransport = flags[++i];
125
+ break;
126
+ case "--port":
127
+ port = parseInt(flags[++i], 10);
128
+ break;
129
+ }
130
+ }
131
+ if (!apiKey) {
132
+ const envKey = process.env.SOLONGATE_API_KEY;
133
+ if (envKey) {
134
+ apiKey = envKey;
135
+ } else {
136
+ throw new Error(
137
+ "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"
138
+ );
139
+ }
140
+ }
141
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
142
+ throw new Error(
143
+ "Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
144
+ );
145
+ }
146
+ const resolvedPolicyPath = resolvePolicyPath(policySource);
147
+ if (configFile) {
148
+ const filePath = resolve(configFile);
149
+ const content = readFileSync(filePath, "utf-8");
150
+ const fileConfig = JSON.parse(content);
151
+ if (!fileConfig.upstream) {
152
+ throw new Error('Config file must include "upstream" with at least "command" or "url"');
153
+ }
154
+ const cfgPolicySource = fileConfig.policy ?? policySource;
155
+ return {
156
+ upstream: fileConfig.upstream,
157
+ policy: loadPolicy(cfgPolicySource),
158
+ name: fileConfig.name ?? name,
159
+ verbose: fileConfig.verbose ?? verbose,
160
+ rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
161
+ globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
162
+ apiKey: apiKey ?? fileConfig.apiKey,
163
+ apiUrl: apiUrl ?? fileConfig.apiUrl,
164
+ port: port ?? fileConfig.port,
165
+ policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0
166
+ };
167
+ }
168
+ if (upstreamUrl) {
169
+ const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
170
+ return {
171
+ upstream: {
172
+ transport,
173
+ command: "",
174
+ // not used for URL-based transports
175
+ url: upstreamUrl
176
+ },
177
+ policy: loadPolicy(policySource),
178
+ name,
179
+ verbose,
180
+ rateLimitPerTool,
181
+ globalRateLimit,
182
+ apiKey,
183
+ apiUrl,
184
+ port,
185
+ policyPath: resolvedPolicyPath ?? void 0
186
+ };
187
+ }
188
+ if (upstreamArgs.length === 0) {
189
+ throw new Error(
190
+ "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"
191
+ );
192
+ }
193
+ const [command, ...commandArgs] = upstreamArgs;
194
+ return {
195
+ upstream: {
196
+ transport: upstreamTransport ?? "stdio",
197
+ command,
198
+ args: commandArgs,
199
+ env: { ...process.env }
200
+ },
201
+ policy: loadPolicy(policySource),
202
+ name,
203
+ verbose,
204
+ rateLimitPerTool,
205
+ globalRateLimit,
206
+ apiKey,
207
+ apiUrl,
208
+ port,
209
+ policyPath: resolvedPolicyPath ?? void 0
210
+ };
211
+ }
212
+ function resolvePolicyPath(source) {
213
+ if (PRESETS[source]) return null;
214
+ const filePath = resolve(source);
215
+ if (existsSync(filePath)) return filePath;
216
+ return null;
217
+ }
218
+ var PRESETS;
219
+ var init_config = __esm({
220
+ "src/config.ts"() {
221
+ "use strict";
222
+ PRESETS = {
223
+ restricted: {
224
+ id: "restricted",
225
+ name: "Restricted",
226
+ description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
227
+ version: 1,
228
+ rules: [
229
+ {
230
+ id: "deny-dangerous-commands",
231
+ description: "Block dangerous shell commands",
232
+ effect: "DENY",
233
+ priority: 50,
234
+ toolPattern: "*",
235
+ permission: "EXECUTE",
236
+ minimumTrustLevel: "UNTRUSTED",
237
+ enabled: true,
238
+ commandConstraints: {
239
+ denied: [
240
+ "rm -rf *",
241
+ "rm -r /*",
242
+ "mkfs*",
243
+ "dd if=*",
244
+ "curl*|*bash*",
245
+ "curl*|*sh*",
246
+ "wget*|*bash*",
247
+ "wget*|*sh*",
248
+ "shutdown*",
249
+ "reboot*",
250
+ "kill -9*",
251
+ "chmod*777*",
252
+ "iptables*",
253
+ "passwd*",
254
+ "useradd*",
255
+ "userdel*"
256
+ ]
257
+ },
258
+ createdAt: "",
259
+ updatedAt: ""
260
+ },
261
+ {
262
+ id: "deny-sensitive-paths",
263
+ description: "Block access to sensitive files",
264
+ effect: "DENY",
265
+ priority: 51,
266
+ toolPattern: "*",
267
+ permission: "EXECUTE",
268
+ minimumTrustLevel: "UNTRUSTED",
269
+ enabled: true,
270
+ pathConstraints: {
271
+ denied: [
272
+ "**/.env*",
273
+ "**/.ssh/**",
274
+ "**/.aws/**",
275
+ "**/.kube/**",
276
+ "**/credentials*",
277
+ "**/secrets*",
278
+ "**/*.pem",
279
+ "**/*.key",
280
+ "/etc/passwd",
281
+ "/etc/shadow",
282
+ "/proc/**",
283
+ "/dev/**"
284
+ ]
285
+ },
286
+ createdAt: "",
287
+ updatedAt: ""
288
+ },
289
+ {
290
+ id: "allow-rest",
291
+ description: "Allow all other tool calls",
292
+ effect: "ALLOW",
293
+ priority: 1e3,
294
+ toolPattern: "*",
295
+ permission: "EXECUTE",
296
+ minimumTrustLevel: "UNTRUSTED",
297
+ enabled: true,
298
+ createdAt: "",
299
+ updatedAt: ""
300
+ }
301
+ ],
302
+ createdAt: "",
303
+ updatedAt: ""
304
+ },
305
+ "read-only": {
306
+ id: "read-only",
307
+ name: "Read Only",
308
+ description: "Only allows read/list/get/search/query tools",
309
+ version: 1,
310
+ rules: [
311
+ {
312
+ id: "allow-read",
313
+ description: "Allow read tools",
314
+ effect: "ALLOW",
315
+ priority: 100,
316
+ toolPattern: "*read*",
317
+ permission: "EXECUTE",
318
+ minimumTrustLevel: "UNTRUSTED",
319
+ enabled: true,
320
+ createdAt: "",
321
+ updatedAt: ""
322
+ },
323
+ {
324
+ id: "allow-list",
325
+ description: "Allow list tools",
326
+ effect: "ALLOW",
327
+ priority: 101,
328
+ toolPattern: "*list*",
329
+ permission: "EXECUTE",
330
+ minimumTrustLevel: "UNTRUSTED",
331
+ enabled: true,
332
+ createdAt: "",
333
+ updatedAt: ""
334
+ },
335
+ {
336
+ id: "allow-get",
337
+ description: "Allow get tools",
338
+ effect: "ALLOW",
339
+ priority: 102,
340
+ toolPattern: "*get*",
341
+ permission: "EXECUTE",
342
+ minimumTrustLevel: "UNTRUSTED",
343
+ enabled: true,
344
+ createdAt: "",
345
+ updatedAt: ""
346
+ },
347
+ {
348
+ id: "allow-search",
349
+ description: "Allow search tools",
350
+ effect: "ALLOW",
351
+ priority: 103,
352
+ toolPattern: "*search*",
353
+ permission: "EXECUTE",
354
+ minimumTrustLevel: "UNTRUSTED",
355
+ enabled: true,
356
+ createdAt: "",
357
+ updatedAt: ""
358
+ },
359
+ {
360
+ id: "allow-query",
361
+ description: "Allow query tools",
362
+ effect: "ALLOW",
363
+ priority: 104,
364
+ toolPattern: "*query*",
365
+ permission: "EXECUTE",
366
+ minimumTrustLevel: "UNTRUSTED",
367
+ enabled: true,
368
+ createdAt: "",
369
+ updatedAt: ""
370
+ }
371
+ ],
372
+ createdAt: "",
373
+ updatedAt: ""
374
+ },
375
+ sandbox: {
376
+ id: "sandbox",
377
+ name: "Sandbox",
378
+ description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
379
+ version: 1,
380
+ rules: [
381
+ {
382
+ id: "deny-dangerous-commands",
383
+ description: "Block dangerous shell commands",
384
+ effect: "DENY",
385
+ priority: 50,
386
+ toolPattern: "*",
387
+ permission: "EXECUTE",
388
+ minimumTrustLevel: "UNTRUSTED",
389
+ enabled: true,
390
+ commandConstraints: {
391
+ denied: [
392
+ "rm -rf *",
393
+ "rm -r /*",
394
+ "mkfs*",
395
+ "dd if=*",
396
+ "curl*|*bash*",
397
+ "wget*|*sh*",
398
+ "shutdown*",
399
+ "reboot*",
400
+ "chmod*777*"
401
+ ]
402
+ },
403
+ createdAt: "",
404
+ updatedAt: ""
405
+ },
406
+ {
407
+ id: "allow-safe-commands",
408
+ description: "Allow safe shell commands only",
409
+ effect: "ALLOW",
410
+ priority: 100,
411
+ toolPattern: "*shell*",
412
+ permission: "EXECUTE",
413
+ minimumTrustLevel: "UNTRUSTED",
414
+ enabled: true,
415
+ commandConstraints: {
416
+ allowed: [
417
+ "ls*",
418
+ "cat*",
419
+ "head*",
420
+ "tail*",
421
+ "wc*",
422
+ "grep*",
423
+ "find*",
424
+ "echo*",
425
+ "pwd",
426
+ "whoami",
427
+ "date",
428
+ "env",
429
+ "git*",
430
+ "npm*",
431
+ "pnpm*",
432
+ "yarn*",
433
+ "node*",
434
+ "python*",
435
+ "pip*"
436
+ ]
437
+ },
438
+ createdAt: "",
439
+ updatedAt: ""
440
+ },
441
+ {
442
+ id: "deny-sensitive-paths",
443
+ description: "Block access to sensitive files",
444
+ effect: "DENY",
445
+ priority: 51,
446
+ toolPattern: "*",
447
+ permission: "EXECUTE",
448
+ minimumTrustLevel: "UNTRUSTED",
449
+ enabled: true,
450
+ pathConstraints: {
451
+ denied: [
452
+ "**/.env*",
453
+ "**/.ssh/**",
454
+ "**/.aws/**",
455
+ "**/credentials*",
456
+ "**/*.pem",
457
+ "**/*.key"
458
+ ]
459
+ },
460
+ createdAt: "",
461
+ updatedAt: ""
462
+ },
463
+ {
464
+ id: "allow-rest",
465
+ description: "Allow all other tools",
466
+ effect: "ALLOW",
467
+ priority: 1e3,
468
+ toolPattern: "*",
469
+ permission: "EXECUTE",
470
+ minimumTrustLevel: "UNTRUSTED",
471
+ enabled: true,
472
+ createdAt: "",
473
+ updatedAt: ""
474
+ }
475
+ ],
476
+ createdAt: "",
477
+ updatedAt: ""
478
+ },
479
+ permissive: {
480
+ id: "permissive",
481
+ name: "Permissive",
482
+ description: "Allows all tool calls (monitoring only)",
483
+ version: 1,
484
+ rules: [
485
+ {
486
+ id: "allow-all",
487
+ description: "Allow all",
488
+ effect: "ALLOW",
489
+ priority: 1e3,
490
+ toolPattern: "*",
491
+ permission: "EXECUTE",
492
+ minimumTrustLevel: "UNTRUSTED",
493
+ enabled: true,
494
+ createdAt: "",
495
+ updatedAt: ""
496
+ }
497
+ ],
498
+ createdAt: "",
499
+ updatedAt: ""
500
+ },
501
+ "deny-all": {
502
+ id: "deny-all",
503
+ name: "Deny All",
504
+ description: "Blocks all tool calls",
505
+ version: 1,
506
+ rules: [],
507
+ createdAt: "",
508
+ updatedAt: ""
509
+ }
510
+ };
511
+ }
512
+ });
513
+
7
514
  // src/init.ts
8
515
  var init_exports = {};
9
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, copyFileSync, mkdirSync } from "fs";
516
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
10
517
  import { resolve as resolve2, join } from "path";
11
518
  import { createInterface } from "readline";
12
519
  function findConfigFile(explicitPath) {
13
520
  if (explicitPath) {
14
- if (existsSync2(explicitPath)) {
521
+ if (existsSync3(explicitPath)) {
15
522
  return { path: resolve2(explicitPath), type: "mcp" };
16
523
  }
17
524
  return null;
18
525
  }
19
526
  for (const searchPath of SEARCH_PATHS) {
20
527
  const full = resolve2(searchPath);
21
- if (existsSync2(full)) return { path: full, type: "mcp" };
528
+ if (existsSync3(full)) return { path: full, type: "mcp" };
22
529
  }
23
530
  for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
24
- if (existsSync2(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
531
+ if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
25
532
  }
26
533
  return null;
27
534
  }
28
535
  function readConfig(filePath) {
29
- const content = readFileSync2(filePath, "utf-8");
536
+ const content = readFileSync3(filePath, "utf-8");
30
537
  const parsed = JSON.parse(content);
31
538
  if (parsed.mcpServers) return parsed;
32
539
  throw new Error(`Unrecognized config format in ${filePath}`);
@@ -97,7 +604,7 @@ function parseInitArgs(argv) {
97
604
  }
98
605
  }
99
606
  if (!POLICY_PRESETS.includes(options.policy)) {
100
- if (!existsSync2(resolve2(options.policy))) {
607
+ if (!existsSync3(resolve2(options.policy))) {
101
608
  console.error(`Unknown policy: ${options.policy}`);
102
609
  console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
103
610
  process.exit(1);
@@ -141,16 +648,16 @@ function installClaudeCodeHooks(apiKey) {
141
648
  const hooksDir = resolve2(".claude", "hooks");
142
649
  mkdirSync(hooksDir, { recursive: true });
143
650
  const guardPath = join(hooksDir, "guard.mjs");
144
- writeFileSync(guardPath, GUARD_SCRIPT);
651
+ writeFileSync2(guardPath, GUARD_SCRIPT);
145
652
  console.error(` Created ${guardPath}`);
146
653
  const auditPath = join(hooksDir, "audit.mjs");
147
- writeFileSync(auditPath, AUDIT_SCRIPT);
654
+ writeFileSync2(auditPath, AUDIT_SCRIPT);
148
655
  console.error(` Created ${auditPath}`);
149
656
  const settingsPath = resolve2(".claude", "settings.json");
150
657
  let settings = {};
151
- if (existsSync2(settingsPath)) {
658
+ if (existsSync3(settingsPath)) {
152
659
  try {
153
- settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
660
+ settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
154
661
  } catch {
155
662
  }
156
663
  }
@@ -184,7 +691,7 @@ function installClaudeCodeHooks(apiKey) {
184
691
  const envObj = settings.env || {};
185
692
  envObj.SOLONGATE_API_KEY = apiKey;
186
693
  settings.env = envObj;
187
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
694
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
188
695
  console.error(` Created ${settingsPath}`);
189
696
  console.error("");
190
697
  console.error(" Claude Code hooks installed!");
@@ -193,7 +700,7 @@ function installClaudeCodeHooks(apiKey) {
193
700
  }
194
701
  function ensureEnvFile() {
195
702
  const envPath = resolve2(".env");
196
- if (!existsSync2(envPath)) {
703
+ if (!existsSync3(envPath)) {
197
704
  const envContent = `# SolonGate API Keys
198
705
  # Get your keys from the dashboard: https://solongate.com
199
706
  # IMPORTANT: Never commit this file to git!
@@ -204,14 +711,14 @@ SOLONGATE_API_KEY=sg_live_your_key_here
204
711
  # Test key \u2014 local-only, no cloud connection (for development)
205
712
  # SOLONGATE_API_KEY=sg_test_your_key_here
206
713
  `;
207
- writeFileSync(envPath, envContent);
714
+ writeFileSync2(envPath, envContent);
208
715
  console.error(` Created .env with placeholder API keys`);
209
716
  console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
210
717
  console.error("");
211
718
  }
212
719
  const gitignorePath = resolve2(".gitignore");
213
- if (existsSync2(gitignorePath)) {
214
- let gitignore = readFileSync2(gitignorePath, "utf-8");
720
+ if (existsSync3(gitignorePath)) {
721
+ let gitignore = readFileSync3(gitignorePath, "utf-8");
215
722
  let updated = false;
216
723
  if (!gitignore.includes(".env")) {
217
724
  gitignore = gitignore.trimEnd() + "\n.env\n.env.local\n";
@@ -222,11 +729,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
222
729
  updated = true;
223
730
  }
224
731
  if (updated) {
225
- writeFileSync(gitignorePath, gitignore);
732
+ writeFileSync2(gitignorePath, gitignore);
226
733
  console.error(` Updated .gitignore (added .env + .mcp.json)`);
227
734
  }
228
735
  } else {
229
- writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
736
+ writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
230
737
  console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
231
738
  }
232
739
  }
@@ -274,7 +781,7 @@ async function main() {
274
781
  console.error("");
275
782
  const backupPath = configInfo.path + ".solongate-backup";
276
783
  if (options.restore) {
277
- if (!existsSync2(backupPath)) {
784
+ if (!existsSync3(backupPath)) {
278
785
  console.error(" No backup found. Nothing to restore.");
279
786
  process.exit(1);
280
787
  }
@@ -331,8 +838,8 @@ async function main() {
331
838
  let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
332
839
  if (!apiKey) {
333
840
  const envPath = resolve2(".env");
334
- if (existsSync2(envPath)) {
335
- const envContent = readFileSync2(envPath, "utf-8");
841
+ if (existsSync3(envPath)) {
842
+ const envContent = readFileSync3(envPath, "utf-8");
336
843
  const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
337
844
  if (match) apiKey = match[1];
338
845
  }
@@ -367,16 +874,16 @@ async function main() {
367
874
  console.error(JSON.stringify(newConfig, null, 2));
368
875
  process.exit(0);
369
876
  }
370
- if (!existsSync2(backupPath)) {
877
+ if (!existsSync3(backupPath)) {
371
878
  copyFileSync(configInfo.path, backupPath);
372
879
  console.error(` Backup: ${backupPath}`);
373
880
  }
374
881
  if (configInfo.type === "claude-desktop") {
375
- const original = JSON.parse(readFileSync2(configInfo.path, "utf-8"));
882
+ const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
376
883
  original.mcpServers = newConfig.mcpServers;
377
- writeFileSync(configInfo.path, JSON.stringify(original, null, 2) + "\n");
884
+ writeFileSync2(configInfo.path, JSON.stringify(original, null, 2) + "\n");
378
885
  } else {
379
- writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
886
+ writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
380
887
  }
381
888
  console.error(" Config updated!");
382
889
  console.error("");
@@ -635,7 +1142,7 @@ process.stdin.on('end', async () => {
635
1142
 
636
1143
  // src/inject.ts
637
1144
  var inject_exports = {};
638
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync as copyFileSync2 } from "fs";
1145
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync as copyFileSync2 } from "fs";
639
1146
  import { resolve as resolve3 } from "path";
640
1147
  import { execSync } from "child_process";
641
1148
  function parseInjectArgs(argv) {
@@ -668,7 +1175,7 @@ function parseInjectArgs(argv) {
668
1175
  return opts;
669
1176
  }
670
1177
  function printHelp2() {
671
- log2(`
1178
+ log3(`
672
1179
  SolonGate Inject \u2014 Add security to your MCP server in seconds
673
1180
 
674
1181
  USAGE
@@ -692,7 +1199,7 @@ WHAT IT DOES
692
1199
  All tool() calls are automatically protected by SolonGate.
693
1200
  `);
694
1201
  }
695
- function log2(msg) {
1202
+ function log3(msg) {
696
1203
  process.stderr.write(msg + "\n");
697
1204
  }
698
1205
  function printBanner(subtitle) {
@@ -705,18 +1212,18 @@ function printBanner(subtitle) {
705
1212
  " \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
1213
  ];
707
1214
  const colors = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
708
- log2("");
1215
+ log3("");
709
1216
  for (let i = 0; i < lines.length; i++) {
710
- log2(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
1217
+ log3(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
711
1218
  }
712
- log2("");
713
- log2(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
714
- log2("");
1219
+ log3("");
1220
+ log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
1221
+ log3("");
715
1222
  }
716
1223
  function detectProject() {
717
- if (!existsSync3(resolve3("package.json"))) return false;
1224
+ if (!existsSync4(resolve3("package.json"))) return false;
718
1225
  try {
719
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1226
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
720
1227
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
721
1228
  return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
722
1229
  } catch {
@@ -725,18 +1232,18 @@ function detectProject() {
725
1232
  }
726
1233
  function findTsEntryFile() {
727
1234
  try {
728
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1235
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
729
1236
  if (pkg.bin) {
730
1237
  const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
731
1238
  if (typeof binPath === "string") {
732
1239
  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);
1240
+ if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
1241
+ if (existsSync4(resolve3(binPath))) return resolve3(binPath);
735
1242
  }
736
1243
  }
737
1244
  if (pkg.main) {
738
1245
  const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
739
- if (existsSync3(resolve3(srcPath))) return resolve3(srcPath);
1246
+ if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
740
1247
  }
741
1248
  } catch {
742
1249
  }
@@ -750,9 +1257,9 @@ function findTsEntryFile() {
750
1257
  ];
751
1258
  for (const c3 of candidates) {
752
1259
  const full = resolve3(c3);
753
- if (existsSync3(full)) {
1260
+ if (existsSync4(full)) {
754
1261
  try {
755
- const content = readFileSync3(full, "utf-8");
1262
+ const content = readFileSync4(full, "utf-8");
756
1263
  if (content.includes("McpServer") || content.includes("McpServer")) {
757
1264
  return full;
758
1265
  }
@@ -761,39 +1268,39 @@ function findTsEntryFile() {
761
1268
  }
762
1269
  }
763
1270
  for (const c3 of candidates) {
764
- if (existsSync3(resolve3(c3))) return resolve3(c3);
1271
+ if (existsSync4(resolve3(c3))) return resolve3(c3);
765
1272
  }
766
1273
  return null;
767
1274
  }
768
1275
  function detectPackageManager() {
769
- if (existsSync3(resolve3("pnpm-lock.yaml"))) return "pnpm";
770
- if (existsSync3(resolve3("yarn.lock"))) return "yarn";
1276
+ if (existsSync4(resolve3("pnpm-lock.yaml"))) return "pnpm";
1277
+ if (existsSync4(resolve3("yarn.lock"))) return "yarn";
771
1278
  return "npm";
772
1279
  }
773
1280
  function installSdk() {
774
1281
  try {
775
- const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
1282
+ const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
776
1283
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
777
1284
  if (allDeps["@solongate/sdk"]) {
778
- log2(" @solongate/sdk already installed");
1285
+ log3(" @solongate/sdk already installed");
779
1286
  return true;
780
1287
  }
781
1288
  } catch {
782
1289
  }
783
1290
  const pm = detectPackageManager();
784
1291
  const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
785
- log2(` Installing @solongate/sdk via ${pm}...`);
1292
+ log3(` Installing @solongate/sdk via ${pm}...`);
786
1293
  try {
787
1294
  execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
788
1295
  return true;
789
1296
  } catch (err) {
790
- log2(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
791
- log2(" You can install manually: npm install @solongate/sdk");
1297
+ log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
1298
+ log3(" You can install manually: npm install @solongate/sdk");
792
1299
  return false;
793
1300
  }
794
1301
  }
795
1302
  function injectTypeScript(filePath) {
796
- const original = readFileSync3(filePath, "utf-8");
1303
+ const original = readFileSync4(filePath, "utf-8");
797
1304
  const changes = [];
798
1305
  let modified = original;
799
1306
  if (modified.includes("SecureMcpServer")) {
@@ -871,14 +1378,14 @@ function findImportInsertPosition(content) {
871
1378
  }
872
1379
  function showDiff(result) {
873
1380
  if (result.original === result.modified) {
874
- log2(" No changes needed.");
1381
+ log3(" No changes needed.");
875
1382
  return;
876
1383
  }
877
1384
  const origLines = result.original.split("\n");
878
1385
  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");
1386
+ log3("");
1387
+ log3(` File: ${result.file}`);
1388
+ 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
1389
  const maxLines = Math.max(origLines.length, modLines.length);
883
1390
  let diffCount = 0;
884
1391
  for (let i = 0; i < maxLines; i++) {
@@ -886,99 +1393,99 @@ function showDiff(result) {
886
1393
  const mod = modLines[i];
887
1394
  if (orig !== mod) {
888
1395
  if (diffCount < 30) {
889
- if (orig !== void 0) log2(` - ${orig}`);
890
- if (mod !== void 0) log2(` + ${mod}`);
1396
+ if (orig !== void 0) log3(` - ${orig}`);
1397
+ if (mod !== void 0) log3(` + ${mod}`);
891
1398
  }
892
1399
  diffCount++;
893
1400
  }
894
1401
  }
895
1402
  if (diffCount > 30) {
896
- log2(` ... and ${diffCount - 30} more line changes`);
1403
+ log3(` ... and ${diffCount - 30} more line changes`);
897
1404
  }
898
- log2("");
1405
+ log3("");
899
1406
  }
900
1407
  async function main2() {
901
1408
  const opts = parseInjectArgs(process.argv);
902
1409
  printBanner("Inject SDK");
903
1410
  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>");
1411
+ log3(" Could not detect a TypeScript MCP server project.");
1412
+ log3(" Make sure you are in a project directory with:");
1413
+ log3(" package.json + @modelcontextprotocol/sdk in dependencies");
1414
+ log3("");
1415
+ log3(" To create a new MCP server: npx @solongate/proxy create <name>");
909
1416
  process.exit(1);
910
1417
  }
911
- log2(" Language: TypeScript");
1418
+ log3(" Language: TypeScript");
912
1419
  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");
1420
+ if (!entryFile || !existsSync4(entryFile)) {
1421
+ log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
1422
+ log3("");
1423
+ log3(" Specify it manually: --file <path>");
1424
+ log3("");
1425
+ log3(" Common entry points:");
1426
+ log3(" src/index.ts, src/server.ts, index.ts");
920
1427
  process.exit(1);
921
1428
  }
922
- log2(` Entry: ${entryFile}`);
923
- log2("");
1429
+ log3(` Entry: ${entryFile}`);
1430
+ log3("");
924
1431
  const backupPath = entryFile + ".solongate-backup";
925
1432
  if (opts.restore) {
926
- if (!existsSync3(backupPath)) {
927
- log2(" No backup found. Nothing to restore.");
1433
+ if (!existsSync4(backupPath)) {
1434
+ log3(" No backup found. Nothing to restore.");
928
1435
  process.exit(1);
929
1436
  }
930
1437
  copyFileSync2(backupPath, entryFile);
931
- log2(` Restored original file from backup.`);
932
- log2(` Backup: ${backupPath}`);
1438
+ log3(` Restored original file from backup.`);
1439
+ log3(` Backup: ${backupPath}`);
933
1440
  process.exit(0);
934
1441
  }
935
1442
  if (!opts.skipInstall && !opts.dryRun) {
936
1443
  installSdk();
937
- log2("");
1444
+ log3("");
938
1445
  }
939
1446
  const result = injectTypeScript(entryFile);
940
- log2(` Changes (${result.changes.length}):`);
1447
+ log3(` Changes (${result.changes.length}):`);
941
1448
  for (const change of result.changes) {
942
- log2(` - ${change}`);
1449
+ log3(` - ${change}`);
943
1450
  }
944
1451
  if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
945
- log2("");
946
- log2(" Your MCP server is already protected by SolonGate!");
1452
+ log3("");
1453
+ log3(" Your MCP server is already protected by SolonGate!");
947
1454
  process.exit(0);
948
1455
  }
949
1456
  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");
1457
+ log3("");
1458
+ log3(" No changes were made. The file may not contain recognizable MCP patterns.");
1459
+ log3(" See docs: https://solongate.com/docs/integration");
953
1460
  process.exit(0);
954
1461
  }
955
1462
  if (opts.dryRun) {
956
- log2("");
957
- log2(" --- DRY RUN (no changes written) ---");
1463
+ log3("");
1464
+ log3(" --- DRY RUN (no changes written) ---");
958
1465
  showDiff(result);
959
- log2(" To apply: npx @solongate/proxy inject");
1466
+ log3(" To apply: npx @solongate/proxy inject");
960
1467
  process.exit(0);
961
1468
  }
962
- if (!existsSync3(backupPath)) {
1469
+ if (!existsSync4(backupPath)) {
963
1470
  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("");
1471
+ log3("");
1472
+ log3(` Backup: ${backupPath}`);
1473
+ }
1474
+ writeFileSync3(entryFile, result.modified);
1475
+ log3("");
1476
+ 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");
1477
+ log3(" \u2502 SolonGate SDK injected successfully! \u2502");
1478
+ log3(" \u2502 \u2502");
1479
+ log3(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
1480
+ log3(" \u2502 All tool() calls are now auto-protected. \u2502");
1481
+ log3(" \u2502 \u2502");
1482
+ log3(" \u2502 Set your API key: \u2502");
1483
+ log3(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
1484
+ log3(" \u2502 \u2502");
1485
+ log3(" \u2502 To undo: \u2502");
1486
+ log3(" \u2502 npx @solongate/proxy inject --restore \u2502");
1487
+ 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");
1488
+ log3("");
982
1489
  }
983
1490
  var c;
984
1491
  var init_inject = __esm({
@@ -999,7 +1506,7 @@ var init_inject = __esm({
999
1506
  blue6: "\x1B[38;2;170;200;250m"
1000
1507
  };
1001
1508
  main2().catch((err) => {
1002
- log2(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1509
+ log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1003
1510
  process.exit(1);
1004
1511
  });
1005
1512
  }
@@ -1007,10 +1514,10 @@ var init_inject = __esm({
1007
1514
 
1008
1515
  // src/create.ts
1009
1516
  var create_exports = {};
1010
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
1517
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
1011
1518
  import { resolve as resolve4, join as join2 } from "path";
1012
1519
  import { execSync as execSync2 } from "child_process";
1013
- function log3(msg) {
1520
+ function log4(msg) {
1014
1521
  process.stderr.write(msg + "\n");
1015
1522
  }
1016
1523
  function printBanner2(subtitle) {
@@ -1023,13 +1530,13 @@ function printBanner2(subtitle) {
1023
1530
  " \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
1531
  ];
1025
1532
  const colors = [c2.blue1, c2.blue2, c2.blue3, c2.blue4, c2.blue5, c2.blue6];
1026
- log3("");
1533
+ log4("");
1027
1534
  for (let i = 0; i < lines.length; i++) {
1028
- log3(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
1535
+ log4(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
1029
1536
  }
1030
- log3("");
1031
- log3(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
1032
- log3("");
1537
+ log4("");
1538
+ log4(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
1539
+ log4("");
1033
1540
  }
1034
1541
  function withSpinner(message, fn) {
1035
1542
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -1078,20 +1585,20 @@ function parseCreateArgs(argv) {
1078
1585
  }
1079
1586
  }
1080
1587
  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");
1588
+ log4("");
1589
+ log4(" Error: Project name required.");
1590
+ log4("");
1591
+ log4(" Usage: npx @solongate/proxy create <name>");
1592
+ log4("");
1593
+ log4(" Examples:");
1594
+ log4(" npx @solongate/proxy create my-mcp-server");
1595
+ log4(" npx @solongate/proxy create weather-api");
1089
1596
  process.exit(1);
1090
1597
  }
1091
1598
  return opts;
1092
1599
  }
1093
1600
  function printHelp3() {
1094
- log3(`
1601
+ log4(`
1095
1602
  SolonGate Create \u2014 Scaffold a secure MCP server in seconds
1096
1603
 
1097
1604
  USAGE
@@ -1108,7 +1615,7 @@ EXAMPLES
1108
1615
  `);
1109
1616
  }
1110
1617
  function createProject(dir, name, _policy) {
1111
- writeFileSync3(
1618
+ writeFileSync4(
1112
1619
  join2(dir, "package.json"),
1113
1620
  JSON.stringify(
1114
1621
  {
@@ -1138,7 +1645,7 @@ function createProject(dir, name, _policy) {
1138
1645
  2
1139
1646
  ) + "\n"
1140
1647
  );
1141
- writeFileSync3(
1648
+ writeFileSync4(
1142
1649
  join2(dir, "tsconfig.json"),
1143
1650
  JSON.stringify(
1144
1651
  {
@@ -1160,7 +1667,7 @@ function createProject(dir, name, _policy) {
1160
1667
  ) + "\n"
1161
1668
  );
1162
1669
  mkdirSync2(join2(dir, "src"), { recursive: true });
1163
- writeFileSync3(
1670
+ writeFileSync4(
1164
1671
  join2(dir, "src", "index.ts"),
1165
1672
  `#!/usr/bin/env node
1166
1673
 
@@ -1205,7 +1712,7 @@ console.log('');
1205
1712
  console.log('Press Ctrl+C to stop.');
1206
1713
  `
1207
1714
  );
1208
- writeFileSync3(
1715
+ writeFileSync4(
1209
1716
  join2(dir, ".mcp.json"),
1210
1717
  JSON.stringify(
1211
1718
  {
@@ -1223,12 +1730,12 @@ console.log('Press Ctrl+C to stop.');
1223
1730
  2
1224
1731
  ) + "\n"
1225
1732
  );
1226
- writeFileSync3(
1733
+ writeFileSync4(
1227
1734
  join2(dir, ".env"),
1228
1735
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
1229
1736
  `
1230
1737
  );
1231
- writeFileSync3(
1738
+ writeFileSync4(
1232
1739
  join2(dir, ".gitignore"),
1233
1740
  `node_modules/
1234
1741
  dist/
@@ -1243,8 +1750,8 @@ async function main3() {
1243
1750
  const opts = parseCreateArgs(process.argv);
1244
1751
  const dir = resolve4(opts.name);
1245
1752
  printBanner2("Create MCP Server");
1246
- if (existsSync4(dir)) {
1247
- log3(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
1753
+ if (existsSync5(dir)) {
1754
+ log4(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
1248
1755
  process.exit(1);
1249
1756
  }
1250
1757
  withSpinner(`Setting up ${opts.name}...`, () => {
@@ -1256,19 +1763,19 @@ async function main3() {
1256
1763
  execSync2("npm install", { cwd: dir, stdio: "pipe" });
1257
1764
  });
1258
1765
  }
1259
- log3("");
1766
+ log4("");
1260
1767
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
1261
1768
  const W = 52;
1262
1769
  const hr = "\u2500".repeat(W + 2);
1263
1770
  const bLine = (text) => {
1264
1771
  const visible = stripAnsi(text);
1265
1772
  const padding = W - visible.length;
1266
- log3(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
1773
+ log4(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
1267
1774
  };
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}`);
1775
+ const bEmpty = () => log4(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
1776
+ log4(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
1777
+ 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}`);
1778
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1272
1779
  bEmpty();
1273
1780
  bLine(`${c2.yellow}Next steps:${c2.reset}`);
1274
1781
  bEmpty();
@@ -1277,7 +1784,7 @@ async function main3() {
1277
1784
  bLine(` ${c2.cyan}$${c2.reset} npm run dev ${c2.dim}# dev mode${c2.reset}`);
1278
1785
  bLine(` ${c2.cyan}$${c2.reset} npm start ${c2.dim}# production${c2.reset}`);
1279
1786
  bEmpty();
1280
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1787
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1281
1788
  bEmpty();
1282
1789
  bLine(`${c2.yellow}Set your API key:${c2.reset}`);
1283
1790
  bEmpty();
@@ -1285,14 +1792,14 @@ async function main3() {
1285
1792
  bEmpty();
1286
1793
  bLine(`${c2.dim}https://dashboard.solongate.com/api-keys/${c2.reset}`);
1287
1794
  bEmpty();
1288
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1795
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1289
1796
  bEmpty();
1290
1797
  bLine(`${c2.yellow}Use with OpenClaw:${c2.reset}`);
1291
1798
  bEmpty();
1292
1799
  bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @openclaw/server`);
1293
1800
  bLine(` ${c2.dim}Wraps OpenClaw servers with SolonGate protection${c2.reset}`);
1294
1801
  bEmpty();
1295
- log3(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1802
+ log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
1296
1803
  bEmpty();
1297
1804
  bLine(`${c2.yellow}Use with Claude Code / Cursor / MCP client:${c2.reset}`);
1298
1805
  bEmpty();
@@ -1305,512 +1812,190 @@ async function main3() {
1305
1812
  bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
1306
1813
  bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
1307
1814
  bEmpty();
1308
- log3(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
1309
- log3("");
1815
+ log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
1816
+ log4("");
1310
1817
  }
1311
1818
  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;
1819
+ var init_create = __esm({
1820
+ "src/create.ts"() {
1821
+ "use strict";
1822
+ c2 = {
1823
+ reset: "\x1B[0m",
1824
+ bold: "\x1B[1m",
1825
+ dim: "\x1B[2m",
1826
+ italic: "\x1B[3m",
1827
+ white: "\x1B[97m",
1828
+ gray: "\x1B[90m",
1829
+ blue1: "\x1B[38;2;20;50;160m",
1830
+ blue2: "\x1B[38;2;40;80;190m",
1831
+ blue3: "\x1B[38;2;60;110;215m",
1832
+ blue4: "\x1B[38;2;90;140;230m",
1833
+ blue5: "\x1B[38;2;130;170;240m",
1834
+ blue6: "\x1B[38;2;170;200;250m",
1835
+ green: "\x1B[38;2;80;200;120m",
1836
+ red: "\x1B[38;2;220;80;80m",
1837
+ cyan: "\x1B[38;2;100;200;220m",
1838
+ yellow: "\x1B[38;2;220;200;80m",
1839
+ bgBlue: "\x1B[48;2;20;50;160m"
1840
+ };
1841
+ main3().catch((err) => {
1842
+ log4(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
1843
+ process.exit(1);
1844
+ });
1845
+ }
1846
+ });
1847
+
1848
+ // src/pull-push.ts
1849
+ var pull_push_exports = {};
1850
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
1851
+ import { resolve as resolve5 } from "path";
1852
+ function loadEnv() {
1853
+ if (process.env.SOLONGATE_API_KEY) return;
1854
+ const envPath = resolve5(".env");
1855
+ if (!existsSync6(envPath)) return;
1856
+ try {
1857
+ const content = readFileSync5(envPath, "utf-8");
1858
+ for (const line of content.split("\n")) {
1859
+ const trimmed = line.trim();
1860
+ if (!trimmed || trimmed.startsWith("#")) continue;
1861
+ const eq = trimmed.indexOf("=");
1862
+ if (eq === -1) continue;
1863
+ const key = trimmed.slice(0, eq).trim();
1864
+ const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
1865
+ if (key === "SOLONGATE_API_KEY") {
1866
+ process.env.SOLONGATE_API_KEY = val;
1867
+ return;
1868
+ }
1869
+ }
1870
+ } catch {
1871
+ }
1872
+ }
1873
+ function parseCliArgs() {
1874
+ loadEnv();
1875
+ const args = process.argv.slice(2);
1876
+ const command = args[0];
1877
+ let apiKey = process.env.SOLONGATE_API_KEY || "";
1878
+ let file = "policy.json";
1879
+ let policyId;
1880
+ for (let i = 1; i < args.length; i++) {
1881
+ switch (args[i]) {
1721
1882
  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];
1883
+ apiKey = args[++i];
1729
1884
  break;
1730
- case "--upstream-transport":
1731
- upstreamTransport = flags[++i];
1885
+ case "--policy":
1886
+ case "--output":
1887
+ case "--file":
1888
+ case "-f":
1889
+ case "-o":
1890
+ file = args[++i];
1732
1891
  break;
1733
- case "--port":
1734
- port = parseInt(flags[++i], 10);
1892
+ case "--policy-id":
1893
+ case "--id":
1894
+ policyId = args[++i];
1735
1895
  break;
1736
1896
  }
1737
1897
  }
1738
1898
  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
- );
1899
+ log5("ERROR: API key not found.");
1900
+ log5("");
1901
+ log5("Set it in .env file:");
1902
+ log5(" SOLONGATE_API_KEY=sg_live_...");
1903
+ log5("");
1904
+ log5("Or pass via environment:");
1905
+ log5(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
1906
+ process.exit(1);
1752
1907
  }
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
- };
1908
+ if (!apiKey.startsWith("sg_live_")) {
1909
+ log5("ERROR: Pull/push requires a live API key (sg_live_...).");
1910
+ process.exit(1);
1771
1911
  }
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
- };
1912
+ return { command, apiKey, file: resolve5(file), policyId };
1913
+ }
1914
+ async function pull(apiKey, file, policyId) {
1915
+ const apiUrl = "https://api.solongate.com";
1916
+ log5(`Pulling policy from dashboard...`);
1917
+ const policy = await fetchCloudPolicy(apiKey, apiUrl, policyId);
1918
+ const json = JSON.stringify(policy, null, 2) + "\n";
1919
+ writeFileSync5(file, json, "utf-8");
1920
+ log5(`Saved: ${file}`);
1921
+ log5(` Name: ${policy.name}`);
1922
+ log5(` Version: ${policy.version}`);
1923
+ log5(` Rules: ${policy.rules.length}`);
1924
+ log5("");
1925
+ log5("Done. Policy pulled from dashboard to local file.");
1926
+ }
1927
+ async function push(apiKey, file) {
1928
+ const apiUrl = "https://api.solongate.com";
1929
+ if (!existsSync6(file)) {
1930
+ log5(`ERROR: File not found: ${file}`);
1931
+ process.exit(1);
1790
1932
  }
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
- );
1933
+ const content = readFileSync5(file, "utf-8");
1934
+ let policy;
1935
+ try {
1936
+ policy = JSON.parse(content);
1937
+ } catch {
1938
+ log5(`ERROR: Invalid JSON in ${file}`);
1939
+ process.exit(1);
1795
1940
  }
1796
- const [command, ...commandArgs] = upstreamArgs;
1797
- return {
1798
- upstream: {
1799
- transport: upstreamTransport ?? "stdio",
1800
- command,
1801
- args: commandArgs,
1802
- env: { ...process.env }
1941
+ log5(`Pushing policy to dashboard...`);
1942
+ log5(` File: ${file}`);
1943
+ log5(` Name: ${policy.name || "Unnamed"}`);
1944
+ log5(` Rules: ${(policy.rules || []).length}`);
1945
+ const res = await fetch(`${apiUrl}/api/v1/policies`, {
1946
+ method: "POST",
1947
+ headers: {
1948
+ "Authorization": `Bearer ${apiKey}`,
1949
+ "Content-Type": "application/json"
1803
1950
  },
1804
- policy: loadPolicy(policySource),
1805
- name,
1806
- verbose,
1807
- rateLimitPerTool,
1808
- globalRateLimit,
1809
- apiKey,
1810
- apiUrl,
1811
- port
1812
- };
1951
+ body: JSON.stringify({
1952
+ id: policy.id || "default",
1953
+ name: policy.name || "Local Policy",
1954
+ description: policy.description || "Pushed from local file",
1955
+ version: policy.version || 1,
1956
+ rules: policy.rules || []
1957
+ })
1958
+ });
1959
+ if (!res.ok) {
1960
+ const body = await res.text().catch(() => "");
1961
+ log5(`ERROR: Push failed (${res.status}): ${body}`);
1962
+ process.exit(1);
1963
+ }
1964
+ const data = await res.json();
1965
+ log5(` Cloud version: ${data._version ?? "created"}`);
1966
+ log5("");
1967
+ log5("Done. Policy pushed from local file to dashboard.");
1968
+ }
1969
+ async function main4() {
1970
+ const { command, apiKey, file, policyId } = parseCliArgs();
1971
+ try {
1972
+ if (command === "pull") {
1973
+ await pull(apiKey, file, policyId);
1974
+ } else if (command === "push") {
1975
+ await push(apiKey, file);
1976
+ } else {
1977
+ log5(`Unknown command: ${command}`);
1978
+ log5("Usage: solongate-proxy pull|push [--file policy.json]");
1979
+ process.exit(1);
1980
+ }
1981
+ } catch (err) {
1982
+ log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
1983
+ process.exit(1);
1984
+ }
1813
1985
  }
1986
+ var log5;
1987
+ var init_pull_push = __esm({
1988
+ "src/pull-push.ts"() {
1989
+ "use strict";
1990
+ init_config();
1991
+ log5 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
1992
+ `);
1993
+ main4();
1994
+ }
1995
+ });
1996
+
1997
+ // src/index.ts
1998
+ init_config();
1814
1999
 
1815
2000
  // src/proxy.ts
1816
2001
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -3348,7 +3533,202 @@ var SolonGate = class {
3348
3533
  };
3349
3534
 
3350
3535
  // src/proxy.ts
3351
- var log = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
3536
+ init_config();
3537
+
3538
+ // src/sync.ts
3539
+ init_config();
3540
+ import { readFileSync as readFileSync2, writeFileSync, watch, existsSync as existsSync2 } from "fs";
3541
+ var log = (...args) => process.stderr.write(`[SolonGate Sync] ${args.map(String).join(" ")}
3542
+ `);
3543
+ var PolicySyncManager = class {
3544
+ localPath;
3545
+ apiKey;
3546
+ apiUrl;
3547
+ pollIntervalMs;
3548
+ onPolicyUpdate;
3549
+ currentPolicy;
3550
+ localVersion;
3551
+ cloudVersion;
3552
+ skipNextWatch = false;
3553
+ debounceTimer = null;
3554
+ pollTimer = null;
3555
+ watcher = null;
3556
+ isLiveKey;
3557
+ constructor(opts) {
3558
+ this.localPath = opts.localPath;
3559
+ this.apiKey = opts.apiKey;
3560
+ this.apiUrl = opts.apiUrl;
3561
+ this.pollIntervalMs = opts.pollIntervalMs ?? 6e4;
3562
+ this.onPolicyUpdate = opts.onPolicyUpdate;
3563
+ this.currentPolicy = opts.initialPolicy;
3564
+ this.localVersion = opts.initialPolicy.version ?? 0;
3565
+ this.cloudVersion = 0;
3566
+ this.isLiveKey = opts.apiKey.startsWith("sg_live_");
3567
+ }
3568
+ /**
3569
+ * Start watching local file and polling cloud.
3570
+ */
3571
+ start() {
3572
+ if (this.localPath && existsSync2(this.localPath)) {
3573
+ this.startFileWatcher();
3574
+ }
3575
+ if (this.isLiveKey) {
3576
+ this.pushToCloud(this.currentPolicy).catch(() => {
3577
+ });
3578
+ this.startPolling();
3579
+ }
3580
+ }
3581
+ /**
3582
+ * Stop all watchers and timers.
3583
+ */
3584
+ stop() {
3585
+ if (this.watcher) {
3586
+ this.watcher.close();
3587
+ this.watcher = null;
3588
+ }
3589
+ if (this.pollTimer) {
3590
+ clearInterval(this.pollTimer);
3591
+ this.pollTimer = null;
3592
+ }
3593
+ if (this.debounceTimer) {
3594
+ clearTimeout(this.debounceTimer);
3595
+ this.debounceTimer = null;
3596
+ }
3597
+ }
3598
+ /**
3599
+ * Watch local file for changes (debounced).
3600
+ */
3601
+ startFileWatcher() {
3602
+ if (!this.localPath) return;
3603
+ const filePath = this.localPath;
3604
+ try {
3605
+ this.watcher = watch(filePath, () => {
3606
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
3607
+ this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
3608
+ });
3609
+ log(`Watching ${filePath} for changes`);
3610
+ } catch (err) {
3611
+ log(`File watch failed: ${err instanceof Error ? err.message : String(err)}`);
3612
+ }
3613
+ }
3614
+ /**
3615
+ * Handle local file change event.
3616
+ */
3617
+ async onFileChange(filePath) {
3618
+ if (this.skipNextWatch) {
3619
+ this.skipNextWatch = false;
3620
+ return;
3621
+ }
3622
+ try {
3623
+ if (!existsSync2(filePath)) {
3624
+ log("Policy file deleted \u2014 keeping current policy");
3625
+ return;
3626
+ }
3627
+ const content = readFileSync2(filePath, "utf-8");
3628
+ const newPolicy = JSON.parse(content);
3629
+ if (newPolicy.version <= this.localVersion) {
3630
+ newPolicy.version = Math.max(this.localVersion, this.cloudVersion) + 1;
3631
+ this.writeToFile(newPolicy);
3632
+ }
3633
+ if (this.policiesEqual(newPolicy, this.currentPolicy)) return;
3634
+ log(`File changed: ${newPolicy.name} v${newPolicy.version}`);
3635
+ this.localVersion = newPolicy.version;
3636
+ this.currentPolicy = newPolicy;
3637
+ this.onPolicyUpdate(newPolicy);
3638
+ if (this.isLiveKey) {
3639
+ try {
3640
+ const result = await this.pushToCloud(newPolicy);
3641
+ this.cloudVersion = result.version;
3642
+ log(`Pushed to cloud: v${result.version}`);
3643
+ } catch (err) {
3644
+ log(`Cloud push failed: ${err instanceof Error ? err.message : String(err)}`);
3645
+ }
3646
+ }
3647
+ } catch (err) {
3648
+ log(`File read error: ${err instanceof Error ? err.message : String(err)}`);
3649
+ }
3650
+ }
3651
+ /**
3652
+ * Poll cloud for policy changes.
3653
+ */
3654
+ startPolling() {
3655
+ this.pollTimer = setInterval(() => this.onPollTick(), this.pollIntervalMs);
3656
+ }
3657
+ /**
3658
+ * Handle poll tick — fetch cloud policy and compare.
3659
+ */
3660
+ async onPollTick() {
3661
+ try {
3662
+ const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl);
3663
+ const cloudVer = cloudPolicy.version ?? 0;
3664
+ if (cloudVer <= this.localVersion && this.policiesEqual(cloudPolicy, this.currentPolicy)) {
3665
+ return;
3666
+ }
3667
+ if (cloudVer > this.localVersion || !this.policiesEqual(cloudPolicy, this.currentPolicy)) {
3668
+ log(`Cloud update: ${cloudPolicy.name} v${cloudVer} (was v${this.localVersion})`);
3669
+ this.cloudVersion = cloudVer;
3670
+ this.localVersion = cloudVer;
3671
+ this.currentPolicy = cloudPolicy;
3672
+ this.onPolicyUpdate(cloudPolicy);
3673
+ if (this.localPath) {
3674
+ this.writeToFile(cloudPolicy);
3675
+ log(`Updated local file: ${this.localPath}`);
3676
+ }
3677
+ }
3678
+ } catch {
3679
+ }
3680
+ }
3681
+ /**
3682
+ * Push policy to cloud API.
3683
+ */
3684
+ async pushToCloud(policy) {
3685
+ const url = `${this.apiUrl}/api/v1/policies`;
3686
+ const res = await fetch(url, {
3687
+ method: "POST",
3688
+ headers: {
3689
+ "Authorization": `Bearer ${this.apiKey}`,
3690
+ "Content-Type": "application/json"
3691
+ },
3692
+ body: JSON.stringify({
3693
+ id: policy.id || "default",
3694
+ name: policy.name || "Default Policy",
3695
+ description: policy.description || "Synced from proxy",
3696
+ version: policy.version || 1,
3697
+ rules: policy.rules
3698
+ })
3699
+ });
3700
+ if (!res.ok) {
3701
+ const body = await res.text().catch(() => "");
3702
+ throw new Error(`Push failed (${res.status}): ${body}`);
3703
+ }
3704
+ const data = await res.json();
3705
+ return { version: Number(data._version ?? policy.version) };
3706
+ }
3707
+ /**
3708
+ * Write policy to local file (with loop prevention).
3709
+ */
3710
+ writeToFile(policy) {
3711
+ if (!this.localPath) return;
3712
+ this.skipNextWatch = true;
3713
+ try {
3714
+ const json = JSON.stringify(policy, null, 2) + "\n";
3715
+ writeFileSync(this.localPath, json, "utf-8");
3716
+ } catch (err) {
3717
+ log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
3718
+ this.skipNextWatch = false;
3719
+ }
3720
+ }
3721
+ /**
3722
+ * Compare two policies by rules content (ignoring timestamps).
3723
+ */
3724
+ policiesEqual(a, b) {
3725
+ if (a.id !== b.id || a.name !== b.name || a.rules.length !== b.rules.length) return false;
3726
+ return JSON.stringify(a.rules) === JSON.stringify(b.rules);
3727
+ }
3728
+ };
3729
+
3730
+ // src/proxy.ts
3731
+ var log2 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
3352
3732
  `);
3353
3733
  var Mutex = class {
3354
3734
  queue = [];
@@ -3358,8 +3738,8 @@ var Mutex = class {
3358
3738
  this.locked = true;
3359
3739
  return;
3360
3740
  }
3361
- return new Promise((resolve5) => {
3362
- this.queue.push(resolve5);
3741
+ return new Promise((resolve6) => {
3742
+ this.queue.push(resolve6);
3363
3743
  });
3364
3744
  }
3365
3745
  release() {
@@ -3377,6 +3757,7 @@ var SolonGateProxy = class {
3377
3757
  client = null;
3378
3758
  server = null;
3379
3759
  callMutex = new Mutex();
3760
+ syncManager = null;
3380
3761
  upstreamTools = [];
3381
3762
  constructor(config) {
3382
3763
  this.config = config;
@@ -3393,20 +3774,20 @@ var SolonGateProxy = class {
3393
3774
  });
3394
3775
  const warnings = this.gate.getWarnings();
3395
3776
  for (const w of warnings) {
3396
- log("WARNING:", w);
3777
+ log2("WARNING:", w);
3397
3778
  }
3398
3779
  }
3399
3780
  /**
3400
3781
  * Start the proxy: connect to upstream, then serve downstream.
3401
3782
  */
3402
3783
  async start() {
3403
- log("Starting SolonGate Proxy...");
3784
+ log2("Starting SolonGate Proxy...");
3404
3785
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3405
3786
  if (this.config.apiKey) {
3406
3787
  if (this.config.apiKey.startsWith("sg_test_")) {
3407
- log("Using test API key \u2014 skipping online validation.");
3788
+ log2("Using test API key \u2014 skipping online validation.");
3408
3789
  } else {
3409
- log(`Validating license with ${apiUrl}...`);
3790
+ log2(`Validating license with ${apiUrl}...`);
3410
3791
  try {
3411
3792
  const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
3412
3793
  headers: {
@@ -3416,17 +3797,17 @@ var SolonGateProxy = class {
3416
3797
  signal: AbortSignal.timeout(1e4)
3417
3798
  });
3418
3799
  if (res.status === 401) {
3419
- log("ERROR: Invalid or expired API key.");
3800
+ log2("ERROR: Invalid or expired API key.");
3420
3801
  process.exit(1);
3421
3802
  }
3422
3803
  if (res.status === 403) {
3423
- log("ERROR: Your subscription is inactive. Renew at https://solongate.com");
3804
+ log2("ERROR: Your subscription is inactive. Renew at https://solongate.com");
3424
3805
  process.exit(1);
3425
3806
  }
3426
- log("License validated.");
3807
+ log2("License validated.");
3427
3808
  } 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)}`);
3809
+ log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
3810
+ log2(`Details: ${err instanceof Error ? err.message : String(err)}`);
3430
3811
  process.exit(1);
3431
3812
  }
3432
3813
  }
@@ -3434,28 +3815,27 @@ var SolonGateProxy = class {
3434
3815
  try {
3435
3816
  const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
3436
3817
  this.config.policy = cloudPolicy;
3437
- log(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
3818
+ log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
3438
3819
  } catch (err) {
3439
- log(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
3820
+ log2(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
3440
3821
  }
3441
3822
  }
3442
3823
  }
3443
3824
  this.gate.loadPolicy(this.config.policy);
3444
- log(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
3825
+ log2(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
3445
3826
  const transport = this.config.upstream.transport ?? "stdio";
3446
3827
  if (transport === "stdio") {
3447
- log(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3828
+ log2(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3448
3829
  } else {
3449
- log(`Upstream: [${transport}] ${this.config.upstream.url}`);
3830
+ log2(`Upstream: [${transport}] ${this.config.upstream.url}`);
3450
3831
  }
3451
3832
  await this.connectUpstream();
3452
3833
  await this.discoverTools();
3453
3834
  this.registerToolsToCloud();
3454
3835
  this.registerServerToCloud();
3455
- this.syncPolicyToCloud();
3836
+ this.startPolicySync();
3456
3837
  this.createServer();
3457
3838
  await this.serve();
3458
- this.startPolicyPolling();
3459
3839
  }
3460
3840
  /**
3461
3841
  * Connect to the upstream MCP server.
@@ -3493,7 +3873,7 @@ var SolonGateProxy = class {
3493
3873
  break;
3494
3874
  }
3495
3875
  }
3496
- log(`Connected to upstream server (${upstreamTransport})`);
3876
+ log2(`Connected to upstream server (${upstreamTransport})`);
3497
3877
  }
3498
3878
  /**
3499
3879
  * Discover tools from the upstream server.
@@ -3506,9 +3886,9 @@ var SolonGateProxy = class {
3506
3886
  description: t.description,
3507
3887
  inputSchema: t.inputSchema
3508
3888
  }));
3509
- log(`Discovered ${this.upstreamTools.length} tools from upstream:`);
3889
+ log2(`Discovered ${this.upstreamTools.length} tools from upstream:`);
3510
3890
  for (const tool of this.upstreamTools) {
3511
- log(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
3891
+ log2(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
3512
3892
  }
3513
3893
  }
3514
3894
  /**
@@ -3537,13 +3917,13 @@ var SolonGateProxy = class {
3537
3917
  const { name, arguments: args } = request.params;
3538
3918
  const argsSize = JSON.stringify(args ?? {}).length;
3539
3919
  if (argsSize > MAX_ARGUMENT_SIZE) {
3540
- log(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
3920
+ log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
3541
3921
  return {
3542
3922
  content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
3543
3923
  isError: true
3544
3924
  };
3545
3925
  }
3546
- log(`Tool call: ${name}`);
3926
+ log2(`Tool call: ${name}`);
3547
3927
  await this.callMutex.acquire();
3548
3928
  const startTime = Date.now();
3549
3929
  try {
@@ -3560,10 +3940,10 @@ var SolonGateProxy = class {
3560
3940
  );
3561
3941
  const decision = result.isError ? "DENY" : "ALLOW";
3562
3942
  const evaluationTimeMs = Date.now() - startTime;
3563
- log(`Result: ${decision} (${evaluationTimeMs}ms)`);
3943
+ log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
3564
3944
  if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
3565
3945
  const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
3566
- log(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
3946
+ log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
3567
3947
  let reason = "allowed";
3568
3948
  let matchedRule;
3569
3949
  if (result.isError) {
@@ -3586,7 +3966,7 @@ var SolonGateProxy = class {
3586
3966
  evaluationTimeMs
3587
3967
  });
3588
3968
  } else {
3589
- log(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
3969
+ log2(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
3590
3970
  }
3591
3971
  return {
3592
3972
  content: [...result.content],
@@ -3660,13 +4040,13 @@ var SolonGateProxy = class {
3660
4040
  registered++;
3661
4041
  } else {
3662
4042
  const body = await res.text().catch(() => "");
3663
- log(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
4043
+ log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
3664
4044
  }
3665
4045
  }).catch((err) => {
3666
- log(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
4046
+ log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
3667
4047
  });
3668
4048
  }
3669
- log(`Registering ${total} tools to dashboard...`);
4049
+ log2(`Registering ${total} tools to dashboard...`);
3670
4050
  }
3671
4051
  /**
3672
4052
  * Guess tool permissions from tool name.
@@ -3720,68 +4100,42 @@ var SolonGateProxy = class {
3720
4100
  })
3721
4101
  }).then(async (res) => {
3722
4102
  if (res.ok) {
3723
- log(`Registered MCP server "${serverName}" to dashboard.`);
4103
+ log2(`Registered MCP server "${serverName}" to dashboard.`);
3724
4104
  } else if (res.status === 409) {
3725
- log(`MCP server "${serverName}" already registered.`);
4105
+ log2(`MCP server "${serverName}" already registered.`);
3726
4106
  } else {
3727
4107
  const body = await res.text().catch(() => "");
3728
- log(`MCP server registration failed (${res.status}): ${body}`);
4108
+ log2(`MCP server registration failed (${res.status}): ${body}`);
3729
4109
  }
3730
4110
  }).catch((err) => {
3731
- log(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
4111
+ log2(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
3732
4112
  });
3733
4113
  }
3734
4114
  /**
3735
- * Sync the active policy to the SolonGate Cloud API.
3736
- * This makes it visible on the Dashboard Policies page.
4115
+ * Start bidirectional policy sync between local JSON file and cloud dashboard.
4116
+ *
4117
+ * - Watches local policy.json for changes → pushes to cloud API
4118
+ * - Polls cloud API for dashboard changes → writes to local policy.json
4119
+ * - Version number determines which is newer (higher wins, cloud wins on tie)
3737
4120
  */
3738
- syncPolicyToCloud() {
3739
- if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
4121
+ startPolicySync() {
4122
+ const apiKey = this.config.apiKey;
4123
+ if (!apiKey) return;
3740
4124
  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}`);
4125
+ this.syncManager = new PolicySyncManager({
4126
+ localPath: this.config.policyPath ?? null,
4127
+ apiKey,
4128
+ apiUrl,
4129
+ pollIntervalMs: 6e4,
4130
+ initialPolicy: this.config.policy,
4131
+ onPolicyUpdate: (policy) => {
4132
+ this.config.policy = policy;
4133
+ this.gate.loadPolicy(policy);
4134
+ log2(`Policy hot-reloaded: ${policy.name} v${policy.version} (${policy.rules.length} rules)`);
3761
4135
  }
3762
- }).catch((err) => {
3763
- log(`Policy sync error: ${err instanceof Error ? err.message : String(err)}`);
3764
4136
  });
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);
4137
+ this.syncManager.start();
4138
+ log2("Bidirectional policy sync started.");
3785
4139
  }
3786
4140
  /**
3787
4141
  * Start serving downstream.
@@ -3807,14 +4161,14 @@ var SolonGateProxy = class {
3807
4161
  }
3808
4162
  });
3809
4163
  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.");
4164
+ log2(`Proxy is live on http://localhost:${this.config.port}/mcp`);
4165
+ log2("All tool calls are now protected by SolonGate.");
3812
4166
  });
3813
4167
  } else {
3814
4168
  const transport = new StdioServerTransport();
3815
4169
  await this.server.connect(transport);
3816
- log("Proxy is live. All tool calls are now protected by SolonGate.");
3817
- log("Waiting for requests...");
4170
+ log2("Proxy is live. All tool calls are now protected by SolonGate.");
4171
+ log2("Waiting for requests...");
3818
4172
  }
3819
4173
  }
3820
4174
  };
@@ -3832,7 +4186,7 @@ console.error = (...args) => {
3832
4186
  process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
3833
4187
  `);
3834
4188
  };
3835
- async function main4() {
4189
+ async function main5() {
3836
4190
  const subcommand = process.argv[2];
3837
4191
  if (subcommand === "init") {
3838
4192
  process.argv.splice(2, 1);
@@ -3849,6 +4203,10 @@ async function main4() {
3849
4203
  await Promise.resolve().then(() => (init_create(), create_exports));
3850
4204
  return;
3851
4205
  }
4206
+ if (subcommand === "pull" || subcommand === "push") {
4207
+ await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
4208
+ return;
4209
+ }
3852
4210
  try {
3853
4211
  const config = parseArgs(process.argv);
3854
4212
  const proxy = new SolonGateProxy(config);
@@ -3860,4 +4218,4 @@ async function main4() {
3860
4218
  process.exit(1);
3861
4219
  }
3862
4220
  }
3863
- main4();
4221
+ main5();