@solongate/proxy 0.83.6 → 0.83.7

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.
package/dist/index.js CHANGED
@@ -7573,6 +7573,41 @@ import { closeSync, existsSync as existsSync4, openSync as openSync3, readdirSyn
7573
7573
  import { homedir as homedir6 } from "os";
7574
7574
  import { createHash as createHash2 } from "crypto";
7575
7575
  import { isAbsolute, join as join8 } from "path";
7576
+ function localLogsSetting() {
7577
+ const off = { enabled: false, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
7578
+ try {
7579
+ for (const cache of policyCachesNewestFirst()) {
7580
+ try {
7581
+ const c2 = JSON.parse(readFileSync8(cache, "utf-8"));
7582
+ const l = c2?.security?.localLogs;
7583
+ if (!l || typeof l.enabled !== "boolean") continue;
7584
+ if (!l.enabled) return { ...off, configuredPath: typeof l.path === "string" ? l.path.trim() || null : null };
7585
+ const raw = typeof l.path === "string" ? l.path.trim() : "";
7586
+ const dir = raw.replace(/[\\/]+$/, "");
7587
+ if (!dir) return { enabled: true, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
7588
+ if (!isAbsolute(dir)) return { enabled: true, configuredPath: raw, usableHere: false, file: DEFAULT_LOCAL_LOG };
7589
+ const file = join8(dir, "solongate-audit.jsonl");
7590
+ const usable = existsSync4(file) || existsSync4(dir);
7591
+ return { enabled: true, configuredPath: raw, usableHere: usable, file: usable ? file : DEFAULT_LOCAL_LOG };
7592
+ } catch {
7593
+ }
7594
+ }
7595
+ } catch {
7596
+ }
7597
+ return off;
7598
+ }
7599
+ function policyCachesNewestFirst() {
7600
+ const sgDir = join8(homedir6(), ".solongate");
7601
+ return readdirSync(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
7602
+ const p = join8(sgDir, f);
7603
+ let mtime = 0;
7604
+ try {
7605
+ mtime = statSync(p).mtimeMs;
7606
+ } catch {
7607
+ }
7608
+ return { p, mtime };
7609
+ }).sort((a, b) => b.mtime - a.mtime).map((x) => x.p);
7610
+ }
7576
7611
  function localLogFile() {
7577
7612
  try {
7578
7613
  const sgDir = join8(homedir6(), ".solongate");
@@ -8498,6 +8533,7 @@ function LivePanel({ active: active2 }) {
8498
8533
  const [cloudBuf, setCloudBuf] = useState2([]);
8499
8534
  const [localBuf, setLocalBuf] = useState2([]);
8500
8535
  const [localOn, setLocalOn] = useState2(null);
8536
+ const [localPath, setLocalPath] = useState2(null);
8501
8537
  const [ring, setRing] = useState2(null);
8502
8538
  const [log3, setLog] = useState2([]);
8503
8539
  const [filter, setFilter] = useState2("all");
@@ -8552,12 +8588,11 @@ function LivePanel({ active: active2 }) {
8552
8588
  const paused = () => frozenRef.current || Date.now() < pausedUntil.current;
8553
8589
  const pollLocal = useCallback2(() => {
8554
8590
  if (frozenRef.current) return;
8555
- const lines = tailLines(localLogFile());
8556
- if (!lines.length) {
8557
- setLocalOn((prev) => prev === null ? false : prev);
8558
- return;
8559
- }
8560
- setLocalOn(true);
8591
+ const setting = localLogsSetting();
8592
+ setLocalOn(setting.enabled);
8593
+ setLocalPath(setting);
8594
+ const lines = tailLines(setting.file);
8595
+ if (!lines.length) return;
8561
8596
  const fresh = [];
8562
8597
  for (const line of lines) {
8563
8598
  try {
@@ -9369,7 +9404,17 @@ function LivePanel({ active: active2 }) {
9369
9404
  searchRow,
9370
9405
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
9371
9406
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
9372
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings \xB7 hooks write ${localLogFile()}` })
9407
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings" })
9408
+ ] }) : localOn && localPath && !localPath.usableHere ? (
9409
+ // The folder is a PROJECT setting, so it can name a path that exists
9410
+ // on another machine. Saying so beats showing a path nothing writes to.
9411
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
9412
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs on" }),
9413
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 ${localPath.configuredPath} is not a folder on this device \xB7 writing to ${localPath.file}` })
9414
+ ] })
9415
+ ) : localOn && localBuf.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
9416
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "local logs on" }),
9417
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 no entries yet \xB7 hooks write ${localPath?.file ?? ""}` })
9373
9418
  ] }) : null,
9374
9419
  windowed.length === 0 ? (
9375
9420
  // With a source filter on, "awaiting traffic" reads as "nothing is
@@ -11573,6 +11618,18 @@ function SettingsPanel({
11573
11618
  const [diagBusy, setDiagBusy] = useState8(null);
11574
11619
  const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
11575
11620
  const [updBusy, setUpdBusy] = useState8(false);
11621
+ const [llSetting, setLlSetting] = useState8(null);
11622
+ useEffect7(() => {
11623
+ const read = () => {
11624
+ try {
11625
+ setLlSetting(localLogsSetting());
11626
+ } catch {
11627
+ }
11628
+ };
11629
+ read();
11630
+ const t = setInterval(read, 5e3);
11631
+ return () => clearInterval(t);
11632
+ }, []);
11576
11633
  const [canInstall, setCanInstall] = useState8(null);
11577
11634
  useEffect7(() => {
11578
11635
  let live2 = true;
@@ -12229,7 +12286,16 @@ function SettingsPanel({
12229
12286
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
12230
12287
  cursor(r),
12231
12288
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(11) }),
12232
- local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
12289
+ local?.path ? (
12290
+ // The folder is a PROJECT setting shared by every device on it, so
12291
+ // it can name a path that only exists on another OS. The hooks then
12292
+ // quietly fall back and this row was the last place still claiming
12293
+ // the configured path was in use.
12294
+ llSetting && local.enabled && !llSetting.usableHere ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
12295
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: truncate2(local.path, Math.max(12, Math.floor((cols - 20) / 2))) }),
12296
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` not valid here \u2192 ${truncate2(llSetting.file, Math.max(12, Math.floor((cols - 20) / 2)))}` })
12297
+ ] }) : /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(local.path, cols - 14) })
12298
+ ) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
12233
12299
  ] });
12234
12300
  case "ll-server":
12235
12301
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
package/dist/tui/index.js CHANGED
@@ -741,6 +741,41 @@ import { homedir as homedir2 } from "os";
741
741
  import { createHash } from "crypto";
742
742
  import { isAbsolute, join as join2 } from "path";
743
743
  var DEFAULT_LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
744
+ function localLogsSetting() {
745
+ const off = { enabled: false, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
746
+ try {
747
+ for (const cache of policyCachesNewestFirst()) {
748
+ try {
749
+ const c2 = JSON.parse(readFileSync2(cache, "utf-8"));
750
+ const l = c2?.security?.localLogs;
751
+ if (!l || typeof l.enabled !== "boolean") continue;
752
+ if (!l.enabled) return { ...off, configuredPath: typeof l.path === "string" ? l.path.trim() || null : null };
753
+ const raw = typeof l.path === "string" ? l.path.trim() : "";
754
+ const dir = raw.replace(/[\\/]+$/, "");
755
+ if (!dir) return { enabled: true, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
756
+ if (!isAbsolute(dir)) return { enabled: true, configuredPath: raw, usableHere: false, file: DEFAULT_LOCAL_LOG };
757
+ const file = join2(dir, "solongate-audit.jsonl");
758
+ const usable = existsSync(file) || existsSync(dir);
759
+ return { enabled: true, configuredPath: raw, usableHere: usable, file: usable ? file : DEFAULT_LOCAL_LOG };
760
+ } catch {
761
+ }
762
+ }
763
+ } catch {
764
+ }
765
+ return off;
766
+ }
767
+ function policyCachesNewestFirst() {
768
+ const sgDir = join2(homedir2(), ".solongate");
769
+ return readdirSync(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
770
+ const p = join2(sgDir, f);
771
+ let mtime = 0;
772
+ try {
773
+ mtime = statSync(p).mtimeMs;
774
+ } catch {
775
+ }
776
+ return { p, mtime };
777
+ }).sort((a, b) => b.mtime - a.mtime).map((x) => x.p);
778
+ }
744
779
  function localLogFile() {
745
780
  try {
746
781
  const sgDir = join2(homedir2(), ".solongate");
@@ -1619,6 +1654,7 @@ function LivePanel({ active: active2 }) {
1619
1654
  const [cloudBuf, setCloudBuf] = useState2([]);
1620
1655
  const [localBuf, setLocalBuf] = useState2([]);
1621
1656
  const [localOn, setLocalOn] = useState2(null);
1657
+ const [localPath, setLocalPath] = useState2(null);
1622
1658
  const [ring, setRing] = useState2(null);
1623
1659
  const [log, setLog] = useState2([]);
1624
1660
  const [filter, setFilter] = useState2("all");
@@ -1673,12 +1709,11 @@ function LivePanel({ active: active2 }) {
1673
1709
  const paused = () => frozenRef.current || Date.now() < pausedUntil.current;
1674
1710
  const pollLocal = useCallback2(() => {
1675
1711
  if (frozenRef.current) return;
1676
- const lines = tailLines(localLogFile());
1677
- if (!lines.length) {
1678
- setLocalOn((prev) => prev === null ? false : prev);
1679
- return;
1680
- }
1681
- setLocalOn(true);
1712
+ const setting = localLogsSetting();
1713
+ setLocalOn(setting.enabled);
1714
+ setLocalPath(setting);
1715
+ const lines = tailLines(setting.file);
1716
+ if (!lines.length) return;
1682
1717
  const fresh = [];
1683
1718
  for (const line of lines) {
1684
1719
  try {
@@ -2490,7 +2525,17 @@ function LivePanel({ active: active2 }) {
2490
2525
  searchRow,
2491
2526
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
2492
2527
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
2493
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings \xB7 hooks write ${localLogFile()}` })
2528
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings" })
2529
+ ] }) : localOn && localPath && !localPath.usableHere ? (
2530
+ // The folder is a PROJECT setting, so it can name a path that exists
2531
+ // on another machine. Saying so beats showing a path nothing writes to.
2532
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
2533
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs on" }),
2534
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 ${localPath.configuredPath} is not a folder on this device \xB7 writing to ${localPath.file}` })
2535
+ ] })
2536
+ ) : localOn && localBuf.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
2537
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "local logs on" }),
2538
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 no entries yet \xB7 hooks write ${localPath?.file ?? ""}` })
2494
2539
  ] }) : null,
