@trops/dash-core 0.1.504 → 0.1.506

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.
@@ -3,7 +3,7 @@
3
3
  var require$$0$1 = require('electron');
4
4
  var require$$1$1 = require('path');
5
5
  var require$$0$2 = require('fs');
6
- var require$$8$1 = require('objects-to-csv');
6
+ var require$$9$1 = require('objects-to-csv');
7
7
  var require$$1$2 = require('readline');
8
8
  var require$$2 = require('xtreamer');
9
9
  var require$$3$1 = require('xml2js');
@@ -11,7 +11,7 @@ var require$$4 = require('JSONStream');
11
11
  var require$$5 = require('stream');
12
12
  var require$$6 = require('csv-parser');
13
13
  var require$$0$3 = require('quickjs-emscripten');
14
- var require$$10 = require('https');
14
+ var require$$11 = require('https');
15
15
  var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
16
16
  var require$$1$3 = require('@modelcontextprotocol/sdk/client/stdio.js');
17
17
  var require$$0$4 = require('pkce-challenge');
@@ -28,13 +28,13 @@ var require$$0$7 = require('openai');
28
28
  require('live-plugin-manager');
29
29
  var require$$0$a = require('@anthropic-ai/sdk');
30
30
  var require$$3$4 = require('crypto');
31
- var require$$8$2 = require('zod');
31
+ var require$$8$1 = require('zod');
32
32
  var require$$0$8 = require('http');
33
33
  var require$$1$5 = require('http2');
34
34
  var require$$2$4 = require('node-forge');
35
35
  var require$$0$9 = require('css');
36
36
  var require$$1$6 = require('node-vibrant/node');
37
- var require$$0$b = require('ws');
37
+ var require$$3$5 = require('ws');
38
38
 
39
39
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
40
40
 
@@ -1054,7 +1054,7 @@ var secureStoreController$1 = {
1054
1054
  decryptString,
1055
1055
  };
1056
1056
 
