@solongate/proxy 0.81.81 → 0.81.83

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.
@@ -11,6 +11,13 @@ export declare function globalPaths(): {
11
11
  };
12
12
  export declare function clearGuardUpdateCheck(): boolean;
13
13
  export declare function runGlobalRestore(): void;
14
+ /**
15
+ * Delete the guard's cached policy/security config (`~/.solongate/.policy-cache-*.json`)
16
+ * so the next tool call RE-FETCHES it from the API. Fixes a stale cache — e.g. the
17
+ * guard still enforcing DLP `detect` after you switched it to `block` in the
18
+ * dataroom. Best-effort; returns how many cache files it cleared.
19
+ */
20
+ export declare function clearPolicyCache(): number;
14
21
  /**
15
22
  * TUI-safe (re)install — the dataroom's one-key "install / update guard" action.
16
23
  * Writes the hook files THIS CLI ships (readGuard = the current package's bundle,
@@ -25,6 +32,14 @@ export declare function installGlobalQuiet(): {
25
32
  ok: boolean;
26
33
  message: string;
27
34
  };
35
+ /** HOOK_VERSION of the guard hook currently installed on THIS device (read from
36
+ * the file), or null when not installed. The LOCAL truth — independent of the
37
+ * cloud guard-status (which only knows what the device last REPORTED). */
38
+ export declare function installedGuardVersion(): number | null;
39
+ /** True when the installed guard hook DIFFERS from the one this CLI ships — i.e.
40
+ * a newer hook is available locally (the CLI self-updated). Cloud-independent,
41
+ * so the dataroom can offer/apply an update even when the API is behind. */
42
+ export declare function guardHookOutdated(): boolean;
28
43
  /**
29
44
  * Are the SolonGate guard hooks currently installed in the global Claude settings
30
45
  * on THIS device? Reflects a local install/remove IMMEDIATELY — unlike the cloud
@@ -1,5 +1,5 @@
1
1
  // src/global-install.ts
2
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync } from "fs";
3
3
  import { resolve, join, dirname } from "path";
4
4
  import { homedir } from "os";
5
5
  import { fileURLToPath } from "url";
@@ -137,6 +137,24 @@ function runGlobalRestore() {
137
137
  }
138
138
  console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
139
139
  }
140
+ function clearPolicyCache() {
141
+ try {
142
+ const dir = globalPaths().sgDir;
143
+ let n = 0;
144
+ for (const f of readdirSync(dir)) {
145
+ if (f.startsWith(".policy-cache-") && f.endsWith(".json")) {
146
+ try {
147
+ rmSync(join(dir, f), { force: true });
148
+ n++;
149
+ } catch {
150
+ }
151
+ }
152
+ }
153
+ return n;
154
+ } catch {
155
+ return 0;
156
+ }
157
+ }
140
158
  function installGlobalQuiet() {
141
159
  try {
142
160
  const p = globalPaths();
@@ -179,12 +197,31 @@ function installGlobalQuiet() {
179
197
  }
180
198
  };
181
199
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
200
+ clearPolicyCache();
182
201
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
183
- return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
202
+ return { ok: true, message: "guard installed + policy refreshed (open a new session)" };
184
203
  } catch (e) {
185
204
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
186
205
  }
187
206
  }
207
+ function installedGuardVersion() {
208
+ try {
209
+ const p = globalPaths();
210
+ const s = readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8");
211
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
212
+ return m ? parseInt(m[1], 10) : null;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ function guardHookOutdated() {
218
+ try {
219
+ const p = globalPaths();
220
+ return readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
188
225
  function isGuardInstalled() {
189
226
  try {
190
227
  const p = globalPaths();
@@ -200,18 +237,11 @@ function uninstallGlobalQuiet() {
200
237
  const p = globalPaths();
201
238
  unlockProtected();
202
239
  removeClaudeShim();
203
- const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
204
- if (existsSync(p.backupPath)) {
205
- writeFileSync(p.settingsPath, readFileSync(p.backupPath, "utf-8"));
206
- return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
207
- }
208
- if (existsSync(p.settingsPath)) {
209
- const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
210
- delete s.hooks;
211
- writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
212
- return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
213
- }
214
- return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
240
+ if (!existsSync(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
241
+ const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
242
+ delete s.hooks;
243
+ writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
244
+ return { ok: true, message: "guard removed (open a new session)" };
215
245
  } catch (e) {
216
246
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
217
247
  }
@@ -355,10 +385,13 @@ async function installGlobalWithKey(apiKey, apiUrl) {
355
385
  }
356
386
  export {
357
387
  clearGuardUpdateCheck,
388
+ clearPolicyCache,
358
389
  globalPaths,
390
+ guardHookOutdated,
359
391
  installClaudeShim,
360
392
  installGlobalQuiet,
361
393
  installGlobalWithKey,
394
+ installedGuardVersion,
362
395
  isGuardInstalled,
363
396
  lockProtected,
364
397
  removeClaudeShim,
package/dist/index.js CHANGED
@@ -10085,7 +10085,7 @@ var init_Audit = __esm({
10085
10085
  });
10086
10086
 
10087
10087
  // src/global-install.ts
10088
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync4, mkdirSync as mkdirSync8, rmSync as rmSync2 } from "fs";
10088
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync4, mkdirSync as mkdirSync8, rmSync as rmSync2, readdirSync } from "fs";
10089
10089
  import { resolve as resolve4, join as join11, dirname as dirname3 } from "path";
10090
10090
  import { homedir as homedir9 } from "os";
10091
10091
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -10186,6 +10186,24 @@ function readGuard() {
10186
10186
  const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
10187
10187
  return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
10188
10188
  }
10189
+ function clearPolicyCache() {
10190
+ try {
10191
+ const dir = globalPaths().sgDir;
10192
+ let n = 0;
10193
+ for (const f of readdirSync(dir)) {
10194
+ if (f.startsWith(".policy-cache-") && f.endsWith(".json")) {
10195
+ try {
10196
+ rmSync2(join11(dir, f), { force: true });
10197
+ n++;
10198
+ } catch {
10199
+ }
10200
+ }
10201
+ }
10202
+ return n;
10203
+ } catch {
10204
+ return 0;
10205
+ }
10206
+ }
10189
10207
  function installGlobalQuiet() {
10190
10208
  try {
10191
10209
  const p = globalPaths();
@@ -10228,12 +10246,31 @@ function installGlobalQuiet() {
10228
10246
  }
10229
10247
  };
10230
10248
  writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
10249
+ clearPolicyCache();
10231
10250
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
10232
- return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
10251
+ return { ok: true, message: "guard installed + policy refreshed (open a new session)" };
10233
10252
  } catch (e) {
10234
10253
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
10235
10254
  }
10236
10255
  }
10256
+ function installedGuardVersion() {
10257
+ try {
10258
+ const p = globalPaths();
10259
+ const s = readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8");
10260
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
10261
+ return m ? parseInt(m[1], 10) : null;
10262
+ } catch {
10263
+ return null;
10264
+ }
10265
+ }
10266
+ function guardHookOutdated() {
10267
+ try {
10268
+ const p = globalPaths();
10269
+ return readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
10270
+ } catch {
10271
+ return false;
10272
+ }
10273
+ }
10237
10274
  function isGuardInstalled() {
10238
10275
  try {
10239
10276
  const p = globalPaths();
@@ -10249,18 +10286,11 @@ function uninstallGlobalQuiet() {
10249
10286
  const p = globalPaths();
10250
10287
  unlockProtected();
10251
10288
  removeClaudeShim();
10252
- const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
10253
- if (existsSync4(p.backupPath)) {
10254
- writeFileSync9(p.settingsPath, readFileSync9(p.backupPath, "utf-8"));
10255
- return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
10256
- }
10257
- if (existsSync4(p.settingsPath)) {
10258
- const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10259
- delete s.hooks;
10260
- writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10261
- return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
10262
- }
10263
- return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
10289
+ if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
10290
+ const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10291
+ delete s.hooks;
10292
+ writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10293
+ return { ok: true, message: "guard removed (open a new session)" };
10264
10294
  } catch (e) {
10265
10295
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
10266
10296
  }
@@ -10336,6 +10366,13 @@ function SettingsPanel({
10336
10366
  const abort = useRef3(false);
10337
10367
  const refreshAccounts = () => setAccounts(listAccounts());
10338
10368
  const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
10369
+ const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
10370
+ const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
10371
+ const refreshGuard = () => {
10372
+ setGuardHere(isGuardInstalled());
10373
+ setGuardVer(installedGuardVersion());
10374
+ setGuardOld(guardHookOutdated());
10375
+ };
10339
10376
  const locked = accounts.length === 0;
10340
10377
  const [srv, setSrv] = useState7(() => logsServerStatus());
10341
10378
  useEffect6(() => {
@@ -10353,21 +10390,20 @@ function SettingsPanel({
10353
10390
  const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
10354
10391
  const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
10355
10392
  const local = localQ.data;
10356
- const guard = guardQ.data;
10357
10393
  const selfProt = selfQ.data;
10358
10394
  const autoInstalledRef = useRef3(false);
10359
10395
  useEffect6(() => {
10360
10396
  if (autoInstalledRef.current) return;
10361
- if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
10397
+ if (guardHere && guardOld) {
10362
10398
  autoInstalledRef.current = true;
10363
10399
  const res = installGlobalQuiet();
10364
10400
  if (res.ok) {
10365
- setGuardHere(isGuardInstalled());
10366
- setMsg({ text: `\u2713 guard auto-updated \u2192 v${guard.latest} hooks written \xB7 open a new Claude Code session to apply`, level: "ok" });
10401
+ refreshGuard();
10402
+ setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
10367
10403
  guardQ.reload();
10368
10404
  }
10369
10405
  }
10370
- }, [guardHere, guard]);
10406
+ }, [guardHere, guardOld]);
10371
10407
  const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
10372
10408
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
10373
10409
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
@@ -10495,7 +10531,7 @@ function SettingsPanel({
10495
10531
  } else if (r.kind === "guard") {
10496
10532
  const res = installGlobalQuiet();
10497
10533
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10498
- setGuardHere(isGuardInstalled());
10534
+ refreshGuard();
10499
10535
  guardQ.reload();
10500
10536
  } else if (r.kind === "self") {
10501
10537
  if (!selfProt) return;
@@ -10659,7 +10695,7 @@ function SettingsPanel({
10659
10695
  setConfirmDel(null);
10660
10696
  const res = uninstallGlobalQuiet();
10661
10697
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10662
- setGuardHere(isGuardInstalled());
10698
+ refreshGuard();
10663
10699
  guardQ.reload();
10664
10700
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
10665
10701
  const k = keyOf(cur);
@@ -10768,13 +10804,13 @@ function SettingsPanel({
10768
10804
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10769
10805
  !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10770
10806
  /* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
10771
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 new Claude Code sessions are unguarded \xB7 " }),
10807
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
10772
10808
  /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
10773
- ] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10774
- /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
10775
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest) \xB7 enter reinstall" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
10776
- /* @__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` })
10777
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "installed \xB7 enter reinstall/update \xB7 d remove" })
10809
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
10810
+ /* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
10811
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
10812
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
10813
+ ] })
10778
10814
  ] });
10779
10815
  case "self":
10780
10816
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
@@ -12333,7 +12369,7 @@ import { createServer, request as httpRequest } from "http";
12333
12369
  import { request as httpsRequest } from "https";
12334
12370
  import { spawn as spawn5 } from "child_process";
12335
12371
  import { URL as URL2 } from "url";
12336
- import { readFileSync as readFileSync11, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
12372
+ import { readFileSync as readFileSync11, existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
12337
12373
  import { resolve as resolve5 } from "path";
12338
12374
  import { homedir as homedir13 } from "os";
12339
12375
  function findCacheFile() {
@@ -12345,7 +12381,7 @@ function findCacheFile() {
12345
12381
  }
12346
12382
  let best = null, bestTs = -1;
12347
12383
  try {
12348
- for (const name of readdirSync(dir)) {
12384
+ for (const name of readdirSync2(dir)) {
12349
12385
  if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
12350
12386
  const full = resolve5(dir, name);
12351
12387
  const ts = statSync4(full).mtimeMs;
@@ -12655,7 +12691,7 @@ import { createServer as createServer2 } from "http";
12655
12691
  import { readFileSync as readFileSync12, statSync as statSync5 } from "fs";
12656
12692
  import { resolve as resolve6, join as join15, isAbsolute } from "path";
12657
12693
  import { homedir as homedir14 } from "os";
12658
- import { readdirSync as readdirSync2 } from "fs";
12694
+ import { readdirSync as readdirSync3 } from "fs";
12659
12695
  function allowedOrigins() {
12660
12696
  const base = [
12661
12697
  "https://dashboard.solongate.com",
@@ -12676,7 +12712,7 @@ function resolveLocalLogDir(rawPath) {
12676
12712
  async function findLogDir() {
12677
12713
  const base = resolve6(homedir14(), ".solongate");
12678
12714
  try {
12679
- const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
12715
+ const files = readdirSync3(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
12680
12716
  for (const f of files) {
12681
12717
  try {
12682
12718
  const c2 = JSON.parse(readFileSync12(join15(base, f), "utf-8"));
package/dist/login.js CHANGED
@@ -26,7 +26,7 @@ var c = {
26
26
  var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
27
27
 
28
28
  // src/global-install.ts
29
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs";
29
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync } from "fs";
30
30
  import { resolve, join, dirname } from "path";
31
31
  import { homedir } from "os";
32
32
  import { fileURLToPath } from "url";
package/dist/tui/index.js CHANGED
@@ -3140,7 +3140,7 @@ import TextInput5 from "ink-text-input";
3140
3140
  import { useEffect as useEffect6, useRef as useRef3, useState as useState7 } from "react";
3141
3141
 
3142
3142
  // src/global-install.ts
3143
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
3143
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync, readdirSync } from "fs";
3144
3144
  import { resolve as resolve2, join as join6, dirname } from "path";
3145
3145
  import { homedir as homedir6 } from "os";
3146
3146
  import { fileURLToPath } from "url";
@@ -3243,6 +3243,24 @@ function readGuard() {
3243
3243
  const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
3244
3244
  return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
3245
3245
  }
3246
+ function clearPolicyCache() {
3247
+ try {
3248
+ const dir = globalPaths().sgDir;
3249
+ let n = 0;
3250
+ for (const f of readdirSync(dir)) {
3251
+ if (f.startsWith(".policy-cache-") && f.endsWith(".json")) {
3252
+ try {
3253
+ rmSync(join6(dir, f), { force: true });
3254
+ n++;
3255
+ } catch {
3256
+ }
3257
+ }
3258
+ }
3259
+ return n;
3260
+ } catch {
3261
+ return 0;
3262
+ }
3263
+ }
3246
3264
  function installGlobalQuiet() {
3247
3265
  try {
3248
3266
  const p = globalPaths();
@@ -3285,12 +3303,31 @@ function installGlobalQuiet() {
3285
3303
  }
3286
3304
  };
3287
3305
  writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
3306
+ clearPolicyCache();
3288
3307
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
3289
- return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
3308
+ return { ok: true, message: "guard installed + policy refreshed (open a new session)" };
3290
3309
  } catch (e) {
3291
3310
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
3292
3311
  }
3293
3312
  }
3313
+ function installedGuardVersion() {
3314
+ try {
3315
+ const p = globalPaths();
3316
+ const s = readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8");
3317
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
3318
+ return m ? parseInt(m[1], 10) : null;
3319
+ } catch {
3320
+ return null;
3321
+ }
3322
+ }
3323
+ function guardHookOutdated() {
3324
+ try {
3325
+ const p = globalPaths();
3326
+ return readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
3327
+ } catch {
3328
+ return false;
3329
+ }
3330
+ }
3294
3331
  function isGuardInstalled() {
3295
3332
  try {
3296
3333
  const p = globalPaths();
@@ -3306,18 +3343,11 @@ function uninstallGlobalQuiet() {
3306
3343
  const p = globalPaths();
3307
3344
  unlockProtected();
3308
3345
  removeClaudeShim();
3309
- const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
3310
- if (existsSync2(p.backupPath)) {
3311
- writeFileSync5(p.settingsPath, readFileSync4(p.backupPath, "utf-8"));
3312
- return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
3313
- }
3314
- if (existsSync2(p.settingsPath)) {
3315
- const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3316
- delete s.hooks;
3317
- writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
3318
- return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
3319
- }
3320
- return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
3346
+ if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
3347
+ const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3348
+ delete s.hooks;
3349
+ writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
3350
+ return { ok: true, message: "guard removed (open a new session)" };
3321
3351
  } catch (e) {
3322
3352
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
3323
3353
  }
@@ -3473,6 +3503,13 @@ function SettingsPanel({
3473
3503
  const abort = useRef3(false);
3474
3504
  const refreshAccounts = () => setAccounts(listAccounts());
3475
3505
  const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
3506
+ const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
3507
+ const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
3508
+ const refreshGuard = () => {
3509
+ setGuardHere(isGuardInstalled());
3510
+ setGuardVer(installedGuardVersion());
3511
+ setGuardOld(guardHookOutdated());
3512
+ };
3476
3513
  const locked = accounts.length === 0;
3477
3514
  const [srv, setSrv] = useState7(() => logsServerStatus());
3478
3515
  useEffect6(() => {
@@ -3490,21 +3527,20 @@ function SettingsPanel({
3490
3527
  const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
3491
3528
  const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
3492
3529
  const local = localQ.data;
3493
- const guard = guardQ.data;
3494
3530
  const selfProt = selfQ.data;
3495
3531
  const autoInstalledRef = useRef3(false);
3496
3532
  useEffect6(() => {
3497
3533
  if (autoInstalledRef.current) return;
3498
- if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
3534
+ if (guardHere && guardOld) {
3499
3535
  autoInstalledRef.current = true;
3500
3536
  const res = installGlobalQuiet();
3501
3537
  if (res.ok) {
3502
- setGuardHere(isGuardInstalled());
3503
- setMsg({ text: `\u2713 guard auto-updated \u2192 v${guard.latest} hooks written \xB7 open a new Claude Code session to apply`, level: "ok" });
3538
+ refreshGuard();
3539
+ setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
3504
3540
  guardQ.reload();
3505
3541
  }
3506
3542
  }
3507
- }, [guardHere, guard]);
3543
+ }, [guardHere, guardOld]);
3508
3544
  const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
3509
3545
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
3510
3546
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
@@ -3632,7 +3668,7 @@ function SettingsPanel({
3632
3668
  } else if (r.kind === "guard") {
3633
3669
  const res = installGlobalQuiet();
3634
3670
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3635
- setGuardHere(isGuardInstalled());
3671
+ refreshGuard();
3636
3672
  guardQ.reload();
3637
3673
  } else if (r.kind === "self") {
3638
3674
  if (!selfProt) return;
@@ -3796,7 +3832,7 @@ function SettingsPanel({
3796
3832
  setConfirmDel(null);
3797
3833
  const res = uninstallGlobalQuiet();
3798
3834
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3799
- setGuardHere(isGuardInstalled());
3835
+ refreshGuard();
3800
3836
  guardQ.reload();
3801
3837
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
3802
3838
  const k = keyOf(cur);
@@ -3905,13 +3941,13 @@ function SettingsPanel({
3905
3941
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3906
3942
  !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3907
3943
  /* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
3908
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 new Claude Code sessions are unguarded \xB7 " }),
3944
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
3909
3945
  /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
3910
- ] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3911
- /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
3912
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest) \xB7 enter reinstall" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
3913
- /* @__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` })
3914
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "installed \xB7 enter reinstall/update \xB7 d remove" })
3946
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
3947
+ /* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
3948
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
3949
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
3950
+ ] })
3915
3951
  ] });
3916
3952
  case "self":
3917
3953
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.81",
3
+ "version": "0.81.83",
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": {