2495
2540
  windowed.length === 0 ? (
2496
2541
  // With a source filter on, "awaiting traffic" reads as "nothing is
@@ -4699,6 +4744,18 @@ function SettingsPanel({
4699
4744
  const [diagBusy, setDiagBusy] = useState8(null);
4700
4745
  const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
4701
4746
  const [updBusy, setUpdBusy] = useState8(false);
4747
+ const [llSetting, setLlSetting] = useState8(null);
4748
+ useEffect7(() => {
4749
+ const read = () => {
4750
+ try {
4751
+ setLlSetting(localLogsSetting());
4752
+ } catch {
4753
+ }
4754
+ };
4755
+ read();
4756
+ const t = setInterval(read, 5e3);
4757
+ return () => clearInterval(t);
4758
+ }, []);
4702
4759
  const [canInstall, setCanInstall] = useState8(null);
4703
4760
  useEffect7(() => {
4704
4761
  let live2 = true;
@@ -5355,7 +5412,16 @@ function SettingsPanel({
5355
5412
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
5356
5413
  cursor(r),
5357
5414
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(11) }),
5358
- local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
5415
+ local?.path ? (
5416
+ // The folder is a PROJECT setting shared by every device on it, so
5417
+ // it can name a path that only exists on another OS. The hooks then
5418
+ // quietly fall back and this row was the last place still claiming
5419
+ // the configured path was in use.
5420
+ llSetting && local.enabled && !llSetting.usableHere ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
5421
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: truncate(local.path, Math.max(12, Math.floor((cols - 20) / 2))) }),
5422
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` not valid here \u2192 ${truncate(llSetting.file, Math.max(12, Math.floor((cols - 20) / 2)))}` })
5423
+ ] }) : /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) })
5424
+ ) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
5359
5425
  ] });
