@suveren/gateway 0.7.1 → 0.7.3

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/README.md CHANGED
@@ -119,7 +119,16 @@ suveren-gateway stop # stop a detached run
119
119
 
120
120
  Open `http://localhost:3400`. The MCP server is at `http://localhost:3430`.
121
121
 
122
- To upgrade later: `npm install -g @suveren/gateway@latest && suveren-gateway restart`.
122
+ To upgrade later, run these two commands:
123
+
124
+ ```
125
+ npm install -g @suveren/gateway@latest
126
+ suveren-gateway restart
127
+ ```
128
+
129
+ (Written as two lines on purpose: `&&` is not a valid separator in Windows
130
+ PowerShell 5.1, which is what ships with Windows. Use `;` there if you want
131
+ them on one line.)
123
132
 
124
133
  ### Connecting an MCP client
125
134
 
@@ -323,8 +323,14 @@ function serviceRunning() {
323
323
  return p.status === 0 && /state = running/.test(p.stdout || '');
324
324
  }
325
325
  if (os === 'win32') {
326
- const p = runQuiet('schtasks', ['/Query', '/TN', WIN_TASK_NAME, '/FO', 'LIST']);
327
- return p.status === 0 && /Status:\s+Running/i.test(p.stdout || '');
326
+ // NOT schtasks /Query: its LIST output is localized — a German Windows
327
+ // prints "Wird ausgeführt", so matching the English word "Running" reports
328
+ // every non-English machine as stopped, and restart silently falls back to
329
+ // the manual path. Get-ScheduledTask's State is a .NET enum; its name is
330
+ // English on every locale.
331
+ const p = runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
332
+ `(Get-ScheduledTask -TaskName '${WIN_TASK_NAME}' -ErrorAction Stop).State`]);
333
+ return p.status === 0 && /^Running/m.test((p.stdout || '').trim());
328
334
  }
329
335
  const p = runQuiet('systemctl', ['--user', 'is-active', SYSTEMD_UNIT]);
330
336
  return (p.stdout || '').trim() === 'active';