1057
- const path$m = require$$1$1;
1057
+ const path$n = require$$1$1;
1058
1058
  const {
1059
1059
  readFileSync,
1060
1060
  writeFileSync: writeFileSync$3,
@@ -1072,7 +1072,7 @@ const {
1072
1072
  function ensureDirectoryExistence$1(filePath) {
1073
1073
  try {
1074
1074
  // isDirectory
1075
- var dirname = path$m.dirname(filePath);
1075
+ var dirname = path$n.dirname(filePath);
1076
1076
  // check if the directory exists...return true
1077
1077
  // if not, we can pass in the dirname as the filepath
1078
1078
  // and check each directory recursively.
@@ -1187,7 +1187,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1187
1187
 
1188
1188
  for (const file of files) {
1189
1189
  if (!excludeFiles.includes(file)) {
1190
- unlinkSync(path$m.join(directory, file), (err) => {
1190
+ unlinkSync(path$n.join(directory, file), (err) => {
1191
1191
  if (err) throw err;
1192
1192
  });
1193
1193
  }
@@ -1204,8 +1204,8 @@ var file = {
1204
1204
  checkDirectory: checkDirectory$1,
1205
1205
  };
1206
1206
 
1207
- const { app: app$e } = require$$0$1;
1208
- const path$l = require$$1$1;
1207
+ const { app: app$f } = require$$0$1;
1208
+ const path$m = require$$1$1;
1209
1209
  const { writeFileSync: writeFileSync$2 } = require$$0$2;
1210
1210
  const { getFileContents: getFileContents$7 } = file;
1211
1211
 
@@ -1252,8 +1252,8 @@ const workspaceController$3 = {
1252
1252
  saveWorkspaceForApplication: (win, appId, workspaceObject) => {
1253
1253
  try {
1254
1254
  // filename to the pages file (live pages)
1255
- const filename = path$l.join(
1256
- app$e.getPath("userData"),
1255
+ const filename = path$m.join(
1256
+ app$f.getPath("userData"),
1257
1257
  appName$7,
1258
1258
  appId,
1259
1259
  configFilename$5,
@@ -1301,8 +1301,8 @@ const workspaceController$3 = {
1301
1301
  saveMenuItemsForApplication: (win, appId, menuItems) => {
1302
1302
  try {
1303
1303
  // filename to the workspaces file
1304
- const filename = path$l.join(
1305
- app$e.getPath("userData"),
1304
+ const filename = path$m.join(
1305
+ app$f.getPath("userData"),
1306
1306
  appName$7,
1307
1307
  appId,
1308
1308
  configFilename$5,
@@ -1350,8 +1350,8 @@ const workspaceController$3 = {
1350
1350
  */
1351
1351
  deleteWorkspaceForApplication: (win, appId, workspaceId) => {
1352
1352
  try {
1353
- const filename = path$l.join(
1354
- app$e.getPath("userData"),
1353
+ const filename = path$m.join(
1354
+ app$f.getPath("userData"),
1355
1355
  appName$7,
1356
1356
  appId,
1357
1357
  configFilename$5,
@@ -1384,8 +1384,8 @@ const workspaceController$3 = {
1384
1384
 
1385
1385
  listWorkspacesForApplication: (win, appId) => {
1386
1386
  try {
1387
- const filename = path$l.join(
1388
- app$e.getPath("userData"),
1387
+ const filename = path$m.join(
1388
+ app$f.getPath("userData"),
1389
1389
  appName$7,
1390
1390
  appId,
1391
1391
  configFilename$5,
@@ -1412,8 +1412,8 @@ const workspaceController$3 = {
1412
1412
 
1413
1413
  listMenuItemsForApplication: (win, appId) => {
1414
1414
  try {
1415
- const filename = path$l.join(
1416
- app$e.getPath("userData"),
1415
+ const filename = path$m.join(
1416
+ app$f.getPath("userData"),
1417
1417
  appName$7,
1418
1418
  appId,
1419
1419
  configFilename$5,
@@ -1456,8 +1456,8 @@ const workspaceController$3 = {
1456
1456
 
1457
1457
  var workspaceController_1 = workspaceController$3;
1458
1458
 
1459
- const { app: app$d } = require$$0$1;
1460
- const path$k = require$$1$1;
1459
+ const { app: app$e } = require$$0$1;
1460
+ const path$l = require$$1$1;
1461
1461
  const { writeFileSync: writeFileSync$1 } = require$$0$2;
1462
1462
  const { getFileContents: getFileContents$6 } = file;
1463
1463
 
@@ -1477,8 +1477,8 @@ const themeController$5 = {
1477
1477
  saveThemeForApplication: (win, appId, name, obj) => {
1478
1478
  try {
1479
1479
  // filename to the pages file (live pages)
1480
- const filename = path$k.join(
1481
- app$d.getPath("userData"),
1480
+ const filename = path$l.join(
1481
+ app$e.getPath("userData"),
1482
1482
  appName$6,
1483
1483
  appId,
1484
1484
  configFilename$4,
@@ -1523,8 +1523,8 @@ const themeController$5 = {
1523
1523
  */
1524
1524
  listThemesForApplication: (win, appId) => {
1525
1525
  try {
1526
- const filename = path$k.join(
1527
- app$d.getPath("userData"),
1526
+ const filename = path$l.join(
1527
+ app$e.getPath("userData"),
1528
1528
  appName$6,
1529
1529
  appId,
1530
1530
  configFilename$4,
@@ -1565,8 +1565,8 @@ const themeController$5 = {
1565
1565
  */
1566
1566
  deleteThemeForApplication: (win, appId, themeKey) => {
1567
1567
  try {
1568
- const filename = path$k.join(
1569
- app$d.getPath("userData"),
1568
+ const filename = path$l.join(
1569
+ app$e.getPath("userData"),
1570
1570
  appName$6,
1571
1571
  appId,
1572
1572
  configFilename$4,
@@ -1642,9 +1642,9 @@ var themeController_1 = themeController$5;
1642
1642
  * `/data/`.
1643
1643
  */
1644
1644
 
1645
- const path$j = require$$1$1;
1646
- const fs$e = require$$0$2;
1647
- const { app: app$c } = require$$0$1;
1645
+ const path$k = require$$1$1;
1646
+ const fs$f = require$$0$2;
1647
+ const { app: app$d } = require$$0$1;
1648
1648
 
1649
1649
  const APP_NAME = "Dashboard";
1650
1650
 
@@ -1653,10 +1653,10 @@ const APP_NAME = "Dashboard";
1653
1653
  * @returns {string[]} ordered allowed roots for that category
1654
1654
  */
1655
1655
  function getAllowedRoots$2(category) {
1656
- const userData = app$c.getPath("userData");
1656
+ const userData = app$d.getPath("userData");
1657
1657
  switch (category) {
1658
1658
  case "data": {
1659
- const def = path$j.join(userData, APP_NAME, "data");
1659
+ const def = path$k.join(userData, APP_NAME, "data");
1660
1660
  // The user can configure a custom data directory in
1661
1661
  // Settings → General → Data Directory. If set, that
1662
1662
  // location is ALSO an allowed root. We don't replace the
@@ -1666,13 +1666,13 @@ function getAllowedRoots$2(category) {
1666
1666
  return override ? [def, override] : [def];
1667
1667
  }
1668
1668
  case "themes":
1669
- return [path$j.join(userData, APP_NAME, "themes")];
1669
+ return [path$k.join(userData, APP_NAME, "themes")];
1670
1670
  case "widgets":
1671
- return [path$j.join(userData, "widgets")];
1671
+ return [path$k.join(userData, "widgets")];
1672
1672
  case "plugins":
1673
- return [path$j.join(userData, "plugins")];
1673
+ return [path$k.join(userData, "plugins")];
1674
1674
  case "downloads":
1675
- return [app$c.getPath("downloads")];
1675
+ return [app$d.getPath("downloads")];
1676
1676
  default:
1677
1677
  throw new Error("safePath: unknown allowed-roots category: " + category);
1678
1678
  }
@@ -1687,13 +1687,13 @@ function getAllowedRoots$2(category) {
1687
1687
  */
1688
1688
  function readDataDirectoryFromSettings() {
1689
1689
  try {
1690
- const settingsPath = path$j.join(
1691
- app$c.getPath("userData"),
1690
+ const settingsPath = path$k.join(
1691
+ app$d.getPath("userData"),
1692
1692
  APP_NAME,
1693
1693
  "settings.json",
1694
1694
  );
1695
- if (!fs$e.existsSync(settingsPath)) return undefined;
1696
- const raw = fs$e.readFileSync(settingsPath, "utf8");
1695
+ if (!fs$f.existsSync(settingsPath)) return undefined;
1696
+ const raw = fs$f.readFileSync(settingsPath, "utf8");
1697
1697
  const settings = JSON.parse(raw);
1698
1698
  const dir = settings && settings.dataDirectory;
1699
1699
  if (typeof dir === "string" && dir) return dir;
@@ -1719,18 +1719,18 @@ function safePath$3(requested, allowedRoots) {
1719
1719
  throw new Error("safePath: allowedRoots must be a non-empty array");
1720
1720
  }
1721
1721
 
1722
- const resolved = path$j.resolve(requested);
1722
+ const resolved = path$k.resolve(requested);
1723
1723
 
1724
1724
  // Real-path through symlinks. If the file doesn't exist yet (a
1725
1725
  // create-new operation), real-path the parent so a symlink in the
1726
1726
  // parent chain can't trick us.
1727
1727
  let real = resolved;
1728
1728
  try {
1729
- real = fs$e.realpathSync(resolved);
1729
+ real = fs$f.realpathSync(resolved);
1730
1730
  } catch (_e) {
1731
1731
  try {
1732
- const parent = fs$e.realpathSync(path$j.dirname(resolved));
1733
- real = path$j.join(parent, path$j.basename(resolved));
1732
+ const parent = fs$f.realpathSync(path$k.dirname(resolved));
1733
+ real = path$k.join(parent, path$k.basename(resolved));
1734
1734
  } catch (_e2) {
1735
1735
  // Parent doesn't exist either. Use the resolved-but-not-
1736
1736
  // real path; the caller's mkdirSync will happen inside the
@@ -1742,14 +1742,14 @@ function safePath$3(requested, allowedRoots) {
1742
1742
  for (const root of allowedRoots) {
1743
1743
  let realRoot = root;
1744
1744
  try {
1745
- if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
1745
+ if (fs$f.existsSync(root)) realRoot = fs$f.realpathSync(root);
1746
1746
  } catch (_e) {
1747
1747
  // root doesn't exist or isn't reachable — keep as-is for
1748
1748
  // the comparison below
1749
1749
  }
1750
1750
  // Exact match OR strictly-inside (with separator to prevent
1751
1751
  // /data-evil/ matching /data/).
1752
- if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
1752
+ if (real === realRoot || real.startsWith(realRoot + path$k.sep)) {
1753
1753
  return real;
1754
1754
  }
1755
1755
  }
@@ -1810,9 +1810,9 @@ var safePath_1 = {
1810
1810
  * clearCache() → void // test-only
1811
1811
  */
1812
1812
 
1813
- const fs$d = require$$0$2;
1814
- const path$i = require$$1$1;
1815
- const { app: app$b } = require$$0$1;
1813
+ const fs$e = require$$0$2;
1814
+ const path$j = require$$1$1;
1815
+ const { app: app$c } = require$$0$1;
1816
1816
 
1817
1817
  const FILE_NAME = "widgetMcpGrants.json";
1818
1818
 
@@ -1821,14 +1821,14 @@ const FILE_NAME = "widgetMcpGrants.json";
1821
1821
  let _cache$1 = null;
1822
1822
 
1823
1823
  function grantsFilePath() {
1824
- return path$i.join(app$b.getPath("userData"), FILE_NAME);
1824
+ return path$j.join(app$c.getPath("userData"), FILE_NAME);
1825
1825
  }
1826
1826
 
1827
1827
  function loadFromDisk() {
1828
1828
  const p = grantsFilePath();
1829
- if (!fs$d.existsSync(p)) return {};
1829
+ if (!fs$e.existsSync(p)) return {};
1830
1830
  try {
1831
- const raw = fs$d.readFileSync(p, "utf8");
1831
+ const raw = fs$e.readFileSync(p, "utf8");
1832
1832
  const parsed = JSON.parse(raw);
1833
1833
  if (!parsed || typeof parsed !== "object") return {};
1834
1834
  return parsed;
@@ -1848,9 +1848,9 @@ function writeToDisk(data) {
1848
1848
  const tmp = p + ".tmp";
1849
1849
  // Ensure parent dir exists (userData should already, but be defensive
1850
1850
  // for first-launch / freshly-cleared profile cases).
1851
- fs$d.mkdirSync(path$i.dirname(p), { recursive: true });
1852
- fs$d.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
1853
- fs$d.renameSync(tmp, p);
1851
+ fs$e.mkdirSync(path$j.dirname(p), { recursive: true });
1852
+ fs$e.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
1853
+ fs$e.renameSync(tmp, p);
1854
1854
  }
1855
1855
 
1856
1856
  // Recognized origins for a persisted grant.
@@ -1935,6 +1935,12 @@ function sanitizePerms(perms) {
1935
1935
  ? raw.writePaths.filter((p) => typeof p === "string")
1936
1936
  : [],
1937
1937
  };
1938
+ } else if (name === "network") {
1939
+ domains.network = {
1940
+ hosts: Array.isArray(raw.hosts)
1941
+ ? raw.hosts.filter((h) => typeof h === "string")
1942
+ : [],
1943
+ };
1938
1944
  }
1939
1945
  // Future domains plug in here. Unknown domain names are dropped.
1940
1946
  }
@@ -1952,13 +1958,13 @@ function sanitizePerms(perms) {
1952
1958
  return out;
1953
1959
  }
1954
1960
 
1955
- function getGrant$3(widgetId) {
1961
+ function getGrant$4(widgetId) {
1956
1962
  if (typeof widgetId !== "string" || !widgetId) return null;
1957
1963
  const all = ensureCache();
1958
1964
  return all[widgetId] || null;
1959
1965
  }
1960
1966
 
1961
- function setGrant$3(widgetId, perms) {
1967
+ function setGrant$4(widgetId, perms) {
1962
1968
  if (typeof widgetId !== "string" || !widgetId) return false;
1963
1969
  const sanitized = sanitizePerms(perms);
1964
1970
  if (!sanitized) return false;
@@ -2039,8 +2045,8 @@ function clearCache$1() {
2039
2045
  }
2040
2046
 
2041
2047
  var grantedPermissions = {
2042
- getGrant: getGrant$3,
2043
- setGrant: setGrant$3,
2048
+ getGrant: getGrant$4,
2049
+ setGrant: setGrant$4,
2044
2050
  revokeGrant: revokeGrant$1,
2045
2051
  revokeServer: revokeServer$1,
2046
2052
  listAllGrants: listAllGrants$1,
@@ -2159,7 +2165,7 @@ function validateRequest(req) {
2159
2165
  *
2160
2166
  * `scope` informs the caller how to write the resulting grant.
2161
2167
  */
2162
- function requestApproval$2(req, opts = {}) {
2168
+ function requestApproval$3(req, opts = {}) {
2163
2169
  const validation = validateRequest(req);
2164
2170
  if (validation) {
2165
2171
  return Promise.reject(new Error(validation));
@@ -2251,7 +2257,7 @@ function setupJitConsentHandlers() {
2251
2257
  }
2252
2258
 
2253
2259
  var jitConsent$1 = {
2254
- requestApproval: requestApproval$2,
2260
+ requestApproval: requestApproval$3,
2255
2261
  setupJitConsentHandlers,
2256
2262
  _handleResponse,
2257
2263
  _resetForTest,
@@ -2295,8 +2301,8 @@ var jitConsent$1 = {
2295
2301
  * read-only entry) stay synchronous.
2296
2302
  */
2297
2303
 
2298
- const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
2299
- const { requestApproval: requestApproval$1 } = jitConsent$1;
2304
+ const { getGrant: getGrant$3, setGrant: setGrant$3 } = grantedPermissions;
2305
+ const { requestApproval: requestApproval$2 } = jitConsent$1;
2300
2306
 
2301
2307
  // Action names treated as writes. Anything not in this set is a read.
2302
2308
  // Conservative — when in doubt, classify as a read so write-protected
@@ -2315,7 +2321,7 @@ function isFsWriteAction(action) {
2315
2321
  return WRITE_ACTIONS.has(action);
2316
2322
  }
2317
2323
 
2318
- function _isNoGrantDenial$1(reason) {
2324
+ function _isNoGrantDenial$2(reason) {
2319
2325
  return (
2320
2326
  typeof reason === "string" && /no fs permissions granted/i.test(reason)
2321
2327
  );
@@ -2352,7 +2358,7 @@ function gateFsCall$1({ widgetId, action, args }) {
2352
2358
  };
2353
2359
  }
2354
2360
 
2355
- const grant = getGrant$2(widgetId);
2361
+ const grant = getGrant$3(widgetId);
2356
2362
  const fsPerms = grant && grant.domains && grant.domains.fs;
2357
2363
  if (!fsPerms) {
2358
2364
  return {
@@ -2453,11 +2459,11 @@ async function gateFsCallWithJit$1(req, opts = {}) {
2453
2459
  const initial = gateFsCall$1(req);
2454
2460
  if (initial.allow) return initial;
2455
2461
  if (!opts.enableJit) return initial;
2456
- if (!_isNoGrantDenial$1(initial.reason)) return initial;
2462
+ if (!_isNoGrantDenial$2(initial.reason)) return initial;
2457
2463
 
2458
2464
  let decision;
2459
2465
  try {
2460
- decision = await requestApproval$1(
2466
+ decision = await requestApproval$2(
2461
2467
  {
2462
2468
  widgetId: req.widgetId,
2463
2469
  domain: "fs",
@@ -2506,9 +2512,9 @@ async function gateFsCallWithJit$1(req, opts = {}) {
2506
2512
  addition.grantOrigin = "live";
2507
2513
 
2508
2514
  try {
2509
- const current = getGrant$2(req.widgetId);
2515
+ const current = getGrant$3(req.widgetId);
2510
2516
  const merged = _mergeFsGrant(current, addition);
2511
- setGrant$2(req.widgetId, merged);
2517
+ setGrant$3(req.widgetId, merged);
2512
2518
  } catch (e) {
2513
2519
  return {
2514
2520
  allow: false,
@@ -2528,6 +2534,232 @@ var fsGate = {
2528
2534
  WRITE_ACTIONS,
2529
2535
  };
2530
2536
 
2537
+ /**
2538
+ * networkGate.js
2539
+ *
2540
+ * Per-widget gate for outbound-network IPC actions (Phase 3 of JIT
2541
+ * consent). Same shape as `fsGate.js` but for hostname-based scoping.
2542
+ *
2543
+ * Channels currently gated by this:
2544
+ * - readDataFromURL (dataController) — fetch a URL into a file
2545
+ * - wsConnect (webSocketController) — open a WebSocket
2546
+ *
2547
+ * Channels NOT gated:
2548
+ * - WS_SEND/DISCONNECT/STATUS/GET_ALL — operate on already-authorized
2549
+ * connections, intentionally shared across widgets via the
2550
+ * `consumers: Set<webContentsId>` design.
2551
+ * - THEME_EXTRACT_FROM_URL — admin-only callers (Settings panels);
2552
+ * no widget caller exists in the codebase.
2553
+ *
2554
+ * Grant shape (under `grant.domains.network`):
2555
+ * {
2556
+ * hosts: ["api.example.com", "*", ...]
2557
+ * }
2558
+ *
2559
+ * Hostname matching:
2560
+ * - Exact match (case-insensitive)
2561
+ * - "*" wildcard in the grant matches any host (escape hatch;
2562
+ * surfaced in the JIT modal as "no host scope — risky")
2563
+ * - Subdomain wildcards ("*.example.com") are NOT supported in this
2564
+ * slice — deferred to a follow-up.
2565
+ *
2566
+ * URL parsing uses the WHATWG URL constructor. Malformed URLs deny
2567
+ * synchronously and do NOT escalate to JIT (a JIT prompt would let a
2568
+ * widget probe URL parser quirks, which we'd rather not).
2569
+ *
2570
+ * JIT escalation: when the runtime calls `gateNetworkCallWithJit` and
2571
+ * the gate denies for "no network permissions granted", a
2572
+ * permission-required IPC fires and the user's response is merged
2573
+ * into the persisted grant. Other denial reasons (host not in
2574
+ * allowlist, malformed url, missing url) stay synchronous.
2575
+ */
2576
+
2577
+ const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
2578
+ const { requestApproval: requestApproval$1 } = jitConsent$1;
2579
+
2580
+ function _isNoGrantDenial$1(reason) {
2581
+ return (
2582
+ typeof reason === "string" && /no network permissions granted/i.test(reason)
2583
+ );
2584
+ }
2585
+
2586
+ function _hostMatches(host, allowedList) {
2587
+ if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
2588
+ if (allowedList.includes("*")) return true;
2589
+ const lower = host.toLowerCase();
2590
+ return allowedList.some(
2591
+ (h) => typeof h === "string" && h.toLowerCase() === lower,
2592
+ );
2593
+ }
2594
+
2595
+ function _parseHost(url) {
2596
+ try {
2597
+ return new URL(url).hostname;
2598
+ } catch {
2599
+ return null;
2600
+ }
2601
+ }
2602
+
2603
+ /**
2604
+ * Synchronous gate evaluation.
2605
+ * @returns {{ allow: true } | { allow: false, reason: string }}
2606
+ */
2607
+ function gateNetworkCall$2({ widgetId, action, args }) {
2608
+ if (!widgetId) {
2609
+ return {
2610
+ allow: false,
2611
+ reason: "no widgetId supplied; cannot determine network permissions",
2612
+ };
2613
+ }
2614
+
2615
+ const url = args && typeof args === "object" ? args.url : null;
2616
+ if (typeof url !== "string" || !url) {
2617
+ return {
2618
+ allow: false,
2619
+ reason:
2620
+ "network gate: action '" +
2621
+ action +
2622
+ "' requires args.url (got: " +
2623
+ JSON.stringify(url) +
2624
+ ")",
2625
+ };
2626
+ }
2627
+
2628
+ const host = _parseHost(url);
2629
+ if (!host) {
2630
+ return {
2631
+ allow: false,
2632
+ reason: "network gate: malformed url '" + url + "'",
2633
+ };
2634
+ }
2635
+
2636
+ const grant = getGrant$2(widgetId);
2637
+ const netPerms = grant && grant.domains && grant.domains.network;
2638
+ if (!netPerms) {
2639
+ return {
2640
+ allow: false,
2641
+ reason:
2642
+ "widget '" +
2643
+ widgetId +
2644
+ "' has no network permissions granted; user must approve at runtime or in Settings → Privacy & Security",
2645
+ };
2646
+ }
2647
+
2648
+ if (_hostMatches(host, netPerms.hosts)) {
2649
+ return { allow: true };
2650
+ }
2651
+ return {
2652
+ allow: false,
2653
+ reason:
2654
+ "network gate: host '" +
2655
+ host +
2656
+ "' rejected — not in allowed hosts for widget '" +
2657
+ widgetId +
2658
+ "'",
2659
+ };
2660
+ }
2661
+
2662
+ /**
2663
+ * Merge an approved JIT decision's grant into the widget's existing
2664
+ * grant under domains.network. Mirrors fsGate._mergeFsGrant.
2665
+ */
2666
+ function _mergeNetworkGrant(current, addition) {
2667
+ const out = {
2668
+ grantOrigin: addition.grantOrigin || current?.grantOrigin || null,
2669
+ servers: { ...(current?.servers || {}) },
2670
+ domains: { ...(current?.domains || {}) },
2671
+ };
2672
+ const additionNet = addition?.domains?.network;
2673
+ if (additionNet) {
2674
+ const existingNet = out.domains.network || { hosts: [] };
2675
+ out.domains.network = {
2676
+ hosts: [
2677
+ ...new Set([
2678
+ ...(existingNet.hosts || []),
2679
+ ...(Array.isArray(additionNet.hosts) ? additionNet.hosts : []),
2680
+ ]),
2681
+ ],
2682
+ };
2683
+ }
2684
+ return out;
2685
+ }
2686
+
2687
+ /**
2688
+ * Async gate that escalates "no network grant" denials to a JIT
2689
+ * consent prompt when `opts.enableJit` is true. On approval, merges
2690
+ * the decision's grant blob into the persisted grant and re-evaluates.
2691
+ */
2692
+ async function gateNetworkCallWithJit$2(req, opts = {}) {
2693
+ const initial = gateNetworkCall$2(req);
2694
+ if (initial.allow) return initial;
2695
+ if (!opts.enableJit) return initial;
2696
+ if (!_isNoGrantDenial$1(initial.reason)) return initial;
2697
+
2698
+ let decision;
2699
+ try {
2700
+ decision = await requestApproval$1(
2701
+ {
2702
+ widgetId: req.widgetId,
2703
+ domain: "network",
2704
+ action: req.action,
2705
+ args: req.args || {},
2706
+ },
2707
+ { timeoutMs: opts.timeoutMs },
2708
+ );
2709
+ } catch (e) {
2710
+ return {
2711
+ allow: false,
2712
+ reason:
2713
+ "JIT consent " +
2714
+ (e && e.message ? e.message : "failed") +
2715
+ "; original denial: " +
2716
+ initial.reason,
2717
+ };
2718
+ }
2719
+
2720
+ if (!decision || decision.approve !== true) {
2721
+ return {
2722
+ allow: false,
2723
+ reason:
2724
+ "user declined JIT consent for widget '" +
2725
+ req.widgetId +
2726
+ "' calling network '" +
2727
+ req.action +
2728
+ "'",
2729
+ };
2730
+ }
2731
+
2732
+ const host = _parseHost(req.args?.url) || "*";
2733
+ const addition =
2734
+ decision.granted && typeof decision.granted === "object"
2735
+ ? decision.granted
2736
+ : {
2737
+ grantOrigin: "live",
2738
+ domains: { network: { hosts: [host] } },
2739
+ };
2740
+ addition.grantOrigin = "live";
2741
+
2742
+ try {
2743
+ const current = getGrant$2(req.widgetId);
2744
+ const merged = _mergeNetworkGrant(current, addition);
2745
+ setGrant$2(req.widgetId, merged);
2746
+ } catch (e) {
2747
+ return {
2748
+ allow: false,
2749
+ reason:
2750
+ "JIT consent: failed to persist network grant: " +
2751
+ (e && e.message ? e.message : String(e)),
2752
+ };
2753
+ }
2754
+
2755
+ return gateNetworkCall$2(req);
2756
+ }
2757
+
2758
+ var networkGate = {
2759
+ gateNetworkCall: gateNetworkCall$2,
2760
+ gateNetworkCallWithJit: gateNetworkCallWithJit$2,
2761
+ };
2762
+
2531
2763
  /**
2532
2764
  * securityFlags.js
2533
2765
  *
@@ -2549,15 +2781,15 @@ var fsGate = {
2549
2781
  * settings.json IO.
2550
2782
  */
2551
2783
 
2552
- function readEnforceFlag$2(settings) {
2784
+ function readEnforceFlag$3(settings) {
2553
2785
  return settings?.security?.enforceWidgetMcpPermissions !== false;
2554
2786
  }
2555
2787
 
2556
- function readJitFlag$2(settings) {
2788
+ function readJitFlag$3(settings) {
2557
2789
  return settings?.security?.enableJitConsent !== false;
2558
2790
  }
2559
2791
 
2560
- var securityFlags = { readEnforceFlag: readEnforceFlag$2, readJitFlag: readJitFlag$2 };
2792
+ var securityFlags = { readEnforceFlag: readEnforceFlag$3, readJitFlag: readJitFlag$3 };
2561
2793
 
2562
2794
  /**
2563
2795
  * safeJsExecutor.js
@@ -3258,28 +3490,32 @@ function requireTransform () {
3258
3490
  return transform;
3259
3491
  }
3260
3492
 
3261
- const { app: app$a } = require$$0$1;
3262
- var fs$c = require$$0$2;
3263
- const path$h = require$$1$1;
3493
+ const { app: app$b } = require$$0$1;
3494
+ var fs$d = require$$0$2;
3495
+ const path$i = require$$1$1;
3264
3496
  const events$5 = events$8;
3265
3497
  const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
3266
3498
  const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
3267
3499
  const { gateFsCall, gateFsCallWithJit } = fsGate;
3268
- const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
3500
+ const {
3501
+ gateNetworkCall: gateNetworkCall$1,
3502
+ gateNetworkCallWithJit: gateNetworkCallWithJit$1,
3503
+ } = networkGate;
3504
+ const { readEnforceFlag: readEnforceFlag$2, readJitFlag: readJitFlag$2 } = securityFlags;
3269
3505
 
3270
3506
  // Reads the enforcement + JIT flags from settings.json. Mirrors the
3271
3507
  // helper in mcpController. The flag is shared across MCP and fs domains
3272
3508
  // — see Phase 2 plan for rationale (the cosmetic rename to a
3273
3509
  // domain-neutral name is a separate slice).
3274
- function _loadFlags() {
3510
+ function _loadFlags$1() {
3275
3511
  try {
3276
- const settingsPath = path$h.join(
3277
- app$a.getPath("userData"),
3512
+ const settingsPath = path$i.join(
3513
+ app$b.getPath("userData"),
3278
3514
  appName$5,
3279
3515
  "settings.json",
3280
3516
  );
3281
- if (!fs$c.existsSync(settingsPath)) return null;
3282
- return JSON.parse(fs$c.readFileSync(settingsPath, "utf8"));
3517
+ if (!fs$d.existsSync(settingsPath)) return null;
3518
+ return JSON.parse(fs$d.readFileSync(settingsPath, "utf8"));
3283
3519
  } catch (_e) {
3284
3520
  return null;
3285
3521
  }
@@ -3293,10 +3529,10 @@ function _loadFlags() {
3293
3529
  * @returns {Promise<boolean>}
3294
3530
  */
3295
3531
  async function _runFsGate(win, action, widgetId, args, errorEvent) {
3296
- const settings = _loadFlags();
3297
- if (!readEnforceFlag$1(settings)) return true; // gate disabled
3532
+ const settings = _loadFlags$1();
3533
+ if (!readEnforceFlag$2(settings)) return true; // gate disabled
3298
3534
  if (!widgetId) return true; // legacy callers without widgetId — see plan
3299
- const gate = readJitFlag$1(settings)
3535
+ const gate = readJitFlag$2(settings)
3300
3536
  ? await gateFsCallWithJit({ widgetId, action, args }, { enableJit: true })
3301
3537
  : gateFsCall({ widgetId, action, args });
3302
3538
  if (gate.allow) return true;
@@ -3309,10 +3545,34 @@ async function _runFsGate(win, action, widgetId, args, errorEvent) {
3309
3545
  return false;
3310
3546
  }
3311
3547
 
3548
+ /**
3549
+ * Phase 3 network gate. Same shape as _runFsGate but for outbound
3550
+ * URLs. Mirrors fs's "disabled / no widgetId / sync vs async" branching.
3551
+ */
3552
+ async function _runNetworkGate$1(win, action, widgetId, args, errorEvent) {
3553
+ const settings = _loadFlags$1();
3554
+ if (!readEnforceFlag$2(settings)) return true;
3555
+ if (!widgetId) return true;
3556
+ const gate = readJitFlag$2(settings)
3557
+ ? await gateNetworkCallWithJit$1(
3558
+ { widgetId, action, args },
3559
+ { enableJit: true },
3560
+ )
3561
+ : gateNetworkCall$1({ widgetId, action, args });
3562
+ if (gate.allow) return true;
3563
+ if (win && errorEvent) {
3564
+ win.webContents.send(errorEvent, {
3565
+ success: false,
3566
+ message: "network permission gate: " + gate.reason,
3567
+ });
3568
+ }
3569
+ return false;
3570
+ }
3571
+
3312
3572
  // Convert Json to Csv
3313
- const ObjectsToCsv = require$$8$1;
3573
+ const ObjectsToCsv = require$$9$1;
3314
3574
  const Transform = requireTransform();
3315
- const https$3 = require$$10;
3575
+ const https$3 = require$$11;
3316
3576
  const appName$5 = "Dashboard";
3317
3577
 
3318
3578
  const dataController$1 = {
@@ -3329,8 +3589,8 @@ const dataController$1 = {
3329
3589
  // Validate the renderer-supplied filename is contained within
3330
3590
  // the data directory. path.join doesn't reject `..` segments;
3331
3591
  // safePath does.
3332
- const candidate = path$h.join(
3333
- app$a.getPath("userData"),
3592
+ const candidate = path$i.join(
3593
+ app$b.getPath("userData"),
3334
3594
  appName$5,
3335
3595
  appId,
3336
3596
  "data",
@@ -3468,7 +3728,18 @@ const dataController$1 = {
3468
3728
  }
3469
3729
  },
3470
3730
 
3471
- readDataFromURL: (win, url, toFilepath) => {
3731
+ readDataFromURL: async (win, url, toFilepath, widgetId = null) => {
3732
+ // Phase 3 network gate. Runs before HTTPS-protocol + safePath
3733
+ // checks so JIT can prompt the user without leaking URL parser
3734
+ // edge cases through error timing.
3735
+ const gateOk = await _runNetworkGate$1(
3736
+ win,
3737
+ "readDataFromURL",
3738
+ widgetId,
3739
+ { url },
3740
+ events$5.READ_DATA_URL_ERROR,
3741
+ );
3742
+ if (!gateOk) return;
3472
3743
  try {
3473
3744
  // Validate URL is https protocol only
3474
3745
  let parsedUrl;
@@ -3488,7 +3759,7 @@ const dataController$1 = {
3488
3759
  // intent, plus realpath/symlink protection.
3489
3760
  const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
3490
3761
 
3491
- const writeStream = fs$c.createWriteStream(resolvedFilepath);
3762
+ const writeStream = fs$d.createWriteStream(resolvedFilepath);
3492
3763
 
3493
3764
  https$3
3494
3765
  .get(url, (resp) => {
@@ -3670,8 +3941,8 @@ const dataController$1 = {
3670
3941
  if (data) {
3671
3942
  // Validate filename is contained within the data directory.
3672
3943
  // path.join doesn't reject `..` segments; safePath does.
3673
- const candidate = path$h.join(
3674
- app$a.getPath("userData"),
3944
+ const candidate = path$i.join(
3945
+ app$b.getPath("userData"),
3675
3946
  appName$5,
3676
3947
  "data",
3677
3948
  filename,
@@ -3772,8 +4043,8 @@ const dataController$1 = {
3772
4043
  try {
3773
4044
  if (filename) {
3774
4045
  // filename to the pages file (live pages)
3775
- const fromFilename = path$h.join(
3776
- app$a.getPath("userData"),
4046
+ const fromFilename = path$i.join(
4047
+ app$b.getPath("userData"),
3777
4048
  appName$5,
3778
4049
  "data",
3779
4050
  filename,
@@ -3853,9 +4124,9 @@ var dataController_1 = dataController$1;
3853
4124
  * settingsController
3854
4125
  */
3855
4126
 
3856
- const { app: app$9 } = require$$0$1;
3857
- const path$g = require$$1$1;
3858
- const fs$b = require$$0$2;
4127
+ const { app: app$a } = require$$0$1;
4128
+ const path$h = require$$1$1;
4129
+ const fs$c = require$$0$2;
3859
4130
  const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
3860
4131
 
3861
4132
  const configFilename$3 = "settings.json";
@@ -3863,15 +4134,15 @@ const appName$4 = "Dashboard";
3863
4134
 
3864
4135
  // Helper function to recursively copy directory
3865
4136
  function copyDirectory(source, destination) {
3866
- if (!fs$b.existsSync(destination)) {
3867
- fs$b.mkdirSync(destination, { recursive: true });
4137
+ if (!fs$c.existsSync(destination)) {
4138
+ fs$c.mkdirSync(destination, { recursive: true });
3868
4139
  }
3869
4140
 
3870
- const files = fs$b.readdirSync(source);
4141
+ const files = fs$c.readdirSync(source);
3871
4142
  for (const file of files) {
3872
- const srcPath = path$g.join(source, file);
3873
- const destPath = path$g.join(destination, file);
3874
- const stat = fs$b.lstatSync(srcPath);
4143
+ const srcPath = path$h.join(source, file);
4144
+ const destPath = path$h.join(destination, file);
4145
+ const stat = fs$c.lstatSync(srcPath);
3875
4146
 
3876
4147
  // Skip symlinks to prevent following links to sensitive files
3877
4148
  if (stat.isSymbolicLink()) {
@@ -3882,7 +4153,7 @@ function copyDirectory(source, destination) {
3882
4153
  if (stat.isDirectory()) {
3883
4154
  copyDirectory(srcPath, destPath);
3884
4155
  } else {
3885
- fs$b.copyFileSync(srcPath, destPath);
4156
+ fs$c.copyFileSync(srcPath, destPath);
3886
4157
  }
3887
4158
  }
3888
4159
  }
@@ -3898,8 +4169,8 @@ const settingsController$4 = {
3898
4169
  try {
3899
4170
  if (data) {
3900
4171
  // <appId>/settings.json
3901
- const filename = path$g.join(
3902
- app$9.getPath("userData"),
4172
+ const filename = path$h.join(
4173
+ app$a.getPath("userData"),
3903
4174
  appName$4,
3904
4175
  configFilename$3,
3905
4176
  );
@@ -3934,8 +4205,8 @@ const settingsController$4 = {
3934
4205
  getSettingsForApplication: (win) => {
3935
4206
  try {
3936
4207
  // <appId>/settings.json
3937
- const filename = path$g.join(
3938
- app$9.getPath("userData"),
4208
+ const filename = path$h.join(
4209
+ app$a.getPath("userData"),
3939
4210
  appName$4,
3940
4211
  configFilename$3,
3941
4212
  );
@@ -3965,15 +4236,15 @@ const settingsController$4 = {
3965
4236
  */
3966
4237
  getDataDirectory: (win) => {
3967
4238
  try {
3968
- const settingsPath = path$g.join(
3969
- app$9.getPath("userData"),
4239
+ const settingsPath = path$h.join(
4240
+ app$a.getPath("userData"),
3970
4241
  appName$4,
3971
4242
  configFilename$3,
3972
4243
  );
3973
4244
  const settings = getFileContents$4(settingsPath, {});
3974
4245
  const userDataDir =
3975
4246
  settings.userDataDirectory ||
3976
- path$g.join(app$9.getPath("userData"), appName$4);
4247
+ path$h.join(app$a.getPath("userData"), appName$4);
3977
4248
 
3978
4249
  console.log("[settingsController] Data directory retrieved successfully");
3979
4250
  // Return the data for ipcMain.handle() - modern promise-based approach
@@ -4000,18 +4271,18 @@ const settingsController$4 = {
4000
4271
  setDataDirectory: (win, newPath) => {
4001
4272
  try {
4002
4273
  // Validate the path exists and is a directory
4003
- if (!fs$b.existsSync(newPath)) {
4004
- fs$b.mkdirSync(newPath, { recursive: true });
4274
+ if (!fs$c.existsSync(newPath)) {
4275
+ fs$c.mkdirSync(newPath, { recursive: true });
4005
4276
  }
4006
4277
 
4007
- const stats = fs$b.statSync(newPath);
4278
+ const stats = fs$c.statSync(newPath);
4008
4279
  if (!stats.isDirectory()) {
4009
4280
  throw new Error("Path is not a directory");
4010
4281
  }
4011
4282
 
4012
4283
  // Update settings
4013
- const settingsPath = path$g.join(
4014
- app$9.getPath("userData"),
4284
+ const settingsPath = path$h.join(
4285
+ app$a.getPath("userData"),
4015
4286
  appName$4,
4016
4287
  configFilename$3,
4017
4288
  );
@@ -4044,20 +4315,20 @@ const settingsController$4 = {
4044
4315
  migrateDataDirectory: (win, oldPath, newPath) => {
4045
4316
  try {
4046
4317
  // Resolve paths to prevent traversal
4047
- const resolvedOldPath = path$g.resolve(oldPath);
4048
- const resolvedNewPath = path$g.resolve(newPath);
4318
+ const resolvedOldPath = path$h.resolve(oldPath);
4319
+ const resolvedNewPath = path$h.resolve(newPath);
4049
4320
 
4050
4321
  // Validate oldPath is the current configured data directory
4051
- const settingsCheckPath = path$g.join(
4052
- app$9.getPath("userData"),
4322
+ const settingsCheckPath = path$h.join(
4323
+ app$a.getPath("userData"),
4053
4324
  appName$4,
4054
4325
  configFilename$3,
4055
4326
  );
4056
4327
  const currentSettings = getFileContents$4(settingsCheckPath, {});
4057
4328
  const currentDataDir =
4058
4329
  currentSettings.userDataDirectory ||
4059
- path$g.join(app$9.getPath("userData"), appName$4);
4060
- if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
4330
+ path$h.join(app$a.getPath("userData"), appName$4);
4331
+ if (resolvedOldPath !== path$h.resolve(currentDataDir)) {
4061
4332
  throw new Error("Source path must be the current data directory");
4062
4333
  }
4063
4334
 
@@ -4081,20 +4352,20 @@ const settingsController$4 = {
4081
4352
  }
4082
4353
 
4083
4354
  // Validate paths
4084
- if (!fs$b.existsSync(resolvedOldPath)) {
4355
+ if (!fs$c.existsSync(resolvedOldPath)) {
4085
4356
  throw new Error("Source directory does not exist");
4086
4357
  }
4087
4358
 
4088
- if (!fs$b.existsSync(resolvedNewPath)) {
4089
- fs$b.mkdirSync(resolvedNewPath, { recursive: true });
4359
+ if (!fs$c.existsSync(resolvedNewPath)) {
4360
+ fs$c.mkdirSync(resolvedNewPath, { recursive: true });
4090
4361
  }
4091
4362
 
4092
4363
  // Copy files
4093
4364
  copyDirectory(resolvedOldPath, resolvedNewPath);
4094
4365
 
4095
4366
  // Update settings to use new path
4096
- const settingsPath = path$g.join(
4097
- app$9.getPath("userData"),
4367
+ const settingsPath = path$h.join(
4368
+ app$a.getPath("userData"),
4098
4369
  appName$4,
4099
4370
  configFilename$3,
4100
4371
  );
@@ -4758,8 +5029,8 @@ function requireProviderController () {
4758
5029
  return providerController_1;
4759
5030
  }
4760
5031
 
4761
- const { app: app$8 } = require$$0$1;
4762
- const path$f = require$$1$1;
5032
+ const { app: app$9 } = require$$0$1;
5033
+ const path$g = require$$1$1;
4763
5034
  const events$4 = events$8;
4764
5035
  const { getFileContents: getFileContents$3 } = file;
4765
5036
 
@@ -4775,8 +5046,8 @@ const layoutController$1 = {
4775
5046
  */
4776
5047
  listLayoutsForApplication: (win, appId) => {
4777
5048
  try {
4778
- const filename = path$f.join(
4779
- app$8.getPath("userData"),
5049
+ const filename = path$g.join(
5050
+ app$9.getPath("userData"),
4780
5051
  appName$3,
4781
5052
  appId,
4782
5053
  configFilename$2,
@@ -22439,15 +22710,15 @@ const {
22439
22710
  const {
22440
22711
  StreamableHTTPClientTransport,
22441
22712
  } = streamableHttp$1;
22442
- const path$e = require$$1$1;
22443
- const fs$a = require$$0$2;
22713
+ const path$f = require$$1$1;
22714
+ const fs$b = require$$0$2;
22444
22715
  const os$2 = require$$2$1;
22445
22716
  const responseCache$2 = responseCache_1;
22446
22717
  const { gateToolCall, gateToolCallWithJit } = permissionGate;
22447
22718
  const { serverKey, parseServerKey } = mcpServerKey;
22448
22719
  const { applyPathScopeToCredentials } = mcpScopeResolver;
22449
- const { readEnforceFlag, readJitFlag } = securityFlags;
22450
- const { app: app$7 } = require$$0$1;
22720
+ const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
22721
+ const { app: app$8 } = require$$0$1;
22451
22722
 
22452
22723
  /**
22453
22724
  * Load the user's settings.json (or null on absence/parse error). The
@@ -22456,13 +22727,13 @@ const { app: app$7 } = require$$0$1;
22456
22727
  */
22457
22728
  function loadSettingsForFlags() {
22458
22729
  try {
22459
- const settingsPath = path$e.join(
22460
- app$7.getPath("userData"),
22730
+ const settingsPath = path$f.join(
22731
+ app$8.getPath("userData"),
22461
22732
  "Dashboard",
22462
22733
  "settings.json",
22463
22734
  );
22464
- if (!fs$a.existsSync(settingsPath)) return null;
22465
- const raw = fs$a.readFileSync(settingsPath, "utf8");
22735
+ if (!fs$b.existsSync(settingsPath)) return null;
22736
+ const raw = fs$b.readFileSync(settingsPath, "utf8");
22466
22737
  return JSON.parse(raw);
22467
22738
  } catch (_e) {
22468
22739
  return null;
@@ -22475,13 +22746,13 @@ function loadSettingsForFlags() {
22475
22746
  // confirm-on-disable dialog. See electron/utils/securityFlags.js for
22476
22747
  // the pinned default semantics.
22477
22748
  function isWidgetPermissionEnforcementEnabled() {
22478
- return readEnforceFlag(loadSettingsForFlags());
22749
+ return readEnforceFlag$1(loadSettingsForFlags());
22479
22750
  }
22480
22751
 
22481
22752
  // JIT consent flag. **Default ON.** Same semantics as the enforcement
22482
22753
  // flag — explicit false to opt out, otherwise on.
22483
22754
  function isJitConsentEnabled() {
22484
- return readJitFlag(loadSettingsForFlags());
22755
+ return readJitFlag$1(loadSettingsForFlags());
22485
22756
  }
22486
22757
 
22487
22758
  /**
@@ -22629,7 +22900,7 @@ function getShellPath$1() {
22629
22900
  fallbackDirs.push(`${home}/.nodenv/shims`);
22630
22901
  try {
22631
22902
  const nvmDir = `${home}/.nvm/versions/node`;
22632
- const versions = fs$a.readdirSync(nvmDir).sort();
22903
+ const versions = fs$b.readdirSync(nvmDir).sort();
22633
22904
  if (versions.length > 0) {
22634
22905
  // Find the highest compatible version (v18/v20/v22)
22635
22906
  for (let i = versions.length - 1; i >= 0; i--) {
@@ -22765,15 +23036,15 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22765
23036
  const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
22766
23037
  const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
22767
23038
 
22768
- if (!fs$a.existsSync(credPath) || !fs$a.existsSync(keysPath)) {
23039
+ if (!fs$b.existsSync(credPath) || !fs$b.existsSync(keysPath)) {
22769
23040
  console.log(
22770
23041
  "[mcpController] Token refresh skipped: credential files not found",
22771
23042
  );
22772
23043
  return;
22773
23044
  }
22774
23045
 
22775
- const credentials = JSON.parse(fs$a.readFileSync(credPath, "utf8"));
22776
- const keysFile = JSON.parse(fs$a.readFileSync(keysPath, "utf8"));
23046
+ const credentials = JSON.parse(fs$b.readFileSync(credPath, "utf8"));
23047
+ const keysFile = JSON.parse(fs$b.readFileSync(keysPath, "utf8"));
22777
23048
  const keyData = keysFile.installed || keysFile.web;
22778
23049
 
22779
23050
  if (
@@ -22798,7 +23069,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22798
23069
 
22799
23070
  console.log("[mcpController] Refreshing Google OAuth token...");
22800
23071
 
22801
- const https = require$$10;
23072
+ const https = require$$11;
22802
23073
  const postData = [
22803
23074
  `client_id=${encodeURIComponent(keyData.client_id)}`,
22804
23075
  `client_secret=${encodeURIComponent(keyData.client_secret)}`,
@@ -22842,7 +23113,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22842
23113
  credentials.refresh_token = body.refresh_token;
22843
23114
  }
22844
23115
 
22845
- fs$a.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
23116
+ fs$b.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
22846
23117
  console.log("[mcpController] Google OAuth token refreshed successfully");
22847
23118
  }
22848
23119
 
@@ -23029,7 +23300,7 @@ const mcpController$3 = {
23029
23300
  }
23030
23301
 
23031
23302
  // Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
23032
- const mcpDir = path$e.join(__dirname, "..", "mcp");
23303
+ const mcpDir = path$f.join(__dirname, "..", "mcp");
23033
23304
  for (let i = 0; i < args.length; i++) {
23034
23305
  if (
23035
23306
  typeof args[i] === "string" &&
@@ -23480,20 +23751,20 @@ const mcpController$3 = {
23480
23751
  */
23481
23752
  getCatalog: (win) => {
23482
23753
  try {
23483
- const catalogPath = path$e.join(
23754
+ const catalogPath = path$f.join(
23484
23755
  __dirname,
23485
23756
  "..",
23486
23757
  "mcp",
23487
23758
  "mcpServerCatalog.json",
23488
23759
  );
23489
23760
 
23490
- if (!fs$a.existsSync(catalogPath)) {
23761
+ if (!fs$b.existsSync(catalogPath)) {
23491
23762
  return {
23492
23763
  catalog: [],
23493
23764
  };
23494
23765
  }
23495
23766
 
23496
- const catalogData = fs$a.readFileSync(catalogPath, "utf8");
23767
+ const catalogData = fs$b.readFileSync(catalogPath, "utf8");
23497
23768
  const catalog = JSON.parse(catalogData);
23498
23769
 
23499
23770
  return {
@@ -23521,18 +23792,18 @@ const mcpController$3 = {
23521
23792
  */
23522
23793
  getKnownExternalCatalog: () => {
23523
23794
  try {
23524
- const catalogPath = path$e.join(
23795
+ const catalogPath = path$f.join(
23525
23796
  __dirname,
23526
23797
  "..",
23527
23798
  "mcp",
23528
23799
  "knownExternalMcpServers.json",
23529
23800
  );
23530
23801
 
23531
- if (!fs$a.existsSync(catalogPath)) {
23802
+ if (!fs$b.existsSync(catalogPath)) {
23532
23803
  return { success: true, servers: [] };
23533
23804
  }
23534
23805
 
23535
- const catalogData = fs$a.readFileSync(catalogPath, "utf8");
23806
+ const catalogData = fs$b.readFileSync(catalogPath, "utf8");
23536
23807
  const catalog = JSON.parse(catalogData);
23537
23808
 
23538
23809
  return {
@@ -23597,8 +23868,8 @@ const mcpController$3 = {
23597
23868
  const destPath = to.replace(/^~/, os$2.homedir());
23598
23869
  const destDir = require$$1$1.dirname(destPath);
23599
23870
  try {
23600
- fs$a.mkdirSync(destDir, { recursive: true });
23601
- fs$a.copyFileSync(sourcePath, destPath);
23871
+ fs$b.mkdirSync(destDir, { recursive: true });
23872
+ fs$b.copyFileSync(sourcePath, destPath);
23602
23873
  } catch (err) {
23603
23874
  return {
23604
23875
  error: true,
@@ -23634,7 +23905,7 @@ const mcpController$3 = {
23634
23905
  }
23635
23906
 
23636
23907
  // Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
23637
- const mcpDir = path$e.join(__dirname, "..", "mcp");
23908
+ const mcpDir = path$f.join(__dirname, "..", "mcp");
23638
23909
  const resolvedArgs = (authCommand.args || []).map((arg) =>
23639
23910
  typeof arg === "string" && arg.includes("{{MCP_DIR}}")
23640
23911
  ? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
@@ -24182,8 +24453,8 @@ function commonjsRequire(path) {
24182
24453
  * Runs in the Electron main process at widget install time.
24183
24454
  */
24184
24455
 
24185
- const fs$9 = require$$0$2;
24186
- const path$d = require$$1$1;
24456
+ const fs$a = require$$0$2;
24457
+ const path$e = require$$1$1;
24187
24458
 
24188
24459
  /**
24189
24460
  * Structured error thrown by compileWidget() when the underlying
@@ -24218,7 +24489,7 @@ function getEsbuildDiagnostics() {
24218
24489
 
24219
24490
  try {
24220
24491
  const pkgJsonPath = require.resolve("esbuild/package.json");
24221
- diagnostics.esbuildPackageDir = path$d.dirname(pkgJsonPath);
24492
+ diagnostics.esbuildPackageDir = path$e.dirname(pkgJsonPath);
24222
24493
  diagnostics.esbuildVersion = commonjsRequire(pkgJsonPath).version;
24223
24494
  } catch (err) {
24224
24495
  diagnostics.esbuildResolveError = err.message;
@@ -24228,15 +24499,15 @@ function getEsbuildDiagnostics() {
24228
24499
  const archPkgJson = require.resolve(
24229
24500
  `${diagnostics.archPackage}/package.json`,
24230
24501
  );
24231
- const archDir = path$d.dirname(archPkgJson);
24502
+ const archDir = path$e.dirname(archPkgJson);
24232
24503
  // esbuild's native binary on macOS/Linux is bin/esbuild;
24233
24504
  // on Windows it's esbuild.exe at the package root.
24234
24505
  const candidate =
24235
24506
  process.platform === "win32"
24236
- ? path$d.join(archDir, "esbuild.exe")
24237
- : path$d.join(archDir, "bin", "esbuild");
24507
+ ? path$e.join(archDir, "esbuild.exe")
24508
+ : path$e.join(archDir, "bin", "esbuild");
24238
24509
  diagnostics.nativeBinaryPath = candidate;
24239
- diagnostics.nativeBinaryExists = fs$9.existsSync(candidate);
24510
+ diagnostics.nativeBinaryExists = fs$a.existsSync(candidate);
24240
24511
  } catch (err) {
24241
24512
  diagnostics.archResolveError = err.message;
24242
24513
  }
@@ -24282,26 +24553,26 @@ async function healthCheck() {
24282
24553
  * @returns {string|null} Path to the widgets/ directory, or null
24283
24554
  */
24284
24555
  function findWidgetsDir$2(widgetPath) {
24285
- const direct = path$d.join(widgetPath, "widgets");
24286
- if (fs$9.existsSync(direct)) {
24556
+ const direct = path$e.join(widgetPath, "widgets");
24557
+ if (fs$a.existsSync(direct)) {
24287
24558
  return direct;
24288
24559
  }
24289
24560
 
24290
24561
  // Check configs/widgets/ (packageZip.js nests .dash.js files here)
24291
- const configsWidgets = path$d.join(widgetPath, "configs", "widgets");
24292
- if (fs$9.existsSync(configsWidgets)) {
24562
+ const configsWidgets = path$e.join(widgetPath, "configs", "widgets");
24563
+ if (fs$a.existsSync(configsWidgets)) {
24293
24564
  return configsWidgets;
24294
24565
  }
24295
24566
 
24296
24567
  // Check configs/ directory (used by packageZip.js for distributed widgets)
24297
- const configs = path$d.join(widgetPath, "configs");
24298
- if (fs$9.existsSync(configs)) {
24568
+ const configs = path$e.join(widgetPath, "configs");
24569
+ if (fs$a.existsSync(configs)) {
24299
24570
  return configs;
24300
24571
  }
24301
24572
 
24302
24573
  // Check one level deeper for nested ZIP extraction
24303
24574
  try {
24304
- const entries = fs$9.readdirSync(widgetPath, { withFileTypes: true });
24575
+ const entries = fs$a.readdirSync(widgetPath, { withFileTypes: true });
24305
24576
  const subdirs = entries.filter(
24306
24577
  (e) =>
24307
24578
  e.isDirectory() &&
@@ -24311,8 +24582,8 @@ function findWidgetsDir$2(widgetPath) {
24311
24582
  );
24312
24583
 
24313
24584
  for (const subdir of subdirs) {
24314
- const nested = path$d.join(widgetPath, subdir.name, "widgets");
24315
- if (fs$9.existsSync(nested)) {
24585
+ const nested = path$e.join(widgetPath, subdir.name, "widgets");
24586
+ if (fs$a.existsSync(nested)) {
24316
24587
  console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
24317
24588
  return nested;
24318
24589
  }
@@ -24346,7 +24617,7 @@ async function compileWidget$1(widgetPath) {
24346
24617
  }
24347
24618
 
24348
24619
  // Discover .dash.js config files
24349
- const files = fs$9.readdirSync(widgetsDir);
24620
+ const files = fs$a.readdirSync(widgetsDir);
24350
24621
  const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
24351
24622
 
24352
24623
  if (dashFiles.length === 0) {
@@ -24360,15 +24631,15 @@ async function compileWidget$1(widgetPath) {
24360
24631
  // Compute relative path from the entry file (in widgetPath) to widgetsDir,
24361
24632
  // since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
24362
24633
  const relWidgetsDir =
24363
- "./" + path$d.relative(widgetPath, widgetsDir).split(path$d.sep).join("/");
24634
+ "./" + path$e.relative(widgetPath, widgetsDir).split(path$e.sep).join("/");
24364
24635
  const imports = [];
24365
24636
  const exportParts = [];
24366
24637
 
24367
24638
  for (const dashFile of dashFiles) {
24368
24639
  const componentName = dashFile.replace(".dash.js", "");
24369
24640
  const componentFile = `${componentName}.js`;
24370
- const componentFilePath = path$d.join(widgetsDir, componentFile);
24371
- const hasComponent = fs$9.existsSync(componentFilePath);
24641
+ const componentFilePath = path$e.join(widgetsDir, componentFile);
24642
+ const hasComponent = fs$a.existsSync(componentFilePath);
24372
24643
 
24373
24644
  // Import the config (always)
24374
24645
  imports.push(
@@ -24400,17 +24671,17 @@ async function compileWidget$1(widgetPath) {
24400
24671
  const entryContent = [...imports, "", ...exportParts, ""].join("\n");
24401
24672
 
24402
24673
  // Write temporary entry file in the widget root
24403
- const entryPath = path$d.join(widgetPath, "__compile_entry.js");
24404
- const distDir = path$d.join(widgetPath, "dist");
24405
- const outPath = path$d.join(distDir, "index.cjs.js");
24674
+ const entryPath = path$e.join(widgetPath, "__compile_entry.js");
24675
+ const distDir = path$e.join(widgetPath, "dist");
24676
+ const outPath = path$e.join(distDir, "index.cjs.js");
24406
24677
 
24407
24678
  try {
24408
24679
  // Ensure dist/ directory exists
24409
- if (!fs$9.existsSync(distDir)) {
24410
- fs$9.mkdirSync(distDir, { recursive: true });
24680
+ if (!fs$a.existsSync(distDir)) {
24681
+ fs$a.mkdirSync(distDir, { recursive: true });
24411
24682
  }
24412
24683
 
24413
- fs$9.writeFileSync(entryPath, entryContent, "utf8");
24684
+ fs$a.writeFileSync(entryPath, entryContent, "utf8");
24414
24685
 
24415
24686
  console.log(
24416
24687
  `[WidgetCompiler] Compiling ${dashFiles.length} component(s) from ${widgetPath}`,
@@ -24466,8 +24737,8 @@ async function compileWidget$1(widgetPath) {
24466
24737
  } finally {
24467
24738
  // Clean up temporary entry file
24468
24739
  try {
24469
- if (fs$9.existsSync(entryPath)) {
24470
- fs$9.unlinkSync(entryPath);
24740
+ if (fs$a.existsSync(entryPath)) {
24741
+ fs$a.unlinkSync(entryPath);
24471
24742
  }
24472
24743
  } catch (cleanupError) {
24473
24744
  // Non-fatal
@@ -24499,8 +24770,8 @@ var widgetCompiler$1 = {
24499
24770
  * Integrates with ComponentManager for automatic registration
24500
24771
  */
24501
24772
 
24502
- const fs$8 = require$$0$2;
24503
- const path$c = require$$1$1;
24773
+ const fs$9 = require$$0$2;
24774
+ const path$d = require$$1$1;
24504
24775
  const vm = require$$2$2;
24505
24776
  const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
24506
24777
 
@@ -24588,14 +24859,14 @@ class DynamicWidgetLoader {
24588
24859
  );
24589
24860
 
24590
24861
  const widgetsDir =
24591
- findWidgetsDir$1(widgetPath) || path$c.join(widgetPath, "widgets");
24592
- const componentPath = path$c.join(widgetsDir, `${componentName}.js`);
24593
- const configPath = path$c.join(widgetsDir, `${componentName}.dash.js`);
24862
+ findWidgetsDir$1(widgetPath) || path$d.join(widgetPath, "widgets");
24863
+ const componentPath = path$d.join(widgetsDir, `${componentName}.js`);
24864
+ const configPath = path$d.join(widgetsDir, `${componentName}.dash.js`);
24594
24865
 
24595
- if (!fs$8.existsSync(componentPath)) {
24866
+ if (!fs$9.existsSync(componentPath)) {
24596
24867
  throw new Error(`Component file not found: ${componentPath}`);
24597
24868
  }
24598
- if (!fs$8.existsSync(configPath)) {
24869
+ if (!fs$9.existsSync(configPath)) {
24599
24870
  throw new Error(`Config file not found: ${configPath}`);
24600
24871
  }
24601
24872
 
@@ -24646,7 +24917,7 @@ class DynamicWidgetLoader {
24646
24917
  */
24647
24918
  async loadConfigFile(configPath) {
24648
24919
  try {
24649
- const source = fs$8.readFileSync(configPath, "utf8");
24920
+ const source = fs$9.readFileSync(configPath, "utf8");
24650
24921
 
24651
24922
  let exportMatch = source.match(/export\s+default\s+({[\s\S]*});?\s*$/);
24652
24923
 
@@ -24714,7 +24985,7 @@ class DynamicWidgetLoader {
24714
24985
  return [];
24715
24986
  }
24716
24987
 
24717
- const files = fs$8.readdirSync(widgetsDir);
24988
+ const files = fs$9.readdirSync(widgetsDir);
24718
24989
  const widgets = new Set();
24719
24990
 
24720
24991
  files.forEach((file) => {
@@ -24790,10 +25061,10 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
24790
25061
  * Test-only. Drops the in-process cache so tests can re-read.
24791
25062
  */
24792
25063
 
24793
- const fs$7 = require$$0$2;
24794
- const path$b = require$$1$1;
25064
+ const fs$8 = require$$0$2;
25065
+ const path$c = require$$1$1;
24795
25066
  const os$1 = require$$2$1;
24796
- const { app: app$6 } = require$$0$1;
25067
+ const { app: app$7 } = require$$0$1;
24797
25068
 
24798
25069
  // Cache: widgetId → permissions | null. Populated lazily on first
24799
25070
  // lookup; invalidated when a widget is installed/uninstalled (the
@@ -24808,7 +25079,7 @@ function expandHome(p) {
24808
25079
  if (typeof p !== "string" || !p) return p;
24809
25080
  if (p === "~") return os$1.homedir();
24810
25081
  if (p.startsWith("~/") || p.startsWith("~\\")) {
24811
- return path$b.join(os$1.homedir(), p.slice(2));
25082
+ return path$c.join(os$1.homedir(), p.slice(2));
24812
25083
  }
24813
25084
  return p;
24814
25085
  }
@@ -24848,10 +25119,10 @@ function parseManifestPermissions(packageJson) {
24848
25119
  */
24849
25120
  function resolveWidgetPackagePath(widgetId) {
24850
25121
  if (typeof widgetId !== "string" || !widgetId) return null;
24851
- const widgetsRoot = path$b.join(app$6.getPath("userData"), "widgets");
25122
+ const widgetsRoot = path$c.join(app$7.getPath("userData"), "widgets");
24852
25123
  // Split scope from name for "@scope/name" form.
24853
25124
  const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
24854
- return path$b.join(widgetsRoot, ...parts, "package.json");
25125
+ return path$c.join(widgetsRoot, ...parts, "package.json");
24855
25126
  }
24856
25127
 
24857
25128
  /**
@@ -24865,12 +25136,12 @@ function resolveWidgetPackagePath(widgetId) {
24865
25136
  function getWidgetMcpPermissions$1(widgetId) {
24866
25137
  if (_cache.has(widgetId)) return _cache.get(widgetId);
24867
25138
  const pkgPath = resolveWidgetPackagePath(widgetId);
24868
- if (!pkgPath || !fs$7.existsSync(pkgPath)) {
25139
+ if (!pkgPath || !fs$8.existsSync(pkgPath)) {
24869
25140
  _cache.set(widgetId, null);
24870
25141
  return null;
24871
25142
  }
24872
25143
  try {
24873
- const raw = fs$7.readFileSync(pkgPath, "utf8");
25144
+ const raw = fs$8.readFileSync(pkgPath, "utf8");
24874
25145
  const pkg = JSON.parse(raw);
24875
25146
  const perms = parseManifestPermissions(pkg);
24876
25147
  _cache.set(widgetId, perms);
@@ -24919,8 +25190,8 @@ var widgetPermissions = {
24919
25190
  * (Slices 1-3) is the actual boundary.
24920
25191
  */
24921
25192
 
24922
- const fs$6 = require$$0$2;
24923
- const path$a = require$$1$1;
25193
+ const fs$7 = require$$0$2;
25194
+ const path$b = require$$1$1;
24924
25195
 
24925
25196
  const SOURCE_EXTENSIONS = new Set([
24926
25197
  ".js",
@@ -24973,24 +25244,24 @@ function readSourceFiles(dir) {
24973
25244
  function walk(current, relBase) {
24974
25245
  let entries;
24975
25246
  try {
24976
- entries = fs$6.readdirSync(current, { withFileTypes: true });
25247
+ entries = fs$7.readdirSync(current, { withFileTypes: true });
24977
25248
  } catch {
24978
25249
  return;
24979
25250
  }
24980
25251
  for (const entry of entries) {
24981
- const abs = path$a.join(current, entry.name);
24982
- const rel = relBase ? path$a.join(relBase, entry.name) : entry.name;
25252
+ const abs = path$b.join(current, entry.name);
25253
+ const rel = relBase ? path$b.join(relBase, entry.name) : entry.name;
24983
25254
  if (entry.isDirectory()) {
24984
25255
  if (skipDirs.has(entry.name)) continue;
24985
25256
  walk(abs, rel);
24986
25257
  } else if (entry.isFile()) {
24987
- const ext = path$a.extname(entry.name).toLowerCase();
25258
+ const ext = path$b.extname(entry.name).toLowerCase();
24988
25259
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
24989
25260
  if (result.length >= SCAN_FILE_LIMIT) return;
24990
25261
  try {
24991
25262
  result.push({
24992
25263
  relPath: rel,
24993
- source: fs$6.readFileSync(abs, "utf8"),
25264
+ source: fs$7.readFileSync(abs, "utf8"),
24994
25265
  });
24995
25266
  } catch {
24996
25267
  // unreadable — skip
@@ -25010,7 +25281,7 @@ function scanForMcpUsage(input) {
25010
25281
  let fileList = [];
25011
25282
  if (input.files && typeof input.files === "object") {
25012
25283
  for (const [relPath, source] of Object.entries(input.files)) {
25013
- const ext = path$a.extname(relPath).toLowerCase();
25284
+ const ext = path$b.extname(relPath).toLowerCase();
25014
25285
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
25015
25286
  if (typeof source !== "string") continue;
25016
25287
  fileList.push({ relPath, source });
@@ -27146,8 +27417,8 @@ var widgetRegistryExports = widgetRegistry$1.exports;
27146
27417
  * - Support two-level browsing: packages (bundles) and widgets within packages
27147
27418
  */
27148
27419
 
27149
- const path$9 = require$$1$1;
27150
- const fs$5 = require$$0$2;
27420
+ const path$a = require$$1$1;
27421
+ const fs$6 = require$$0$2;
27151
27422
  const os = require$$2$1;
27152
27423
  const { toPackageId } = packageId;
27153
27424
  const { getStoredToken: getStoredToken$3 } = registryAuthController$2;
@@ -27183,7 +27454,7 @@ function getCacheKey() {
27183
27454
  * Get the local test registry path for dev mode
27184
27455
  */
27185
27456
  function getTestRegistryPath() {
27186
- return path$9.join(__dirname, "..", "registry", "test-registry-index.json");
27457
+ return path$a.join(__dirname, "..", "registry", "test-registry-index.json");
27187
27458
  }
27188
27459
 
27189
27460
  /**
@@ -27223,12 +27494,12 @@ async function fetchRegistryIndex(forceRefresh = false) {
27223
27494
  if (isDev()) {
27224
27495
  // In dev mode, try local test file first
27225
27496
  const testPath = getTestRegistryPath();
27226
- if (fs$5.existsSync(testPath)) {
27497
+ if (fs$6.existsSync(testPath)) {
27227
27498
  console.log(
27228
27499
  "[RegistryController] Loading test registry from:",
27229
27500
  testPath,
27230
27501
  );
27231
- const raw = fs$5.readFileSync(testPath, "utf8");
27502
+ const raw = fs$6.readFileSync(testPath, "utf8");
27232
27503
  indexData = JSON.parse(raw);
27233
27504
  } else {
27234
27505
  // Fall back to API (supports DASH_REGISTRY_URL as full-URL override)
@@ -27641,7 +27912,7 @@ async function fetchPackageSource(packageName, componentName = null) {
27641
27912
 
27642
27913
  const zip = new AdmZip(buffer);
27643
27914
  const safeName = (pkg.name || "pkg").replace(/[^a-zA-Z0-9-_]/g, "_");
27644
- const tempDir = path$9.join(
27915
+ const tempDir = path$a.join(
27645
27916
  os.tmpdir(),
27646
27917
  `dash-registry-preview-${safeName}-${Date.now()}`,
27647
27918
  );
@@ -27650,13 +27921,13 @@ async function fetchPackageSource(packageName, componentName = null) {
27650
27921
  validateZipEntries(zip, tempDir);
27651
27922
  zip.extractAllTo(tempDir, true);
27652
27923
 
27653
- const widgetsDir = path$9.join(tempDir, "widgets");
27924
+ const widgetsDir = path$a.join(tempDir, "widgets");
27654
27925
  let componentCode = "";
27655
27926
  let configCode = "";
27656
27927
  let widgetName = null;
27657
27928
 
27658
- if (fs$5.existsSync(widgetsDir)) {
27659
- const files = fs$5.readdirSync(widgetsDir);
27929
+ if (fs$6.existsSync(widgetsDir)) {
27930
+ const files = fs$6.readdirSync(widgetsDir);
27660
27931
  const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
27661
27932
  const componentFiles = files.filter(
27662
27933
  (f) => f.endsWith(".js") && !f.endsWith(".dash.js") && f !== "index.js",
@@ -27678,7 +27949,7 @@ async function fetchPackageSource(packageName, componentName = null) {
27678
27949
  if (!configFile) configFile = dashFiles[0];
27679
27950
 
27680
27951
  if (configFile) {
27681
- configCode = fs$5.readFileSync(path$9.join(widgetsDir, configFile), "utf8");
27952
+ configCode = fs$6.readFileSync(path$a.join(widgetsDir, configFile), "utf8");
27682
27953
  widgetName = configFile.replace(/\.dash\.js$/, "");
27683
27954
  }
27684
27955
 
@@ -27692,8 +27963,8 @@ async function fetchPackageSource(packageName, componentName = null) {
27692
27963
  if (!componentFile) componentFile = componentFiles[0];
27693
27964
 
27694
27965
  if (componentFile) {
27695
- componentCode = fs$5.readFileSync(
27696
- path$9.join(widgetsDir, componentFile),
27966
+ componentCode = fs$6.readFileSync(
27967
+ path$a.join(widgetsDir, componentFile),
27697
27968
  "utf8",
27698
27969
  );
27699
27970
  if (!widgetName) widgetName = componentFile.replace(/\.js$/, "");
@@ -27701,16 +27972,16 @@ async function fetchPackageSource(packageName, componentName = null) {
27701
27972
  }
27702
27973
 
27703
27974
  let bundleSource = null;
27704
- const bundlePath = path$9.join(tempDir, "dist", "index.cjs.js");
27705
- if (fs$5.existsSync(bundlePath)) {
27706
- bundleSource = fs$5.readFileSync(bundlePath, "utf8");
27975
+ const bundlePath = path$a.join(tempDir, "dist", "index.cjs.js");
27976
+ if (fs$6.existsSync(bundlePath)) {
27977
+ bundleSource = fs$6.readFileSync(bundlePath, "utf8");
27707
27978
  }
27708
27979
 
27709
27980
  let dashMeta = {};
27710
- const dashPath = path$9.join(tempDir, "dash.json");
27711
- if (fs$5.existsSync(dashPath)) {
27981
+ const dashPath = path$a.join(tempDir, "dash.json");
27982
+ if (fs$6.existsSync(dashPath)) {
27712
27983
  try {
27713
- dashMeta = JSON.parse(fs$5.readFileSync(dashPath, "utf8"));
27984
+ dashMeta = JSON.parse(fs$6.readFileSync(dashPath, "utf8"));
27714
27985
  } catch {
27715
27986
  // Ignore — metadata is optional for preview
27716
27987
  }
@@ -27729,7 +28000,7 @@ async function fetchPackageSource(packageName, componentName = null) {
27729
28000
  };
27730
28001
  } finally {
27731
28002
  try {
27732
- fs$5.rmSync(tempDir, { recursive: true, force: true });
28003
+ fs$6.rmSync(tempDir, { recursive: true, force: true });
27733
28004
  } catch (err) {
27734
28005
  console.warn(
27735
28006
  `[RegistryController] Failed to clean up preview temp dir ${tempDir}:`,
@@ -27749,10 +28020,10 @@ var registryController$3 = {
27749
28020
  fetchPackageSource,
27750
28021
  };
27751
28022
 
27752
- var fs$4 = require$$0$2;
28023
+ var fs$5 = require$$0$2;
27753
28024
  var JSONStream = require$$4;
27754
28025
  const algoliasearch$1 = require$$2$3;
27755
- const path$8 = require$$3$3;
28026
+ const path$9 = require$$3$3;
27756
28027
  const { ensureDirectoryExistence, checkDirectory } = file;
27757
28028
 
27758
28029
  let AlgoliaIndex$1 = class AlgoliaIndex {
@@ -27790,7 +28061,7 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
27790
28061
  var batchNumber = 1;
27791
28062
 
27792
28063
  // create the readStream to parse the large file (json)
27793
- var readStream = fs$4.createReadStream(filepath).pipe(parser);
28064
+ var readStream = fs$5.createReadStream(filepath).pipe(parser);
27794
28065
 
27795
28066
  var batch = [];
27796
28067
 
@@ -27804,7 +28075,7 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
27804
28075
  // lets write to the batch file
27805
28076
  if (countForBatch === batchSize) {
27806
28077
  // write to the batch file
27807
- var writeStream = fs$4.createWriteStream(
28078
+ var writeStream = fs$5.createWriteStream(
27808
28079
  batchFilepath + "/batch_" + batchNumber + ".json",
27809
28080
  );
27810
28081
  writeStream.write(JSON.stringify(batch));
@@ -27855,11 +28126,11 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
27855
28126
  return new Promise((resolve, reject) => {
27856
28127
  try {
27857
28128
  checkDirectory(directoryPath);
27858
- fs$4.readdir(directoryPath, (err, files) => {
28129
+ fs$5.readdir(directoryPath, (err, files) => {
27859
28130
  if (err) reject(err);
27860
28131
  if (files) {
27861
28132
  files.forEach((file) => {
27862
- fs$4.unlinkSync(path$8.join(directoryPath, file));
28133
+ fs$5.unlinkSync(path$9.join(directoryPath, file));
27863
28134
  });
27864
28135
  resolve();
27865
28136
  }
@@ -27878,11 +28149,11 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
27878
28149
  ) {
27879
28150
  try {
27880
28151
  // read the directory...
27881
- const files = await fs$4.readdirSync(batchFilepath);
28152
+ const files = await fs$5.readdirSync(batchFilepath);
27882
28153
  let results = [];
27883
28154
  for (const fileIndex in files) {
27884
28155
  // for each file lets read the file and then push to algolia
27885
- const pathToBatch = path$8.join(batchFilepath, files[fileIndex]);
28156
+ const pathToBatch = path$9.join(batchFilepath, files[fileIndex]);
27886
28157
  const fileContents = await this.readFile(pathToBatch);
27887
28158
  if (fileContents) {
27888
28159
  if ("data" in fileContents && "filepath" in fileContents) {
@@ -27907,7 +28178,7 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
27907
28178
 
27908
28179
  async readFile(filepath) {
27909
28180
  return await new Promise((resolve, reject) => {
27910
- fs$4.readFile(filepath, "utf8", (err, data) => {
28181
+ fs$5.readFile(filepath, "utf8", (err, data) => {
27911
28182
  if (err) {
27912
28183
  reject(err);
27913
28184
  }
@@ -28057,7 +28328,7 @@ var algolia = AlgoliaIndex$1;
28057
28328
  const algoliasearch = require$$2$3;
28058
28329
  const events$3 = events$8;
28059
28330
  const AlgoliaIndex = algolia;
28060
- var fs$3 = require$$0$2;
28331
+ var fs$4 = require$$0$2;
28061
28332
  const { safePath, getAllowedRoots } = safePath_1;
28062
28333
 
28063
28334
  const algoliaController$1 = {
@@ -28166,7 +28437,7 @@ const algoliaController$1 = {
28166
28437
  // init the Algolia Index helper
28167
28438
  const a = new AlgoliaIndex(appId, apiKey, indexName);
28168
28439
  // create the write stream to store the hits
28169
- const writeStream = fs$3.createWriteStream(toFilename);
28440
+ const writeStream = fs$4.createWriteStream(toFilename);
28170
28441
  writeStream.write("[");
28171
28442
 
28172
28443
  let sep = "";
@@ -28419,8 +28690,8 @@ function upsertMenuItem$1(items, menuItem) {
28419
28690
 
28420
28691
  var upsertMenuItem_1 = { upsertMenuItem: upsertMenuItem$1 };
28421
28692
 
28422
- const { app: app$5 } = require$$0$1;
28423
- const path$7 = require$$1$1;
28693
+ const { app: app$6 } = require$$0$1;
28694
+ const path$8 = require$$1$1;
28424
28695
  const { writeFileSync } = require$$0$2;
28425
28696
  const { getFileContents: getFileContents$2 } = file;
28426
28697
  const { upsertMenuItem } = upsertMenuItem_1;
@@ -28431,8 +28702,8 @@ const appName$2 = "Dashboard";
28431
28702
  const menuItemsController$1 = {
28432
28703
  saveMenuItemForApplication: (win, appId, menuItem) => {
28433
28704
  try {
28434
- const filename = path$7.join(
28435
- app$5.getPath("userData"),
28705
+ const filename = path$8.join(
28706
+ app$6.getPath("userData"),
28436
28707
  appName$2,
28437
28708
  appId,
28438
28709
  configFilename$1,
@@ -28450,8 +28721,8 @@ const menuItemsController$1 = {
28450
28721
 
28451
28722
  listMenuItemsForApplication: (win, appId) => {
28452
28723
  try {
28453
- const filename = path$7.join(
28454
- app$5.getPath("userData"),
28724
+ const filename = path$8.join(
28725
+ app$6.getPath("userData"),
28455
28726
  appName$2,
28456
28727
  appId,
28457
28728
  configFilename$1,
@@ -28476,14 +28747,14 @@ const menuItemsController$1 = {
28476
28747
 
28477
28748
  var menuItemsController_1 = menuItemsController$1;
28478
28749
 
28479
- const path$6 = require$$1$1;
28480
- const { app: app$4 } = require$$0$1;
28750
+ const path$7 = require$$1$1;
28751
+ const { app: app$5 } = require$$0$1;
28481
28752
 
28482
28753
  const pluginController$1 = {
28483
28754
  install: (win, packageName, filepath) => {
28484
28755
  try {
28485
- const rootPath = path$6.join(
28486
- app$4.getPath("userData"),
28756
+ const rootPath = path$7.join(
28757
+ app$5.getPath("userData"),
28487
28758
  "plugins",
28488
28759
  packageName,
28489
28760
  );
@@ -45747,7 +46018,7 @@ const completable_js_1 = completable;
45747
46018
  const uriTemplate_js_1 = uriTemplate;
45748
46019
  const toolNameValidation_js_1 = toolNameValidation;
45749
46020
  const mcp_server_js_1 = mcpServer$1;
45750
- const zod_1 = require$$8$2;
46021
+ const zod_1 = require$$8$1;
45751
46022
  /**
45752
46023
  * High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
45753
46024
  * For advanced usage (like sending notifications or setting custom request handlers), use the underlying
@@ -48278,8 +48549,8 @@ streamableHttp.StreamableHTTPServerTransport = StreamableHTTPServerTransport$1;
48278
48549
  * https.createServer({ key, cert }, handler);
48279
48550
  */
48280
48551
 
48281
- const fs$2 = require$$0$2;
48282
- const path$5 = require$$1$1;
48552
+ const fs$3 = require$$0$2;
48553
+ const path$6 = require$$1$1;
48283
48554
  const forge = require$$2$4;
48284
48555
 
48285
48556
  /**
@@ -48288,14 +48559,14 @@ const forge = require$$2$4;
48288
48559
  * @returns {{ cert: string, key: string }} PEM-encoded certificate and private key
48289
48560
  */
48290
48561
  function getOrCreateCert$1(certsDir) {
48291
- const certPath = path$5.join(certsDir, "cert.pem");
48292
- const keyPath = path$5.join(certsDir, "key.pem");
48562
+ const certPath = path$6.join(certsDir, "cert.pem");
48563
+ const keyPath = path$6.join(certsDir, "key.pem");
48293
48564
 
48294
48565
  // Return existing cert if valid
48295
- if (fs$2.existsSync(certPath) && fs$2.existsSync(keyPath)) {
48566
+ if (fs$3.existsSync(certPath) && fs$3.existsSync(keyPath)) {
48296
48567
  try {
48297
- const cert = fs$2.readFileSync(certPath, "utf8");
48298
- const key = fs$2.readFileSync(keyPath, "utf8");
48568
+ const cert = fs$3.readFileSync(certPath, "utf8");
48569
+ const key = fs$3.readFileSync(keyPath, "utf8");
48299
48570
  // Verify cert is not expired
48300
48571
  const parsed = forge.pki.certificateFromPem(cert);
48301
48572
  if (parsed.validity.notAfter > new Date()) {
@@ -48361,9 +48632,9 @@ function getOrCreateCert$1(certsDir) {
48361
48632
  const keyPem = forge.pki.privateKeyToPem(keys.privateKey);
48362
48633
 
48363
48634
  // Write to disk
48364
- fs$2.mkdirSync(certsDir, { recursive: true });
48365
- fs$2.writeFileSync(certPath, certPem, { mode: 0o644 });
48366
- fs$2.writeFileSync(keyPath, keyPem, { mode: 0o600 });
48635
+ fs$3.mkdirSync(certsDir, { recursive: true });
48636
+ fs$3.writeFileSync(certPath, certPem, { mode: 0o644 });
48637
+ fs$3.writeFileSync(keyPath, keyPem, { mode: 0o600 });
48367
48638
 
48368
48639
  console.log(`[tlsCert] Certificate saved to ${certsDir}`);
48369
48640
 
@@ -48380,7 +48651,7 @@ var tlsCert = { getOrCreateCert: getOrCreateCert$1 };
48380
48651
  * for Zod schemas in tool input validation (safeParseAsync).
48381
48652
  */
48382
48653
 
48383
- const z$1 = require$$8$2;
48654
+ const z$1 = require$$8$1;
48384
48655
 
48385
48656
  /**
48386
48657
  * Convert a JSON Schema property definition to a Zod v3 schema.
@@ -48473,7 +48744,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
48473
48744
  * - Rate limiting via token bucket (60 req/min)
48474
48745
  */
48475
48746
 
48476
- const https$1 = require$$10;
48747
+ const https$1 = require$$11;
48477
48748
  const { randomUUID } = require$$3$4;
48478
48749
  const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
48479
48750
  const { McpServer } = mcp;
@@ -48604,7 +48875,7 @@ function registerPrompt$1(promptDef) {
48604
48875
  registeredPrompts.push(promptDef);
48605
48876
  }
48606
48877
 
48607
- const z = require$$8$2;
48878
+ const z = require$$8$1;
48608
48879
  const { jsonSchemaToZod } = jsonSchemaToZod_1;
48609
48880
 
48610
48881
  /**
@@ -50282,7 +50553,7 @@ var themeFromUrlErrors$1 = {
50282
50553
 
50283
50554
  const css = require$$0$9;
50284
50555
  const { Vibrant } = require$$1$6;
50285
- const https = require$$10;
50556
+ const https = require$$11;
50286
50557
  const http = require$$0$8;
50287
50558
  const { URL: URL$1 } = require$$4$1;
50288
50559
  const {
@@ -57023,8 +57294,8 @@ var dashboardConfigUtils$1 = {
57023
57294
  * Handles publishing packages and generating registry URLs.
57024
57295
  */
57025
57296
 
57026
- const fs$1 = require$$0$2;
57027
- const path$4 = require$$1$1;
57297
+ const fs$2 = require$$0$2;
57298
+ const path$5 = require$$1$1;
57028
57299
  const { getStoredToken: getStoredToken$2 } = registryAuthController$2;
57029
57300
 
57030
57301
  const REGISTRY_BASE_URL =
@@ -57050,14 +57321,14 @@ async function publishToRegistry$1(zipPath, manifest) {
57050
57321
 
57051
57322
  try {
57052
57323
  // Read the ZIP file
57053
- const zipBuffer = fs$1.readFileSync(zipPath);
57324
+ const zipBuffer = fs$2.readFileSync(zipPath);
57054
57325
 
57055
57326
  // Create FormData with the ZIP and manifest
57056
57327
  const formData = new FormData();
57057
57328
  formData.append(
57058
57329
  "file",
57059
57330
  new Blob([zipBuffer], { type: "application/zip" }),
57060
- path$4.basename(zipPath),
57331
+ path$5.basename(zipPath),
57061
57332
  );
57062
57333
  formData.append("manifest", JSON.stringify(manifest));
57063
57334
 
@@ -57299,8 +57570,8 @@ function requireWidgetPublishManifest () {
57299
57570
  * Mirrors dashboardConfigController patterns for ZIP creation, manifest generation,
57300
57571
  * and registry interaction.
57301
57572
  */
57302
- const path$3 = require$$1$1;
57303
- const { app: app$3, dialog: dialog$1 } = require$$0$1;
57573
+ const path$4 = require$$1$1;
57574
+ const { app: app$4, dialog: dialog$1 } = require$$0$1;
57304
57575
  const AdmZip$2 = require$$3$2;
57305
57576
 
57306
57577
  const themeController$3 = themeController_1;
@@ -57805,7 +58076,7 @@ async function installThemeFromRegistry$1(win, appId, packageName) {
57805
58076
  // Validate entry path (security: prevent path traversal)
57806
58077
  if (
57807
58078
  themeEntry.entryName.includes("..") ||
57808
- path$3.isAbsolute(themeEntry.entryName)
58079
+ path$4.isAbsolute(themeEntry.entryName)
57809
58080
  ) {
57810
58081
  return {
57811
58082
  success: false,
@@ -58038,8 +58309,8 @@ var themeRegistryController$1 = {
58038
58309
  * applies event wiring. (Import is implemented in DASH-13.)
58039
58310
  */
58040
58311
 
58041
- const { app: app$2, dialog } = require$$0$1;
58042
- const path$2 = require$$1$1;
58312
+ const { app: app$3, dialog } = require$$0$1;
58313
+ const path$3 = require$$1$1;
58043
58314
  const AdmZip$1 = require$$3$2;
58044
58315
  const { getFileContents: getFileContents$1 } = file;
58045
58316
  const {
@@ -58088,8 +58359,8 @@ async function exportDashboardConfig$1(
58088
58359
  ) {
58089
58360
  try {
58090
58361
  // 1. Read workspace from workspaces.json
58091
- const filename = path$2.join(
58092
- app$2.getPath("userData"),
58362
+ const filename = path$3.join(
58363
+ app$3.getPath("userData"),
58093
58364
  appName$1,
58094
58365
  appId,
58095
58366
  configFilename,
@@ -58202,8 +58473,8 @@ async function exportDashboardConfig$1(
58202
58473
 
58203
58474
  const { canceled, filePath } = await dialog.showSaveDialog(win, {
58204
58475
  title: "Export Dashboard as ZIP",
58205
- defaultPath: path$2.join(
58206
- app$2.getPath("desktop"),
58476
+ defaultPath: path$3.join(
58477
+ app$3.getPath("desktop"),
58207
58478
  `dashboard-${sanitizedName}.zip`,
58208
58479
  ),
58209
58480
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
@@ -58268,7 +58539,7 @@ async function selectDashboardFile$1(win) {
58268
58539
 
58269
58540
  // Extract and validate
58270
58541
  const zip = new AdmZip$1(zipPath);
58271
- const tempDir = path$2.join(app$2.getPath("temp"), "dash-import");
58542
+ const tempDir = path$3.join(app$3.getPath("temp"), "dash-import");
58272
58543
  const { validateZipEntries } = widgetRegistryExports;
58273
58544
  validateZipEntries(zip, tempDir);
58274
58545
 
@@ -58378,7 +58649,7 @@ async function importDashboardConfig$1(
58378
58649
  const zip = new AdmZip$1(zipPath);
58379
58650
 
58380
58651
  // Validate ZIP entries for path traversal
58381
- const tempDir = path$2.join(app$2.getPath("temp"), "dash-import");
58652
+ const tempDir = path$3.join(app$3.getPath("temp"), "dash-import");
58382
58653
  const { validateZipEntries } = widgetRegistryExports;
58383
58654
  validateZipEntries(zip, tempDir);
58384
58655
 
@@ -59018,7 +59289,7 @@ async function installDashboardFromRegistry$1(
59018
59289
  const zip = new AdmZip$1(zipBuffer);
59019
59290
 
59020
59291
  // 3. Validate ZIP entries
59021
- const tempDir = path$2.join(app$2.getPath("temp"), "dash-registry-import");
59292
+ const tempDir = path$3.join(app$3.getPath("temp"), "dash-registry-import");
59022
59293
  const { validateZipEntries } = widgetRegistryExports;
59023
59294
  validateZipEntries(zip, tempDir);
59024
59295
 
@@ -59154,8 +59425,8 @@ async function collectDashboardDependencies$1(
59154
59425
  ) {
59155
59426
  try {
59156
59427
  // 1. Read workspace
59157
- const filename = path$2.join(
59158
- app$2.getPath("userData"),
59428
+ const filename = path$3.join(
59429
+ app$3.getPath("userData"),
59159
59430
  appName$1,
59160
59431
  appId,
59161
59432
  configFilename,
@@ -59433,8 +59704,8 @@ async function prepareDashboardForPublish$1(
59433
59704
  const { resolveNextVersion } = requireWidgetPublishManifest();
59434
59705
 
59435
59706
  // 1. Read workspace
59436
- const filename = path$2.join(
59437
- app$2.getPath("userData"),
59707
+ const filename = path$3.join(
59708
+ app$3.getPath("userData"),
59438
59709
  appName$1,
59439
59710
  appId,
59440
59711
  configFilename,
@@ -59711,8 +59982,8 @@ async function prepareDashboardForPublish$1(
59711
59982
  const sanitizedName = manifest.name;
59712
59983
  const { canceled, filePath } = await dialog.showSaveDialog(win, {
59713
59984
  title: "Save Dashboard Package for Registry",
59714
- defaultPath: path$2.join(
59715
- app$2.getPath("desktop"),
59985
+ defaultPath: path$3.join(
59986
+ app$3.getPath("desktop"),
59716
59987
  `dashboard-${sanitizedName}-v${manifest.version}.zip`,
59717
59988
  ),
59718
59989
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
@@ -59879,8 +60150,8 @@ async function checkDashboardUpdatesForApp$1(appId) {
59879
60150
  const { fetchRegistryIndex } = registryController$3;
59880
60151
 
59881
60152
  try {
59882
- const filename = path$2.join(
59883
- app$2.getPath("userData"),
60153
+ const filename = path$3.join(
60154
+ app$3.getPath("userData"),
59884
60155
  appName$1,
59885
60156
  appId,
59886
60157
  configFilename,
@@ -59950,8 +60221,8 @@ function getProviderSetupManifest$1(appId, requiredProviders = []) {
59950
60221
  */
59951
60222
  function getDashboardPublishPreview$1(appId, workspaceId, widgetRegistry = null) {
59952
60223
  try {
59953
- const filename = path$2.join(
59954
- app$2.getPath("userData"),
60224
+ const filename = path$3.join(
60225
+ app$3.getPath("userData"),
59955
60226
  appName$1,
59956
60227
  appId,
59957
60228
  configFilename,
@@ -60259,9 +60530,50 @@ var notificationController_1 = notificationController$2;
60259
60530
  *
60260
60531
  * Uses the `ws` package (installed in dash-electron) for WebSocket clients.
60261
60532
  * Multiple widgets referencing the same provider share a single socket.
60533
+ *
60534
+ * Phase 3 (network domain JIT) gates `connect` by hostname when a
60535
+ * widgetId is supplied. Once a connection exists, `send` /
60536
+ * `disconnect` / `getStatus` / `getAll` operate on the
60537
+ * already-authorized socket and are intentionally NOT re-gated —
60538
+ * connections are designed to be shared across widgets via the
60539
+ * `consumers: Set` field, and re-gating sends would break that.
60262
60540
  */
60263
60541
 
60264
- const WebSocket = require$$0$b;
60542
+ const { app: app$2 } = require$$0$1;
60543
+ const fs$1 = require$$0$2;
60544
+ const path$2 = require$$1$1;
60545
+ const WebSocket = require$$3$5;
60546
+ const {
60547
+ gateNetworkCall,
60548
+ gateNetworkCallWithJit,
60549
+ } = networkGate;
60550
+ const { readEnforceFlag, readJitFlag } = securityFlags;
60551
+
60552
+ function _loadFlags() {
60553
+ try {
60554
+ const settingsPath = path$2.join(
60555
+ app$2.getPath("userData"),
60556
+ "Dashboard",
60557
+ "settings.json",
60558
+ );
60559
+ if (!fs$1.existsSync(settingsPath)) return null;
60560
+ return JSON.parse(fs$1.readFileSync(settingsPath, "utf8"));
60561
+ } catch (_e) {
60562
+ return null;
60563
+ }
60564
+ }
60565
+
60566
+ async function _runNetworkGate(action, widgetId, args) {
60567
+ const settings = _loadFlags();
60568
+ if (!readEnforceFlag(settings)) return { allow: true };
60569
+ if (!widgetId) return { allow: true }; // legacy callers
60570
+ return readJitFlag(settings)
60571
+ ? await gateNetworkCallWithJit(
60572
+ { widgetId, action, args },
60573
+ { enableJit: true },
60574
+ )
60575
+ : gateNetworkCall({ widgetId, action, args });
60576
+ }
60265
60577
 
60266
60578
  /**
60267
60579
  * Active WebSocket connections
@@ -60692,7 +61004,26 @@ const webSocketController$1 = {
60692
61004
  * @param {object} config - { url, headers, subprotocols, credentials }
60693
61005
  * @returns {{ success, providerName, status } | { error, message }}
60694
61006
  */
60695
- connect: async (win, providerName, config) => {
61007
+ connect: async (win, providerName, config, widgetId = null) => {
61008
+ // Phase 3 network gate — fires only when an explicit widgetId is
61009
+ // supplied. The interpolated URL (with credentials substituted) is
61010
+ // what we actually open the socket to, so the gate uses it for
61011
+ // hostname extraction.
61012
+ const interpolatedForGate = config?.credentials
61013
+ ? interpolate(config.url, config.credentials)
61014
+ : config?.url;
61015
+ const gateResult = await _runNetworkGate("wsConnect", widgetId, {
61016
+ url: interpolatedForGate,
61017
+ });
61018
+ if (!gateResult.allow) {
61019
+ return {
61020
+ error: true,
61021
+ message: "network permission gate: " + gateResult.reason,
61022
+ providerName,
61023
+ status: STATUS.ERROR,
61024
+ };
61025
+ }
61026
+
60696
61027
  // 1. Already connected? Return existing connection
60697
61028
  const existing = activeConnections.get(providerName);
60698
61029
  if (existing && existing.status === STATUS.CONNECTED) {
@@ -62728,8 +63059,8 @@ const dataApi$2 = {
62728
63059
  ipcRenderer$m.invoke(READ_JSON, { filepath, objectCount });
62729
63060
  },
62730
63061
 
62731
- readDataFromURL: (url, toFilepath) => {
62732
- ipcRenderer$m.invoke(READ_DATA_URL, { url, toFilepath });
63062
+ readDataFromURL: (url, toFilepath, widgetId = null) => {
63063
+ ipcRenderer$m.invoke(READ_DATA_URL, { url, toFilepath, widgetId });
62733
63064
  },
62734
63065
 
62735
63066
  /*
@@ -64732,8 +65063,8 @@ const webSocketApi$2 = {
64732
65063
  * @param {object} config { url, headers, subprotocols, credentials }
64733
65064
  * @returns {Promise<{ success, providerName, status } | { error, message }>}
64734
65065
  */
64735
- connect: (providerName, config) =>
64736
- ipcRenderer$4.invoke(WS_CONNECT, { providerName, config }),
65066
+ connect: (providerName, config, widgetId = null) =>
65067
+ ipcRenderer$4.invoke(WS_CONNECT, { providerName, config, widgetId }),
64737
65068
 
64738
65069
  /**
64739
65070
  * disconnect