@solongate/proxy 0.82.14 → 0.82.16

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.
@@ -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
  }
@@ -114,6 +114,17 @@ function clearGuardUpdateCheck() {
114
114
  function readHook(filename) {
115
115
  return readFileSync(join(HOOKS_DIR, filename), "utf-8");
116
116
  }
117
+ function firstAccountCredential() {
118
+ try {
119
+ const raw = JSON.parse(readFileSync(join(homedir(), ".solongate", "accounts.json"), "utf-8"));
120
+ if (Array.isArray(raw)) {
121
+ const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
122
+ if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
123
+ }
124
+ } catch {
125
+ }
126
+ return {};
127
+ }
117
128
  function readGuard() {
118
129
  const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
119
130
  return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
@@ -193,6 +204,13 @@ function installGlobalQuiet() {
193
204
  if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
194
205
  } catch {
195
206
  }
207
+ if (!apiKey) {
208
+ const acc = firstAccountCredential();
209
+ if (acc.apiKey) {
210
+ apiKey = acc.apiKey;
211
+ if (acc.apiUrl) apiUrl = acc.apiUrl;
212
+ }
213
+ }
196
214
  if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
197
215
  mkdirSync(p.hooksDir, { recursive: true });
198
216
  mkdirSync(p.claudeDir, { recursive: true });
@@ -323,7 +341,7 @@ function writeShimBlock(file, block) {
323
341
  function installClaudeShim(shieldPath) {
324
342
  const real = resolveRealClaude();
325
343
  if (!real) {
326
- console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
344
+ console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-open `solongate` \u2192 Settings \u2192 guard.)");
327
345
  return;
328
346
  }
329
347
  const node = process.execPath.replace(/\\/g, "/");
@@ -361,6 +379,10 @@ async function runGlobalInstall(opts = {}) {
361
379
  } catch {
362
380
  }
363
381
  }
382
+ if (!apiKey || apiKey === "sg_live_your_key_here") {
383
+ const acc = firstAccountCredential();
384
+ if (acc.apiKey) apiKey = acc.apiKey;
385
+ }
364
386
  if (!apiKey || apiKey === "sg_live_your_key_here") {
365
387
  apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
366
388
  }
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;
@@ -10245,6 +10245,17 @@ function globalPaths() {
10245
10245
  function readHook(filename) {
10246
10246
  return readFileSync9(join11(HOOKS_DIR, filename), "utf-8");
10247
10247
  }
10248
+ function firstAccountCredential() {
10249
+ try {
10250
+ const raw = JSON.parse(readFileSync9(join11(homedir9(), ".solongate", "accounts.json"), "utf-8"));
10251
+ if (Array.isArray(raw)) {
10252
+ const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
10253
+ if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
10254
+ }
10255
+ } catch {
10256
+ }
10257
+ return {};
10258
+ }
10248
10259
  function readGuard() {
10249
10260
  const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
10250
10261
  return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
@@ -10295,6 +10306,13 @@ function installGlobalQuiet() {
10295
10306
  if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
10296
10307
  } catch {
10297
10308
  }
10309
+ if (!apiKey) {
10310
+ const acc = firstAccountCredential();
10311
+ if (acc.apiKey) {
10312
+ apiKey = acc.apiKey;
10313
+ if (acc.apiUrl) apiUrl = acc.apiUrl;
10314
+ }
10315
+ }
10298
10316
  if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
10299
10317
  mkdirSync8(p.hooksDir, { recursive: true });
10300
10318
  mkdirSync8(p.claudeDir, { recursive: true });
@@ -12012,7 +12030,7 @@ async function run6(argv) {
12012
12030
  const json = flagBool(flags, "json");
12013
12031
  const checks = [];
12014
12032
  if (!isAuthenticated()) {
12015
- checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate login`" });
12033
+ checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
12016
12034
  } else {
12017
12035
  const { apiUrl } = resolveCredentials();
12018
12036
  checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
@@ -12032,7 +12050,7 @@ async function run6(argv) {
12032
12050
  }
12033
12051
  try {
12034
12052
  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\`` });
12053
+ 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
12054
  } catch {
12037
12055
  }
12038
12056
  }
@@ -16968,7 +16986,7 @@ ${msg.content.text}`;
16968
16986
 
16969
16987
  // src/index.ts
16970
16988
  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"]);
16989
+ 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
16990
  var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
16973
16991
  if (!IS_HUMAN_CLI) {
16974
16992
  console.log = (...args) => {
@@ -17034,8 +17052,10 @@ async function main4() {
17034
17052
  process.exit(code);
17035
17053
  }
17036
17054
  if (subcommand === "login") {
17037
- const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
17038
- await launchTui2();
17055
+ console.log("");
17056
+ console.log(` ${c.dim}\`solongate login\` was removed.${c.reset} Run ${c.cyan}solongate${c.reset} and add your`);
17057
+ console.log(` account from the Accounts panel \u2014 the guard installs and updates from there.`);
17058
+ console.log("");
17039
17059
  return;
17040
17060
  }
17041
17061
  if (subcommand === "shield") {
package/dist/tui/index.js CHANGED
@@ -3303,6 +3303,17 @@ function globalPaths() {
3303
3303
  function readHook(filename) {
3304
3304
  return readFileSync4(join6(HOOKS_DIR, filename), "utf-8");
3305
3305
  }
3306
+ function firstAccountCredential() {
3307
+ try {
3308
+ const raw = JSON.parse(readFileSync4(join6(homedir6(), ".solongate", "accounts.json"), "utf-8"));
3309
+ if (Array.isArray(raw)) {
3310
+ const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
3311
+ if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
3312
+ }
3313
+ } catch {
3314
+ }
3315
+ return {};
3316
+ }
3306
3317
  function readGuard() {
3307
3318
  const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
3308
3319
  return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
@@ -3354,6 +3365,13 @@ function installGlobalQuiet() {
3354
3365
  if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
3355
3366
  } catch {
3356
3367
  }
3368
+ if (!apiKey) {
3369
+ const acc = firstAccountCredential();
3370
+ if (acc.apiKey) {
3371
+ apiKey = acc.apiKey;
3372
+ if (acc.apiUrl) apiUrl = acc.apiUrl;
3373
+ }
3374
+ }
3357
3375
  if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
3358
3376
  mkdirSync4(p.hooksDir, { recursive: true });
3359
3377
  mkdirSync4(p.claudeDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.14",
3
+ "version": "0.82.16",
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
- });