@@ -344,7 +350,16 @@ function serviceRestart() {
344
350
  if (os === 'darwin') {
345
351
  runQuiet('launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${LAUNCH_AGENT_LABEL}`]);
346
352
  } else if (os === 'win32') {
347
- runQuiet('schtasks', ['/End', '/TN', WIN_TASK_NAME]);
353
+ // NOT schtasks /End: it TerminateProcess()es only the task's ROOT process
354
+ // (node server.js). No signal is delivered, so the shutdown handlers never
355
+ // run and the control-plane / MCP children survive — still holding ports
356
+ // 3400/3430, which makes the relaunched instance die on EADDRINUSE and
357
+ // "restart" quietly become "stop". taskkill /T takes the whole tree.
358
+ const like = SERVER_ENTRY.replace(/'/g, "''").replace(/([\[\]*?])/g, '`$1');
359
+ runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
360
+ `Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ` +
361
+ `Where-Object { $_.CommandLine -like '*${like}*' } | ` +
362
+ `ForEach-Object { taskkill.exe /PID $_.ProcessId /T /F } | Out-Null`]);
348
363
  runQuiet('schtasks', ['/Run', '/TN', WIN_TASK_NAME]);
349
364
  } else {
350
365
  runQuiet('systemctl', ['--user', 'restart', SYSTEMD_UNIT]);
@@ -573,8 +588,12 @@ async function serviceStatusWindows() {
573
588
  console.log(`Login service: ${installed ? 'installed' : 'not installed'}`);
574
589
  if (installed) {
575
590
  console.log(` Task: ${WIN_TASK_NAME}`);
576
- const state = /Status:\s*(\S+)/.exec(r.stdout || '');
577
- if (state) console.log(` State: ${state[1]}`);
591
+ // Locale-independent state (see serviceRunning): the schtasks LIST value
592
+ // is localized; the PowerShell enum name is not.
593
+ const st = runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
594
+ `(Get-ScheduledTask -TaskName '${WIN_TASK_NAME}' -ErrorAction Stop).State`]);
595
+ const state = (st.stdout || '').trim();
596
+ if (st.status === 0 && state) console.log(` State: ${state}`);
578
597
  }
579
598
  }
580
599
 
@@ -5,9 +5,9 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import { randomBytes as randomBytes2 } from "crypto";
8
- import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync } from "fs";
9
- import { dirname as dirname4, join as join5 } from "path";
10
- import { homedir as homedir4 } from "os";
8
+ import { existsSync as existsSync6, readFileSync as readFileSync5, renameSync } from "fs";
9
+ import { dirname as dirname5, join as join6 } from "path";
10
+ import { homedir as homedir5 } from "os";
11
11
  import express from "express";
12
12
  import { createProxyMiddleware } from "http-proxy-middleware";
13
13
 
@@ -685,6 +685,64 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
685
685
 
686
686
  // src/routes/vault.ts
687
687
  import { Router as Router2 } from "express";
688
+
689
+ // src/lib/credential-meta.ts
690
+ var META_KEY = "__updatedAt";
691
+ var MIN_PREVIEW_LENGTH = 12;
692
+ var PREFIX_RE = /^([A-Za-z][A-Za-z0-9]*(?:[-_][A-Za-z0-9]+)*[-_])/;
693
+ var MAX_PREFIX_LENGTH = 12;
694
+ var HIDDEN_FLOOR = 12;
695
+ function previewSecret(value) {
696
+ if (value.length < MIN_PREVIEW_LENGTH) return { preview: null };
697
+ const match = PREFIX_RE.exec(value)?.[1] ?? "";
698
+ const prefix = match && match.length <= MAX_PREFIX_LENGTH && value.length - match.length - 4 >= HIDDEN_FLOOR ? match : "";
699
+ if (!prefix && value.length - 4 < 8) return { preview: null };
700
+ return { preview: `${prefix}\u2026${value.slice(-4)}` };
701
+ }
702
+ var AI_CONFIG_TEXT_FIELDS = /* @__PURE__ */ new Set(["provider", "endpoint", "model"]);
703
+ function textFieldsFor(name, manifests) {
704
+ if (name === "ai-config") return AI_CONFIG_TEXT_FIELDS;
705
+ const list = Array.isArray(manifests) ? manifests : [];
706
+ const manifest = list.find((m) => m?.id === name);
707
+ const out = /* @__PURE__ */ new Set();
708
+ for (const f of manifest?.credentials?.fields ?? []) {
709
+ if (typeof f?.key === "string" && f?.type === "text") out.add(f.key);
710
+ }
711
+ return out;
712
+ }
713
+ function credentialView(cred, textFields) {
714
+ const fields = {};
715
+ const secrets = {};
716
+ let updatedAt;
717
+ for (const [key, value] of Object.entries(cred)) {
718
+ if (key === META_KEY) {
719
+ updatedAt = value;
720
+ continue;
721
+ }
722
+ if (textFields.has(key)) fields[key] = value;
723
+ else secrets[key] = previewSecret(value);
724
+ }
725
+ return {
726
+ configured: true,
727
+ fieldNames: Object.keys(fields).concat(Object.keys(secrets)),
728
+ fields,
729
+ secrets,
730
+ ...updatedAt ? { updatedAt } : {}
731
+ };
732
+ }
733
+
734
+ // src/routes/vault.ts
735
+ var manifestCache = null;
736
+ async function cachedManifests() {
737
+ if (manifestCache && Date.now() - manifestCache.at < 6e4) return manifestCache.value;
738
+ try {
739
+ const m = await getManifests();
740
+ manifestCache = { at: Date.now(), value: m?.manifests ?? m };
741
+ } catch {
742
+ manifestCache = { at: Date.now(), value: [] };
743
+ }
744
+ return manifestCache.value;
745
+ }
688
746
  function createVaultRouter(vault2) {
689
747
  const router = Router2();
690
748
  router.get("/status", (_req, res) => {
@@ -696,14 +754,14 @@ function createVaultRouter(vault2) {
696
754
  serviceCount: services.length
697
755
  });
698
756
  });
699
- router.get("/credentials/:name", (req, res) => {
757
+ router.get("/credentials/:name", async (req, res) => {
700
758
  const { name } = req.params;
701
759
  const cred = vault2.getCredential(name);
702
760
  if (!cred) {
703
761
  res.json({ configured: false });
704
762
  return;
705
763
  }
706
- res.json({ configured: true, fieldNames: Object.keys(cred), fields: cred });
764
+ res.json(credentialView(cred, textFieldsFor(name, await cachedManifests())));
707
765
  });
708
766
  router.put("/credentials/:name", async (req, res) => {
709
767
  const { name } = req.params;
@@ -714,6 +772,7 @@ function createVaultRouter(vault2) {
714
772
  if (existing) fields = { ...existing, ...incoming };
715
773
  } catch {
716
774
  }
775
+ fields = { ...fields, [META_KEY]: (/* @__PURE__ */ new Date()).toISOString() };
717
776
  vault2.setCredential(name, fields);
718
777
  try {
719
778
  await pushServiceCredentials(name, fields);
@@ -1540,15 +1599,15 @@ import { promisify } from "util";
1540
1599
  var run = promisify(execFile);
1541
1600
  var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
1542
1601
  var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
1543
- function findCli(dirname5 = MODULE_DIR) {
1602
+ function findCli(dirname6 = MODULE_DIR) {
1544
1603
  const candidates = [
1545
1604
  // Bundled (the shipped layout): dist/control-plane → dist → root → bin
1546
- resolve(dirname5, "..", "..", "bin", "suveren-gateway.js"),
1605
+ resolve(dirname6, "..", "..", "bin", "suveren-gateway.js"),
1547
1606
  // Bundled, were the routes/ directory ever preserved.
1548
- resolve(dirname5, "..", "..", "..", "bin", "suveren-gateway.js"),
1607
+ resolve(dirname6, "..", "..", "..", "bin", "suveren-gateway.js"),
1549
1608
  // Dev, running from source: the repo's own bundle directory.
1550
- resolve(dirname5, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1551
- resolve(dirname5, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1609
+ resolve(dirname6, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1610
+ resolve(dirname6, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1552
1611
  // Deliberately NO process.cwd() fallback: it makes the result depend on
1553
1612
  // where the process happened to be launched from, so the same install
1554
1613
  // resolves differently between a shell and a login service — and it would
@@ -1633,38 +1692,86 @@ function createAutostartRouter() {
1633
1692
 
1634
1693
  // src/lib/desktop-notify.ts
1635
1694
  import { spawn, spawnSync } from "child_process";
1695
+
1696
+ // src/lib/notifier-icon.ts
1697
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
1698
+ import { homedir as homedir2 } from "os";
1699
+ import { join as join2, dirname as dirname2 } from "path";
1700
+ var ICON_BASE64 = "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";
1701
+ function ensureNotifierIcon(dataDir) {
1702
+ try {
1703
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
1704
+ const path = join2(dir, "notifier-icon.png");
1705
+ if (!existsSync3(path)) {
1706
+ mkdirSync2(dirname2(path), { recursive: true });
1707
+ writeFileSync2(path, Buffer.from(ICON_BASE64, "base64"));
1708
+ }
1709
+ return path;
1710
+ } catch {
1711
+ return null;
1712
+ }
1713
+ }
1714
+
1715
+ // src/lib/desktop-notify.ts
1636
1716
  function osaQuote(value) {
1637
1717
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1638
1718
  }
1639
1719
  function psQuote(value) {
1640
1720
  return `'${value.replace(/'/g, "''")}'`;
1641
1721
  }
1722
+ function xmlEscape(value) {
1723
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1724
+ }
1725
+ var WINDOWS_AUMID = "Suveren.Gateway";
1726
+ function windowsToastScript(title, message, url, iconPath) {
1727
+ const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
1728
+ return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
1729
+ }
1730
+ var MAC_SOUND = "Glass";
1642
1731
  function buildNotifyCommand(platform, title, message, options = {}) {
1643
- const { url, hasTerminalNotifier: hasTerminalNotifier2 = false } = options;
1732
+ const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
1644
1733
  switch (platform) {
1645
1734
  case "darwin":
1646
1735
  if (url && hasTerminalNotifier2) {
1647
1736
  return {
1648
1737
  cmd: "terminal-notifier",
1649
- args: ["-title", title, "-message", message, "-open", url, "-group", "ai.suveren.gateway"]
1738
+ args: [
1739
+ "-title",
1740
+ title,
1741
+ "-message",
1742
+ message,
1743
+ "-open",
1744
+ url,
1745
+ "-sound",
1746
+ MAC_SOUND,
1747
+ ...iconPath ? ["-appIcon", iconPath] : [],
1748
+ "-group",
1749
+ "ai.suveren.gateway"
1750
+ ]
1650
1751
  };
1651
1752
  }
1652
1753
  return {
1653
1754
  cmd: "osascript",
1654
- args: ["-e", `display notification ${osaQuote(message)} with title ${osaQuote(title)}`]
1755
+ args: [
1756
+ "-e",
1757
+ `display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
1758
+ ]
1655
1759
  };
1656
1760
  case "win32":
1657
1761
  return {
1658
1762
  cmd: "powershell",
1763
+ args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
1764
+ };
1765
+ case "linux":
1766
+ return {
1767
+ cmd: "notify-send",
1659
1768
  args: [
1660
- "-NoProfile",
1661
- "-NonInteractive",
1662
- "-Command",
1663
- `Add-Type -AssemblyName System.Windows.Forms; $n = New-Object System.Windows.Forms.NotifyIcon; $n.Icon = [System.Drawing.SystemIcons]::Information; $n.BalloonTipTitle = ${psQuote(title)}; $n.BalloonTipText = ${psQuote(message)}; $n.Visible = $true; $n.ShowBalloonTip(15000); Start-Sleep -Seconds 16; $n.Dispose()`
1769
+ "--app-name=Suveren",
1770
+ ...iconPath ? ["-i", iconPath] : [],
1771
+ title,
1772
+ message
1664
1773
  ]
1665
1774
  };
1666
- case "linux":
1667
- return { cmd: "notify-send", args: ["--app-name=Suveren", title, message] };
1668
1775
  default:
1669
1776
  return null;
1670
1777
  }
@@ -1688,15 +1795,32 @@ function hasTerminalNotifier() {
1688
1795
  }
1689
1796
  return terminalNotifierCache;
1690
1797
  }
1798
+ function notifySpawnOptions(platform) {
1799
+ return {
1800
+ detached: platform !== "win32",
1801
+ stdio: ["ignore", "ignore", "pipe"]
1802
+ };
1803
+ }
1691
1804
  function notify(title, message, platform = process.platform, url) {
1692
1805
  const command = buildNotifyCommand(platform, title, message, {
1693
1806
  url,
1694
- // Only probe when a URL could actually be attached.
1695
- hasTerminalNotifier: url !== void 0 && platform === "darwin" ? hasTerminalNotifier() : false
1807
+ hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
1808
+ // Windows and Linux show it on the toast itself; macOS only via
1809
+ // terminal-notifier. Failure to write it must never block the sound.
1810
+ iconPath: ensureNotifierIcon()
1696
1811
  });
1697
1812
  if (!command) return;
1698
1813
  try {
1699
- const child = spawn(command.cmd, command.args, { detached: true, stdio: "ignore" });
1814
+ const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
1815
+ let stderr = "";
1816
+ child.stderr?.on("data", (d) => {
1817
+ stderr += d.toString().slice(0, 500);
1818
+ });
1819
+ child.on("close", (code) => {
1820
+ if (code !== 0) {
1821
+ console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
1822
+ }
1823
+ });
1700
1824
  child.on("error", () => {
1701
1825
  });
1702
1826
  child.unref();
@@ -1812,13 +1936,13 @@ function createDecryptIntentRouter(vault2) {
1812
1936
 
1813
1937
  // src/routes/approved-intents.ts
1814
1938
  import { Router as Router9 } from "express";
1815
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1816
- import { homedir as homedir2 } from "os";
1817
- import { join as join2 } from "path";
1818
- var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
1819
- var FILE_PATH = join2(SUVEREN_DATA_DIR, "approved-intents.enc.json");
1939
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
1940
+ import { homedir as homedir3 } from "os";
1941
+ import { join as join3 } from "path";
1942
+ var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
1943
+ var FILE_PATH = join3(SUVEREN_DATA_DIR, "approved-intents.enc.json");
1820
1944
  function readFile() {
1821
- if (!existsSync3(FILE_PATH)) {
1945
+ if (!existsSync4(FILE_PATH)) {
1822
1946
  return { version: 1, entries: {} };
1823
1947
  }
1824
1948
  try {
@@ -1828,8 +1952,8 @@ function readFile() {
1828
1952
  }
1829
1953
  }
1830
1954
  function writeFile(data) {
1831
- mkdirSync2(SUVEREN_DATA_DIR, { recursive: true });
1832
- writeFileSync2(FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
1955
+ mkdirSync3(SUVEREN_DATA_DIR, { recursive: true });
1956
+ writeFileSync3(FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
1833
1957
  }
1834
1958
  function createApprovedIntentsRouter(vault2) {
1835
1959
  const router = Router9();
@@ -1875,7 +1999,7 @@ function createApprovedIntentsRouter(vault2) {
1875
1999
 
1876
2000
  // src/lib/update-checker.ts
1877
2001
  import { execSync } from "child_process";
1878
- import { dirname as dirname2 } from "path";
2002
+ import { dirname as dirname3 } from "path";
1879
2003
  import { fileURLToPath as fileURLToPath2 } from "url";
1880
2004
  var GHCR_IMAGE = "suverenai/suveren-gateway";
1881
2005
  var NPM_PACKAGE = "@suveren/gateway";
@@ -1941,7 +2065,7 @@ function compareSemver(a, b) {
1941
2065
  return 0;
1942
2066
  }
1943
2067
  function checkDev() {
1944
- const cwd = dirname2(fileURLToPath2(import.meta.url));
2068
+ const cwd = dirname3(fileURLToPath2(import.meta.url));
1945
2069
  try {
1946
2070
  execSync("git fetch origin --quiet", { cwd, stdio: "ignore", timeout: 15e3 });
1947
2071
  const out = execSync("git rev-list HEAD..origin/main --count", {
@@ -2012,15 +2136,15 @@ data: null
2012
2136
  import { Router as Router10 } from "express";
2013
2137
 
2014
2138
  // src/lib/gateway-settings.ts
2015
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
2016
- import { homedir as homedir3 } from "os";
2017
- import { join as join3, dirname as dirname3 } from "path";
2139
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
2140
+ import { homedir as homedir4 } from "os";
2141
+ import { join as join4, dirname as dirname4 } from "path";
2018
2142
  var DEFAULT_SETTINGS = {
2019
2143
  desktopNotifications: true
2020
2144
  };
2021
2145
  function settingsPath(dataDir) {
2022
- const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
2023
- return join3(dir, "settings.json");
2146
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join4(homedir4(), ".suveren");
2147
+ return join4(dir, "settings.json");
2024
2148
  }
2025
2149
  function readSettings(dataDir) {
2026
2150
  try {
@@ -2038,8 +2162,8 @@ function writeSettings(patch, dataDir) {
2038
2162
  ...typeof patch.desktopNotifications === "boolean" ? { desktopNotifications: patch.desktopNotifications } : {}
2039
2163
  };
2040
2164
  const path = settingsPath(dataDir);
2041
- mkdirSync3(dirname3(path), { recursive: true });
2042
- writeFileSync3(path, JSON.stringify(next, null, 2) + "\n", "utf8");
2165
+ mkdirSync4(dirname4(path), { recursive: true });
2166
+ writeFileSync4(path, JSON.stringify(next, null, 2) + "\n", "utf8");
2043
2167
  return next;
2044
2168
  }
2045
2169
 
@@ -2184,16 +2308,16 @@ function startNotificationDispatcher(opts = {}) {
2184
2308
  }
2185
2309
 
2186
2310
  // src/lib/denials-reader.ts
2187
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2188
- import { join as join4 } from "path";
2311
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2312
+ import { join as join5 } from "path";
2189
2313
  function loadDenials(dataDir, decrypt) {
2190
- const encPath = join4(dataDir, "denials.enc.json");
2191
- const plainPath = join4(dataDir, "denials.json");
2192
- if (existsSync4(encPath)) {
2314
+ const encPath = join5(dataDir, "denials.enc.json");
2315
+ const plainPath = join5(dataDir, "denials.json");
2316
+ if (existsSync5(encPath)) {
2193
2317
  const { blob } = JSON.parse(readFileSync4(encPath, "utf-8"));
2194
2318
  return JSON.parse(decrypt(blob)).records ?? [];
2195
2319
  }
2196
- if (existsSync4(plainPath)) {
2320
+ if (existsSync5(plainPath)) {
2197
2321
  return JSON.parse(readFileSync4(plainPath, "utf-8")).records ?? [];
2198
2322
  }
2199
2323
  return [];
@@ -2209,9 +2333,9 @@ function selectDenials(records, opts = {}) {
2209
2333
  // src/index.ts
2210
2334
  (function migrateDataDir() {
2211
2335
  if (process.env.SUVEREN_DATA_DIR) return;
2212
- const oldDir = join5(homedir4(), ".hap");
2213
- const newDir = join5(homedir4(), ".suveren");
2214
- if (!existsSync5(newDir) && existsSync5(oldDir)) {
2336
+ const oldDir = join6(homedir5(), ".hap");
2337
+ const newDir = join6(homedir5(), ".suveren");
2338
+ if (!existsSync6(newDir) && existsSync6(oldDir)) {
2215
2339
  try {
2216
2340
  renameSync(oldDir, newDir);
2217
2341
  console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
@@ -2224,7 +2348,7 @@ function selectDenials(records, opts = {}) {
2224
2348
  var SP_URL2 = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
2225
2349
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
2226
2350
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
2227
- var UI_DIST = process.env.HAP_UI_DIST ?? join5(import.meta.dirname ?? __dirname, "../../ui/dist");
2351
+ var UI_DIST = process.env.HAP_UI_DIST ?? join6(import.meta.dirname ?? __dirname, "../../ui/dist");
2228
2352
  var vault = new Vault();
2229
2353
  var internalSecret2 = process.env.SUVEREN_INTERNAL_SECRET ?? randomBytes2(32).toString("hex");
2230
2354
  setInternalSecret(internalSecret2);
@@ -2262,7 +2386,7 @@ function resolveOrigin(req) {
2262
2386
  return `${req.protocol}://${req.get("host")}`;
2263
2387
  }
2264
2388
  var oauthRedirectUris = /* @__PURE__ */ new Map();
2265
- var manifestCache = null;
2389
+ var manifestCache2 = null;
2266
2390
  function resolvePath(obj, path) {
2267
2391
  const v = path.split(".").reduce((acc, key) => {
2268
2392
  return acc && typeof acc === "object" ? acc[key] : void 0;
@@ -2282,15 +2406,15 @@ function decodeJwtEmail(idToken) {
2282
2406
  }
2283
2407
  }
2284
2408
  async function getOAuthManifest(integrationId) {
2285
- if (!manifestCache) {
2409
+ if (!manifestCache2) {
2286
2410
  try {
2287
2411
  const data = await getManifests();
2288
- manifestCache = data.manifests;
2412
+ manifestCache2 = data.manifests;
2289
2413
  } catch {
2290
2414
  return null;
2291
2415
  }
2292
2416
  }
2293
- return manifestCache?.find((m) => m.id === integrationId && m.oauth) ?? null;
2417
+ return manifestCache2?.find((m) => m.id === integrationId && m.oauth) ?? null;
2294
2418
  }
2295
2419
  app.get("/auth/oauth/:integrationId/start", async (req, res) => {
2296
2420
  const { integrationId } = req.params;
@@ -2592,7 +2716,7 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
2592
2716
  });
2593
2717
  app.get("/denials", authGuard, (req, res) => {
2594
2718
  try {
2595
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2719
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
2596
2720
  const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
2597
2721
  const since = Number(req.query.since);
2598
2722
  const limit = Number(req.query.limit);
@@ -2625,11 +2749,11 @@ app.post("/resync-gates", authGuard, async (_req, res) => {
2625
2749
  }
2626
2750
  });
2627
2751
  var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
2628
- var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2752
+ var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
2629
2753
  app.get("/agent-brief/context", authGuard, async (_req, res) => {
2630
2754
  try {
2631
2755
  const { readFileSync: readFileSync6, existsSync: fileExists } = await import("fs");
2632
- const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2756
+ const filePath = join6(SUVEREN_DATA_DIR2, "context.md");
2633
2757
  if (!fileExists(filePath)) {
2634
2758
  res.json({ content: "" });
2635
2759
  return;
@@ -2652,11 +2776,11 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
2652
2776
  res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
2653
2777
  return;
2654
2778
  }
2655
- const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, renameSync: renameSync2 } = await import("fs");
2656
- mkdirSync4(SUVEREN_DATA_DIR2, { recursive: true });
2657
- const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2779
+ const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, renameSync: renameSync2 } = await import("fs");
2780
+ mkdirSync5(SUVEREN_DATA_DIR2, { recursive: true });
2781
+ const filePath = join6(SUVEREN_DATA_DIR2, "context.md");
2658
2782
  const tmpPath = `${filePath}.tmp`;
2659
- writeFileSync4(tmpPath, content, "utf-8");
2783
+ writeFileSync5(tmpPath, content, "utf-8");
2660
2784
  renameSync2(tmpPath, filePath);
2661
2785
  res.json({ ok: true });
2662
2786
  } catch (err) {
@@ -2706,13 +2830,13 @@ app.use(
2706
2830
  })
2707
2831
  );
2708
2832
  function detectInstallMethod() {
2709
- if (existsSync5("/.dockerenv")) return "docker";
2833
+ if (existsSync6("/.dockerenv")) return "docker";
2710
2834
  const dir = import.meta.dirname ?? __dirname;
2711
2835
  if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
2712
2836
  let cursor = dir;
2713
2837
  for (let i = 0; i < 8; i++) {
2714
- if (existsSync5(join5(cursor, ".git"))) return "dev";
2715
- const parent = dirname4(cursor);
2838
+ if (existsSync6(join6(cursor, ".git"))) return "dev";
2839
+ const parent = dirname5(cursor);
2716
2840
  if (parent === cursor) break;
2717
2841
  cursor = parent;
2718
2842
  }
@@ -2724,8 +2848,8 @@ function detectRunningVersion() {
2724
2848
  return process.env.HAP_BUILD_SHA ?? "dev";
2725
2849
  }
2726
2850
  const dir = import.meta.dirname ?? __dirname;
2727
- const bundlePkg = join5(dir, "..", "..", "package.json");
2728
- if (existsSync5(bundlePkg)) {
2851
+ const bundlePkg = join6(dir, "..", "..", "package.json");
2852
+ if (existsSync6(bundlePkg)) {
2729
2853
  try {
2730
2854
  const pkg = JSON.parse(readFileSync5(bundlePkg, "utf8"));
2731
2855
  if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
@@ -2755,7 +2879,7 @@ app.get("/health", async (req, res) => {
2755
2879
  }
2756
2880
  });
2757
2881
  });
2758
- if (existsSync5(UI_DIST)) {
2882
+ if (existsSync6(UI_DIST)) {
2759
2883
  app.use(express.static(UI_DIST, {
2760
2884
  setHeaders: (res, filePath) => {
2761
2885
  if (filePath.endsWith("index.html")) {
@@ -2765,7 +2889,7 @@ if (existsSync5(UI_DIST)) {
2765
2889
  }));
2766
2890
  app.get("*", (_req, res) => {
2767
2891
  res.setHeader("Cache-Control", "no-store, must-revalidate");
2768
- res.sendFile(join5(UI_DIST, "index.html"));
2892
+ res.sendFile(join6(UI_DIST, "index.html"));
2769
2893
  });
2770
2894
  } else {
2771
2895
  app.get("/", (_req, res) => {