@solongate/proxy 0.83.3 → 0.83.4

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.
@@ -405,6 +405,16 @@ function repairQuiet() {
405
405
  }
406
406
  async function runRepair() {
407
407
  const out = (s) => void process.stderr.write(s + "\n");
408
+ if (process.platform !== "win32" && typeof process.getuid === "function" && process.getuid() === 0 && process.env["SOLONGATE_INTERNAL"] !== "1") {
409
+ out("");
410
+ out(" Do not run `solongate repair` with sudo.");
411
+ out(" It installs the guard into your home directory, and as root it would");
412
+ out(` arm root\u2019s home instead${process.env["SUDO_USER"] ? ` of ${process.env["SUDO_USER"]}\u2019s` : ""} \u2014 reporting success while nothing changed for you.`);
413
+ out("");
414
+ out(" Run it as yourself: solongate repair");
415
+ out("");
416
+ return 1;
417
+ }
408
418
  const rep = repairQuiet();
409
419
  out("");
410
420
  out(" SolonGate repair");
package/dist/index.js CHANGED
@@ -465,13 +465,13 @@ ${offset}${err2}${errEnd}`;
465
465
  return next >= src.length || src[next] === "\n" ? str + "\n" : str;
466
466
  }
467
467
  // ^(---|...)
468
- static atDocumentBoundary(src, offset, sep) {
468
+ static atDocumentBoundary(src, offset, sep2) {
469
469
  const ch0 = src[offset];
470
470
  if (!ch0) return true;
471
471
  const prev = src[offset - 1];
472
472
  if (prev && prev !== "\n") return false;
473
- if (sep) {
474
- if (ch0 !== sep) return false;
473
+ if (sep2) {
474
+ if (ch0 !== sep2) return false;
475
475
  } else {
476
476
  if (ch0 !== Char.DIRECTIVES_END && ch0 !== Char.DOCUMENT_END) return false;
477
477
  }
@@ -1647,7 +1647,7 @@ var require_parse_cst = __commonJS({
1647
1647
  const folded = this.type === PlainValue.Type.BLOCK_FOLDED;
1648
1648
  let atStart = true;
1649
1649
  let str = "";
1650
- let sep = "";
1650
+ let sep2 = "";
1651
1651
  let prevMoreIndented = false;
1652
1652
  for (let i = start; i < end; ++i) {
1653
1653
  for (let j = 0; j < bi; ++j) {
@@ -1656,21 +1656,21 @@ var require_parse_cst = __commonJS({
1656
1656
  }
1657
1657
  const ch2 = src[i];
1658
1658
  if (ch2 === "\n") {
1659
- if (sep === "\n") str += "\n";
1660
- else sep = "\n";
1659
+ if (sep2 === "\n") str += "\n";
1660
+ else sep2 = "\n";
1661
1661
  } else {
1662
1662
  const lineEnd = PlainValue.Node.endOfLine(src, i);
1663
1663
  const line = src.slice(i, lineEnd);
1664
1664
  i = lineEnd;
1665
1665
  if (folded && (ch2 === " " || ch2 === " ") && i < keepStart) {
1666
- if (sep === " ") sep = "\n";
1667
- else if (!prevMoreIndented && !atStart && sep === "\n") sep = "\n\n";
1668
- str += sep + line;
1669
- sep = lineEnd < end && src[lineEnd] || "";
1666
+ if (sep2 === " ") sep2 = "\n";
1667
+ else if (!prevMoreIndented && !atStart && sep2 === "\n") sep2 = "\n\n";
1668
+ str += sep2 + line;
1669
+ sep2 = lineEnd < end && src[lineEnd] || "";
1670
1670
  prevMoreIndented = true;
1671
1671
  } else {
1672
- str += sep + line;
1673
- sep = folded && i < keepStart ? " " : "\n";
1672
+ str += sep2 + line;
1673
+ sep2 = folded && i < keepStart ? " " : "\n";
1674
1674
  prevMoreIndented = false;
1675
1675
  }
1676
1676
  if (atStart && line !== "") atStart = false;
@@ -6612,6 +6612,16 @@ function repairQuiet() {
6612
6612
  }
6613
6613
  async function runRepair() {
6614
6614
  const out2 = (s) => void process.stderr.write(s + "\n");
6615
+ if (process.platform !== "win32" && typeof process.getuid === "function" && process.getuid() === 0 && process.env["SOLONGATE_INTERNAL"] !== "1") {
6616
+ out2("");
6617
+ out2(" Do not run `solongate repair` with sudo.");
6618
+ out2(" It installs the guard into your home directory, and as root it would");
6619
+ out2(` arm root\u2019s home instead${process.env["SUDO_USER"] ? ` of ${process.env["SUDO_USER"]}\u2019s` : ""} \u2014 reporting success while nothing changed for you.`);
6620
+ out2("");
6621
+ out2(" Run it as yourself: solongate repair");
6622
+ out2("");
6623
+ return 1;
6624
+ }
6615
6625
  const rep = repairQuiet();
6616
6626
  out2("");
6617
6627
  out2(" SolonGate repair");
@@ -6924,22 +6934,25 @@ var init_global_install = __esm({
6924
6934
  var self_update_exports = {};
6925
6935
  __export(self_update_exports, {
6926
6936
  adminInstallCommand: () => adminInstallCommand,
6937
+ adminUpdateSteps: () => adminUpdateSteps,
6927
6938
  autoUpdateEnabled: () => autoUpdateEnabled,
6928
6939
  autoUpdateForcedByEnv: () => autoUpdateForcedByEnv,
6929
6940
  currentVersion: () => currentVersion,
6941
+ globalInstallCheck: () => globalInstallCheck,
6930
6942
  latestVersion: () => latestVersion,
6931
6943
  maybeSelfUpdate: () => maybeSelfUpdate,
6932
6944
  newerThan: () => newerThan,
6933
6945
  runAutoUpdateCommand: () => runAutoUpdateCommand,
6934
6946
  runUpdateCommand: () => runUpdateCommand,
6947
+ runningAsRoot: () => runningAsRoot,
6935
6948
  setAutoUpdate: () => setAutoUpdate,
6936
6949
  tuiUpdateFlow: () => tuiUpdateFlow,
6937
6950
  updateNow: () => updateNow
6938
6951
  });
6939
6952
  import { execFile, execFileSync as execFileSync3, spawn } from "child_process";
6940
- import { mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
6953
+ import { access, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4, constants as FS } from "fs";
6941
6954
  import { homedir as homedir3 } from "os";
6942
- import { dirname as dirname2, join as join5 } from "path";
6955
+ import { dirname as dirname2, join as join5, sep } from "path";
6943
6956
  import { fileURLToPath as fileURLToPath2 } from "url";
6944
6957
  function readState() {
6945
6958
  try {
@@ -7026,6 +7039,17 @@ function spawnGlobalInstall(version) {
7026
7039
  }
7027
7040
  async function runUpdateCommand() {
7028
7041
  const out2 = (s) => void process.stderr.write(s + "\n");
7042
+ if (runningAsRoot()) {
7043
+ out2("");
7044
+ out2(" Do not run `solongate update` with sudo.");
7045
+ out2(" npm would install fine, but the guard hooks would be written into root\u2019s");
7046
+ out2(" home instead of yours, and your agents would stop being guarded.");
7047
+ out2("");
7048
+ out2(" Run these two instead:");
7049
+ for (const s of adminUpdateSteps()) out2(" " + s);
7050
+ out2("");
7051
+ return 1;
7052
+ }
7029
7053
  const cur = currentVersion();
7030
7054
  out2(` current ${cur}`);
7031
7055
  const latest = await latestVersion();
@@ -7034,13 +7058,26 @@ async function runUpdateCommand() {
7034
7058
  return 1;
7035
7059
  }
7036
7060
  if (newerThan(latest, cur)) {
7061
+ const pre = await globalInstallCheck();
7062
+ if (pre.writable === false) {
7063
+ writeState({ ...readState(), needsAdmin: latest });
7064
+ out2(` v${latest} is out, but npm\u2019s global folder needs admin rights on this machine:`);
7065
+ out2(` ${pre.dir}`);
7066
+ out2(" That is the normal macOS setup \u2014 SolonGate cannot install there on its own.");
7067
+ out2("");
7068
+ out2(" Run these two:");
7069
+ for (const s of adminUpdateSteps()) out2(" " + s);
7070
+ out2("");
7071
+ return 1;
7072
+ }
7037
7073
  out2(` updating ${cur} -> ${latest} ...`);
7038
7074
  const r = await runGlobalInstall2(latest);
7039
7075
  if (!r.ok) {
7040
7076
  if (r.needsAdmin) {
7041
7077
  writeState({ ...readState(), needsAdmin: latest });
7042
- out2(` npm could not write to the global folder (it needs admin rights).`);
7043
- out2(` Run: ${adminInstallCommand()}`);
7078
+ out2(" npm could not write to the global folder (it needs admin rights).");
7079
+ out2(" Run these two:");
7080
+ for (const s of adminUpdateSteps()) out2(" " + s);
7044
7081
  } else {
7045
7082
  out2(" update failed. Try: npm i -g @solongate/proxy@latest");
7046
7083
  }
@@ -7095,11 +7132,43 @@ ${output}
7095
7132
  function adminInstallCommand() {
7096
7133
  return process.platform === "win32" ? `npm i -g ${PKG}@latest (in an Administrator terminal)` : `sudo npm i -g ${PKG}@latest`;
7097
7134
  }
7135
+ function runningAsRoot() {
7136
+ return process.platform !== "win32" && typeof process.getuid === "function" && process.getuid() === 0;
7137
+ }
7138
+ function ownNodeModules() {
7139
+ let dir = dirname2(fileURLToPath2(import.meta.url));
7140
+ for (let i = 0; i < 12; i++) {
7141
+ const parent = dirname2(dir);
7142
+ if (parent === dir) break;
7143
+ if (dir.endsWith(`${sep}node_modules`)) return dir;
7144
+ dir = parent;
7145
+ }
7146
+ return null;
7147
+ }
7148
+ function globalInstallCheck() {
7149
+ prefixCheck ??= new Promise((resolve6) => {
7150
+ try {
7151
+ const dir = ownNodeModules();
7152
+ if (!dir) return resolve6({ writable: null, dir: null });
7153
+ access(dir, FS.W_OK, (e) => resolve6({ writable: !e, dir }));
7154
+ } catch {
7155
+ resolve6({ writable: null, dir: null });
7156
+ }
7157
+ });
7158
+ return prefixCheck;
7159
+ }
7160
+ function adminUpdateSteps() {
7161
+ return [` ${adminInstallCommand()}`, ` solongate repair${process.platform === "win32" ? "" : " (this one WITHOUT sudo)"}`];
7162
+ }
7098
7163
  async function updateNow() {
7099
7164
  const cur = currentVersion();
7100
7165
  const latest = await fetchLatest();
7101
7166
  if (!latest) return { status: "unreachable", version: cur };
7102
7167
  if (!newerThan(latest, cur)) return { status: "current", version: cur };
7168
+ if ((await globalInstallCheck()).writable === false) {
7169
+ writeState({ ...readState(), needsAdmin: latest });
7170
+ return { status: "needs-admin", version: latest };
7171
+ }
7103
7172
  const r = await runGlobalInstall2(latest);
7104
7173
  if (r.ok) {
7105
7174
  writeState({ ...readState(), installed: latest, needsAdmin: void 0 });
@@ -7111,7 +7180,7 @@ async function updateNow() {
7111
7180
  }
7112
7181
  return { status: "failed", version: latest };
7113
7182
  }
7114
- function runAutoUpdateCommand(arg) {
7183
+ async function runAutoUpdateCommand(arg) {
7115
7184
  const out2 = (s) => void process.stderr.write(s + "\n");
7116
7185
  const envForced = autoUpdateForcedByEnv();
7117
7186
  if (arg === void 0) {
@@ -7128,6 +7197,13 @@ function runAutoUpdateCommand(arg) {
7128
7197
  }
7129
7198
  setAutoUpdate(on);
7130
7199
  out2(on ? " \u2713 auto-update on \u2014 new versions install in the background" : " \u2713 auto-update off \u2014 update with: solongate update");
7200
+ if (on && (await globalInstallCheck()).writable === false) {
7201
+ out2("");
7202
+ out2(" Heads up: npm\u2019s global folder needs admin rights on this machine, so a");
7203
+ out2(" background install cannot succeed here. Updates will still be announced,");
7204
+ out2(" and installing them takes:");
7205
+ for (const s of adminUpdateSteps()) out2(" " + s);
7206
+ }
7131
7207
  if (envForced) out2(` note: SOLONGATE_AUTO_UPDATE is set and overrides this while it stays set`);
7132
7208
  return 0;
7133
7209
  }
@@ -7178,18 +7254,25 @@ function maybeSelfUpdate(notify = (l) => process.stderr.write(l + "\n")) {
7178
7254
  const attempts = readState().attempts ?? {};
7179
7255
  if (now - (attempts[latest] ?? 0) < ATTEMPT_EVERY_MS) return;
7180
7256
  writeState({ ...readState(), attempts: { [latest]: now } });
7257
+ const needsAdmin = (await globalInstallCheck()).writable === false;
7258
+ const head = `${c.dim}\u2191 solongate v${latest} available (running v${current})`;
7259
+ if (needsAdmin) {
7260
+ notify(`${head} \u2014 needs admin rights on this machine:${c.reset}`);
7261
+ for (const s of adminUpdateSteps()) notify(`${c.cyan} ${s.trim()}${c.reset}`);
7262
+ return;
7263
+ }
7181
7264
  if (!autoUpdateEnabled()) {
7182
- notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 run ${c.reset}${c.cyan}solongate update${c.reset}`);
7265
+ notify(`${head} \u2014 run ${c.reset}${c.cyan}solongate update${c.reset}`);
7183
7266
  return;