5360
5426
  case "ll-server":
5361
5427
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
@@ -16,6 +16,27 @@ export declare const DEFAULT_LOCAL_LOG: string;
16
16
  * it the same way they do, preferring the most recently refreshed cache. Falls
17
17
  * back to the default path when nothing is configured or readable.
18
18
  */
19
+ /** What local logging is set to on this device, and where it can actually go. */
20
+ export interface LocalLogSetting {
21
+ /** The project setting, as the hooks read it. */
22
+ enabled: boolean;
23
+ /** The folder the project asked for, verbatim (null when nothing is set). */
24
+ configuredPath: string | null;
25
+ /** False when that folder cannot be used HERE — e.g. a Windows path on Linux. */
26
+ usableHere: boolean;
27
+ /** The file entries actually land in, after any fallback. */
28
+ file: string;
29
+ }
30
+ /**
31
+ * Read the local-logging SETTING, not its output.
32
+ *
33
+ * Live used to decide on/off by whether the log file had any lines in it, so an
34
+ * enabled log that happened to be empty — a fresh install, a folder just
35
+ * changed, an account just switched — reported "local logs off" and told the
36
+ * user to go and enable something that was already on. The setting and the
37
+ * evidence of the setting are different questions; this answers the first.
38
+ */
39
+ export declare function localLogsSetting(): LocalLogSetting;
19
40
  export declare function localLogFile(): string;
20
41
  /** Resolved once per process for callers that want a plain path (the folder can
21
42
  * only change via a dashboard edit, which needs a new session anyway). Prefer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.6",
3
+ "version": "0.83.7",
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": {