@solongate/proxy 0.81.78 → 0.81.80

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
+ * Are the SolonGate guard hooks currently installed in the global Claude settings
16
+ * on THIS device? Reflects a local install/remove IMMEDIATELY — unlike the cloud
17
+ * guard-status (which shows the version this device last reported and lingers for
18
+ * ~14 days), so the dataroom can show "removed" the instant you remove it.
19
+ */
20
+ export declare function isGuardInstalled(): boolean;
14
21
  /**
15
22
  * TUI-safe uninstall (dataroom Settings → guard → remove). Same effect as
16
23
  * runGlobalRestore but returns a status string instead of writing to stdout —
@@ -137,20 +137,31 @@ function runGlobalRestore() {
137
137
  }
138
138
  console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
139
139
  }
140
+ function isGuardInstalled() {
141
+ try {
142
+ const p = globalPaths();
143
+ if (!existsSync(p.settingsPath)) return false;
144
+ const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
145
+ return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
140
150
  function uninstallGlobalQuiet() {
141
151
  try {
142
152
  const p = globalPaths();
143
153
  unlockProtected();
144
154
  removeClaudeShim();
155
+ const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
145
156
  if (existsSync(p.backupPath)) {
146
157
  writeFileSync(p.settingsPath, readFileSync(p.backupPath, "utf-8"));
147
- return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
158
+ return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
148
159
  }
149
160
  if (existsSync(p.settingsPath)) {
150
161
  const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
151
162
  delete s.hooks;
152
163
  writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
153
- return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
164
+ return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
154
165
  }
155
166
  return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
156
167
  } catch (e) {
@@ -299,6 +310,7 @@ export {
299
310
  globalPaths,
300
311
  installClaudeShim,
301
312
  installGlobalWithKey,
313
+ isGuardInstalled,
302
314
  lockProtected,
303
315
  removeClaudeShim,
304
316
  runGlobalInstall,
package/dist/index.js CHANGED
@@ -6562,7 +6562,7 @@ __export(self_update_exports, {
6562
6562
  tuiUpdateFlow: () => tuiUpdateFlow
6563
6563
  });
6564
6564
  import { execFile, spawn } from "child_process";
6565
- import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync3 } from "fs";
6565
+ import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
6566
6566
  import { homedir as homedir2 } from "os";
6567
6567
  import { dirname, join as join4 } from "path";
6568
6568
  import { fileURLToPath } from "url";
@@ -6614,28 +6614,6 @@ async function fetchLatest() {
6614
6614
  return null;
6615
6615
  }
6616
6616
  }
6617
- function npmGlobalRoot() {
6618
- return new Promise((resolve10) => {
6619
- try {
6620
- execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err2, stdout) => {
6621
- resolve10(err2 ? null : stdout.trim() || null);
6622
- });
6623
- } catch {
6624
- resolve10(null);
6625
- }
6626
- });
6627
- }
6628
- async function isGlobalInstall() {
6629
- try {
6630
- const script = realpathSync(process.argv[1] ?? "");
6631
- if (!script) return false;
6632
- const root = await npmGlobalRoot();
6633
- if (!root) return false;
6634
- return script.startsWith(realpathSync(root));
6635
- } catch {
6636
- return false;
6637
- }
6638
- }
6639
6617
  function spawnGlobalInstall(version) {
6640
6618
  try {
6641
6619
  mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
@@ -6687,7 +6665,7 @@ async function tuiUpdateFlow(onStatus) {
6687
6665
  if (!latest || !newerThan(latest, current)) return;
6688
6666
  const attempts = readState().attempts ?? {};
6689
6667
  const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
6690
- if (await isGlobalInstall() && canRetry) {
6668
+ if (canRetry) {
6691
6669
  writeState({ ...readState(), attempts: { [latest]: Date.now() } });
6692
6670
  onStatus({ kind: "updating", version: latest });
6693
6671
  if (await runGlobalInstall(latest)) {
@@ -6713,16 +6691,14 @@ function maybeSelfUpdate(notify = (l) => process.stderr.write(l + "\n")) {
6713
6691
  }
6714
6692
  if (!latest || !newerThan(latest, current)) return;
6715
6693
  const attempts = readState().attempts ?? {};
6716
- if (await isGlobalInstall()) {
6717
- if (now - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS) {
6718
- writeState({ ...readState(), attempts: { [latest]: now } });
6719
- if (spawnGlobalInstall(latest)) {
6720
- notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
6721
- return;
6722
- }
6723
- } else {
6694
+ if (now - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS) {
6695
+ writeState({ ...readState(), attempts: { [latest]: now } });
6696
+ if (spawnGlobalInstall(latest)) {
6697
+ notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
6724
6698
  return;
6725
6699
  }
6700
+ } else {
6701
+ return;
6726
6702
  }
6727
6703
  notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 update: ${c.reset}${c.cyan}npm i -g ${PKG}@latest${c.reset}`);
6728
6704
  } catch {
@@ -10182,20 +10158,31 @@ function clearGuardUpdateCheck() {
10182
10158
  return false;
10183
10159
  }
10184
10160
  }
10161
+ function isGuardInstalled() {
10162
+ try {
10163
+ const p = globalPaths();
10164
+ if (!existsSync4(p.settingsPath)) return false;
10165
+ const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10166
+ return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
10167
+ } catch {
10168
+ return false;
10169
+ }
10170
+ }
10185
10171
  function uninstallGlobalQuiet() {
10186
10172
  try {
10187
10173
  const p = globalPaths();
10188
10174
  unlockProtected();
10189
10175
  removeClaudeShim();
10176
+ const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
10190
10177
  if (existsSync4(p.backupPath)) {
10191
10178
  writeFileSync9(p.settingsPath, readFileSync9(p.backupPath, "utf-8"));
10192
- return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
10179
+ return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
10193
10180
  }
10194
10181
  if (existsSync4(p.settingsPath)) {
10195
10182
  const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10196
10183
  delete s.hooks;
10197
10184
  writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10198
- return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
10185
+ return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
10199
10186
  }
10200
10187
  return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
10201
10188
  } catch (e) {
@@ -10272,6 +10259,7 @@ function SettingsPanel({
10272
10259
  const [tick, setTick] = useState7(0);
10273
10260
  const abort = useRef3(false);
10274
10261
  const refreshAccounts = () => setAccounts(listAccounts());
10262
+ const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
10275
10263
  const locked = accounts.length === 0;
10276
10264
  const [srv, setSrv] = useState7(() => logsServerStatus());
10277
10265
  useEffect6(() => {
@@ -10416,6 +10404,10 @@ function SettingsPanel({
10416
10404
  } else if (r.kind === "acct-add") {
10417
10405
  beginLogin();
10418
10406
  } else if (r.kind === "guard") {
10407
+ if (!guardHere) {
10408
+ setMsg({ text: "guard is removed on this device \u2014 log in again (Accounts \u2192 + add) to reinstall it", level: "bad" });
10409
+ return;
10410
+ }
10419
10411
  if (!guard) return;
10420
10412
  if (guard.up_to_date) {
10421
10413
  setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
@@ -10574,6 +10566,10 @@ function SettingsPanel({
10574
10566
  } else if (cur.kind === "alert-add-email") openAlertEditor("email");
10575
10567
  else if (cur.kind === "alert-add-tg") openAlertEditor("telegram");
10576
10568
  } else if (inp === "d" && cur.kind === "guard") {
10569
+ if (!guardHere) {
10570
+ setMsg({ text: "guard already removed on this device", level: "ok" });
10571
+ return;
10572
+ }
10577
10573
  const k = keyOf(cur);
10578
10574
  if (confirmDel !== k) {
10579
10575
  setConfirmDel(k);
@@ -10583,6 +10579,7 @@ function SettingsPanel({
10583
10579
  setConfirmDel(null);
10584
10580
  const res = uninstallGlobalQuiet();
10585
10581
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
10582
+ setGuardHere(isGuardInstalled());
10586
10583
  guardQ.reload();
10587
10584
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
10588
10585
  const k = keyOf(cur);
@@ -10689,7 +10686,10 @@ function SettingsPanel({
10689
10686
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10690
10687
  cursor(r),
10691
10688
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10692
- guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10689
+ !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10690
+ /* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
10691
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 not installed on this device \xB7 new Claude Code sessions are unguarded" })
10692
+ ] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
10693
10693
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
10694
10694
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
10695
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` })
package/dist/tui/index.js CHANGED
@@ -3215,20 +3215,31 @@ function clearGuardUpdateCheck() {
3215
3215
  return false;
3216
3216
  }
3217
3217
  }
3218
+ function isGuardInstalled() {
3219
+ try {
3220
+ const p = globalPaths();
3221
+ if (!existsSync2(p.settingsPath)) return false;
3222
+ const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3223
+ return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
3224
+ } catch {
3225
+ return false;
3226
+ }
3227
+ }
3218
3228
  function uninstallGlobalQuiet() {
3219
3229
  try {
3220
3230
  const p = globalPaths();
3221
3231
  unlockProtected();
3222
3232
  removeClaudeShim();
3233
+ const applies = "applies to NEW Claude Code sessions \u2014 a session already running keeps its loaded hooks until it ends";
3223
3234
  if (existsSync2(p.backupPath)) {
3224
3235
  writeFileSync5(p.settingsPath, readFileSync4(p.backupPath, "utf-8"));
3225
- return { ok: true, message: "guard removed \u2014 restored the pre-install settings (restart Claude Code)" };
3236
+ return { ok: true, message: `guard removed \u2014 restored the pre-install settings \xB7 ${applies}` };
3226
3237
  }
3227
3238
  if (existsSync2(p.settingsPath)) {
3228
3239
  const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3229
3240
  delete s.hooks;
3230
3241
  writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
3231
- return { ok: true, message: "guard removed \u2014 SolonGate hooks cleared (restart Claude Code)" };
3242
+ return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
3232
3243
  }
3233
3244
  return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
3234
3245
  } catch (e) {
@@ -3385,6 +3396,7 @@ function SettingsPanel({
3385
3396
  const [tick, setTick] = useState7(0);
3386
3397
  const abort = useRef3(false);
3387
3398
  const refreshAccounts = () => setAccounts(listAccounts());
3399
+ const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
3388
3400
  const locked = accounts.length === 0;
3389
3401
  const [srv, setSrv] = useState7(() => logsServerStatus());
3390
3402
  useEffect6(() => {
@@ -3529,6 +3541,10 @@ function SettingsPanel({
3529
3541
  } else if (r.kind === "acct-add") {
3530
3542
  beginLogin();
3531
3543
  } else if (r.kind === "guard") {
3544
+ if (!guardHere) {
3545
+ setMsg({ text: "guard is removed on this device \u2014 log in again (Accounts \u2192 + add) to reinstall it", level: "bad" });
3546
+ return;
3547
+ }
3532
3548
  if (!guard) return;
3533
3549
  if (guard.up_to_date) {
3534
3550
  setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
@@ -3687,6 +3703,10 @@ function SettingsPanel({
3687
3703
  } else if (cur.kind === "alert-add-email") openAlertEditor("email");
3688
3704
  else if (cur.kind === "alert-add-tg") openAlertEditor("telegram");
3689
3705
  } else if (inp === "d" && cur.kind === "guard") {
3706
+ if (!guardHere) {
3707
+ setMsg({ text: "guard already removed on this device", level: "ok" });
3708
+ return;
3709
+ }
3690
3710
  const k = keyOf(cur);
3691
3711
  if (confirmDel !== k) {
3692
3712
  setConfirmDel(k);
@@ -3696,6 +3716,7 @@ function SettingsPanel({
3696
3716
  setConfirmDel(null);
3697
3717
  const res = uninstallGlobalQuiet();
3698
3718
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
3719
+ setGuardHere(isGuardInstalled());
3699
3720
  guardQ.reload();
3700
3721
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
3701
3722
  const k = keyOf(cur);
@@ -3802,7 +3823,10 @@ function SettingsPanel({
3802
3823
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3803
3824
  cursor(r),
3804
3825
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3805
- guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3826
+ !guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3827
+ /* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
3828
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 not installed on this device \xB7 new Claude Code sessions are unguarded" })
3829
+ ] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3806
3830
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
3807
3831
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
3808
3832
  /* @__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` })
@@ -3928,7 +3952,7 @@ function SettingsPanel({
3928
3952
 
3929
3953
  // src/self-update.ts
3930
3954
  import { execFile, spawn as spawn4 } from "child_process";
3931
- import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync6, realpathSync, writeFileSync as writeFileSync7 } from "fs";
3955
+ import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
3932
3956
  import { homedir as homedir8 } from "os";
3933
3957
  import { dirname as dirname3, join as join8 } from "path";
3934
3958
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -3985,28 +4009,6 @@ async function fetchLatest() {
3985
4009
  return null;
3986
4010
  }
3987
4011
  }
3988
- function npmGlobalRoot() {
3989
- return new Promise((resolve3) => {
3990
- try {
3991
- execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err, stdout) => {
3992
- resolve3(err ? null : stdout.trim() || null);
3993
- });
3994
- } catch {
3995
- resolve3(null);
3996
- }
3997
- });
3998
- }
3999
- async function isGlobalInstall() {
4000
- try {
4001
- const script = realpathSync(process.argv[1] ?? "");
4002
- if (!script) return false;
4003
- const root = await npmGlobalRoot();
4004
- if (!root) return false;
4005
- return script.startsWith(realpathSync(root));
4006
- } catch {
4007
- return false;
4008
- }
4009
- }
4010
4012
  function runGlobalInstall(version) {
4011
4013
  return new Promise((resolve3) => {
4012
4014
  try {
@@ -4040,7 +4042,7 @@ async function tuiUpdateFlow(onStatus) {
4040
4042
  if (!latest || !newerThan(latest, current)) return;
4041
4043
  const attempts = readState2().attempts ?? {};
4042
4044
  const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
4043
- if (await isGlobalInstall() && canRetry) {
4045
+ if (canRetry) {
4044
4046
  writeState2({ ...readState2(), attempts: { [latest]: Date.now() } });
4045
4047
  onStatus({ kind: "updating", version: latest });
4046
4048
  if (await runGlobalInstall(latest)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.78",
3
+ "version": "0.81.80",
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": {