@solongate/proxy 0.82.13 → 0.82.15

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/README.md CHANGED
@@ -61,7 +61,7 @@ Manage everything from the **Policies**, **Audit**, and **Settings** pages in th
61
61
  <table cellspacing="0" cellpadding="0" border="0" align="center">
62
62
  <tr>
63
63
  <td><a href="https://claude.com/claude-code"><img src="https://solongate.com/cells/claude.png" width="200" alt="Claude Code" /></a></td>
64
- <td><a href="https://antigravity.google"><img src="https://solongate.com/cells/antigravity.png" width="200" alt="Antigravity CLI" /></a></td>
64
+ <td><a href="https://antigravity.google"><img src="https://solongate.com/cells/antigravity.png?v=2" width="200" alt="Antigravity CLI" /></a></td>
65
65
  <td><a href="https://openclaw.ai"><img src="https://solongate.com/cells/openclaw.png" width="200" alt="OpenClaw (soon)" /></a></td>
66
66
  <td><a href="https://openai.com/codex"><img src="https://solongate.com/cells/codex.png" width="200" alt="Codex (soon)" /></a></td>
67
67
  </tr>
@@ -44,7 +44,7 @@ export declare class ApiError extends Error {
44
44
  readonly code: string;
45
45
  constructor(status: number, code: string, message: string);
46
46
  }
47
- /** Raised when no API key can be found — tells the user to run `solongate login`. */
47
+ /** Raised when no API key can be found — tells the user to run `solongate` and pair. */
48
48
  export declare class NotAuthenticatedError extends Error {
49
49
  constructor();
50
50
  }
@@ -1039,7 +1039,7 @@ async function run6(argv) {
1039
1039
  const json = flagBool(flags, "json");
1040
1040
  const checks = [];
1041
1041
  if (!isAuthenticated()) {
1042
- checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate login`" });
1042
+ checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
1043
1043
  } else {
1044
1044
  const { apiUrl } = resolveCredentials();
1045
1045
  checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
@@ -1059,7 +1059,7 @@ async function run6(argv) {
1059
1059
  }
1060
1060
  try {
1061
1061
  const g = await api.settings.getGuardStatus();
1062
- checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 run \`solongate login\`` });
1062
+ checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 update from \`solongate\` \u2192 Settings \u2192 guard` });
1063
1063
  } catch {
1064
1064
  }
1065
1065
  }
@@ -323,7 +323,7 @@ function writeShimBlock(file, block) {
323
323
  function installClaudeShim(shieldPath) {
324
324
  const real = resolveRealClaude();
325
325
  if (!real) {
326
- console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
326
+ console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-open `solongate` \u2192 Settings \u2192 guard.)");
327
327
  return;
328
328
  }
329
329
  const node = process.execPath.replace(/\\/g, "/");
package/dist/index.js CHANGED
@@ -271,7 +271,7 @@ function parseArgs(argv) {
271
271
  }
272
272
  if (!apiKey) {
273
273
  throw new Error(
274
- "Not logged in. Run this once to get started:\n\n solongate login\n"
274
+ "Not logged in. Run this once to get started:\n\n solongate\n\n then add your account from the Accounts panel.\n"
275
275
  );
276
276
  }
277
277
  if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
@@ -327,7 +327,7 @@ function parseArgs(argv) {
327
327
  }
328
328
  if (upstreamArgs.length === 0) {
329
329
  throw new Error(
330
- "No upstream server command provided.\n\nIf you just want to get started, run:\n solongate login\n"
330
+ "No upstream server command provided.\n\nIf you just want to get started, run:\n solongate\n"
331
331
  );
332
332
  }
333
333
  const [command, ...commandArgs] = upstreamArgs;
@@ -12012,7 +12012,7 @@ async function run6(argv) {
12012
12012
  const json = flagBool(flags, "json");
12013
12013
  const checks = [];
12014
12014
  if (!isAuthenticated()) {
12015
- checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate login`" });
12015
+ checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
12016
12016
  } else {
12017
12017
  const { apiUrl } = resolveCredentials();
12018
12018
  checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
@@ -12032,7 +12032,7 @@ async function run6(argv) {
12032
12032
  }
12033
12033
  try {
12034
12034
  const g = await api.settings.getGuardStatus();
12035
- checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 run \`solongate login\`` });
12035
+ checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 update from \`solongate\` \u2192 Settings \u2192 guard` });
12036
12036
  } catch {
12037
12037
  }
