@solongate/proxy 0.81.76 → 0.81.78

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.
@@ -22,7 +22,7 @@ var ApiError = class extends Error {
22
22
  };
23
23
  var NotAuthenticatedError = class extends Error {
24
24
  constructor() {
25
- super("Not logged in. Run `solongate login` first.");
25
+ super("Not logged in. Run `solongate` and log in from the Accounts panel.");
26
26
  this.name = "NotAuthenticatedError";
27
27
  }
28
28
  };
@@ -132,7 +132,7 @@ async function request(method, path, opts = {}) {
132
132
  if (typeof envelope === "string") {
133
133
  throw new ApiError(res.status, "ERROR", envelope);
134
134
  }
135
- if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
135
+ if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
136
136
  if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
137
137
  throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
138
138
  }
@@ -11,6 +11,17 @@ export declare function globalPaths(): {
11
11
  };
12
12
  export declare function clearGuardUpdateCheck(): boolean;
13
13
  export declare function runGlobalRestore(): void;
14
+ /**
15
+ * TUI-safe uninstall (dataroom Settings → guard → remove). Same effect as
16
+ * runGlobalRestore but returns a status string instead of writing to stdout —
17
+ * any console output would corrupt the Ink render. Unlocks the protected files,
18
+ * removes the auto-shield shell shim, then strips the SolonGate hooks from the
19
+ * global Claude settings (restoring the pre-install backup when present).
20
+ */
21
+ export declare function uninstallGlobalQuiet(): {
22
+ ok: boolean;
23
+ message: string;
24
+ };
14
25
  export declare function installClaudeShim(shieldPath: string): void;
15
26
  export declare function removeClaudeShim(): void;