7184
7267
  }
7185
7268
  if (spawnGlobalInstall(latest)) {
7186
- notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
7269
+ notify(`${head} \u2014 updating in the background, next run uses it${c.reset}`);
7187
7270
  }
7188
7271
  } catch {
7189
7272
  }
7190
7273
  })();
7191
7274
  }
7192
- var PKG, CHECK_EVERY_MS, ATTEMPT_EVERY_MS, STATE_FILE, LOG_FILE, NEEDS_ADMIN_RE;
7275
+ var PKG, CHECK_EVERY_MS, ATTEMPT_EVERY_MS, STATE_FILE, LOG_FILE, NEEDS_ADMIN_RE, prefixCheck;
7193
7276
  var init_self_update = __esm({
7194
7277
  "src/self-update.ts"() {
7195
7278
  "use strict";
@@ -7200,6 +7283,7 @@ var init_self_update = __esm({
7200
7283
  STATE_FILE = join5(homedir3(), ".solongate", ".self-update.json");
7201
7284
  LOG_FILE = join5(homedir3(), ".solongate", "self-update.log");
7202
7285
  NEEDS_ADMIN_RE = /\bEACCES\b|\bEPERM\b|permission denied|operation not permitted/i;
7286
+ prefixCheck = null;
7203
7287
  }
7204
7288
  });
7205
7289
 
@@ -11543,6 +11627,17 @@ function SettingsPanel({
11543
11627
  const [diagBusy, setDiagBusy] = useState8(null);
11544
11628
  const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
11545
11629
  const [updBusy, setUpdBusy] = useState8(false);
11630
+ const [canInstall, setCanInstall] = useState8(null);
11631
+ useEffect7(() => {
11632
+ let live2 = true;
11633
+ globalInstallCheck().then((r) => {
11634
+ if (live2) setCanInstall(r.writable);
11635
+ }).catch(() => {
11636
+ });
11637
+ return () => {
11638
+ live2 = false;
11639
+ };
11640
+ }, []);
11546
11641
  const [diag, setDiag] = useState8(null);
11547
11642
  const [diagStep, setDiagStep] = useState8(0);
11548
11643
  const DIAG_STEPS = {
@@ -11822,15 +11917,27 @@ function SettingsPanel({
11822
11917
  if (updBusy) return;
11823
11918
  setUpdBusy(true);
11824
11919
  setMsg({ text: "updating\u2026", level: "ok" });
11920
+ setDiag(null);
11825
11921
  void (async () => {
11826
11922
  try {
11827
11923
  const res = await updateNow();
11828
11924
  if (res.status === "updated") setMsg({ text: `\u2713 v${res.version} installed \xB7 restart (q, then solongate) to apply`, level: "ok" });
11829
11925
  else if (res.status === "current") setMsg({ text: `\u2713 already on the latest version (v${res.version})`, level: "ok" });
11830
- else if (res.status === "needs-admin") setMsg({ text: `\u2717 npm needs admin rights here \u2014 run: ${adminInstallCommand()}`, level: "bad" });
11831
- else if (res.status === "unreachable") setMsg({ text: "\u2717 could not reach the npm registry", level: "bad" });
11926
+ else if (res.status === "needs-admin") {
11927
+ setMsg({ text: `\u2717 v${res.version} needs admin rights \u2014 run the two commands below`, level: "bad" });
11928
+ setDiag({
11929
+ of: "cli-update",
11930
+ lines: [
11931
+ { text: "npm\u2019s global folder belongs to root on this machine (the usual macOS", level: "warn" },
11932
+ { text: "setup), so SolonGate cannot install there by itself. In a terminal:", level: "warn" },
11933
+ ...adminUpdateSteps().map((s) => ({ text: s.trim(), level: "ok" })),
11934
+ { text: "run repair WITHOUT sudo, or the guard hooks land in root\u2019s home", level: "dim" }
11935
+ ]
11936
+ });
11937
+ } else if (res.status === "unreachable") setMsg({ text: "\u2717 could not reach the npm registry", level: "bad" });
11832
11938
  else setMsg({ text: `\u2717 update to v${res.version} failed \u2014 see ~/.solongate/self-update.log`, level: "bad" });
11833
11939
  setLatest(await latestVersion());
11940
+ setCanInstall((await globalInstallCheck()).writable);
11834
11941
  } finally {
11835
11942
  setUpdBusy(false);
11836
11943
  }
@@ -11844,7 +11951,7 @@ function SettingsPanel({
11844
11951
  setAutoUpdate(next);
11845
11952
  setAutoUp(next);
11846
11953
  setMsg(
11847
- next ? { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
11954
+ next ? canInstall === false ? { text: "\u2713 auto-update on \u2014 but npm needs sudo here, so it still cannot install on its own", level: "bad" } : { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
11848
11955
  );
11849
11956
  } else if (r.kind === "ll-enabled") {
11850
11957
  if (!local) return;
@@ -12162,7 +12269,10 @@ function SettingsPanel({
12162
12269
  cursor(r),
12163
12270
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "version".padEnd(11) }),
12164
12271
  /* @__PURE__ */ jsx8(Text8, { color: behind ? theme.warn : theme.ok, children: `v${ver}` }),
12165
- updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
12272
+ updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind && canInstall === false ? (
12273
+ // Said before they press enter, not after a minute of npm noise.
12274
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` v${latest} on npm \xB7 needs sudo here \xB7 enter shows how` })
12275
+ ) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
12166
12276
  ] });
12167
12277
  }
12168
12278
  case "auto-update":
@@ -12170,7 +12280,7 @@ function SettingsPanel({
12170
12280
  cursor(r),
12171
12281
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "auto".padEnd(11) }),
12172
12282
  onOff(autoUp),
12173
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
12283
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : canInstall === false ? " cannot work here: npm needs sudo, so updates stay manual \xB7 enter toggles" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
12174
12284
  ] });
12175
12285
  case "ll-enabled":
12176
12286
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
@@ -12269,7 +12379,7 @@ function SettingsPanel({
12269
12379
  lineKey.push("");
12270
12380
  });
12271
12381
  }
12272
- if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair")) {
12382
+ if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair" || r.kind === "cli-update" && diag.of === "cli-update")) {
12273
12383
  diag.lines.forEach((l, i) => {
12274
12384
  lineEls.push(
12275
12385
  /* @__PURE__ */ jsx8(
@@ -17157,7 +17267,7 @@ function printHelp() {
17157
17267
  console.log(` ${c.dim}Usage:${c.reset} ${c.cyan}solongate${c.reset} ${c.dim}[command]${c.reset} ${c.dim}(no command opens the dataroom: all of this in a terminal UI)${c.reset}`);
17158
17268
  head("Setup & status");
17159
17269
  cmd("solongate", "open the dataroom UI (login, policies, audit, settings)");
17160
- cmd("update", "update SolonGate to the newest version and refresh the guard");
17270
+ cmd("update", "update SolonGate and refresh the guard (tells you if it needs sudo)");
17161
17271
  cmd("update auto on|off", "background auto-update (default off \u2014 on macOS npm -g often needs sudo)");
17162
17272
  cmd("repair", "restore the guard + hook + settings files if they were deleted or disarmed");
17163
17273
  cmd("doctor", "health check: login, policy, guard, local logs");
@@ -17308,7 +17418,7 @@ async function main() {
17308
17418
  if (subcommand === "update") {
17309
17419
  if (process.argv[3] === "auto") {
17310
17420
  const { runAutoUpdateCommand: runAutoUpdateCommand2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
17311
- process.exit(runAutoUpdateCommand2(process.argv[4]));
17421
+ process.exit(await runAutoUpdateCommand2(process.argv[4]));
17312
17422
  }
17313
17423
  const { runUpdateCommand: runUpdateCommand2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
17314
17424
  process.exit(await runUpdateCommand2());
@@ -23,6 +23,19 @@ export declare function latestVersion(): Promise<string | null>;
23
23
  export declare function runUpdateCommand(): Promise<number>;
24
24
  /** The exact command that fixes a root-owned global prefix. */
25
25
  export declare function adminInstallCommand(): string;
26
+ /** Are we root — i.e. did someone run this under sudo? */
27
+ export declare function runningAsRoot(): boolean;
28
+ export declare function globalInstallCheck(): Promise<{
29
+ writable: boolean | null;
30
+ dir: string | null;
31
+ }>;
32
+ /**
33
+ * The two commands that update SolonGate on a machine whose global npm folder
34
+ * belongs to root. `repair` is deliberately NOT under sudo: it writes the guard
35
+ * hooks into the invoking user's home, so as root it would arm root's home and
36
+ * leave the actual user unguarded.
37
+ */
38
+ export declare function adminUpdateSteps(): string[];
26
39
  /**
27
40
  * Install the newest version NOW, whatever the auto-update setting says — the
28
41
  * dataroom's UPDATES row and `solongate update` both run through here.
@@ -36,7 +49,7 @@ export declare function updateNow(): Promise<{
36
49
  * Kept separate from `runUpdateCommand` so `solongate update` stays "update me
37
50
  * now" and never changes a setting as a side effect.
38
51
  */
39
- export declare function runAutoUpdateCommand(arg?: string): number;
52
+ export declare function runAutoUpdateCommand(arg?: string): Promise<number>;
40
53
  /** What the dataroom shows about the updater. */
41
54
  export type UpdateStatus = {
42
55
  kind: 'idle';
package/dist/tui/index.js CHANGED
@@ -4495,9 +4495,9 @@ function stopLogsServerDaemon() {
4495
4495
 
4496
4496
  // src/self-update.ts
4497
4497
  import { execFile, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
4498
- import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
4498
+ import { access, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7, constants as FS } from "fs";
4499
4499
  import { homedir as homedir9 } from "os";
4500
- import { dirname as dirname3, join as join9 } from "path";
4500
+ import { dirname as dirname3, join as join9, sep } from "path";
4501
4501
  import { fileURLToPath as fileURLToPath3 } from "url";
4502
4502
  var PKG = "@solongate/proxy";
4503
4503
  var CHECK_EVERY_MS = 30 * 60 * 1e3;
@@ -4598,11 +4598,41 @@ ${output}
4598
4598
  function adminInstallCommand() {
4599
4599
  return process.platform === "win32" ? `npm i -g ${PKG}@latest (in an Administrator terminal)` : `sudo npm i -g ${PKG}@latest`;
4600
4600
  }
4601
+ function ownNodeModules() {
4602
+ let dir = dirname3(fileURLToPath3(import.meta.url));
4603
+ for (let i = 0; i < 12; i++) {
4604
+ const parent = dirname3(dir);
4605
+ if (parent === dir) break;
4606
+ if (dir.endsWith(`${sep}node_modules`)) return dir;
4607
+ dir = parent;
4608
+ }
4609
+ return null;
4610
+ }
4611
+ var prefixCheck = null;
4612
+ function globalInstallCheck() {
4613
+ prefixCheck ??= new Promise((resolve3) => {
4614
+ try {
4615
+ const dir = ownNodeModules();
4616
+ if (!dir) return resolve3({ writable: null, dir: null });
4617
+ access(dir, FS.W_OK, (e) => resolve3({ writable: !e, dir }));
4618
+ } catch {
4619
+ resolve3({ writable: null, dir: null });
4620
+ }
4621
+ });
4622
+ return prefixCheck;
4623
+ }
4624
+ function adminUpdateSteps() {
4625
+ return [` ${adminInstallCommand()}`, ` solongate repair${process.platform === "win32" ? "" : " (this one WITHOUT sudo)"}`];
4626
+ }
4601
4627
  async function updateNow() {
4602
4628
  const cur = currentVersion();
4603
4629
  const latest = await fetchLatest();
4604
4630
  if (!latest) return { status: "unreachable", version: cur };
4605
4631
  if (!newerThan(latest, cur)) return { status: "current", version: cur };
4632
+ if ((await globalInstallCheck()).writable === false) {
4633
+ writeState2({ ...readState2(), needsAdmin: latest });
4634
+ return { status: "needs-admin", version: latest };
4635
+ }
4606
4636
  const r = await runGlobalInstall(latest);
4607
4637
  if (r.ok) {
4608
4638
  writeState2({ ...readState2(), installed: latest, needsAdmin: void 0 });
@@ -4703,6 +4733,17 @@ function SettingsPanel({
4703
4733
  const [diagBusy, setDiagBusy] = useState8(null);
4704
4734
  const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
4705
4735
  const [updBusy, setUpdBusy] = useState8(false);
4736
+ const [canInstall, setCanInstall] = useState8(null);
4737
+ useEffect7(() => {
4738
+ let live2 = true;
4739
+ globalInstallCheck().then((r) => {
4740
+ if (live2) setCanInstall(r.writable);
4741
+ }).catch(() => {
4742
+ });
4743
+ return () => {
4744
+ live2 = false;
4745
+ };
4746
+ }, []);
4706
4747
  const [diag, setDiag] = useState8(null);
4707
4748
  const [diagStep, setDiagStep] = useState8(0);
4708
4749
  const DIAG_STEPS = {
@@ -4982,15 +5023,27 @@ function SettingsPanel({
4982
5023
  if (updBusy) return;
4983
5024
  setUpdBusy(true);
4984
5025
  setMsg({ text: "updating\u2026", level: "ok" });
5026
+ setDiag(null);
4985
5027
  void (async () => {
4986
5028
  try {
4987
5029
  const res = await updateNow();
4988
5030
  if (res.status === "updated") setMsg({ text: `\u2713 v${res.version} installed \xB7 restart (q, then solongate) to apply`, level: "ok" });
4989
5031
  else if (res.status === "current") setMsg({ text: `\u2713 already on the latest version (v${res.version})`, level: "ok" });
4990
- else if (res.status === "needs-admin") setMsg({ text: `\u2717 npm needs admin rights here \u2014 run: ${adminInstallCommand()}`, level: "bad" });
4991
- else if (res.status === "unreachable") setMsg({ text: "\u2717 could not reach the npm registry", level: "bad" });
5032
+ else if (res.status === "needs-admin") {
5033
+ setMsg({ text: `\u2717 v${res.version} needs admin rights \u2014 run the two commands below`, level: "bad" });
5034
+ setDiag({
5035
+ of: "cli-update",
5036
+ lines: [
5037
+ { text: "npm\u2019s global folder belongs to root on this machine (the usual macOS", level: "warn" },
5038
+ { text: "setup), so SolonGate cannot install there by itself. In a terminal:", level: "warn" },
5039
+ ...adminUpdateSteps().map((s) => ({ text: s.trim(), level: "ok" })),
5040
+ { text: "run repair WITHOUT sudo, or the guard hooks land in root\u2019s home", level: "dim" }
5041
+ ]
5042
+ });
5043
+ } else if (res.status === "unreachable") setMsg({ text: "\u2717 could not reach the npm registry", level: "bad" });
4992
5044
  else setMsg({ text: `\u2717 update to v${res.version} failed \u2014 see ~/.solongate/self-update.log`, level: "bad" });
4993
5045
  setLatest(await latestVersion());
5046
+ setCanInstall((await globalInstallCheck()).writable);
4994
5047
  } finally {
4995
5048
  setUpdBusy(false);
4996
5049
  }
@@ -5004,7 +5057,7 @@ function SettingsPanel({
5004
5057
  setAutoUpdate(next);
5005
5058
  setAutoUp(next);
5006
5059
  setMsg(
5007
- next ? { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
5060
+ next ? canInstall === false ? { text: "\u2713 auto-update on \u2014 but npm needs sudo here, so it still cannot install on its own", level: "bad" } : { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
5008
5061
  );
5009
5062
  } else if (r.kind === "ll-enabled") {
5010
5063
  if (!local) return;
@@ -5322,7 +5375,10 @@ function SettingsPanel({
5322
5375
  cursor(r),
5323
5376
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "version".padEnd(11) }),
5324
5377
  /* @__PURE__ */ jsx8(Text8, { color: behind ? theme.warn : theme.ok, children: `v${ver}` }),
5325
- updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
5378
+ updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind && canInstall === false ? (
5379
+ // Said before they press enter, not after a minute of npm noise.
5380
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` v${latest} on npm \xB7 needs sudo here \xB7 enter shows how` })
5381
+ ) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
5326
5382
  ] });
5327
5383
  }
5328
5384
  case "auto-update":
@@ -5330,7 +5386,7 @@ function SettingsPanel({
5330
5386
  cursor(r),
5331
5387
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "auto".padEnd(11) }),
5332
5388
  onOff(autoUp),
5333
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
5389
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : canInstall === false ? " cannot work here: npm needs sudo, so updates stay manual \xB7 enter toggles" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
5334
5390
  ] });
5335
5391
  case "ll-enabled":
5336
5392
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
@@ -5429,7 +5485,7 @@ function SettingsPanel({
5429
5485
  lineKey.push("");
5430
5486
  });
5431
5487
  }
5432
- if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair")) {
5488
+ if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair" || r.kind === "cli-update" && diag.of === "cli-update")) {
5433
5489
  diag.lines.forEach((l, i) => {
5434
5490
  lineEls.push(
5435
5491
  /* @__PURE__ */ jsx8(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.3",
3
+ "version": "0.83.4",
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": {