@solongate/proxy 0.81.81 → 0.81.82

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.
@@ -25,6 +25,14 @@ export declare function installGlobalQuiet(): {
25
25
  ok: boolean;
26
26
  message: string;
27
27
  };
28
+ /** HOOK_VERSION of the guard hook currently installed on THIS device (read from
29
+ * the file), or null when not installed. The LOCAL truth — independent of the
30
+ * cloud guard-status (which only knows what the device last REPORTED). */
31
+ export declare function installedGuardVersion(): number | null;
32
+ /** True when the installed guard hook DIFFERS from the one this CLI ships — i.e.
33
+ * a newer hook is available locally (the CLI self-updated). Cloud-independent,
34
+ * so the dataroom can offer/apply an update even when the API is behind. */
35
+ export declare function guardHookOutdated(): boolean;
28
36
  /**
29
37
  * Are the SolonGate guard hooks currently installed in the global Claude settings
30
38
  * on THIS device? Reflects a local install/remove IMMEDIATELY — unlike the cloud
@@ -180,11 +180,29 @@ function installGlobalQuiet() {
180
180
  };
181
181
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
182
182
  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)" };
183
+ return { ok: true, message: "guard installed (open a new session)" };
184
184
  } catch (e) {
185
185
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
186
186
  }
187
187
  }
188
+ function installedGuardVersion() {
189
+ try {
190
+ const p = globalPaths();
191
+ const s = readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8");
192
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
193
+ return m ? parseInt(m[1], 10) : null;
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+ function guardHookOutdated() {
199
+ try {
200
+ const p = globalPaths();
201
+ return readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
188
206
  function isGuardInstalled() {
189
207
  try {
190
208
  const p = globalPaths();
@@ -200,18 +218,11 @@ function uninstallGlobalQuiet() {
200
218
  const p = globalPaths();
201
219
  unlockProtected();
202
220
  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" };
221
+ if (!existsSync(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
222
+ const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
223
+ delete s.hooks;
224
+ writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
225
+ return { ok: true, message: "guard removed (open a new session)" };
215
226
  } catch (e) {
216
227
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
217
228
  }
@@ -356,9 +367,11 @@ async function installGlobalWithKey(apiKey, apiUrl) {
356
367
  export {
357
368
  clearGuardUpdateCheck,
358
369
  globalPaths,
370
+ guardHookOutdated,
359
371
  installClaudeShim,
360
372
  installGlobalQuiet,
361
373
  installGlobalWithKey,
374
+ installedGuardVersion,
362
375
  isGuardInstalled,
363
376
  lockProtected,
364
377
  removeClaudeShim,
package/dist/index.js CHANGED
@@ -10229,11 +10229,29 @@ function installGlobalQuiet() {
10229
10229
  };
10230
10230
  writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
10231
10231
  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)" };
10232
+ return { ok: true, message: "guard installed (open a new session)" };
10233
10233
  } catch (e) {
10234
10234
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
10235
10235
  }
10236
10236
  }
10237
+ function installedGuardVersion() {
10238
+ try {
10239
+ const p = globalPaths();
10240
+ const s = readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8");
10241
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
10242
+ return m ? parseInt(m[1], 10) : null;
10243
+ } catch {
10244
+ return null;
10245
+ }
10246
+ }
10247
+ function guardHookOutdated() {
10248
+ try {
10249
+ const p = globalPaths();
10250
+ return readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
10251
+ } catch {
10252
+ return false;
10253
+ }
10254
+ }
10237
10255
  function isGuardInstalled() {
10238
10256
  try {
10239
10257
  const p = globalPaths();
@@ -10249,18 +10267,11 @@ function uninstallGlobalQuiet() {
10249
10267
  const p = globalPaths();
10250
10268
  unlockProtected();
10251
10269
  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" };
10270
+ if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
10271
+ const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10272
+ delete s.hooks;
10273
+ writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10274
+ return { ok: true, message: "guard removed (open a new session)" };
10264
10275
  } catch (e) {
10265
10276
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
10266
10277
  }
@@ -10336,6 +10347,13 @@ function SettingsPanel({
10336
10347
  const abort = useRef3(false);
10337
10348
  const refreshAccounts = () => setAccounts(listAccounts());
10338
10349
  const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
10350
+ const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
10351
+ const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
10352
+ const refreshGuard = () => {
10353
+ setGuardHere(isGuardInstalled());
10354
+ setGuardVer(installedGuardVersion());
10355
+ setGuardOld(guardHookOutdated());
10356
+ };
10339
10357
  const locked = accounts.length === 0;
10340
10358
  const [srv, setSrv] = useState7(() => logsServerStatus());
10341
10359
  useEffect6(() => {
@@ -10353,21 +10371,20 @@ function SettingsPanel({
10353
10371
  const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
10354
10372
  const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
10355
10373
  const local = localQ.data;
10356
- const guard = guardQ.data;
10357
10374
  const selfProt = selfQ.data;
10358
10375
  const autoInstalledRef = useRef3(false);
10359
10376
  useEffect6(() => {
10360
10377
  if (autoInstalledRef.current) return;
10361
- if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
10378
+ if (guardHere && guardOld) {
10362
10379
  autoInstalledRef.current = true;
10363
10380
  const res = installGlobalQuiet();
10364
10381
  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" });
10382
+ refreshGuard();
10383
+ setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
10367
10384
  guardQ.reload();
10368
10385
  }
10369
10386
  }
10370
- }, [guardHere, guard]);
10387
+ }, [guardHere, guardOld]);
10371
10388
  const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
10372
10389
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
10373
10390
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
@@ -10495,7 +10512,7 @@ function SettingsPanel({
10495
10512
  } else if (r.kind === "guard") {
10496
10513
  const res = installGlobalQuiet();
10497
10514
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10498
- setGuardHere(isGuardInstalled());
10515
+ refreshGuard();
10499
10516
  guardQ.reload();
10500
10517
  } else if (r.kind === "self") {
10501
10518
  if (!selfProt) return;
@@ -10659,7 +10676,7 @@ function SettingsPanel({
10659
10676
  setConfirmDel(null);
10660
10677
  const res = uninstallGlobalQuiet();
10661
10678
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10662
- setGuardHere(isGuardInstalled());
10679
+ refreshGuard();
10663
10680
  guardQ.reload();
10664
10681
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
10665
10682
  const k = keyOf(cur);
@@ -10768,13 +10785,13 @@ function SettingsPanel({
10768
10785
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10769
10786
  !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10770
10787
  /* @__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 " }),
10788
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
10772
10789
  /* @__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" })
10790
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
10791
+ /* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
10792
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
10793
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
10794
+ ] })
10778
10795
  ] });
10779
10796
  case "self":
10780
10797
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
package/dist/tui/index.js CHANGED
@@ -3286,11 +3286,29 @@ function installGlobalQuiet() {
3286
3286
  };
3287
3287
  writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
3288
3288
  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)" };
3289
+ return { ok: true, message: "guard installed (open a new session)" };
3290
3290
  } catch (e) {
3291
3291
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
3292
3292
  }
3293
3293
  }
3294
+ function installedGuardVersion() {
3295
+ try {
3296
+ const p = globalPaths();
3297
+ const s = readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8");
3298
+ const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
3299
+ return m ? parseInt(m[1], 10) : null;
3300
+ } catch {
3301
+ return null;
3302
+ }
3303
+ }
3304
+ function guardHookOutdated() {
3305
+ try {
3306
+ const p = globalPaths();
3307
+ return readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
3308
+ } catch {
3309
+ return false;
3310
+ }
3311
+ }
3294
3312
  function isGuardInstalled() {
3295
3313
  try {
3296
3314
  const p = globalPaths();
@@ -3306,18 +3324,11 @@ function uninstallGlobalQuiet() {
3306
3324
  const p = globalPaths();
3307
3325
  unlockProtected();
3308
3326
  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" };
3327
+ if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
3328
+ const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3329
+ delete s.hooks;
3330
+ writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
3331
+ return { ok: true, message: "guard removed (open a new session)" };
3321
3332
  } catch (e) {
3322
3333
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
3323
3334
  }
@@ -3473,6 +3484,13 @@ function SettingsPanel({
3473
3484
  const abort = useRef3(false);
3474
3485
  const refreshAccounts = () => setAccounts(listAccounts());
3475
3486
  const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
3487
+ const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
3488
+ const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
3489
+ const refreshGuard = () => {
3490
+ setGuardHere(isGuardInstalled());
3491
+ setGuardVer(installedGuardVersion());
3492
+ setGuardOld(guardHookOutdated());
3493
+ };
3476
3494
  const locked = accounts.length === 0;
3477
3495
  const [srv, setSrv] = useState7(() => logsServerStatus());
3478
3496
  useEffect6(() => {
@@ -3490,21 +3508,20 @@ function SettingsPanel({
3490
3508
  const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
3491
3509
  const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
3492
3510
  const local = localQ.data;
3493
- const guard = guardQ.data;
3494
3511
  const selfProt = selfQ.data;
3495
3512
  const autoInstalledRef = useRef3(false);
3496
3513
  useEffect6(() => {
3497
3514
  if (autoInstalledRef.current) return;
3498
- if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
3515
+ if (guardHere && guardOld) {
3499
3516
  autoInstalledRef.current = true;
3500
3517
  const res = installGlobalQuiet();
3501
3518
  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" });
3519
+ refreshGuard();
3520
+ setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
3504
3521
  guardQ.reload();
3505
3522
  }
3506
3523
  }
3507
- }, [guardHere, guard]);
3524
+ }, [guardHere, guardOld]);
3508
3525
  const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
3509
3526
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
3510
3527
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
@@ -3632,7 +3649,7 @@ function SettingsPanel({
3632
3649
  } else if (r.kind === "guard") {
3633
3650
  const res = installGlobalQuiet();
3634
3651
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3635
- setGuardHere(isGuardInstalled());
3652
+ refreshGuard();
3636
3653
  guardQ.reload();
3637
3654
  } else if (r.kind === "self") {
3638
3655
  if (!selfProt) return;
@@ -3796,7 +3813,7 @@ function SettingsPanel({
3796
3813
  setConfirmDel(null);
3797
3814
  const res = uninstallGlobalQuiet();
3798
3815
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3799
- setGuardHere(isGuardInstalled());
3816
+ refreshGuard();
3800
3817
  guardQ.reload();
3801
3818
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
3802
3819
  const k = keyOf(cur);
@@ -3905,13 +3922,13 @@ function SettingsPanel({
3905
3922
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3906
3923
  !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3907
3924
  /* @__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 " }),
3925
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
3909
3926
  /* @__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" })
3927
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
3928
+ /* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
3929
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
3930
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
3931
+ ] })
3915
3932
  ] });
3916
3933
  case "self":
3917
3934
  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.82",
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": {