12038
12038
  }
@@ -16968,7 +16968,7 @@ ${msg.content.text}`;
16968
16968
 
16969
16969
  // src/index.ts
16970
16970
  init_cli_utils();
16971
- var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks", "dataroom"]);
16971
+ var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks", "dataroom"]);
16972
16972
  var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
16973
16973
  if (!IS_HUMAN_CLI) {
16974
16974
  console.log = (...args) => {
@@ -17034,8 +17034,10 @@ async function main4() {
17034
17034
  process.exit(code);
17035
17035
  }
17036
17036
  if (subcommand === "login") {
17037
- const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
17038
- await launchTui2();
17037
+ console.log("");
17038
+ console.log(` ${c.dim}\`solongate login\` was removed.${c.reset} Run ${c.cyan}solongate${c.reset} and add your`);
17039
+ console.log(` account from the Accounts panel \u2014 the guard installs and updates from there.`);
17040
+ console.log("");
17039
17041
  return;
17040
17042
  }
17041
17043
  if (subcommand === "shield") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.13",
3
+ "version": "0.82.15",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {
package/dist/login.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/login.js DELETED
@@ -1,466 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/login.ts
4
- import { spawn } from "child_process";
5
-
6
- // src/cli-utils.ts
7
- var c = {
8
- reset: "\x1B[0m",
9
- bold: "\x1B[1m",
10
- dim: "\x1B[2m",
11
- italic: "\x1B[3m",
12
- white: "\x1B[97m",
13
- gray: "\x1B[90m",
14
- blue1: "\x1B[38;2;20;50;160m",
15
- blue2: "\x1B[38;2;40;80;190m",
16
- blue3: "\x1B[38;2;60;110;215m",
17
- blue4: "\x1B[38;2;90;140;230m",
18
- blue5: "\x1B[38;2;130;170;240m",
19
- blue6: "\x1B[38;2;170;200;250m",
20
- green: "\x1B[38;2;80;200;120m",
21
- red: "\x1B[38;2;220;80;80m",
22
- cyan: "\x1B[38;2;100;200;220m",
23
- yellow: "\x1B[38;2;220;200;80m",
24
- bgBlue: "\x1B[48;2;20;50;160m"
25
- };
26
- var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
27
-
28
- // src/global-install.ts
29
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs";
30
- import { resolve, join, dirname } from "path";
31
- import { homedir } from "os";
32
- import { fileURLToPath } from "url";
33
- import { createInterface } from "readline";
34
- import { execFileSync } from "child_process";
35
- var __dirname = dirname(fileURLToPath(import.meta.url));
36
- var HOOKS_DIR = resolve(__dirname, "..", "hooks");
37
- function lockFile(file) {
38
- if (!existsSync(file)) return;
39
- try {
40
- if (process.platform === "win32") {
41
- try {
42
- execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
43
- } catch {
44
- }
45
- try {
46
- execFileSync("attrib", ["+R", file], { stdio: "ignore" });
47
- } catch {
48
- }
49
- } else if (process.platform === "darwin") {
50
- try {
51
- execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
52
- } catch {
53
- }
54
- } else {
55
- try {
56
- execFileSync("chattr", ["+i", file], { stdio: "ignore" });
57
- } catch {
58
- }
59
- }
60
- } catch {
61
- }
62
- }
63
- function unlockFile(file) {
64
- if (!existsSync(file)) return;
65
- try {
66
- if (process.platform === "win32") {
67
- try {
68
- execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
69
- } catch {
70
- }
71
- try {
72
- execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
73
- } catch {
74
- }
75
- try {
76
- execFileSync("attrib", ["-R", file], { stdio: "ignore" });
77
- } catch {
78
- }
79
- } else if (process.platform === "darwin") {
80
- try {
81
- execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
82
- } catch {
83
- }
84
- } else {
85
- try {
86
- execFileSync("chattr", ["-i", file], { stdio: "ignore" });
87
- } catch {
88
- }
89
- }
90
- } catch {
91
- }
92
- }
93
- function protectedTargets() {
94
- const p = globalPaths();
95
- return [
96
- join(p.hooksDir, "guard.mjs"),
97
- join(p.hooksDir, "audit.mjs"),
98
- join(p.hooksDir, "stop.mjs"),
99
- join(p.hooksDir, "shield.mjs"),
100
- p.configPath,
101
- p.settingsPath,
102
- p.antigravityHooksPath
103
- ];
104
- }
105
- function lockProtected() {
106
- for (const f of protectedTargets()) lockFile(f);
107
- }
108
- function unlockProtected() {
109
- for (const f of protectedTargets()) unlockFile(f);
110
- }
111
- function globalPaths() {
112
- const home = homedir();
113
- const sgDir = join(home, ".solongate");
114
- const hooksDir = join(sgDir, "hooks");
115
- const claudeDir = join(home, ".claude");
116
- const antigravityDir = join(home, ".gemini", "config");
117
- return {
118
- home,
119
- sgDir,
120
- hooksDir,
121
- claudeDir,
122
- antigravityDir,
123
- settingsPath: join(claudeDir, "settings.json"),
124
- backupPath: join(claudeDir, "settings.solongate.bak"),
125
- configPath: join(sgDir, "cloud-guard.json"),
126
- // Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
127
- // guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
128
- // covered by the passive session-log collector, not a hook.
129
- antigravityHooksPath: join(antigravityDir, "hooks.json"),
130
- antigravityBackupPath: join(antigravityDir, "hooks.solongate.bak")
131
- };
132
- }
133
- function readHook(filename) {
134
- return readFileSync(join(HOOKS_DIR, filename), "utf-8");
135
- }
136
- function readGuard() {
137
- const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
138
- return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
139
- }
140
- var ANTIGRAVITY_GROUP = "solongate-guard";
141
- function antigravityHookCommand(guardAbs) {
142
- const nodeBin = process.execPath.replace(/\\/g, "/");
143
- const call = process.platform === "win32" ? "& " : "";
144
- return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
145
- }
146
- function installAntigravityGuard(p, guardAbs) {
147
- mkdirSync(p.antigravityDir, { recursive: true });
148
- let existing = {};
149
- if (existsSync(p.antigravityHooksPath)) {
150
- const raw = readFileSync(p.antigravityHooksPath, "utf-8");
151
- if (!existsSync(p.antigravityBackupPath)) writeFileSync(p.antigravityBackupPath, raw);
152
- try {
153
- existing = JSON.parse(raw);
154
- } catch {
155
- existing = {};
156
- }
157
- }
158
- const merged = {
159
- ...existing,
160
- [ANTIGRAVITY_GROUP]: {
161
- PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
162
- }
163
- };
164
- writeFileSync(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
165
- }
166
- function ask(question) {
167
- const rl = createInterface({ input: process.stdin, output: process.stderr });
168
- return new Promise((res) => rl.question(question, (a) => {
169
- rl.close();
170
- res(a.trim());
171
- }));
172
- }
173
- var SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
174
- var SHIM_END = "# <<< SolonGate shield <<<";
175
- function escapeRe(s) {
176
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
177
- }
178
- function resolveRealClaude() {
179
- try {
180
- const finder = process.platform === "win32" ? "where" : "which";
181
- const out = execFileSync(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
182
- if (process.platform === "win32") {
183
- const low = (s) => s.toLowerCase();
184
- return out.find((l) => low(l).endsWith(".cmd")) || out.find((l) => low(l).endsWith(".exe")) || out.find((l) => low(l).endsWith(".bat")) || out[0] || null;
185
- }
186
- return out[0] || null;
187
- } catch {
188
- return null;
189
- }
190
- }
191
- function shimTargets() {
192
- if (process.platform === "win32") {
193
- try {
194
- const prof = execFileSync("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
195
- return prof ? [prof] : [];
196
- } catch {
197
- return [];
198
- }
199
- }
200
- return [".bashrc", ".zshrc", ".profile"].map((f) => join(homedir(), f)).filter((f) => existsSync(f));
201
- }
202
- function writeShimBlock(file, block) {
203
- const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
204
- let content = existsSync(file) ? readFileSync(file, "utf-8") : "";
205
- content = content.replace(re, "");
206
- if (block) {
207
- if (content.length && !content.endsWith("\n")) content += "\n";
208
- content += block + "\n";
209
- }
210
- mkdirSync(dirname(file), { recursive: true });
211
- writeFileSync(file, content);
212
- }
213
- function installClaudeShim(shieldPath) {
214
- const real = resolveRealClaude();
215
- if (!real) {
216
- console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
217
- return;
218
- }
219
- const node = process.execPath.replace(/\\/g, "/");
220
- const shield = shieldPath.replace(/\\/g, "/");
221
- const win = process.platform === "win32";
222
- const block = win ? `${SHIM_BEGIN}
223
- function claude { & "${node}" "${shield}" -- "${real}" @args }
224
- ${SHIM_END}` : `${SHIM_BEGIN}
225
- claude() { "${node}" "${shield}" -- "${real}" "$@"; }
226
- ${SHIM_END}`;
227
- const targets = shimTargets();
228
- if (targets.length === 0) return;
229
- for (const file of targets) {
230
- try {
231
- writeShimBlock(file, block);
232
- } catch {
233
- }
234
- }
235
- }
236
- async function runGlobalInstall(opts = {}) {
237
- const p = globalPaths();
238
- let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
239
- if (!apiKey || apiKey === "sg_live_your_key_here") {
240
- try {
241
- const cfg = JSON.parse(readFileSync(p.configPath, "utf-8"));
242
- if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
243
- } catch {
244
- }
245
- }
246
- if (!apiKey || apiKey === "sg_live_your_key_here") {
247
- apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
248
- }
249
- if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
250
- console.log(" Invalid API key. Must start with sg_live_ or sg_test_");
251
- process.exit(1);
252
- }
253
- const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
254
- mkdirSync(p.hooksDir, { recursive: true });
255
- mkdirSync(p.claudeDir, { recursive: true });
256
- unlockProtected();
257
- writeFileSync(join(p.hooksDir, "guard.mjs"), readGuard());
258
- writeFileSync(join(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
259
- writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
260
- writeFileSync(join(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
261
- console.log(` Installed hooks \u2192 ${p.hooksDir}`);
262
- installClaudeShim(join(p.hooksDir, "shield.mjs"));
263
- writeFileSync(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
264
- console.log(` Wrote ${p.configPath}`);
265
- let existing = {};
266
- if (existsSync(p.settingsPath)) {
267
- const raw = readFileSync(p.settingsPath, "utf-8");
268
- if (!existsSync(p.backupPath)) {
269
- writeFileSync(p.backupPath, raw);
270
- console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
271
- }
272
- try {
273
- existing = JSON.parse(raw);
274
- } catch {
275
- existing = {};
276
- }
277
- }
278
- const guardAbs = join(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
279
- const auditAbs = join(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
280
- const stopAbs = join(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
281
- const nodeBin = process.execPath.replace(/\\/g, "/");
282
- const call = process.platform === "win32" ? "& " : "";
283
- const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
284
- const merged = {
285
- ...existing,
286
- hooks: {
287
- PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(guardAbs) }] }],
288
- PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(auditAbs) }] }],
289
- Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
290
- }
291
- };
292
- writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
293
- console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
294
- try {
295
- installAntigravityGuard(p, guardAbs);
296
- console.log(` Registered Antigravity CLI guard \u2192 ${p.antigravityHooksPath}`);
297
- } catch {
298
- }
299
- if (process.env["SOLONGATE_OS_LOCK"] === "1") {
300
- lockProtected();
301
- console.log(" Locked protection files (OS-level read-only/immutable).");
302
- }
303
- }
304
-
305
- // src/api-client/client.ts
306
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
307
- import { resolve as resolve2, join as join2 } from "path";
308
- import { homedir as homedir2 } from "os";
309
- var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
310
- function saveAccount(acc) {
311
- try {
312
- const list = (() => {
313
- try {
314
- const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
315
- return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
316
- } catch {
317
- return [];
318
- }
319
- })();
320
- const next = list.filter((a) => a.apiKey !== acc.apiKey);
321
- next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
322
- mkdirSync2(join2(homedir2(), ".solongate"), { recursive: true });
323
- writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
324
- } catch {
325
- }
326
- }
327
-
328
- // src/login.ts
329
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
330
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
331
- function startSpinner(text) {
332
- if (!process.stderr.isTTY) {
333
- process.stderr.write(` ${text}
334
- `);
335
- return (finalLine) => {
336
- if (finalLine) process.stderr.write(finalLine + "\n");
337
- };
338
- }
339
- let i = 0;
340
- const draw = () => {
341
- const frame = SPINNER_FRAMES[i = (i + 1) % SPINNER_FRAMES.length];
342
- process.stderr.write(`\r ${c.cyan}${frame}${c.reset} ${c.dim}${text}${c.reset}\x1B[K`);
343
- };
344
- draw();
345
- const timer = setInterval(draw, 80);
346
- return (finalLine) => {
347
- clearInterval(timer);
348
- process.stderr.write("\r\x1B[K");
349
- if (finalLine) process.stderr.write(finalLine + "\n");
350
- };
351
- }
352
- function parseArgs(argv) {
353
- const args = argv.slice(3);
354
- let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
355
- let install = true;
356
- for (let i = 0; i < args.length; i++) {
357
- if (args[i] === "--api-url") apiUrl = args[++i] || apiUrl;
358
- else if (args[i] === "--no-install") install = false;
359
- }
360
- return { apiUrl, install };
361
- }
362
- function openBrowser(url) {
363
- try {
364
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
365
- const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
366
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
367
- child.on("error", () => {
368
- });
369
- child.unref();
370
- } catch {
371
- }
372
- }
373
- async function main() {
374
- const { apiUrl, install } = parseArgs(process.argv);
375
- console.log("");
376
- console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}\u2014 Login${c.reset}`);
377
- console.log("");
378
- let start;
379
- try {
380
- const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
381
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
382
- start = await res.json();
383
- } catch (e) {
384
- console.log(` Could not reach SolonGate (${apiUrl}).`);
385
- console.log(` ${e instanceof Error ? e.message : String(e)}`);
386
- process.exit(1);
387
- }
388
- const { device_code, user_code, verification_uri_complete, verification_uri, interval, expires_in } = start;
389
- const verifyUrl = verification_uri_complete || verification_uri;
390
- console.log(` ${c.blue5}\u2192${c.reset} Opening your browser to authorize this device\u2026`);
391
- console.log("");
392
- console.log(` ${c.dim}If it doesn't open, visit this link:${c.reset}`);
393
- console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
394
- console.log("");
395
- void user_code;
396
- openBrowser(verifyUrl);
397
- const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
398
- const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
399
- let apiKey = "";
400
- const stopSpinner = startSpinner("Waiting for authorization\u2026");
401
- while (Date.now() < deadline) {
402
- await sleep(pollMs);
403
- try {
404
- const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
405
- method: "POST",
406
- headers: { "Content-Type": "application/json" },
407
- body: JSON.stringify({ device_code })
408
- });
409
- const data = await res.json().catch(() => ({}));
410
- if (data?.status === "approved" && data?.api_key) {
411
- apiKey = data.api_key;
412
- stopSpinner();
413
- console.log("");
414
- const who = data?.user?.name || data?.user?.email;
415
- const mail = data?.user?.email;
416
- if (who) {
417
- const suffix = mail && mail !== who ? ` ${c.dim}(${mail})${c.reset}` : "";
418
- console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in as ${c.white}${who}${c.reset}${suffix}`);
419
- } else {
420
- console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in${c.reset}`);
421
- }
422
- if (data?.project?.name) {
423
- console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
424
- }
425
- saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email, email: data?.user?.email });
426
- break;
427
- }
428
- if (data?.status === "expired" || data?.status === "not_found") {
429
- stopSpinner(` ${c.red}\u2717${c.reset} Code expired. Run \`login\` again.`);
430
- process.exit(1);
431
- }
432
- } catch {
433
- }
434
- }
435
- if (!apiKey) {
436
- stopSpinner(` ${c.red}\u2717${c.reset} Timed out waiting for authorization. Run \`login\` again.`);
437
- process.exit(1);
438
- }
439
- if (!install) {
440
- console.log("");
441
- console.log(" Logged in. Re-run `login` to enable system-wide enforcement.");
442
- return;
443
- }
444
- console.log("");
445
- await runGlobalInstall({ apiKey, apiUrl });
446
- console.log("");
447
- console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
448
- console.log(" \u2502 You are logged in and protected. \u2502");
449
- console.log(" \u2502 Every Claude Code session on this machine is \u2502");
450
- console.log(" \u2502 now guarded by your cloud policy (OPA WASM). \u2502");
451
- console.log(" \u2502 \u2502");
452
- console.log(" \u2502 Manage policy: https://dashboard.solongate.com \u2502");
453
- console.log(" \u2502 Restart Claude Code to apply. \u2502");
454
- console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
455
- console.log("");
456
- console.log(` ${c.bold}${c.yellow}! Already-open terminals are NOT protected yet.${c.reset}`);
457
- console.log(` ${c.dim} Hooks load only when a session starts, so any terminal`);
458
- console.log(` (and the Claude Code running in it) that was already open`);
459
- console.log(` won't be caught. Open a NEW terminal and start Claude Code`);
460
- console.log(` there for the guard + audit logging to take effect.${c.reset}`);
461
- console.log("");
462
- }
463
- main().catch((err) => {
464
- console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
465
- process.exit(1);
466
- });