16
27
  export declare function runGlobalInstall(opts?: {
@@ -137,6 +137,26 @@ function runGlobalRestore() {
137
137
  }
138
138
  console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
139
139
  }
140
+ function uninstallGlobalQuiet() {
141
+ try {
142
+ const p = globalPaths();
143
+ unlockProtected();
144
+ removeClaudeShim();
145
+ if (existsSync(p.backupPath)) {
146
+ writeFileSync(p.settingsPath, readFileSync(p.backupPath, "utf-8"));
147
+ return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
148
+ }
149
+ if (existsSync(p.settingsPath)) {
150
+ const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
151
+ delete s.hooks;
152
+ writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
153
+ return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
154
+ }
155
+ return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
156
+ } catch (e) {
157
+ return { ok: false, message: e instanceof Error ? e.message : String(e) };
158
+ }
159
+ }
140
160
  var SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
141
161
  var SHIM_END = "# <<< SolonGate shield <<<";
142
162
  function escapeRe(s) {
@@ -283,5 +303,6 @@ export {
283
303
  removeClaudeShim,
284
304
  runGlobalInstall,
285
305
  runGlobalRestore,
306
+ uninstallGlobalQuiet,
286
307
  unlockProtected
287
308
  };
package/dist/index.js CHANGED
@@ -7302,7 +7302,7 @@ async function request(method, path, opts = {}) {
7302
7302
  if (typeof envelope === "string") {
7303
7303
  throw new ApiError(res.status, "ERROR", envelope);
7304
7304
  }
7305
- if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
7305
+ if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
7306
7306
  if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
7307
7307
  throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
7308
7308
  }
@@ -7327,7 +7327,7 @@ var init_client = __esm({
7327
7327
  };
7328
7328
  NotAuthenticatedError = class extends Error {
7329
7329
  constructor() {
7330
- super("Not logged in. Run `solongate login` first.");
7330
+ super("Not logged in. Run `solongate` and log in from the Accounts panel.");
7331
7331
  this.name = "NotAuthenticatedError";
7332
7332
  }
7333
7333
  };
@@ -10115,32 +10115,6 @@ import { homedir as homedir9 } from "os";
10115
10115
  import { fileURLToPath as fileURLToPath3 } from "url";
10116
10116
  import { createInterface } from "readline";
10117
10117
  import { execFileSync as execFileSync2 } from "child_process";
10118
- function lockFile(file) {
10119
- if (!existsSync4(file)) return;
10120
- try {
10121
- if (process.platform === "win32") {
10122
- try {
10123
- execFileSync2("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
10124
- } catch {
10125
- }
10126
- try {
10127
- execFileSync2("attrib", ["+R", file], { stdio: "ignore" });
10128
- } catch {
10129
- }
10130
- } else if (process.platform === "darwin") {
10131
- try {
10132
- execFileSync2("chflags", ["uchg", file], { stdio: "ignore" });
10133
- } catch {
10134
- }
10135
- } else {
10136
- try {
10137
- execFileSync2("chattr", ["+i", file], { stdio: "ignore" });
10138
- } catch {
10139
- }
10140
- }
10141
- } catch {
10142
- }
10143
- }
10144
10118
  function unlockFile(file) {
10145
10119
  if (!existsSync4(file)) return;
10146
10120
  try {
@@ -10182,9 +10156,6 @@ function protectedTargets() {
10182
10156
  p.settingsPath
10183
10157
  ];
10184
10158
  }
10185
- function lockProtected() {
10186
- for (const f of protectedTargets()) lockFile(f);
10187
- }
10188
10159
  function unlockProtected() {
10189
10160
  for (const f of protectedTargets()) unlockFile(f);
10190
10161
  }
@@ -10211,36 +10182,29 @@ function clearGuardUpdateCheck() {
10211
10182
  return false;
10212
10183
  }
10213
10184
  }
10214
- function readHook(filename) {
10215
- return readFileSync9(join11(HOOKS_DIR, filename), "utf-8");
10216
- }
10217
- function readGuard() {
10218
- const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
10219
- return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
10220
- }
10221
- function ask(question) {
10222
- const rl = createInterface({ input: process.stdin, output: process.stderr });
10223
- return new Promise((res) => rl.question(question, (a) => {
10224
- rl.close();
10225
- res(a.trim());
10226
- }));
10185
+ function uninstallGlobalQuiet() {
10186
+ try {
10187
+ const p = globalPaths();
10188
+ unlockProtected();
10189
+ removeClaudeShim();
10190
+ if (existsSync4(p.backupPath)) {
10191
+ writeFileSync9(p.settingsPath, readFileSync9(p.backupPath, "utf-8"));
10192
+ return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
10193
+ }
10194
+ if (existsSync4(p.settingsPath)) {
10195
+ const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10196
+ delete s.hooks;
10197
+ writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10198
+ return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
10199
+ }
10200
+ return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
10201
+ } catch (e) {
10202
+ return { ok: false, message: e instanceof Error ? e.message : String(e) };
10203
+ }
10227
10204
  }
10228
10205
  function escapeRe(s) {
10229
10206
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10230
10207
  }
10231
- function resolveRealClaude() {
10232
- try {
10233
- const finder = process.platform === "win32" ? "where" : "which";
10234
- const out2 = execFileSync2(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
10235
- if (process.platform === "win32") {
10236
- const low = (s) => s.toLowerCase();
10237
- return out2.find((l) => low(l).endsWith(".cmd")) || out2.find((l) => low(l).endsWith(".exe")) || out2.find((l) => low(l).endsWith(".bat")) || out2[0] || null;
10238
- }
10239
- return out2[0] || null;
10240
- } catch {
10241
- return null;
10242
- }
10243
- }
10244
10208
  function shimTargets() {
10245
10209
  if (process.platform === "win32") {
10246
10210
  try {
@@ -10263,92 +10227,14 @@ function writeShimBlock(file, block2) {
10263
10227
  mkdirSync8(dirname3(file), { recursive: true });
10264
10228
  writeFileSync9(file, content);
10265
10229
  }
10266
- function installClaudeShim(shieldPath) {
10267
- const real = resolveRealClaude();
10268
- if (!real) {
10269
- console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
10270
- return;
10271
- }
10272
- const node = process.execPath.replace(/\\/g, "/");
10273
- const shield = shieldPath.replace(/\\/g, "/");
10274
- const win = process.platform === "win32";
10275
- const block2 = win ? `${SHIM_BEGIN}
10276
- function claude { & "${node}" "${shield}" -- "${real}" @args }
10277
- ${SHIM_END}` : `${SHIM_BEGIN}
10278
- claude() { "${node}" "${shield}" -- "${real}" "$@"; }
10279
- ${SHIM_END}`;
10280
- const targets = shimTargets();
10281
- if (targets.length === 0) return;
10282
- for (const file of targets) {
10230
+ function removeClaudeShim() {
10231
+ for (const file of shimTargets()) {
10283
10232
  try {
10284
- writeShimBlock(file, block2);
10233
+ writeShimBlock(file, null);
10285
10234
  } catch {
10286
10235
  }
10287
10236
  }
10288
10237
  }
10289
- async function runGlobalInstall2(opts = {}) {
10290
- const p = globalPaths();
10291
- let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
10292
- if (!apiKey || apiKey === "sg_live_your_key_here") {
10293
- try {
10294
- const cfg = JSON.parse(readFileSync9(p.configPath, "utf-8"));
10295
- if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
10296
- } catch {
10297
- }
10298
- }
10299
- if (!apiKey || apiKey === "sg_live_your_key_here") {
10300
- apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
10301
- }
10302
- if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
10303
- console.log(" Invalid API key. Must start with sg_live_ or sg_test_");
10304
- process.exit(1);
10305
- }
10306
- const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
10307
- mkdirSync8(p.hooksDir, { recursive: true });
10308
- mkdirSync8(p.claudeDir, { recursive: true });
10309
- unlockProtected();
10310
- writeFileSync9(join11(p.hooksDir, "guard.mjs"), readGuard());
10311
- writeFileSync9(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
10312
- writeFileSync9(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
10313
- writeFileSync9(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
10314
- console.log(` Installed hooks \u2192 ${p.hooksDir}`);
10315
- installClaudeShim(join11(p.hooksDir, "shield.mjs"));
10316
- writeFileSync9(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
10317
- console.log(` Wrote ${p.configPath}`);
10318
- let existing = {};
10319
- if (existsSync4(p.settingsPath)) {
10320
- const raw = readFileSync9(p.settingsPath, "utf-8");
10321
- if (!existsSync4(p.backupPath)) {
10322
- writeFileSync9(p.backupPath, raw);
10323
- console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
10324
- }
10325
- try {
10326
- existing = JSON.parse(raw);
10327
- } catch {
10328
- existing = {};
10329
- }
10330
- }
10331
- const guardAbs = join11(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
10332
- const auditAbs = join11(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
10333
- const stopAbs = join11(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
10334
- const nodeBin = process.execPath.replace(/\\/g, "/");
10335
- const call = process.platform === "win32" ? "& " : "";
10336
- const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
10337
- const merged = {
10338
- ...existing,
10339
- hooks: {
10340
- PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(guardAbs) }] }],
10341
- PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(auditAbs) }] }],
10342
- Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
10343
- }
10344
- };
10345
- writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
10346
- console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
10347
- if (process.env["SOLONGATE_OS_LOCK"] === "1") {
10348
- lockProtected();
10349
- console.log(" Locked protection files (OS-level read-only/immutable).");
10350
- }
10351
- }
10352
10238
  var __dirname, HOOKS_DIR, SHIM_BEGIN, SHIM_END;
10353
10239
  var init_global_install = __esm({
10354
10240
  "src/global-install.ts"() {
@@ -10687,6 +10573,17 @@ function SettingsPanel({
10687
10573
  setEditing("wh-url");
10688
10574
  } else if (cur.kind === "alert-add-email") openAlertEditor("email");
10689
10575
  else if (cur.kind === "alert-add-tg") openAlertEditor("telegram");
10576
+ } else if (inp === "d" && cur.kind === "guard") {
10577
+ const k = keyOf(cur);
10578
+ if (confirmDel !== k) {
10579
+ setConfirmDel(k);
10580
+ setMsg({ text: "\u26A0 d again REMOVES the guard from this device \u2014 Claude Code sessions stop being protected until you reinstall", level: "bad" });
10581
+ return;
10582
+ }
10583
+ setConfirmDel(null);
10584
+ const res = uninstallGlobalQuiet();
10585
+ setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10586
+ guardQ.reload();
10690
10587
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
10691
10588
  const k = keyOf(cur);
10692
10589
  if (confirmDel !== k) {
@@ -10794,8 +10691,8 @@ function SettingsPanel({
10794
10691
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10795
10692
  guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10796
10693
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
10797
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter to update` }),
10798
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""}` })
10694
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
10695
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""} \xB7 d remove` })
10799
10696
  ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
10800
10697
  ] });
10801
10698
  case "self":
@@ -10860,7 +10757,7 @@ function SettingsPanel({
10860
10757
  const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
10861
10758
  const SECTION_DESC = {
10862
10759
  ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
10863
- PROTECTION: "guard hook version (enter to update) + self-protection toggle",
10760
+ PROTECTION: "guard hook: enter update \xB7 d remove \xB7 + self-protection toggle",
10864
10761
  "LOCAL LOGS": "mirror every decision to a file + dashboard link",
10865
10762
  WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
10866
10763
  ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
@@ -12346,157 +12243,6 @@ var init_commands = __esm({
12346
12243
  }
12347
12244
  });
12348
12245
 
12349
- // src/login.ts
12350
- var login_exports = {};
12351
- import { spawn as spawn5 } from "child_process";
12352
- function startSpinner(text) {
12353
- if (!process.stderr.isTTY) {
12354
- process.stderr.write(` ${text}
12355
- `);
12356
- return (finalLine) => {
12357
- if (finalLine) process.stderr.write(finalLine + "\n");
12358
- };
12359
- }
12360
- let i = 0;
12361
- const draw = () => {
12362
- const frame = SPINNER_FRAMES[i = (i + 1) % SPINNER_FRAMES.length];
12363
- process.stderr.write(`\r ${c.cyan}${frame}${c.reset} ${c.dim}${text}${c.reset}\x1B[K`);
12364
- };
12365
- draw();
12366
- const timer = setInterval(draw, 80);
12367
- return (finalLine) => {
12368
- clearInterval(timer);
12369
- process.stderr.write("\r\x1B[K");
12370
- if (finalLine) process.stderr.write(finalLine + "\n");
12371
- };
12372
- }
12373
- function parseArgs2(argv) {
12374
- const args = argv.slice(3);
12375
- let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
12376
- let install = true;
12377
- for (let i = 0; i < args.length; i++) {
12378
- if (args[i] === "--api-url") apiUrl = args[++i] || apiUrl;
12379
- else if (args[i] === "--no-install") install = false;
12380
- }
12381
- return { apiUrl, install };
12382
- }
12383
- function openBrowser2(url) {
12384
- try {
12385
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
12386
- const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
12387
- const child = spawn5(cmd, args, { stdio: "ignore", detached: true });
12388
- child.on("error", () => {
12389
- });
12390
- child.unref();
12391
- } catch {
12392
- }
12393
- }
12394
- async function main() {
12395
- const { apiUrl, install } = parseArgs2(process.argv);
12396
- console.log("");
12397
- console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}\u2014 Login${c.reset}`);
12398
- console.log("");
12399
- let start;
12400
- try {
12401
- const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
12402
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
12403
- start = await res.json();
12404
- } catch (e) {
12405
- console.log(` Could not reach SolonGate (${apiUrl}).`);
12406
- console.log(` ${e instanceof Error ? e.message : String(e)}`);
12407
- process.exit(1);
12408
- }
12409
- const { device_code, user_code, verification_uri_complete, verification_uri, interval, expires_in } = start;
12410
- const verifyUrl = verification_uri_complete || verification_uri;
12411
- console.log(` ${c.blue5}\u2192${c.reset} Opening your browser to authorize this device\u2026`);
12412
- console.log("");
12413
- console.log(` ${c.dim}If it doesn't open, visit this link:${c.reset}`);
12414
- console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
12415
- console.log("");
12416
- void user_code;
12417
- openBrowser2(verifyUrl);
12418
- const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
12419
- const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
12420
- let apiKey = "";
12421
- const stopSpinner = startSpinner("Waiting for authorization\u2026");
12422
- while (Date.now() < deadline) {
12423
- await sleep(pollMs);
12424
- try {
12425
- const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
12426
- method: "POST",
12427
- headers: { "Content-Type": "application/json" },
12428
- body: JSON.stringify({ device_code })
12429
- });
12430
- const data = await res.json().catch(() => ({}));
12431
- if (data?.status === "approved" && data?.api_key) {
12432
- apiKey = data.api_key;
12433
- stopSpinner();
12434
- console.log("");
12435
- const who = data?.user?.name || data?.user?.email;
12436
- const mail = data?.user?.email;
12437
- if (who) {
12438
- const suffix = mail && mail !== who ? ` ${c.dim}(${mail})${c.reset}` : "";
12439
- console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in as ${c.white}${who}${c.reset}${suffix}`);
12440
- } else {
12441
- console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in${c.reset}`);
12442
- }
12443
- if (data?.project?.name) {
12444
- console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
12445
- }
12446
- saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email, email: data?.user?.email });
12447
- break;
12448
- }
12449
- if (data?.status === "expired" || data?.status === "not_found") {
12450
- stopSpinner(` ${c.red}\u2717${c.reset} Code expired. Run \`login\` again.`);
12451
- process.exit(1);
12452
- }
12453
- } catch {
12454
- }
12455
- }
12456
- if (!apiKey) {
12457
- stopSpinner(` ${c.red}\u2717${c.reset} Timed out waiting for authorization. Run \`login\` again.`);
12458
- process.exit(1);
12459
- }
12460
- if (!install) {
12461
- console.log("");
12462
- console.log(" Logged in. Re-run `login` to enable system-wide enforcement.");
12463
- return;
12464
- }
12465
- console.log("");
12466
- await runGlobalInstall2({ apiKey, apiUrl });
12467
- console.log("");
12468
- 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");
12469
- console.log(" \u2502 You are logged in and protected. \u2502");
12470
- console.log(" \u2502 Every Claude Code session on this machine is \u2502");
12471
- console.log(" \u2502 now guarded by your cloud policy (OPA WASM). \u2502");
12472
- console.log(" \u2502 \u2502");
12473
- console.log(" \u2502 Manage policy: https://dashboard.solongate.com \u2502");
12474
- console.log(" \u2502 Restart Claude Code to apply. \u2502");
12475
- 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");
12476
- console.log("");
12477
- console.log(` ${c.bold}${c.yellow}! Already-open terminals are NOT protected yet.${c.reset}`);
12478
- console.log(` ${c.dim} Hooks load only when a session starts, so any terminal`);
12479
- console.log(` (and the Claude Code running in it) that was already open`);
12480
- console.log(` won't be caught. Open a NEW terminal and start Claude Code`);
12481
- console.log(` there for the guard + audit logging to take effect.${c.reset}`);
12482
- console.log("");
12483
- }
12484
- var sleep, SPINNER_FRAMES;
12485
- var init_login = __esm({
12486
- "src/login.ts"() {
12487
- "use strict";
12488
- init_cli_utils();
12489
- init_global_install();
12490
- init_client();
12491
- sleep = (ms) => new Promise((r) => setTimeout(r, ms));
12492
- SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
12493
- main().catch((err2) => {
12494
- console.log(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
12495
- process.exit(1);
12496
- });
12497
- }
12498
- });
12499
-
12500
12246
  // src/shield.ts
12501
12247
  var shield_exports = {};
12502
12248
  __export(shield_exports, {
@@ -12504,7 +12250,7 @@ __export(shield_exports, {
12504
12250
  });
12505
12251
  import { createServer, request as httpRequest } from "http";
12506
12252
  import { request as httpsRequest } from "https";
12507
- import { spawn as spawn6 } from "child_process";
12253
+ import { spawn as spawn5 } from "child_process";
12508
12254
  import { URL as URL2 } from "url";
12509
12255
  import { readFileSync as readFileSync11, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
12510
12256
  import { resolve as resolve5 } from "path";
@@ -12765,7 +12511,7 @@ async function runShield() {
12765
12511
  const upstream = pickUpstream();
12766
12512
  const { port, close } = await startProxy(upstream);
12767
12513
  log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
12768
- const child = spawn6(cmd[0], cmd.slice(1), {
12514
+ const child = spawn5(cmd[0], cmd.slice(1), {
12769
12515
  stdio: "inherit",
12770
12516
  env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
12771
12517
  shell: process.platform === "win32"
@@ -13268,7 +13014,7 @@ function showDiff(result) {
13268
13014
  }
13269
13015
  log3("");
13270
13016
  }
13271
- async function main2() {
13017
+ async function main() {
13272
13018
  const opts = parseInjectArgs(process.argv);
13273
13019
  printBanner("Inject SDK");
13274
13020
  if (!detectProject()) {
@@ -13355,7 +13101,7 @@ var init_inject = __esm({
13355
13101
  "src/inject.ts"() {
13356
13102
  "use strict";
13357
13103
  init_cli_utils();
13358
- main2().catch((err2) => {
13104
+ main().catch((err2) => {
13359
13105
  log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
13360
13106
  process.exit(1);
13361
13107
  });
@@ -13572,7 +13318,7 @@ dist/
13572
13318
  `
13573
13319
  );
13574
13320
  }
13575
- async function main3() {
13321
+ async function main2() {
13576
13322
  const opts = parseCreateArgs(process.argv);
13577
13323
  const dir = resolve8(opts.name);
13578
13324
  printBanner("Create MCP Server");
@@ -13646,7 +13392,7 @@ var init_create = __esm({
13646
13392
  "src/create.ts"() {
13647
13393
  "use strict";
13648
13394
  init_cli_utils();
13649
- main3().catch((err2) => {
13395
+ main2().catch((err2) => {
13650
13396
  log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
13651
13397
  process.exit(1);
13652
13398
  });
@@ -13922,7 +13668,7 @@ async function push(apiKey, file, policyId) {
13922
13668
  log5(` ${dim2("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
13923
13669
  log5("");
13924
13670
  }
13925
- async function main4() {
13671
+ async function main3() {
13926
13672
  const { command, apiKey, file, policyId } = parseCliArgs();
13927
13673
  try {
13928
13674
  if (command === "pull") {
@@ -13967,7 +13713,7 @@ var init_pull_push = __esm({
13967
13713
  cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
13968
13714
  magenta = (s) => `\x1B[35m${s}\x1B[0m`;
13969
13715
  API_URL = "https://api.solongate.com";
13970
- main4();
13716
+ main3();
13971
13717
  }
13972
13718
  });
13973
13719
 
@@ -17017,17 +16763,17 @@ function printWelcome() {
17017
16763
  console.log("");
17018
16764
  console.log(" Get started with one command:");
17019
16765
  console.log("");
17020
- console.log(` ${c.cyan}solongate login${c.reset}`);
16766
+ console.log(` ${c.cyan}solongate${c.reset}`);
17021
16767
  console.log("");
17022
- console.log(` ${c.dim}This pairs your device and protects every Claude Code session${c.reset}`);
17023
- console.log(` ${c.dim}on this machine with your cloud policy. Manage it at${c.reset}`);
17024
- console.log(` ${c.cyan}https://dashboard.solongate.com${c.reset}`);
16768
+ console.log(` ${c.dim}This opens the dataroom; log in from the Accounts panel to pair this${c.reset}`);
16769
+ console.log(` ${c.dim}device and protect every Claude Code session with your cloud policy.${c.reset}`);
16770
+ console.log(` ${c.dim}Manage it at ${c.reset}${c.cyan}https://dashboard.solongate.com${c.reset}`);
17025
16771
  console.log("");
17026
16772
  console.log(` ${c.dim}Using local log storage? Show it live in the dashboard with${c.reset}`);
17027
16773
  console.log(` ${c.cyan}solongate logs-server${c.reset}`);
17028
16774
  console.log("");
17029
16775
  }
17030
- async function main5() {
16776
+ async function main4() {
17031
16777
  const subcommand = process.argv[2];
17032
16778
  if (IS_HUMAN_CLI) {
17033
16779
  const tuiBound = subcommand === "dataroom" || process.argv.length <= 2 && process.stdout.isTTY && process.stdin.isTTY;
@@ -17061,7 +16807,8 @@ async function main5() {
17061
16807
  process.exit(code);
17062
16808
  }
17063
16809
  if (subcommand === "login") {
17064
- await Promise.resolve().then(() => (init_login(), login_exports));
16810
+ const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
16811
+ await launchTui2();
17065
16812
  return;
17066
16813
  }
17067
16814
  if (subcommand === "shield") {
@@ -17099,4 +16846,4 @@ async function main5() {
17099
16846
  process.exit(1);
17100
16847
  }
17101
16848
  }
17102
- main5();
16849
+ main4();
package/dist/tui/index.js CHANGED
@@ -400,7 +400,7 @@ var ApiError = class extends Error {
400
400
  };
401
401
  var NotAuthenticatedError = class extends Error {
402
402
  constructor() {
403
- super("Not logged in. Run `solongate login` first.");
403
+ super("Not logged in. Run `solongate` and log in from the Accounts panel.");
404
404
  this.name = "NotAuthenticatedError";
405
405
  }
406
406
  };
@@ -502,7 +502,7 @@ async function request(method, path, opts = {}) {
502
502
  if (typeof envelope === "string") {
503
503
  throw new ApiError(res.status, "ERROR", envelope);
504
504
  }
505
- if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
505
+ if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
506
506
  if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
507
507
  throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
508
508
  }
@@ -3148,6 +3148,50 @@ import { createInterface } from "readline";
3148
3148
  import { execFileSync } from "child_process";
3149
3149
  var __dirname = dirname(fileURLToPath(import.meta.url));
3150
3150
  var HOOKS_DIR = resolve2(__dirname, "..", "hooks");
3151
+ function unlockFile(file) {
3152
+ if (!existsSync2(file)) return;
3153
+ try {
3154
+ if (process.platform === "win32") {
3155
+ try {
3156
+ execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
3157
+ } catch {
3158
+ }
3159
+ try {
3160
+ execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
3161
+ } catch {
3162
+ }
3163
+ try {
3164
+ execFileSync("attrib", ["-R", file], { stdio: "ignore" });
3165
+ } catch {
3166
+ }
3167
+ } else if (process.platform === "darwin") {
3168
+ try {
3169
+ execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
3170
+ } catch {
3171
+ }
3172
+ } else {
3173
+ try {
3174
+ execFileSync("chattr", ["-i", file], { stdio: "ignore" });
3175
+ } catch {
3176
+ }
3177
+ }
3178
+ } catch {
3179
+ }
3180
+ }
3181
+ function protectedTargets() {
3182
+ const p = globalPaths();
3183
+ return [
3184
+ join6(p.hooksDir, "guard.mjs"),
3185
+ join6(p.hooksDir, "audit.mjs"),
3186
+ join6(p.hooksDir, "stop.mjs"),
3187
+ join6(p.hooksDir, "shield.mjs"),
3188
+ p.configPath,
3189
+ p.settingsPath
3190
+ ];
3191
+ }
3192
+ function unlockProtected() {
3193
+ for (const f of protectedTargets()) unlockFile(f);
3194
+ }
3151
3195
  function globalPaths() {
3152
3196
  const home = homedir6();
3153
3197
  const sgDir = join6(home, ".solongate");
@@ -3171,6 +3215,61 @@ function clearGuardUpdateCheck() {
3171
3215
  return false;
3172
3216
  }
3173
3217
  }
3218
+ function uninstallGlobalQuiet() {
3219
+ try {
3220
+ const p = globalPaths();
3221
+ unlockProtected();
3222
+ removeClaudeShim();
3223
+ if (existsSync2(p.backupPath)) {
3224
+ writeFileSync5(p.settingsPath, readFileSync4(p.backupPath, "utf-8"));
3225
+ return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
3226
+ }
3227
+ if (existsSync2(p.settingsPath)) {
3228
+ const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3229
+ delete s.hooks;
3230
+ writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
3231
+ return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
3232
+ }
3233
+ return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
3234
+ } catch (e) {
3235
+ return { ok: false, message: e instanceof Error ? e.message : String(e) };
3236
+ }
3237
+ }
3238
+ var SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
3239
+ var SHIM_END = "# <<< SolonGate shield <<<";
3240
+ function escapeRe(s) {
3241
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3242
+ }
3243
+ function shimTargets() {
3244
+ if (process.platform === "win32") {
3245
+ try {
3246
+ const prof = execFileSync("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
3247
+ return prof ? [prof] : [];
3248
+ } catch {
3249
+ return [];
3250
+ }
3251
+ }
3252
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join6(homedir6(), f)).filter((f) => existsSync2(f));
3253
+ }
3254
+ function writeShimBlock(file, block2) {
3255
+ const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
3256
+ let content = existsSync2(file) ? readFileSync4(file, "utf-8") : "";
3257
+ content = content.replace(re, "");
3258
+ if (block2) {
3259
+ if (content.length && !content.endsWith("\n")) content += "\n";
3260
+ content += block2 + "\n";
3261
+ }
3262
+ mkdirSync4(dirname(file), { recursive: true });
3263
+ writeFileSync5(file, content);
3264
+ }
3265
+ function removeClaudeShim() {
3266
+ for (const file of shimTargets()) {
3267
+ try {
3268
+ writeShimBlock(file, null);
3269
+ } catch {
3270
+ }
3271
+ }
3272
+ }
3174
3273
 
3175
3274
  // src/logs-server-daemon.ts
3176
3275
  import { spawn as spawn3 } from "child_process";
@@ -3587,6 +3686,17 @@ function SettingsPanel({
3587
3686
  setEditing("wh-url");
3588
3687
  } else if (cur.kind === "alert-add-email") openAlertEditor("email");
3589
3688
  else if (cur.kind === "alert-add-tg") openAlertEditor("telegram");
3689
+ } else if (inp === "d" && cur.kind === "guard") {
3690
+ const k = keyOf(cur);
3691
+ if (confirmDel !== k) {
3692
+ setConfirmDel(k);
3693
+ setMsg({ text: "\u26A0 d again REMOVES the guard from this device \u2014 Claude Code sessions stop being protected until you reinstall", level: "bad" });
3694
+ return;
3695
+ }
3696
+ setConfirmDel(null);
3697
+ const res = uninstallGlobalQuiet();
3698
+ setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3699
+ guardQ.reload();
3590
3700
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
3591
3701
  const k = keyOf(cur);
3592
3702
  if (confirmDel !== k) {
@@ -3694,8 +3804,8 @@ function SettingsPanel({
3694
3804
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3695
3805
  guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3696
3806
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
3697
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter to update` }),
3698
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""}` })
3807
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
3808
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""} \xB7 d remove` })
3699
3809
  ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
3700
3810
  ] });
3701
3811
  case "self":
@@ -3760,7 +3870,7 @@ function SettingsPanel({
3760
3870
  const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
3761
3871
  const SECTION_DESC = {
3762
3872
  ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
3763
- PROTECTION: "guard hook version (enter to update) + self-protection toggle",
3873
+ PROTECTION: "guard hook: enter update \xB7 d remove \xB7 + self-protection toggle",
3764
3874
  "LOCAL LOGS": "mirror every decision to a file + dashboard link",
3765
3875
  WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
3766
3876
  ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
@@ -6536,7 +6536,7 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
6536
6536
  import { homedir } from "node:os";
6537
6537
  import { gunzipSync } from "node:zlib";
6538
6538
  import { createHash } from "node:crypto";
6539
- var HOOK_VERSION = 40;
6539
+ var HOOK_VERSION = 41;
6540
6540
  function localLogsOnly(security) {
6541
6541
  if (security && typeof security === "object") {
6542
6542
  const l = security.localLogs;
package/hooks/guard.mjs CHANGED
@@ -32,7 +32,7 @@ import { createHash } from 'node:crypto';
32
32
  // the installed hook self-updates when the cloud version is higher (see
33
33
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
34
34
  // reinstall — the same trust model as the OPA WASM this hook already runs.
35
- const HOOK_VERSION = 40;
35
+ const HOOK_VERSION = 41;
36
36
 
37
37
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
38
38
  // nothing is sent to the cloud audit log.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.76",
3
+ "version": "0.81.78",
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": {