@trops/dash-core 0.1.501 → 0.1.503

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.
@@ -1,40 +1,40 @@
1
1
  'use strict';
2
2
 
3
3
  var require$$0$1 = require('electron');
4
- var require$$1$1 = require('electron-store');
5
- var require$$1$2 = require('path');
4
+ var require$$1$1 = require('path');
6
5
  var require$$0$2 = require('fs');
7
- var require$$6$1 = require('objects-to-csv');
8
- var require$$1$3 = require('readline');
6
+ var require$$8$1 = require('objects-to-csv');
7
+ var require$$1$2 = require('readline');
9
8
  var require$$2 = require('xtreamer');
10
9
  var require$$3$1 = require('xml2js');
11
10
  var require$$4 = require('JSONStream');
12
11
  var require$$5 = require('stream');
13
12
  var require$$6 = require('csv-parser');
14
13
  var require$$0$3 = require('quickjs-emscripten');
15
- var require$$8$1 = require('https');
14
+ var require$$10 = require('https');
16
15
  var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
17
- var require$$1$4 = require('@modelcontextprotocol/sdk/client/stdio.js');
16
+ var require$$1$3 = require('@modelcontextprotocol/sdk/client/stdio.js');
18
17
  var require$$0$4 = require('pkce-challenge');
19
18
  var require$$2$1 = require('os');
20
19
  var require$$12 = require('child_process');
20
+ var require$$0$6 = require('electron-store');
21
21
  var require$$3$2 = require('adm-zip');
22
22
  var require$$4$1 = require('url');
23
23
  var require$$2$2 = require('vm');
24
- var require$$1$5 = require('croner');
24
+ var require$$1$4 = require('croner');
25
25
  var require$$2$3 = require('algoliasearch');
26
26
  var require$$3$3 = require('node:path');
27
- var require$$0$6 = require('openai');
27
+ var require$$0$7 = require('openai');
28
28
  require('live-plugin-manager');
29
- var require$$0$9 = require('@anthropic-ai/sdk');
29
+ var require$$0$a = require('@anthropic-ai/sdk');
30
30
  var require$$3$4 = require('crypto');
31
31
  var require$$8$2 = require('zod');
32
- var require$$0$7 = require('http');
33
- var require$$1$6 = require('http2');
32
+ var require$$0$8 = require('http');
33
+ var require$$1$5 = require('http2');
34
34
  var require$$2$4 = require('node-forge');
35
- var require$$0$8 = require('css');
36
- var require$$1$7 = require('node-vibrant/node');
37
- var require$$0$a = require('ws');
35
+ var require$$0$9 = require('css');
36
+ var require$$1$6 = require('node-vibrant/node');
37
+ var require$$0$b = 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
 
@@ -79,11 +79,11 @@ const SECURE_STORAGE_ENCRYPT_STRING_COMPLETE =
79
79
  const SECURE_STORAGE_ENCRYPT_STRING_ERROR =
80
80
  "secure-storage-encrypt-string-error";
81
81
 
82
- const SECURE_STORE_GET_DATA$1 = "secure-storage-get-data";
82
+ const SECURE_STORE_GET_DATA = "secure-storage-get-data";
83
83
  const SECURE_STORE_GET_DATA_COMPLETE = "secure-storage-get-data-complete";
84
84
  const SECURE_STORE_GET_DATA_ERROR = "secure-storage-get-data-error";
85
85
 
86
- const SECURE_STORE_SET_DATA$1 = "secure-storage-set-data";
86
+ const SECURE_STORE_SET_DATA = "secure-storage-set-data";
87
87
  const SECURE_STORE_SET_DATA_COMPLETE = "secure-storage-set-data-complete";
88
88
  const SECURE_STORE_SET_DATA_ERROR = "secure-storage-set-data-error";
89
89
 
@@ -91,10 +91,10 @@ var secureStorageEvents$1 = {
91
91
  SECURE_STORE_ENCRYPTION_CHECK: SECURE_STORE_ENCRYPTION_CHECK$1,
92
92
  SECURE_STORE_ENCRYPTION_CHECK_COMPLETE,
93
93
  SECURE_STORE_ENCRYPTION_CHECK_ERROR,
94
- SECURE_STORE_SET_DATA: SECURE_STORE_SET_DATA$1,
94
+ SECURE_STORE_SET_DATA,
95
95
  SECURE_STORE_SET_DATA_COMPLETE,
96
96
  SECURE_STORE_SET_DATA_ERROR,
97
- SECURE_STORE_GET_DATA: SECURE_STORE_GET_DATA$1,
97
+ SECURE_STORE_GET_DATA,
98
98
  SECURE_STORE_GET_DATA_COMPLETE,
99
99
  SECURE_STORE_GET_DATA_ERROR,
100
100
  SECURE_STORAGE_ENCRYPT_STRING,
@@ -1044,22 +1044,23 @@ var dialogController$1 = {
1044
1044
  };
1045
1045
 
1046
1046
  /**
1047
- * secureStore
1047
+ * secureStoreController
1048
+ *
1049
+ * Thin wrapper around Electron's `safeStorage` for renderer-side
1050
+ * encryption checks. The `saveData` / `getData` helpers that previously
1051
+ * lived here were unwired (no IPC handler in dash-electron) and
1052
+ * lacked per-widget scoping; they were removed alongside their
1053
+ * widget-facing API entries. See `electron/api/secureStoreApi.js`
1054
+ * and the regression-pin in `secureStoreApi.test.js`.
1055
+ *
1056
+ * Provider credential encryption uses `safeStorage.encryptString` /
1057
+ * `decryptString` directly inside `providerController` — that's the
1058
+ * only internal caller and stays unchanged.
1048
1059
  */
1049
1060
 
1050
1061
  const { safeStorage } = require$$0$1;
1051
- const Store$2 = require$$1$1;
1052
1062
  const events$6 = events$8;
1053
1063
 
1054
- const schema$1 = {
1055
- appId: {
1056
- type: "string",
1057
- },
1058
- apiKey: {
1059
- type: "string",
1060
- },
1061
- };
1062
-
1063
1064
  const isEncryptionAvailable$1 = (win) => {
1064
1065
  const result = safeStorage.isEncryptionAvailable();
1065
1066
  win.webContents.send(events$6.SECURE_STORE_ENCRYPTION_CHECK_COMPLETE, result);
@@ -1075,39 +1076,13 @@ const decryptString = (win, str) => {
1075
1076
  win.webContents.send("secure-storage-decrypt-string-complete", result);
1076
1077
  };
1077
1078
 
1078
- const saveData$1 = (key, value) => {
1079
- try {
1080
- const store = new Store$2({ schema: schema$1 });
1081
- store.set(key, value);
1082
- return getData$1(key);
1083
- } catch (e) {
1084
- return { data: null };
1085
- }
1086
- };
1087
-
1088
- const getData$1 = (key) => {
1089
- try {
1090
- const store = new Store$2({ schema: schema$1 });
1091
- const value = store.get(key);
1092
- if (value) {
1093
- return { [key]: value };
1094
- } else {
1095
- return null;
1096
- }
1097
- } catch (e) {
1098
- return null;
1099
- }
1100
- };
1101
-
1102
1079
  var secureStoreController$1 = {
1103
1080
  isEncryptionAvailable: isEncryptionAvailable$1,
1104
1081
  encryptString,
1105
1082
  decryptString,
1106
- saveData: saveData$1,
1107
- getData: getData$1,
1108
1083
  };
1109
1084
 
1110
- const path$n = require$$1$2;
1085
+ const path$m = require$$1$1;
1111
1086
  const {
1112
1087
  readFileSync,
1113
1088
  writeFileSync: writeFileSync$4,
@@ -1122,10 +1097,10 @@ const {
1122
1097
  lstatSync,
1123
1098
  } = require$$0$2;
1124
1099
 
1125
- function ensureDirectoryExistence$2(filePath) {
1100
+ function ensureDirectoryExistence$1(filePath) {
1126
1101
  try {
1127
1102
  // isDirectory
1128
- var dirname = path$n.dirname(filePath);
1103
+ var dirname = path$m.dirname(filePath);
1129
1104
  // check if the directory exists...return true
1130
1105
  // if not, we can pass in the dirname as the filepath
1131
1106
  // and check each directory recursively.
@@ -1133,7 +1108,7 @@ function ensureDirectoryExistence$2(filePath) {
1133
1108
  return true;
1134
1109
  }
1135
1110
  // recursion...
1136
- ensureDirectoryExistence$2(dirname);
1111
+ ensureDirectoryExistence$1(dirname);
1137
1112
  mkdirSync(dirname);
1138
1113
  } catch (e) {
1139
1114
  console.log("ensure directory " + e.message);
@@ -1179,7 +1154,7 @@ function checkDirectory$1(dir) {
1179
1154
  function getFileContents$8(filepath, defaultReturn = []) {
1180
1155
  try {
1181
1156
  // lets first make sure all is there...
1182
- ensureDirectoryExistence$2(filepath);
1157
+ ensureDirectoryExistence$1(filepath);
1183
1158
 
1184
1159
  // and now lets read the file...
1185
1160
  let fileContents = JSON.stringify(defaultReturn);
@@ -1240,7 +1215,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1240
1215
 
1241
1216
  for (const file of files) {
1242
1217
  if (!excludeFiles.includes(file)) {
1243
- unlinkSync(path$n.join(directory, file), (err) => {
1218
+ unlinkSync(path$m.join(directory, file), (err) => {
1244
1219
  if (err) throw err;
1245
1220
  });
1246
1221
  }
@@ -1250,15 +1225,15 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1250
1225
  }
1251
1226
 
1252
1227
  var file = {
1253
- ensureDirectoryExistence: ensureDirectoryExistence$2,
1228
+ ensureDirectoryExistence: ensureDirectoryExistence$1,
1254
1229
  getFileContents: getFileContents$8,
1255
1230
  writeToFile: writeToFile$3,
1256
1231
  removeFilesFromDirectory,
1257
1232
  checkDirectory: checkDirectory$1,
1258
1233
  };
1259
1234
 
1260
- const { app: app$f } = require$$0$1;
1261
- const path$m = require$$1$2;
1235
+ const { app: app$e } = require$$0$1;
1236
+ const path$l = require$$1$1;
1262
1237
  const { writeFileSync: writeFileSync$3 } = require$$0$2;
1263
1238
  const { getFileContents: getFileContents$7 } = file;
1264
1239
 
@@ -1305,8 +1280,8 @@ const workspaceController$3 = {
1305
1280
  saveWorkspaceForApplication: (win, appId, workspaceObject) => {
1306
1281
  try {
1307
1282
  // filename to the pages file (live pages)
1308
- const filename = path$m.join(
1309
- app$f.getPath("userData"),
1283
+ const filename = path$l.join(
1284
+ app$e.getPath("userData"),
1310
1285
  appName$7,
1311
1286
  appId,
1312
1287
  configFilename$5,
@@ -1354,8 +1329,8 @@ const workspaceController$3 = {
1354
1329
  saveMenuItemsForApplication: (win, appId, menuItems) => {
1355
1330
  try {
1356
1331
  // filename to the workspaces file
1357
- const filename = path$m.join(
1358
- app$f.getPath("userData"),
1332
+ const filename = path$l.join(
1333
+ app$e.getPath("userData"),
1359
1334
  appName$7,
1360
1335
  appId,
1361
1336
  configFilename$5,
@@ -1403,8 +1378,8 @@ const workspaceController$3 = {
1403
1378
  */
1404
1379
  deleteWorkspaceForApplication: (win, appId, workspaceId) => {
1405
1380
  try {
1406
- const filename = path$m.join(
1407
- app$f.getPath("userData"),
1381
+ const filename = path$l.join(
1382
+ app$e.getPath("userData"),
1408
1383
  appName$7,
1409
1384
  appId,
1410
1385
  configFilename$5,
@@ -1437,8 +1412,8 @@ const workspaceController$3 = {
1437
1412
 
1438
1413
  listWorkspacesForApplication: (win, appId) => {
1439
1414
  try {
1440
- const filename = path$m.join(
1441
- app$f.getPath("userData"),
1415
+ const filename = path$l.join(
1416
+ app$e.getPath("userData"),
1442
1417
  appName$7,
1443
1418
  appId,
1444
1419
  configFilename$5,
@@ -1465,8 +1440,8 @@ const workspaceController$3 = {
1465
1440
 
1466
1441
  listMenuItemsForApplication: (win, appId) => {
1467
1442
  try {
1468
- const filename = path$m.join(
1469
- app$f.getPath("userData"),
1443
+ const filename = path$l.join(
1444
+ app$e.getPath("userData"),
1470
1445
  appName$7,
1471
1446
  appId,
1472
1447
  configFilename$5,
@@ -1509,8 +1484,8 @@ const workspaceController$3 = {
1509
1484
 
1510
1485
  var workspaceController_1 = workspaceController$3;
1511
1486
 
1512
- const { app: app$e } = require$$0$1;
1513
- const path$l = require$$1$2;
1487
+ const { app: app$d } = require$$0$1;
1488
+ const path$k = require$$1$1;
1514
1489
  const { writeFileSync: writeFileSync$2 } = require$$0$2;
1515
1490
  const { getFileContents: getFileContents$6 } = file;
1516
1491
 
@@ -1530,8 +1505,8 @@ const themeController$5 = {
1530
1505
  saveThemeForApplication: (win, appId, name, obj) => {
1531
1506
  try {
1532
1507
  // filename to the pages file (live pages)
1533
- const filename = path$l.join(
1534
- app$e.getPath("userData"),
1508
+ const filename = path$k.join(
1509
+ app$d.getPath("userData"),
1535
1510
  appName$6,
1536
1511
  appId,
1537
1512
  configFilename$4,
@@ -1576,8 +1551,8 @@ const themeController$5 = {
1576
1551
  */
1577
1552
  listThemesForApplication: (win, appId) => {
1578
1553
  try {
1579
- const filename = path$l.join(
1580
- app$e.getPath("userData"),
1554
+ const filename = path$k.join(
1555
+ app$d.getPath("userData"),
1581
1556
  appName$6,
1582
1557
  appId,
1583
1558
  configFilename$4,
@@ -1618,8 +1593,8 @@ const themeController$5 = {
1618
1593
  */
1619
1594
  deleteThemeForApplication: (win, appId, themeKey) => {
1620
1595
  try {
1621
- const filename = path$l.join(
1622
- app$e.getPath("userData"),
1596
+ const filename = path$k.join(
1597
+ app$d.getPath("userData"),
1623
1598
  appName$6,
1624
1599
  appId,
1625
1600
  configFilename$4,
@@ -1695,9 +1670,9 @@ var themeController_1 = themeController$5;
1695
1670
  * `/data/`.
1696
1671
  */
1697
1672
 
1698
- const path$k = require$$1$2;
1699
- const fs$f = require$$0$2;
1700
- const { app: app$d } = require$$0$1;
1673
+ const path$j = require$$1$1;
1674
+ const fs$e = require$$0$2;
1675
+ const { app: app$c } = require$$0$1;
1701
1676
 
1702
1677
  const APP_NAME = "Dashboard";
1703
1678
 
@@ -1706,10 +1681,10 @@ const APP_NAME = "Dashboard";
1706
1681
  * @returns {string[]} ordered allowed roots for that category
1707
1682
  */
1708
1683
  function getAllowedRoots$2(category) {
1709
- const userData = app$d.getPath("userData");
1684
+ const userData = app$c.getPath("userData");
1710
1685
  switch (category) {
1711
1686
  case "data": {
1712
- const def = path$k.join(userData, APP_NAME, "data");
1687
+ const def = path$j.join(userData, APP_NAME, "data");
1713
1688
  // The user can configure a custom data directory in
1714
1689
  // Settings → General → Data Directory. If set, that
1715
1690
  // location is ALSO an allowed root. We don't replace the
@@ -1719,13 +1694,13 @@ function getAllowedRoots$2(category) {
1719
1694
  return override ? [def, override] : [def];
1720
1695
  }
1721
1696
  case "themes":
1722
- return [path$k.join(userData, APP_NAME, "themes")];
1697
+ return [path$j.join(userData, APP_NAME, "themes")];
1723
1698
  case "widgets":
1724
- return [path$k.join(userData, "widgets")];
1699
+ return [path$j.join(userData, "widgets")];
1725
1700
  case "plugins":
1726
- return [path$k.join(userData, "plugins")];
1701
+ return [path$j.join(userData, "plugins")];
1727
1702
  case "downloads":
1728
- return [app$d.getPath("downloads")];
1703
+ return [app$c.getPath("downloads")];
1729
1704
  default:
1730
1705
  throw new Error("safePath: unknown allowed-roots category: " + category);
1731
1706
  }
@@ -1740,13 +1715,13 @@ function getAllowedRoots$2(category) {
1740
1715
  */
1741
1716
  function readDataDirectoryFromSettings() {
1742
1717
  try {
1743
- const settingsPath = path$k.join(
1744
- app$d.getPath("userData"),
1718
+ const settingsPath = path$j.join(
1719
+ app$c.getPath("userData"),
1745
1720
  APP_NAME,
1746
1721
  "settings.json",
1747
1722
  );
1748
- if (!fs$f.existsSync(settingsPath)) return undefined;
1749
- const raw = fs$f.readFileSync(settingsPath, "utf8");
1723
+ if (!fs$e.existsSync(settingsPath)) return undefined;
1724
+ const raw = fs$e.readFileSync(settingsPath, "utf8");
1750
1725
  const settings = JSON.parse(raw);
1751
1726
  const dir = settings && settings.dataDirectory;
1752
1727
  if (typeof dir === "string" && dir) return dir;
@@ -1772,18 +1747,18 @@ function safePath$3(requested, allowedRoots) {
1772
1747
  throw new Error("safePath: allowedRoots must be a non-empty array");
1773
1748
  }
1774
1749
 
1775
- const resolved = path$k.resolve(requested);
1750
+ const resolved = path$j.resolve(requested);
1776
1751
 
1777
1752
  // Real-path through symlinks. If the file doesn't exist yet (a
1778
1753
  // create-new operation), real-path the parent so a symlink in the
1779
1754
  // parent chain can't trick us.
1780
1755
  let real = resolved;
1781
1756
  try {
1782
- real = fs$f.realpathSync(resolved);
1757
+ real = fs$e.realpathSync(resolved);
1783
1758
  } catch (_e) {
1784
1759
  try {
1785
- const parent = fs$f.realpathSync(path$k.dirname(resolved));
1786
- real = path$k.join(parent, path$k.basename(resolved));
1760
+ const parent = fs$e.realpathSync(path$j.dirname(resolved));
1761
+ real = path$j.join(parent, path$j.basename(resolved));
1787
1762
  } catch (_e2) {
1788
1763
  // Parent doesn't exist either. Use the resolved-but-not-
1789
1764
  // real path; the caller's mkdirSync will happen inside the
@@ -1795,14 +1770,14 @@ function safePath$3(requested, allowedRoots) {
1795
1770
  for (const root of allowedRoots) {
1796
1771
  let realRoot = root;
1797
1772
  try {
1798
- if (fs$f.existsSync(root)) realRoot = fs$f.realpathSync(root);
1773
+ if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
1799
1774
  } catch (_e) {
1800
1775
  // root doesn't exist or isn't reachable — keep as-is for
1801
1776
  // the comparison below
1802
1777
  }
1803
1778
  // Exact match OR strictly-inside (with separator to prevent
1804
1779
  // /data-evil/ matching /data/).
1805
- if (real === realRoot || real.startsWith(realRoot + path$k.sep)) {
1780
+ if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
1806
1781
  return real;
1807
1782
  }
1808
1783
  }
@@ -1823,6 +1798,795 @@ var safePath_1 = {
1823
1798
  getAllowedRoots: getAllowedRoots$2,
1824
1799
  };
1825
1800
 
1801
+ /**
1802
+ * grantedPermissions.js
1803
+ *
1804
+ * Stores the user's actual MCP permission grants per widget. This is the
1805
+ * Slice-2 enforcement source of truth — separate from the widget's declared
1806
+ * `dash.permissions.mcp` block (which is just a request).
1807
+ *
1808
+ * The runtime gate (permissionGate.gateToolCall) reads from here only.
1809
+ * A widget with a declared manifest but no grant entry has no access:
1810
+ * fail-closed. The user grants permissions at install time (consent modal)
1811
+ * or later in Settings → Privacy & Security.
1812
+ *
1813
+ * Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
1814
+ *
1815
+ * Shape on disk:
1816
+ * {
1817
+ * "@trops/notes-summarizer": {
1818
+ * "servers": {
1819
+ * "filesystem": {
1820
+ * "tools": ["read_file"],
1821
+ * "readPaths": ["/Users/jane/Documents/notes"],
1822
+ * "writePaths": []
1823
+ * }
1824
+ * }
1825
+ * }
1826
+ * }
1827
+ *
1828
+ * Note: paths are stored as-is (already tilde-expanded by the manifest
1829
+ * parser before grants are written). Tests can re-expand via
1830
+ * widgetPermissions.expandHome if they store ~ literals.
1831
+ *
1832
+ * Public API:
1833
+ * getGrant(widgetId) → grant | null
1834
+ * setGrant(widgetId, perms) → boolean
1835
+ * revokeGrant(widgetId) → boolean
1836
+ * revokeServer(widgetId, serverName) → boolean
1837
+ * listAllGrants() → [{ widgetId, granted }]
1838
+ * clearCache() → void // test-only
1839
+ */
1840
+
1841
+ const fs$d = require$$0$2;
1842
+ const path$i = require$$1$1;
1843
+ const { app: app$b } = require$$0$1;
1844
+
1845
+ const FILE_NAME = "widgetMcpGrants.json";
1846
+
1847
+ // In-process cache of the entire grants file. Lazily loaded; invalidated
1848
+ // on every write.
1849
+ let _cache$1 = null;
1850
+
1851
+ function grantsFilePath() {
1852
+ return path$i.join(app$b.getPath("userData"), FILE_NAME);
1853
+ }
1854
+
1855
+ function loadFromDisk() {
1856
+ const p = grantsFilePath();
1857
+ if (!fs$d.existsSync(p)) return {};
1858
+ try {
1859
+ const raw = fs$d.readFileSync(p, "utf8");
1860
+ const parsed = JSON.parse(raw);
1861
+ if (!parsed || typeof parsed !== "object") return {};
1862
+ return parsed;
1863
+ } catch (e) {
1864
+ console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
1865
+ return {};
1866
+ }
1867
+ }
1868
+
1869
+ function ensureCache() {
1870
+ if (_cache$1 === null) _cache$1 = loadFromDisk();
1871
+ return _cache$1;
1872
+ }
1873
+
1874
+ function writeToDisk(data) {
1875
+ const p = grantsFilePath();
1876
+ const tmp = p + ".tmp";
1877
+ // Ensure parent dir exists (userData should already, but be defensive
1878
+ // for first-launch / freshly-cleared profile cases).
1879
+ fs$d.mkdirSync(path$i.dirname(p), { recursive: true });
1880
+ fs$d.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
1881
+ fs$d.renameSync(tmp, p);
1882
+ }
1883
+
1884
+ // Recognized origins for a persisted grant.
1885
+ // "declared" — user approved against the developer's declared
1886
+ // dash.permissions.mcp block at install time.
1887
+ // "discovered" — install-time scanner produced a synthetic manifest
1888
+ // the user approved.
1889
+ // "manual" — user typed entries themselves in
1890
+ // Settings → Privacy & Security with no manifest backing.
1891
+ // "live" — user approved a just-in-time consent prompt at
1892
+ // runtime when a tool call hit the gate without a
1893
+ // matching grant.
1894
+ // Other values are dropped on persist (legacy grants stay null).
1895
+ const ALLOWED_GRANT_ORIGINS = new Set([
1896
+ "declared",
1897
+ "discovered",
1898
+ "manual",
1899
+ "live",
1900
+ ]);
1901
+
1902
+ /**
1903
+ * Sanitize a perms object before persisting. Drops unknown keys, coerces
1904
+ * arrays of strings, and silently ignores malformed servers. Mirrors the
1905
+ * shape produced by parseManifestPermissions so the gate reads either
1906
+ * declared or granted with the same code path.
1907
+ *
1908
+ * Optional `grantOrigin` field is preserved when it's one of the
1909
+ * recognized values; bogus values are dropped.
1910
+ *
1911
+ * Phase 2 (JIT consent for non-MCP domains): also accepts a top-level
1912
+ * `domains` block. Each known domain has its own shape — `domains.fs`
1913
+ * has `readPaths`/`writePaths`, future domains will have their own.
1914
+ * The `servers` block (MCP) and `domains` block coexist on the same
1915
+ * grant; either or both may be present.
1916
+ *
1917
+ * Either `servers` (MCP) or any `domains.*` block is enough to make the
1918
+ * grant non-empty. If neither is present, the grant is rejected as
1919
+ * malformed.
1920
+ */
1921
+ function sanitizePerms(perms) {
1922
+ if (!perms || typeof perms !== "object") return null;
1923
+ const rawServers =
1924
+ perms.servers && typeof perms.servers === "object" ? perms.servers : null;
1925
+ const rawDomains =
1926
+ perms.domains && typeof perms.domains === "object" ? perms.domains : null;
1927
+ if (!rawServers && !rawDomains) return null;
1928
+
1929
+ const out = {};
1930
+
1931
+ if (rawServers) {
1932
+ const servers = {};
1933
+ for (const [name, raw] of Object.entries(rawServers)) {
1934
+ if (!raw || typeof raw !== "object") continue;
1935
+ servers[name] = {
1936
+ tools: Array.isArray(raw.tools)
1937
+ ? raw.tools.filter((t) => typeof t === "string")
1938
+ : [],
1939
+ readPaths: Array.isArray(raw.readPaths)
1940
+ ? raw.readPaths.filter((p) => typeof p === "string")
1941
+ : [],
1942
+ writePaths: Array.isArray(raw.writePaths)
1943
+ ? raw.writePaths.filter((p) => typeof p === "string")
1944
+ : [],
1945
+ };
1946
+ }
1947
+ out.servers = servers;
1948
+ } else {
1949
+ // Always emit `servers` so consumers don't have to null-check it.
1950
+ out.servers = {};
1951
+ }
1952
+
1953
+ if (rawDomains) {
1954
+ const domains = {};
1955
+ for (const [name, raw] of Object.entries(rawDomains)) {
1956
+ if (!raw || typeof raw !== "object") continue;
1957
+ if (name === "fs") {
1958
+ domains.fs = {
1959
+ readPaths: Array.isArray(raw.readPaths)
1960
+ ? raw.readPaths.filter((p) => typeof p === "string")
1961
+ : [],
1962
+ writePaths: Array.isArray(raw.writePaths)
1963
+ ? raw.writePaths.filter((p) => typeof p === "string")
1964
+ : [],
1965
+ };
1966
+ }
1967
+ // Future domains plug in here. Unknown domain names are dropped.
1968
+ }
1969
+ if (Object.keys(domains).length > 0) {
1970
+ out.domains = domains;
1971
+ }
1972
+ }
1973
+
1974
+ if (
1975
+ typeof perms.grantOrigin === "string" &&
1976
+ ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
1977
+ ) {
1978
+ out.grantOrigin = perms.grantOrigin;
1979
+ }
1980
+ return out;
1981
+ }
1982
+
1983
+ function getGrant$3(widgetId) {
1984
+ if (typeof widgetId !== "string" || !widgetId) return null;
1985
+ const all = ensureCache();
1986
+ return all[widgetId] || null;
1987
+ }
1988
+
1989
+ function setGrant$3(widgetId, perms) {
1990
+ if (typeof widgetId !== "string" || !widgetId) return false;
1991
+ const sanitized = sanitizePerms(perms);
1992
+ if (!sanitized) return false;
1993
+ const all = ensureCache();
1994
+ all[widgetId] = sanitized;
1995
+ try {
1996
+ writeToDisk(all);
1997
+ return true;
1998
+ } catch (e) {
1999
+ console.warn(
2000
+ "[grantedPermissions] failed to write grant for " +
2001
+ widgetId +
2002
+ ": " +
2003
+ e.message,
2004
+ );
2005
+ // Roll back the cache entry so memory matches disk.
2006
+ _cache$1 = loadFromDisk();
2007
+ return false;
2008
+ }
2009
+ }
2010
+
2011
+ function revokeGrant$1(widgetId) {
2012
+ if (typeof widgetId !== "string" || !widgetId) return false;
2013
+ const all = ensureCache();
2014
+ if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
2015
+ delete all[widgetId];
2016
+ try {
2017
+ writeToDisk(all);
2018
+ return true;
2019
+ } catch (e) {
2020
+ console.warn(
2021
+ "[grantedPermissions] failed to revoke grant for " +
2022
+ widgetId +
2023
+ ": " +
2024
+ e.message,
2025
+ );
2026
+ _cache$1 = loadFromDisk();
2027
+ return false;
2028
+ }
2029
+ }
2030
+
2031
+ function revokeServer$1(widgetId, serverName) {
2032
+ if (typeof widgetId !== "string" || !widgetId) return false;
2033
+ if (typeof serverName !== "string" || !serverName) return false;
2034
+ const all = ensureCache();
2035
+ const widgetEntry = all[widgetId];
2036
+ if (!widgetEntry || !widgetEntry.servers) return false;
2037
+ if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
2038
+ return false;
2039
+ delete widgetEntry.servers[serverName];
2040
+ try {
2041
+ writeToDisk(all);
2042
+ return true;
2043
+ } catch (e) {
2044
+ console.warn(
2045
+ "[grantedPermissions] failed to revoke server " +
2046
+ serverName +
2047
+ " for " +
2048
+ widgetId +
2049
+ ": " +
2050
+ e.message,
2051
+ );
2052
+ _cache$1 = loadFromDisk();
2053
+ return false;
2054
+ }
2055
+ }
2056
+
2057
+ function listAllGrants$1() {
2058
+ const all = ensureCache();
2059
+ return Object.entries(all).map(([widgetId, granted]) => ({
2060
+ widgetId,
2061
+ granted,
2062
+ }));
2063
+ }
2064
+
2065
+ function clearCache$1() {
2066
+ _cache$1 = null;
2067
+ }
2068
+
2069
+ var grantedPermissions = {
2070
+ getGrant: getGrant$3,
2071
+ setGrant: setGrant$3,
2072
+ revokeGrant: revokeGrant$1,
2073
+ revokeServer: revokeServer$1,
2074
+ listAllGrants: listAllGrants$1,
2075
+ clearCache: clearCache$1,
2076
+ ALLOWED_GRANT_ORIGINS,
2077
+ };
2078
+
2079
+ /**
2080
+ * jitConsent.js
2081
+ *
2082
+ * Just-in-time permission consent for widget→backend calls.
2083
+ *
2084
+ * When a widget hits a gate without an existing grant for the requested
2085
+ * (domain, action, args), the gate calls `requestApproval` which:
2086
+ * 1. Synchronously emits `widget:permission-required` to all
2087
+ * BrowserWindows with a unique requestId.
2088
+ * 2. Returns a Promise that resolves on user response or rejects on
2089
+ * timeout.
2090
+ * 3. Coalesces requests with the same coalescing key so a widget
2091
+ * bursting identical calls produces one prompt, not many.
2092
+ *
2093
+ * The renderer's JitConsentModal subscribes to the event, presents the
2094
+ * user with granularity options (this once / this tool / this tool +
2095
+ * parent dir), and replies via `widget:permission-response` with
2096
+ * `{ requestId, decision }`. main.js wires the IPC handler back to
2097
+ * `_handleResponse`.
2098
+ *
2099
+ * The module is intentionally domain-agnostic in shape — the request
2100
+ * payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
2101
+ * the same machinery. Phase 1 only emits with `domain: "mcp"`.
2102
+ *
2103
+ * Public surface:
2104
+ * requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
2105
+ * _handleResponse({ requestId, decision }) → void (called from main.js IPC)
2106
+ * _resetForTest() → void (test-only)
2107
+ */
2108
+
2109
+ const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
2110
+
2111
+ const REQUEST_CHANNEL = "widget:permission-required";
2112
+ const RESPONSE_CHANNEL = "widget:permission-response";
2113
+ const DEFAULT_TIMEOUT_MS = 60_000;
2114
+
2115
+ // requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
2116
+ const _pending = new Map();
2117
+ // coalesceKey → requestId (so duplicate requests join the live one)
2118
+ const _coalesce = new Map();
2119
+ let _idCounter = 0;
2120
+
2121
+ function nextRequestId() {
2122
+ _idCounter += 1;
2123
+ return `jit-${Date.now()}-${_idCounter}`;
2124
+ }
2125
+
2126
+ /**
2127
+ * Build a coalescing key from the request. Two requests share the same
2128
+ * key iff they're "the same prompt" — same widget, same domain+action,
2129
+ * same target server/tool. Args beyond that (e.g. exact path) DON'T
2130
+ * differentiate; if the user is being asked about read_file already,
2131
+ * approving handles all current paths.
2132
+ */
2133
+ function coalesceKeyOf(req) {
2134
+ if (req.domain === "mcp") {
2135
+ const innerArgs = req.args || {};
2136
+ return [
2137
+ req.widgetId,
2138
+ "mcp",
2139
+ innerArgs.serverName || "",
2140
+ innerArgs.toolName || "",
2141
+ ].join("::");
2142
+ }
2143
+ // Default: domain + action + serialized top-level args
2144
+ return [
2145
+ req.widgetId,
2146
+ req.domain,
2147
+ req.action,
2148
+ JSON.stringify(req.args || {}),
2149
+ ].join("::");
2150
+ }
2151
+
2152
+ function emitEvent(payload) {
2153
+ let wins = [];
2154
+ try {
2155
+ wins = BrowserWindow$2.getAllWindows() || [];
2156
+ } catch {
2157
+ wins = [];
2158
+ }
2159
+ for (const w of wins) {
2160
+ try {
2161
+ w?.webContents?.send?.(REQUEST_CHANNEL, payload);
2162
+ } catch {
2163
+ // best-effort broadcast
2164
+ }
2165
+ }
2166
+ }
2167
+
2168
+ function validateRequest(req) {
2169
+ if (!req || typeof req !== "object") return "invalid request: not an object";
2170
+ if (typeof req.widgetId !== "string" || !req.widgetId)
2171
+ return "invalid request: widgetId required";
2172
+ if (typeof req.domain !== "string" || !req.domain)
2173
+ return "invalid request: domain required";
2174
+ if (typeof req.action !== "string" || !req.action)
2175
+ return "invalid request: action required";
2176
+ return null;
2177
+ }
2178
+
2179
+ /**
2180
+ * Request user approval for an out-of-grant call. Returns a promise
2181
+ * that resolves with the user's decision or rejects on timeout / bad
2182
+ * input.
2183
+ *
2184
+ * decision shape (resolved value):
2185
+ * { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
2186
+ * { approve: false, reason?: string }
2187
+ *
2188
+ * `scope` informs the caller how to write the resulting grant.
2189
+ */
2190
+ function requestApproval$2(req, opts = {}) {
2191
+ const validation = validateRequest(req);
2192
+ if (validation) {
2193
+ return Promise.reject(new Error(validation));
2194
+ }
2195
+
2196
+ const timeoutMs = Number.isFinite(opts.timeoutMs)
2197
+ ? opts.timeoutMs
2198
+ : DEFAULT_TIMEOUT_MS;
2199
+
2200
+ // If a prompt for the same coalesce key is already pending, join it.
2201
+ const key = coalesceKeyOf(req);
2202
+ if (_coalesce.has(key)) {
2203
+ const existingId = _coalesce.get(key);
2204
+ const existing = _pending.get(existingId);
2205
+ if (existing) {
2206
+ return new Promise((resolve, reject) => {
2207
+ existing.joinedResolvers.push({ resolve, reject });
2208
+ });
2209
+ }
2210
+ // Stale coalesce entry; drop and fall through to a fresh request.
2211
+ _coalesce.delete(key);
2212
+ }
2213
+
2214
+ return new Promise((resolve, reject) => {
2215
+ const requestId = nextRequestId();
2216
+ const timeout = setTimeout(() => {
2217
+ const entry = _pending.get(requestId);
2218
+ if (!entry) return;
2219
+ _pending.delete(requestId);
2220
+ _coalesce.delete(entry.coalesceKey);
2221
+ const err = new Error(
2222
+ `JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
2223
+ );
2224
+ reject(err);
2225
+ for (const j of entry.joinedResolvers) j.reject(err);
2226
+ }, timeoutMs);
2227
+
2228
+ _pending.set(requestId, {
2229
+ resolve,
2230
+ reject,
2231
+ timeout,
2232
+ coalesceKey: key,
2233
+ joinedResolvers: [],
2234
+ });
2235
+ _coalesce.set(key, requestId);
2236
+
2237
+ emitEvent({
2238
+ requestId,
2239
+ widgetId: req.widgetId,
2240
+ domain: req.domain,
2241
+ action: req.action,
2242
+ args: req.args || {},
2243
+ });
2244
+ });
2245
+ }
2246
+
2247
+ function _handleResponse({ requestId, decision } = {}) {
2248
+ if (!requestId || typeof requestId !== "string") return;
2249
+ const entry = _pending.get(requestId);
2250
+ if (!entry) return; // unknown request — drop silently
2251
+ clearTimeout(entry.timeout);
2252
+ _pending.delete(requestId);
2253
+ _coalesce.delete(entry.coalesceKey);
2254
+ const safe =
2255
+ decision && typeof decision === "object" ? decision : { approve: false };
2256
+ entry.resolve(safe);
2257
+ for (const j of entry.joinedResolvers) j.resolve(safe);
2258
+ }
2259
+
2260
+ function _resetForTest() {
2261
+ for (const entry of _pending.values()) clearTimeout(entry.timeout);
2262
+ _pending.clear();
2263
+ _coalesce.clear();
2264
+ _idCounter = 0;
2265
+ }
2266
+
2267
+ let _handlersRegistered = false;
2268
+ /**
2269
+ * Wire the renderer→main response IPC. Idempotent.
2270
+ * Call once from main.js alongside other ipcMain setup.
2271
+ */
2272
+ function setupJitConsentHandlers() {
2273
+ if (_handlersRegistered) return;
2274
+ if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
2275
+ ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
2276
+ _handleResponse(payload);
2277
+ });
2278
+ _handlersRegistered = true;
2279
+ }
2280
+
2281
+ var jitConsent$1 = {
2282
+ requestApproval: requestApproval$2,
2283
+ setupJitConsentHandlers,
2284
+ _handleResponse,
2285
+ _resetForTest,
2286
+ REQUEST_CHANNEL,
2287
+ RESPONSE_CHANNEL,
2288
+ DEFAULT_TIMEOUT_MS,
2289
+ };
2290
+
2291
+ /**
2292
+ * fsGate.js
2293
+ *
2294
+ * Per-widget gate for `mainApi.data.*` IPC handlers (Phase 2 of JIT
2295
+ * consent). Same shape as `electron/mcp/permissionGate.js` but for the
2296
+ * filesystem domain — saveToFile/readFromFile in dataController.
2297
+ *
2298
+ * The gate evaluates against the widget's persisted grant under
2299
+ * `grant.domains.fs.{readPaths,writePaths}`. The existing `safePath()`
2300
+ * containment in dataController is unchanged — that constrains paths
2301
+ * to the userData/Dashboard/data dir; this gate adds per-widget
2302
+ * identity scoping on top, so two widgets can't read each other's
2303
+ * files even though both are inside the data dir.
2304
+ *
2305
+ * Action → read/write classification by name. Read tools may match
2306
+ * either readPaths or writePaths (write access implies read); write
2307
+ * tools must match writePaths only.
2308
+ *
2309
+ * Filename matching:
2310
+ * - Exact match
2311
+ * - "*" wildcard in the grant matches any filename (escape hatch
2312
+ * for users who want broad widget access; surfaced in the JIT
2313
+ * modal as "no path scope — risky")
2314
+ *
2315
+ * Path-traversal protection lives in safePath; the gate doesn't
2316
+ * re-check it. The gate is purely an identity+filename allowlist on
2317
+ * top of safePath's containment.
2318
+ *
2319
+ * JIT escalation: when the runtime calls `gateFsCallWithJit` and the
2320
+ * gate denies for "no fs permissions granted", a permission-required
2321
+ * IPC fires and the user's response is merged into the persisted
2322
+ * grant. Other denial reasons (filename not in allowlist, write to a
2323
+ * read-only entry) stay synchronous.
2324
+ */
2325
+
2326
+ const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
2327
+ const { requestApproval: requestApproval$1 } = jitConsent$1;
2328
+
2329
+ // Action names treated as writes. Anything not in this set is a read.
2330
+ // Conservative — when in doubt, classify as a read so write-protected
2331
+ // grants don't accidentally allow writes.
2332
+ const WRITE_ACTIONS = new Set([
2333
+ "saveToFile",
2334
+ "saveData", // future-proof: the renderer-facing API name
2335
+ "convertJsonToCsvFile",
2336
+ "parseXMLStream",
2337
+ "parseCSVStream",
2338
+ "readDataFromURL", // writes to toFilepath despite the name
2339
+ "transformFile",
2340
+ ]);
2341
+
2342
+ function isFsWriteAction(action) {
2343
+ return WRITE_ACTIONS.has(action);
2344
+ }
2345
+
2346
+ function _isNoGrantDenial$1(reason) {
2347
+ return (
2348
+ typeof reason === "string" && /no fs permissions granted/i.test(reason)
2349
+ );
2350
+ }
2351
+
2352
+ function _filenameMatches(filename, allowedList) {
2353
+ if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
2354
+ if (allowedList.includes("*")) return true;
2355
+ return allowedList.includes(filename);
2356
+ }
2357
+
2358
+ /**
2359
+ * Synchronous gate evaluation.
2360
+ * @returns {{ allow: true } | { allow: false, reason: string }}
2361
+ */
2362
+ function gateFsCall$1({ widgetId, action, args }) {
2363
+ if (!widgetId) {
2364
+ return {
2365
+ allow: false,
2366
+ reason: "no widgetId supplied; cannot determine fs permissions",
2367
+ };
2368
+ }
2369
+
2370
+ const filename = args && typeof args === "object" ? args.filename : null;
2371
+ if (typeof filename !== "string" || !filename) {
2372
+ return {
2373
+ allow: false,
2374
+ reason:
2375
+ "fs gate: action '" +
2376
+ action +
2377
+ "' requires args.filename (got: " +
2378
+ JSON.stringify(filename) +
2379
+ ")",
2380
+ };
2381
+ }
2382
+
2383
+ const grant = getGrant$2(widgetId);
2384
+ const fsPerms = grant && grant.domains && grant.domains.fs;
2385
+ if (!fsPerms) {
2386
+ return {
2387
+ allow: false,
2388
+ reason:
2389
+ "widget '" +
2390
+ widgetId +
2391
+ "' has no fs permissions granted; user must approve at runtime or in Settings → Privacy & Security",
2392
+ };
2393
+ }
2394
+
2395
+ const isWrite = isFsWriteAction(action);
2396
+
2397
+ if (isWrite) {
2398
+ if (!Array.isArray(fsPerms.writePaths) || fsPerms.writePaths.length === 0) {
2399
+ return {
2400
+ allow: false,
2401
+ reason:
2402
+ "fs gate: widget '" +
2403
+ widgetId +
2404
+ "' has no writePaths granted for action '" +
2405
+ action +
2406
+ "'",
2407
+ };
2408
+ }
2409
+ if (!_filenameMatches(filename, fsPerms.writePaths)) {
2410
+ return {
2411
+ allow: false,
2412
+ reason:
2413
+ "fs gate: filename '" +
2414
+ filename +
2415
+ "' rejected — not in allowed writePaths for widget '" +
2416
+ widgetId +
2417
+ "'",
2418
+ };
2419
+ }
2420
+ return { allow: true };
2421
+ }
2422
+
2423
+ // Read action — may use readPaths OR writePaths (write implies read)
2424
+ if (
2425
+ _filenameMatches(filename, fsPerms.readPaths) ||
2426
+ _filenameMatches(filename, fsPerms.writePaths)
2427
+ ) {
2428
+ return { allow: true };
2429
+ }
2430
+ return {
2431
+ allow: false,
2432
+ reason:
2433
+ "fs gate: filename '" +
2434
+ filename +
2435
+ "' rejected — not in allowed readPaths or writePaths for widget '" +
2436
+ widgetId +
2437
+ "'",
2438
+ };
2439
+ }
2440
+
2441
+ /**
2442
+ * Merge an approved JIT decision's grant into the widget's existing
2443
+ * grant under domains.fs. Same shape as permissionGate._mergeGrant
2444
+ * but scoped to fs.
2445
+ */
2446
+ function _mergeFsGrant(current, addition) {
2447
+ const out = {
2448
+ grantOrigin: addition.grantOrigin || current?.grantOrigin || null,
2449
+ servers: { ...(current?.servers || {}) },
2450
+ domains: { ...(current?.domains || {}) },
2451
+ };
2452
+ const additionFs = addition?.domains?.fs;
2453
+ if (additionFs) {
2454
+ const existingFs = out.domains.fs || { readPaths: [], writePaths: [] };
2455
+ out.domains.fs = {
2456
+ readPaths: [
2457
+ ...new Set([
2458
+ ...(existingFs.readPaths || []),
2459
+ ...(Array.isArray(additionFs.readPaths) ? additionFs.readPaths : []),
2460
+ ]),
2461
+ ],
2462
+ writePaths: [
2463
+ ...new Set([
2464
+ ...(existingFs.writePaths || []),
2465
+ ...(Array.isArray(additionFs.writePaths)
2466
+ ? additionFs.writePaths
2467
+ : []),
2468
+ ]),
2469
+ ],
2470
+ };
2471
+ }
2472
+ return out;
2473
+ }
2474
+
2475
+ /**
2476
+ * Async gate that escalates "no fs grant" denials to a JIT consent
2477
+ * prompt when `opts.enableJit` is true. On approval, merges the
2478
+ * decision's grant blob into the persisted grant and re-evaluates.
2479
+ */
2480
+ async function gateFsCallWithJit$1(req, opts = {}) {
2481
+ const initial = gateFsCall$1(req);
2482
+ if (initial.allow) return initial;
2483
+ if (!opts.enableJit) return initial;
2484
+ if (!_isNoGrantDenial$1(initial.reason)) return initial;
2485
+
2486
+ let decision;
2487
+ try {
2488
+ decision = await requestApproval$1(
2489
+ {
2490
+ widgetId: req.widgetId,
2491
+ domain: "fs",
2492
+ action: req.action,
2493
+ args: req.args || {},
2494
+ },
2495
+ { timeoutMs: opts.timeoutMs },
2496
+ );
2497
+ } catch (e) {
2498
+ return {
2499
+ allow: false,
2500
+ reason:
2501
+ "JIT consent " +
2502
+ (e && e.message ? e.message : "failed") +
2503
+ "; original denial: " +
2504
+ initial.reason,
2505
+ };
2506
+ }
2507
+
2508
+ if (!decision || decision.approve !== true) {
2509
+ return {
2510
+ allow: false,
2511
+ reason:
2512
+ "user declined JIT consent for widget '" +
2513
+ req.widgetId +
2514
+ "' calling fs '" +
2515
+ req.action +
2516
+ "'",
2517
+ };
2518
+ }
2519
+
2520
+ const filename = req.args?.filename || "*";
2521
+ const isWrite = isFsWriteAction(req.action);
2522
+ const addition =
2523
+ decision.granted && typeof decision.granted === "object"
2524
+ ? decision.granted
2525
+ : {
2526
+ grantOrigin: "live",
2527
+ domains: {
2528
+ fs: {
2529
+ readPaths: !isWrite ? [filename] : [],
2530
+ writePaths: isWrite ? [filename] : [],
2531
+ },
2532
+ },
2533
+ };
2534
+ addition.grantOrigin = "live";
2535
+
2536
+ try {
2537
+ const current = getGrant$2(req.widgetId);
2538
+ const merged = _mergeFsGrant(current, addition);
2539
+ setGrant$2(req.widgetId, merged);
2540
+ } catch (e) {
2541
+ return {
2542
+ allow: false,
2543
+ reason:
2544
+ "JIT consent: failed to persist fs grant: " +
2545
+ (e && e.message ? e.message : String(e)),
2546
+ };
2547
+ }
2548
+
2549
+ return gateFsCall$1(req);
2550
+ }
2551
+
2552
+ var fsGate = {
2553
+ gateFsCall: gateFsCall$1,
2554
+ gateFsCallWithJit: gateFsCallWithJit$1,
2555
+ isFsWriteAction,
2556
+ WRITE_ACTIONS,
2557
+ };
2558
+
2559
+ /**
2560
+ * securityFlags.js
2561
+ *
2562
+ * Centralized readers for the two boolean security flags that gate the
2563
+ * MCP allowlist stack:
2564
+ * - security.enforceWidgetMcpPermissions
2565
+ * - security.enableJitConsent
2566
+ *
2567
+ * **Default semantics: ON.** A missing settings.json, a missing
2568
+ * `security` block, or an undefined field all yield `true`. Only an
2569
+ * explicit `false` opts out. This is intentional — the security stack
2570
+ * is on by default; users have to actively disable it. The
2571
+ * Privacy & Security panel surfaces the toggles + a confirm-on-disable
2572
+ * dialog so the disable path is deliberate.
2573
+ *
2574
+ * The readers are pure functions of a settings object so the
2575
+ * default-on semantics are pinned by unit tests without touching the
2576
+ * filesystem. The callers in mcpController.js wrap these with
2577
+ * settings.json IO.
2578
+ */
2579
+
2580
+ function readEnforceFlag$2(settings) {
2581
+ return settings?.security?.enforceWidgetMcpPermissions !== false;
2582
+ }
2583
+
2584
+ function readJitFlag$2(settings) {
2585
+ return settings?.security?.enableJitConsent !== false;
2586
+ }
2587
+
2588
+ var securityFlags = { readEnforceFlag: readEnforceFlag$2, readJitFlag: readJitFlag$2 };
2589
+
1826
2590
  /**
1827
2591
  * safeJsExecutor.js
1828
2592
  *
@@ -1866,192 +2630,201 @@ var safePath_1 = {
1866
2630
  * while QuickJS is executing synchronously.
1867
2631
  */
1868
2632
 
1869
- // quickjs-emscripten is loaded lazily inside getModule() rather than at
1870
- // module top. Reason: when transform.js (which lives in the same dir)
1871
- // does require("./safeJsExecutor") and rollup-plugin-commonjs sees a
1872
- // statically-required external (`quickjs-emscripten`) inside that file,
1873
- // it can mark the relative import as transitive-external and then fail
1874
- // to resolve back. Deferring the require breaks the static-analysis
1875
- // chain so the rollup build resolves cleanly.
1876
- const DEFAULT_TIMEOUT_MS$1 = 1000;
1877
- const DEFAULT_MEMORY_BYTES = 32 * 1024 * 1024; // 32 MB
2633
+ var safeJsExecutor;
2634
+ var hasRequiredSafeJsExecutor;
2635
+
2636
+ function requireSafeJsExecutor () {
2637
+ if (hasRequiredSafeJsExecutor) return safeJsExecutor;
2638
+ hasRequiredSafeJsExecutor = 1;
2639
+
2640
+ // quickjs-emscripten is loaded lazily inside getModule() rather than at
2641
+ // module top. Reason: when transform.js (which lives in the same dir)
2642
+ // does require("./safeJsExecutor") and rollup-plugin-commonjs sees a
2643
+ // statically-required external (`quickjs-emscripten`) inside that file,
2644
+ // it can mark the relative import as transitive-external and then fail
2645
+ // to resolve back. Deferring the require breaks the static-analysis
2646
+ // chain so the rollup build resolves cleanly.
2647
+ const DEFAULT_TIMEOUT_MS = 1000;
2648
+ const DEFAULT_MEMORY_BYTES = 32 * 1024 * 1024; // 32 MB
2649
+
2650
+ let _modulePromise = null;
2651
+ function getModule() {
2652
+ if (!_modulePromise) {
2653
+ const { getQuickJS } = require$$0$3;
2654
+ _modulePromise = getQuickJS();
2655
+ }
2656
+ return _modulePromise;
2657
+ }
1878
2658
 
1879
- let _modulePromise = null;
1880
- function getModule() {
1881
- if (!_modulePromise) {
1882
- const { getQuickJS } = require$$0$3;
1883
- _modulePromise = getQuickJS();
1884
- }
1885
- return _modulePromise;
1886
- }
2659
+ function injectInputs(vm, args, inputs) {
2660
+ if (!Array.isArray(args) || !Array.isArray(inputs)) {
2661
+ throw new Error(
2662
+ "safeJsExecutor: args and inputs must be arrays of equal length",
2663
+ );
2664
+ }
2665
+ if (args.length !== inputs.length) {
2666
+ throw new Error("safeJsExecutor: args.length must equal inputs.length");
2667
+ }
2668
+ for (let i = 0; i < args.length; i++) {
2669
+ const name = args[i];
2670
+ if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
2671
+ throw new Error("safeJsExecutor: arg names must be valid JS identifiers");
2672
+ }
2673
+ const json = JSON.stringify(inputs[i]);
2674
+ // Use evalCode to materialize the JSON-typed value inside the
2675
+ // VM. For undefined inputs, JSON.stringify returns undefined
2676
+ // (not a string) — fall through to evaluating the literal
2677
+ // `undefined`.
2678
+ const literal = json === undefined ? "undefined" : json;
2679
+ const result = vm.evalCode(`(${literal})`);
2680
+ if (result.error) {
2681
+ const err = vm.dump(result.error);
2682
+ result.error.dispose();
2683
+ throw new Error(
2684
+ `safeJsExecutor: failed to inject "${name}": ${
2685
+ err && err.message ? err.message : err
2686
+ }`,
2687
+ );
2688
+ }
2689
+ vm.setProp(vm.global, name, result.value);
2690
+ result.value.dispose();
2691
+ }
2692
+ }
1887
2693
 
1888
- function injectInputs(vm, args, inputs) {
1889
- if (!Array.isArray(args) || !Array.isArray(inputs)) {
1890
- throw new Error(
1891
- "safeJsExecutor: args and inputs must be arrays of equal length",
1892
- );
1893
- }
1894
- if (args.length !== inputs.length) {
1895
- throw new Error("safeJsExecutor: args.length must equal inputs.length");
1896
- }
1897
- for (let i = 0; i < args.length; i++) {
1898
- const name = args[i];
1899
- if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
1900
- throw new Error("safeJsExecutor: arg names must be valid JS identifiers");
1901
- }
1902
- const json = JSON.stringify(inputs[i]);
1903
- // Use evalCode to materialize the JSON-typed value inside the
1904
- // VM. For undefined inputs, JSON.stringify returns undefined
1905
- // (not a string) — fall through to evaluating the literal
1906
- // `undefined`.
1907
- const literal = json === undefined ? "undefined" : json;
1908
- const result = vm.evalCode(`(${literal})`);
1909
- if (result.error) {
1910
- const err = vm.dump(result.error);
1911
- result.error.dispose();
1912
- throw new Error(
1913
- `safeJsExecutor: failed to inject "${name}": ${
1914
- err && err.message ? err.message : err
1915
- }`,
1916
- );
1917
- }
1918
- vm.setProp(vm.global, name, result.value);
1919
- result.value.dispose();
1920
- }
1921
- }
2694
+ function setDeadline(vm, timeoutMs) {
2695
+ const deadline = Date.now() + Math.max(1, timeoutMs);
2696
+ vm.runtime.setInterruptHandler(() => Date.now() > deadline);
2697
+ }
1922
2698
 
1923
- function setDeadline(vm, timeoutMs) {
1924
- const deadline = Date.now() + Math.max(1, timeoutMs);
1925
- vm.runtime.setInterruptHandler(() => Date.now() > deadline);
1926
- }
2699
+ function buildWrappedBody(args, body) {
2700
+ // Wrap the user body in an IIFE so `return` works at the body level
2701
+ // (matching the dynamic-function-constructor semantics this is
2702
+ // replacing).
2703
+ return `(function(${args.join(",")}){${body}})(${args.join(",")})`;
2704
+ }
1927
2705
 
1928
- function buildWrappedBody(args, body) {
1929
- // Wrap the user body in an IIFE so `return` works at the body level
1930
- // (matching the dynamic-function-constructor semantics this is
1931
- // replacing).
1932
- return `(function(${args.join(",")}){${body}})(${args.join(",")})`;
1933
- }
2706
+ async function runOnce({
2707
+ body,
2708
+ args = [],
2709
+ inputs = [],
2710
+ timeoutMs = DEFAULT_TIMEOUT_MS,
2711
+ memoryBytes = DEFAULT_MEMORY_BYTES,
2712
+ }) {
2713
+ if (typeof body !== "string" || !body.trim()) {
2714
+ return { error: "body must be a non-empty string" };
2715
+ }
2716
+ const QuickJS = await getModule();
2717
+ const vm = QuickJS.newContext();
2718
+ try {
2719
+ vm.runtime.setMemoryLimit(memoryBytes);
2720
+ vm.runtime.setMaxStackSize(1024 * 1024);
2721
+ setDeadline(vm, timeoutMs);
1934
2722
 
1935
- async function runOnce({
1936
- body,
1937
- args = [],
1938
- inputs = [],
1939
- timeoutMs = DEFAULT_TIMEOUT_MS$1,
1940
- memoryBytes = DEFAULT_MEMORY_BYTES,
1941
- }) {
1942
- if (typeof body !== "string" || !body.trim()) {
1943
- return { error: "body must be a non-empty string" };
1944
- }
1945
- const QuickJS = await getModule();
1946
- const vm = QuickJS.newContext();
1947
- try {
1948
- vm.runtime.setMemoryLimit(memoryBytes);
1949
- vm.runtime.setMaxStackSize(1024 * 1024);
1950
- setDeadline(vm, timeoutMs);
2723
+ injectInputs(vm, args, inputs);
2724
+ const wrapped = buildWrappedBody(args, body);
2725
+ const result = vm.evalCode(wrapped);
1951
2726
 
1952
- injectInputs(vm, args, inputs);
1953
- const wrapped = buildWrappedBody(args, body);
1954
- const result = vm.evalCode(wrapped);
2727
+ if (result.error) {
2728
+ const err = vm.dump(result.error);
2729
+ result.error.dispose();
2730
+ return {
2731
+ error:
2732
+ err && err.message
2733
+ ? String(err.message)
2734
+ : typeof err === "string"
2735
+ ? err
2736
+ : JSON.stringify(err),
2737
+ };
2738
+ }
2739
+ const value = vm.dump(result.value);
2740
+ result.value.dispose();
2741
+ return { value };
2742
+ } catch (e) {
2743
+ return { error: e && e.message ? e.message : String(e) };
2744
+ } finally {
2745
+ vm.dispose();
2746
+ }
2747
+ }
1955
2748
 
1956
- if (result.error) {
1957
- const err = vm.dump(result.error);
1958
- result.error.dispose();
1959
- return {
1960
- error:
1961
- err && err.message
1962
- ? String(err.message)
1963
- : typeof err === "string"
1964
- ? err
1965
- : JSON.stringify(err),
1966
- };
1967
- }
1968
- const value = vm.dump(result.value);
1969
- result.value.dispose();
1970
- return { value };
1971
- } catch (e) {
1972
- return { error: e && e.message ? e.message : String(e) };
1973
- } finally {
1974
- vm.dispose();
1975
- }
1976
- }
2749
+ async function createCompiled({
2750
+ body,
2751
+ args = [],
2752
+ memoryBytes = DEFAULT_MEMORY_BYTES,
2753
+ }) {
2754
+ if (typeof body !== "string" || !body.trim()) {
2755
+ throw new Error("body must be a non-empty string");
2756
+ }
2757
+ const QuickJS = await getModule();
2758
+ const vm = QuickJS.newContext();
2759
+ vm.runtime.setMemoryLimit(memoryBytes);
2760
+ vm.runtime.setMaxStackSize(1024 * 1024);
2761
+
2762
+ // Define the user function once on the VM globals so subsequent
2763
+ // run() calls can invoke it with fresh args without re-parsing.
2764
+ const define = vm.evalCode(
2765
+ `globalThis.__userFn = function(${args.join(",")}){${body}};`,
2766
+ );
2767
+ if (define.error) {
2768
+ const err = vm.dump(define.error);
2769
+ define.error.dispose();
2770
+ vm.dispose();
2771
+ throw new Error(
2772
+ "safeJsExecutor: compile failed: " +
2773
+ (err && err.message ? err.message : JSON.stringify(err)),
2774
+ );
2775
+ }
2776
+ define.value.dispose();
1977
2777
 
1978
- async function createCompiled({
1979
- body,
1980
- args = [],
1981
- memoryBytes = DEFAULT_MEMORY_BYTES,
1982
- }) {
1983
- if (typeof body !== "string" || !body.trim()) {
1984
- throw new Error("body must be a non-empty string");
1985
- }
1986
- const QuickJS = await getModule();
1987
- const vm = QuickJS.newContext();
1988
- vm.runtime.setMemoryLimit(memoryBytes);
1989
- vm.runtime.setMaxStackSize(1024 * 1024);
1990
-
1991
- // Define the user function once on the VM globals so subsequent
1992
- // run() calls can invoke it with fresh args without re-parsing.
1993
- const define = vm.evalCode(
1994
- `globalThis.__userFn = function(${args.join(",")}){${body}};`,
1995
- );
1996
- if (define.error) {
1997
- const err = vm.dump(define.error);
1998
- define.error.dispose();
1999
- vm.dispose();
2000
- throw new Error(
2001
- "safeJsExecutor: compile failed: " +
2002
- (err && err.message ? err.message : JSON.stringify(err)),
2003
- );
2004
- }
2005
- define.value.dispose();
2778
+ let disposed = false;
2006
2779
 
2007
- let disposed = false;
2780
+ return {
2781
+ run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS) {
2782
+ if (disposed) {
2783
+ return { error: "executor already disposed" };
2784
+ }
2785
+ try {
2786
+ setDeadline(vm, timeoutMs);
2787
+ const argList = inputs
2788
+ .map((v) => (v === undefined ? "undefined" : JSON.stringify(v)))
2789
+ .join(",");
2790
+ const result = vm.evalCode(`__userFn(${argList})`);
2791
+ if (result.error) {
2792
+ const err = vm.dump(result.error);
2793
+ result.error.dispose();
2794
+ return {
2795
+ error:
2796
+ err && err.message
2797
+ ? String(err.message)
2798
+ : typeof err === "string"
2799
+ ? err
2800
+ : JSON.stringify(err),
2801
+ };
2802
+ }
2803
+ const value = vm.dump(result.value);
2804
+ result.value.dispose();
2805
+ return { value };
2806
+ } catch (e) {
2807
+ return { error: e && e.message ? e.message : String(e) };
2808
+ }
2809
+ },
2810
+ dispose() {
2811
+ if (!disposed) {
2812
+ disposed = true;
2813
+ vm.dispose();
2814
+ }
2815
+ },
2816
+ };
2817
+ }
2008
2818
 
2009
- return {
2010
- run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS$1) {
2011
- if (disposed) {
2012
- return { error: "executor already disposed" };
2013
- }
2014
- try {
2015
- setDeadline(vm, timeoutMs);
2016
- const argList = inputs
2017
- .map((v) => (v === undefined ? "undefined" : JSON.stringify(v)))
2018
- .join(",");
2019
- const result = vm.evalCode(`__userFn(${argList})`);
2020
- if (result.error) {
2021
- const err = vm.dump(result.error);
2022
- result.error.dispose();
2023
- return {
2024
- error:
2025
- err && err.message
2026
- ? String(err.message)
2027
- : typeof err === "string"
2028
- ? err
2029
- : JSON.stringify(err),
2030
- };
2031
- }
2032
- const value = vm.dump(result.value);
2033
- result.value.dispose();
2034
- return { value };
2035
- } catch (e) {
2036
- return { error: e && e.message ? e.message : String(e) };
2037
- }
2038
- },
2039
- dispose() {
2040
- if (!disposed) {
2041
- disposed = true;
2042
- vm.dispose();
2043
- }
2044
- },
2045
- };
2819
+ safeJsExecutor = {
2820
+ runOnce,
2821
+ createCompiled,
2822
+ DEFAULT_TIMEOUT_MS,
2823
+ DEFAULT_MEMORY_BYTES,
2824
+ };
2825
+ return safeJsExecutor;
2046
2826
  }
2047
2827
 
2048
- var safeJsExecutor$1 = {
2049
- runOnce,
2050
- createCompiled,
2051
- DEFAULT_TIMEOUT_MS: DEFAULT_TIMEOUT_MS$1,
2052
- DEFAULT_MEMORY_BYTES,
2053
- };
2054
-
2055
2828
  /**
2056
2829
  * Utils/tranaform
2057
2830
  * Main gial is to transform a file of data into another form (CSV -> Json for example)
@@ -2060,462 +2833,514 @@ var safeJsExecutor$1 = {
2060
2833
  * - CSV -> JSON
2061
2834
  */
2062
2835
 
2063
- var fs$e = require$$0$2;
2064
- var readline = require$$1$3;
2065
- const xtreamer = require$$2;
2066
- var xmlParser = require$$3$1;
2067
- var JSONStream$1 = require$$4;
2068
- const stream$1 = require$$5;
2069
- var csv = require$$6;
2070
- const path$j = require$$1$2;
2071
- const { app: app$c } = require$$0$1;
2072
- const { ensureDirectoryExistence: ensureDirectoryExistence$1 } = file;
2073
- const safeJsExecutor = safeJsExecutor$1;
2074
-
2075
- const TRANSFORM_APP_NAME = "Dashboard";
2076
- const MAX_MAPPING_BODY_SIZE = 10240; // 10KB limit for mapping function body
2077
- // Per-record execution limit. Each mapping function call inside the
2078
- // streaming transform must complete within this budget; a runaway loop
2079
- // in the user's mapping triggers an "interrupted" error and the record
2080
- // is skipped rather than blocking the whole stream.
2081
- const PER_RECORD_TIMEOUT_MS = 1000;
2082
-
2083
- /**
2084
- * XtreamerClientTransform
2085
- * Custom Transform stream to parse the JSON from the XML to String operation
2086
- */
2087
- class XtreamerClientTransform extends stream$1.Transform {
2088
- _transform(value, encoding, callback) {
2089
- this.push(JSON.parse(value));
2090
- callback();
2091
- }
2092
- }
2093
-
2094
- let Transform$1 = class Transform {
2095
- constructor() {
2096
- console.log("constructor");
2097
- }
2098
-
2099
- /**
2100
- * readLinesFromFile
2101
- *
2102
- * If this is a json file we should check in and remove the newLines, and then
2103
- * add commas between records potentially...
2104
- * @param {*} win
2105
- * @param {*} filepath
2106
- * @param {*} linesCount
2107
- * @param {*} callbackEvent
2108
- * @returns
2109
- */
2110
- readLinesFromFile = (
2111
- win,
2112
- filepath,
2113
- linesCount = 100,
2114
- callbackEvent = null,
2115
- ) => {
2116
- return new Promise((resolve, reject) => {
2117
- try {
2118
- let count = 0;
2119
- let lines = [];
2836
+ var transform;
2837
+ var hasRequiredTransform;
2838
+
2839
+ function requireTransform () {
2840
+ if (hasRequiredTransform) return transform;
2841
+ hasRequiredTransform = 1;
2842
+ var fs = require$$0$2;
2843
+ var readline = require$$1$2;
2844
+ const xtreamer = require$$2;
2845
+ var xmlParser = require$$3$1;
2846
+ var JSONStream = require$$4;
2847
+ const stream = require$$5;
2848
+ var csv = require$$6;
2849
+ const path = require$$1$1;
2850
+ const { app } = require$$0$1;
2851
+ const { ensureDirectoryExistence } = file;
2852
+ const safeJsExecutor = requireSafeJsExecutor();
2853
+
2854
+ const TRANSFORM_APP_NAME = "Dashboard";
2855
+ const MAX_MAPPING_BODY_SIZE = 10240; // 10KB limit for mapping function body
2856
+ // Per-record execution limit. Each mapping function call inside the
2857
+ // streaming transform must complete within this budget; a runaway loop
2858
+ // in the user's mapping triggers an "interrupted" error and the record
2859
+ // is skipped rather than blocking the whole stream.
2860
+ const PER_RECORD_TIMEOUT_MS = 1000;
2120
2861
 
2121
- // can we aggregate potentially?
2122
- let lineObject = [];
2862
+ /**
2863
+ * XtreamerClientTransform
2864
+ * Custom Transform stream to parse the JSON from the XML to String operation
2865
+ */
2866
+ class XtreamerClientTransform extends stream.Transform {
2867
+ _transform(value, encoding, callback) {
2868
+ this.push(JSON.parse(value));
2869
+ callback();
2870
+ }
2871
+ }
2123
2872
 
2124
- const readInterface = readline.createInterface({
2125
- input: fs$e.createReadStream(filepath),
2126
- output: process.stdout,
2127
- console: false,
2128
- });
2873
+ class Transform {
2874
+ constructor() {
2875
+ console.log("constructor");
2876
+ }
2129
2877
 
2130
- readInterface.on("line", function (line) {
2131
- if (count < linesCount) {
2132
- //console.log(line);
2133
- lines.push(line);
2134
- count++;
2135
- if (callbackEvent !== null) {
2136
- win.webContents.send(callbackEvent, {
2137
- line,
2138
- });
2139
- }
2140
- } else {
2141
- readInterface.close();
2142
- resolve(lines);
2143
- }
2144
- });
2145
- } catch (e) {
2146
- reject(e);
2147
- }
2148
- });
2149
- };
2878
+ /**
2879
+ * readLinesFromFile
2880
+ *
2881
+ * If this is a json file we should check in and remove the newLines, and then
2882
+ * add commas between records potentially...
2883
+ * @param {*} win
2884
+ * @param {*} filepath
2885
+ * @param {*} linesCount
2886
+ * @param {*} callbackEvent
2887
+ * @returns
2888
+ */
2889
+ readLinesFromFile = (
2890
+ win,
2891
+ filepath,
2892
+ linesCount = 100,
2893
+ callbackEvent = null,
2894
+ ) => {
2895
+ return new Promise((resolve, reject) => {
2896
+ try {
2897
+ let count = 0;
2898
+ let lines = [];
2150
2899
 
2151
- readJSONFromFile = (
2152
- win,
2153
- file = "",
2154
- objectCount = 10,
2155
- callbackEvent = null,
2156
- ) => {
2157
- return new Promise((resolve, reject) => {
2158
- try {
2159
- const parser = JSONStream$1.parse("*");
2160
- const readStream = fs$e.createReadStream(file).pipe(parser);
2900
+ // can we aggregate potentially?
2901
+ let lineObject = [];
2161
2902
 
2162
- let count = 0;
2903
+ const readInterface = readline.createInterface({
2904
+ input: fs.createReadStream(filepath),
2905
+ output: process.stdout,
2906
+ console: false,
2907
+ });
2163
2908
 
2164
- readStream.on("data", (data) => {
2165
- //console.log(data);
2166
- if (callbackEvent !== null) {
2167
- win.webContents.send(callbackEvent, {
2168
- data: JSON.stringify(data),
2169
- });
2170
- }
2171
- if (objectCount !== null && count < objectCount) {
2172
- readStream.destroy();
2173
- resolve("complete");
2174
- }
2175
- count++;
2176
- });
2909
+ readInterface.on("line", function (line) {
2910
+ if (count < linesCount) {
2911
+ //console.log(line);
2912
+ lines.push(line);
2913
+ count++;
2914
+ if (callbackEvent !== null) {
2915
+ win.webContents.send(callbackEvent, {
2916
+ line,
2917
+ });
2918
+ }
2919
+ } else {
2920
+ readInterface.close();
2921
+ resolve(lines);
2922
+ }
2923
+ });
2924
+ } catch (e) {
2925
+ reject(e);
2926
+ }
2927
+ });
2928
+ };
2177
2929
 
2178
- readStream.on("error", (e) => {
2179
- reject(e);
2180
- });
2930
+ readJSONFromFile = (
2931
+ win,
2932
+ file = "",
2933
+ objectCount = 10,
2934
+ callbackEvent = null,
2935
+ ) => {
2936
+ return new Promise((resolve, reject) => {
2937
+ try {
2938
+ const parser = JSONStream.parse("*");
2939
+ const readStream = fs.createReadStream(file).pipe(parser);
2181
2940
 
2182
- readStream.on("end", (data) => {
2183
- resolve("Complete");
2184
- });
2185
- } catch (e) {
2186
- console.log("read json error ", e);
2187
- reject(e);
2188
- }
2189
- });
2190
- };
2941
+ let count = 0;
2191
2942
 
2192
- async parseXMLString(data) {
2193
- let xmlText = data.toString().replace("\ufeff", "");
2194
- return new Promise((resolve, reject) => {
2195
- xmlParser
2196
- .parseStringPromise(xmlText, {
2197
- trim: true,
2198
- compact: true,
2199
- ignoreComment: true,
2200
- ignoreDoctype: true,
2201
- })
2202
- .then((data) => {
2203
- // resolve with the comma
2204
- resolve(JSON.stringify(data) + ",");
2205
- })
2206
- .catch((e) => reject(e));
2207
- });
2208
- }
2943
+ readStream.on("data", (data) => {
2944
+ //console.log(data);
2945
+ if (callbackEvent !== null) {
2946
+ win.webContents.send(callbackEvent, {
2947
+ data: JSON.stringify(data),
2948
+ });
2949
+ }
2950
+ if (objectCount !== null && count < objectCount) {
2951
+ readStream.destroy();
2952
+ resolve("complete");
2953
+ }
2954
+ count++;
2955
+ });
2209
2956
 
2210
- parseXMLStream = (filepath, outpath, start) => {
2211
- return new Promise((resolve, reject) => {
2212
- try {
2213
- const xmlFileReadStream = fs$e.createReadStream(filepath);
2957
+ readStream.on("error", (e) => {
2958
+ reject(e);
2959
+ });
2214
2960
 
2215
- xmlFileReadStream.on("end", () => {
2216
- writeStream.write("\n]");
2217
- resolve("Read End");
2218
- });
2961
+ readStream.on("end", (data) => {
2962
+ resolve("Complete");
2963
+ });
2964
+ } catch (e) {
2965
+ console.log("read json error ", e);
2966
+ reject(e);
2967
+ }
2968
+ });
2969
+ };
2219
2970
 
2220
- xmlFileReadStream.on("finished", () => {
2221
- resolve("Read Finish");
2222
- });
2971
+ async parseXMLString(data) {
2972
+ let xmlText = data.toString().replace("\ufeff", "");
2973
+ return new Promise((resolve, reject) => {
2974
+ xmlParser
2975
+ .parseStringPromise(xmlText, {
2976
+ trim: true,
2977
+ compact: true,
2978
+ ignoreComment: true,
2979
+ ignoreDoctype: true,
2980
+ })
2981
+ .then((data) => {
2982
+ // resolve with the comma
2983
+ resolve(JSON.stringify(data) + ",");
2984
+ })
2985
+ .catch((e) => reject(e));
2986
+ });
2987
+ }
2223
2988
 
2224
- const writeStream = fs$e.createWriteStream(outpath);
2225
- writeStream.write("[\n");
2989
+ parseXMLStream = (filepath, outpath, start) => {
2990
+ return new Promise((resolve, reject) => {
2991
+ try {
2992
+ const xmlFileReadStream = fs.createReadStream(filepath);
2226
2993
 
2227
- const options = {
2228
- headers: { Accept: "application/xml" },
2229
- resolveWithFullResponse: true,
2230
- json: false,
2231
- simple: false,
2232
- max_xml_size: 50000000, // 10000000
2233
- };
2994
+ xmlFileReadStream.on("end", () => {
2995
+ writeStream.write("\n]");
2996
+ resolve("Read End");
2997
+ });
2234
2998
 
2235
- const xtreamerTransform = xtreamer(
2236
- start,
2237
- {
2238
- transformer: this.parseXMLString, // returns Promise
2239
- max_xml_size: 50000000,
2240
- },
2241
- options,
2242
- ).on("error", (e) => {
2243
- console.log(e);
2244
- reject(e);
2245
- });
2999
+ xmlFileReadStream.on("finished", () => {
3000
+ resolve("Read Finish");
3001
+ });
2246
3002
 
2247
- xmlFileReadStream
2248
- .pipe(xtreamerTransform)
2249
- .pipe(new XtreamerClientTransform())
2250
- .pipe(writeStream)
2251
- .on("end", () => {
2252
- console.log("ended");
2253
- })
2254
- .on("finish", () => {
2255
- console.log("finished pipe");
2256
- });
2257
- } catch (e) {
2258
- console.log(e);
2259
- reject(e);
2260
- }
2261
- });
2262
- };
3003
+ const writeStream = fs.createWriteStream(outpath);
3004
+ writeStream.write("[\n");
2263
3005
 
2264
- parseCSVStream = (
2265
- filepath,
2266
- outpath,
2267
- delimiter = ",",
2268
- objectIdKey = null,
2269
- headers = null, // optional array of headings to grab
2270
- win = null,
2271
- callbackEvent = null,
2272
- limit = null,
2273
- ) => {
2274
- return new Promise((resolve, reject) => {
2275
- try {
2276
- const readStream = fs$e
2277
- .createReadStream(filepath)
2278
- .pipe(csv({ separator: delimiter }));
2279
- const writeStream = fs$e.createWriteStream(outpath);
2280
-
2281
- let canParse = true;
3006
+ const options = {
3007
+ headers: { Accept: "application/xml" },
3008
+ resolveWithFullResponse: true,
3009
+ json: false,
3010
+ simple: false,
3011
+ max_xml_size: 50000000, // 10000000
3012
+ };
2282
3013
 
2283
- // separators for JSON
2284
- let sep = "";
2285
- let count = 0;
2286
- writeStream.write("[\n");
3014
+ const xtreamerTransform = xtreamer(
3015
+ start,
3016
+ {
3017
+ transformer: this.parseXMLString, // returns Promise
3018
+ max_xml_size: 50000000,
3019
+ },
3020
+ options,
3021
+ ).on("error", (e) => {
3022
+ console.log(e);
3023
+ reject(e);
3024
+ });
2287
3025
 
2288
- readStream.on("data", (item) => {
2289
- if (count > 0) {
2290
- sep = ",\n";
2291
- }
2292
- // if we have specified a limit...
2293
- if (limit !== null && limit <= count) {
2294
- canParse = false;
2295
- writeStream.write("]");
2296
- readStream.destroy();
2297
- resolve("Complete");
2298
- }
2299
- if (canParse === true) {
2300
- item["objectID"] = item[objectIdKey];
2301
- writeStream.write(sep + JSON.stringify(item));
2302
- count++;
3026
+ xmlFileReadStream
3027
+ .pipe(xtreamerTransform)
3028
+ .pipe(new XtreamerClientTransform())
3029
+ .pipe(writeStream)
3030
+ .on("end", () => {
3031
+ console.log("ended");
3032
+ })
3033
+ .on("finish", () => {
3034
+ console.log("finished pipe");
3035
+ });
3036
+ } catch (e) {
3037
+ console.log(e);
3038
+ reject(e);
3039
+ }
3040
+ });
3041
+ };
2303
3042
 
2304
- if (win !== null) {
2305
- //console.log("have win", count, callbackEvent);
2306
- win.webContents.send(callbackEvent, {
2307
- count,
2308
- });
2309
- }
2310
- }
2311
- });
3043
+ parseCSVStream = (
3044
+ filepath,
3045
+ outpath,
3046
+ delimiter = ",",
3047
+ objectIdKey = null,
3048
+ headers = null, // optional array of headings to grab
3049
+ win = null,
3050
+ callbackEvent = null,
3051
+ limit = null,
3052
+ ) => {
3053
+ return new Promise((resolve, reject) => {
3054
+ try {
3055
+ const readStream = fs
3056
+ .createReadStream(filepath)
3057
+ .pipe(csv({ separator: delimiter }));
3058
+ const writeStream = fs.createWriteStream(outpath);
2312
3059
 
2313
- readStream.on("end", () => {
2314
- writeStream.write("]");
2315
- readStream.destroy();
2316
- resolve("Complete");
2317
- });
2318
- } catch (e) {
2319
- reject(e);
2320
- }
2321
- });
2322
- };
3060
+ let canParse = true;
2323
3061
 
2324
- /**
2325
- * transformFileToFile
2326
- * We want to convert a format from a file into a different format
2327
- * based on the mapper we have provided
2328
- *
2329
- * @param {*} win
2330
- * @param {*} filepath
2331
- * @param {*} outFilepath
2332
- * @param {*} mapping
2333
- */
2334
- transformFileToFile = (
2335
- win,
2336
- filepath,
2337
- outFilepath,
2338
- mappingFunctionBody,
2339
- args = ["refObj", "index"],
2340
- callbackEvent = null,
2341
- ) => {
2342
- return new Promise((resolve, reject) => {
2343
- // Validate mappingFunctionBody is a non-empty string
2344
- if (
2345
- typeof mappingFunctionBody !== "string" ||
2346
- !mappingFunctionBody.trim()
2347
- ) {
2348
- return reject(
2349
- new Error("mappingFunctionBody must be a non-empty string"),
2350
- );
2351
- }
3062
+ // separators for JSON
3063
+ let sep = "";
3064
+ let count = 0;
3065
+ writeStream.write("[\n");
2352
3066
 
2353
- // Enforce size limit on mapping function body
2354
- if (mappingFunctionBody.length > MAX_MAPPING_BODY_SIZE) {
2355
- return reject(
2356
- new Error(
2357
- "mappingFunctionBody exceeds maximum size of " +
2358
- MAX_MAPPING_BODY_SIZE +
2359
- " bytes",
2360
- ),
2361
- );
2362
- }
3067
+ readStream.on("data", (item) => {
3068
+ if (count > 0) {
3069
+ sep = ",\n";
3070
+ }
3071
+ // if we have specified a limit...
3072
+ if (limit !== null && limit <= count) {
3073
+ canParse = false;
3074
+ writeStream.write("]");
3075
+ readStream.destroy();
3076
+ resolve("Complete");
3077
+ }
3078
+ if (canParse === true) {
3079
+ item["objectID"] = item[objectIdKey];
3080
+ writeStream.write(sep + JSON.stringify(item));
3081
+ count++;
3082
+
3083
+ if (win !== null) {
3084
+ //console.log("have win", count, callbackEvent);
3085
+ win.webContents.send(callbackEvent, {
3086
+ count,
3087
+ });
3088
+ }
3089
+ }
3090
+ });
2363
3091
 
2364
- // Validate file paths are within app data directory
2365
- const appDataDir = path$j.join(app$c.getPath("userData"), TRANSFORM_APP_NAME);
2366
- const resolvedFilepath = path$j.resolve(filepath);
2367
- const resolvedOutFilepath = path$j.resolve(outFilepath);
3092
+ readStream.on("end", () => {
3093
+ writeStream.write("]");
3094
+ readStream.destroy();
3095
+ resolve("Complete");
3096
+ });
3097
+ } catch (e) {
3098
+ reject(e);
3099
+ }
3100
+ });
3101
+ };
2368
3102
 
2369
- if (!resolvedFilepath.startsWith(appDataDir + path$j.sep)) {
2370
- return reject(
2371
- new Error(
2372
- "Input file path must be within the application data directory",
2373
- ),
2374
- );
2375
- }
2376
- if (!resolvedOutFilepath.startsWith(appDataDir + path$j.sep)) {
2377
- return reject(
2378
- new Error(
2379
- "Output file path must be within the application data directory",
2380
- ),
2381
- );
2382
- }
3103
+ /**
3104
+ * transformFileToFile
3105
+ * We want to convert a format from a file into a different format
3106
+ * based on the mapper we have provided
3107
+ *
3108
+ * @param {*} win
3109
+ * @param {*} filepath
3110
+ * @param {*} outFilepath
3111
+ * @param {*} mapping
3112
+ */
3113
+ transformFileToFile = (
3114
+ win,
3115
+ filepath,
3116
+ outFilepath,
3117
+ mappingFunctionBody,
3118
+ args = ["refObj", "index"],
3119
+ callbackEvent = null,
3120
+ ) => {
3121
+ return new Promise((resolve, reject) => {
3122
+ // Validate mappingFunctionBody is a non-empty string
3123
+ if (
3124
+ typeof mappingFunctionBody !== "string" ||
3125
+ !mappingFunctionBody.trim()
3126
+ ) {
3127
+ return reject(
3128
+ new Error("mappingFunctionBody must be a non-empty string"),
3129
+ );
3130
+ }
2383
3131
 
2384
- // JSON parser
2385
- var parser = JSONStream$1.parse("*");
3132
+ // Enforce size limit on mapping function body
3133
+ if (mappingFunctionBody.length > MAX_MAPPING_BODY_SIZE) {
3134
+ return reject(
3135
+ new Error(
3136
+ "mappingFunctionBody exceeds maximum size of " +
3137
+ MAX_MAPPING_BODY_SIZE +
3138
+ " bytes",
3139
+ ),
3140
+ );
3141
+ }
2386
3142
 
2387
- if (!fs$e.existsSync(resolvedFilepath)) {
2388
- return reject(new Error("File doesnt exist"));
2389
- }
2390
- console.log("file exists ", resolvedFilepath);
2391
- // create the readStream to parse the large file (json)
2392
- var readStream = fs$e.createReadStream(resolvedFilepath).pipe(parser);
3143
+ // Validate file paths are within app data directory
3144
+ const appDataDir = path.join(app.getPath("userData"), TRANSFORM_APP_NAME);
3145
+ const resolvedFilepath = path.resolve(filepath);
3146
+ const resolvedOutFilepath = path.resolve(outFilepath);
2393
3147
 
2394
- ensureDirectoryExistence$1(resolvedOutFilepath);
3148
+ if (!resolvedFilepath.startsWith(appDataDir + path.sep)) {
3149
+ return reject(
3150
+ new Error(
3151
+ "Input file path must be within the application data directory",
3152
+ ),
3153
+ );
3154
+ }
3155
+ if (!resolvedOutFilepath.startsWith(appDataDir + path.sep)) {
3156
+ return reject(
3157
+ new Error(
3158
+ "Output file path must be within the application data directory",
3159
+ ),
3160
+ );
3161
+ }
2395
3162
 
2396
- var writeStream = fs$e.createWriteStream(resolvedOutFilepath);
3163
+ // JSON parser
3164
+ var parser = JSONStream.parse("*");
2397
3165
 
2398
- let sep = "";
2399
- let count = 0;
3166
+ if (!fs.existsSync(resolvedFilepath)) {
3167
+ return reject(new Error("File doesnt exist"));
3168
+ }
3169
+ console.log("file exists ", resolvedFilepath);
3170
+ // create the readStream to parse the large file (json)
3171
+ var readStream = fs.createReadStream(resolvedFilepath).pipe(parser);
3172
+
3173
+ ensureDirectoryExistence(resolvedOutFilepath);
3174
+
3175
+ var writeStream = fs.createWriteStream(resolvedOutFilepath);
3176
+
3177
+ let sep = "";
3178
+ let count = 0;
3179
+
3180
+ // Compile the user-supplied mapping function inside a QuickJS
3181
+ // sandbox. The previous implementation compiled the body with
3182
+ // full Node.js privileges (filesystem, network, process). The
3183
+ // sandbox gives the body a tiny pure-JS surface (Math, JSON,
3184
+ // Date, primitives) and no host globals — see
3185
+ // electron/utils/safeJsExecutor.js for full rationale.
3186
+ //
3187
+ // createCompiled is async (the WASM module must be loaded). Use
3188
+ // .then/.catch instead of await because we're inside a sync
3189
+ // Promise executor.
3190
+ safeJsExecutor
3191
+ .createCompiled({
3192
+ body: mappingFunctionBody,
3193
+ args,
3194
+ })
3195
+ .then((executor) => {
3196
+ startStreamingWithExecutor(executor);
3197
+ })
3198
+ .catch((e) => {
3199
+ reject(
3200
+ new Error(
3201
+ "mappingFunctionBody failed to compile: " +
3202
+ (e && e.message ? e.message : String(e)),
3203
+ ),
3204
+ );
3205
+ });
2400
3206
 
2401
- // Compile the user-supplied mapping function inside a QuickJS
2402
- // sandbox. The previous implementation compiled the body with
2403
- // full Node.js privileges (filesystem, network, process). The
2404
- // sandbox gives the body a tiny pure-JS surface (Math, JSON,
2405
- // Date, primitives) and no host globals — see
2406
- // electron/utils/safeJsExecutor.js for full rationale.
2407
- //
2408
- // createCompiled is async (the WASM module must be loaded). Use
2409
- // .then/.catch instead of await because we're inside a sync
2410
- // Promise executor.
2411
- safeJsExecutor
2412
- .createCompiled({
2413
- body: mappingFunctionBody,
2414
- args,
2415
- })
2416
- .then((executor) => {
2417
- startStreamingWithExecutor(executor);
2418
- })
2419
- .catch((e) => {
2420
- reject(
2421
- new Error(
2422
- "mappingFunctionBody failed to compile: " +
2423
- (e && e.message ? e.message : String(e)),
2424
- ),
2425
- );
2426
- });
3207
+ function startStreamingWithExecutor(executor) {
3208
+ // begin the write stream
3209
+ writeStream.write("[\n");
2427
3210
 
2428
- function startStreamingWithExecutor(executor) {
2429
- // begin the write stream
2430
- writeStream.write("[\n");
3211
+ readStream.on("data", (data) => {
3212
+ try {
3213
+ if (count > 0) {
3214
+ sep = ",\n";
3215
+ }
2431
3216
 
2432
- readStream.on("data", (data) => {
2433
- try {
2434
- if (count > 0) {
2435
- sep = ",\n";
2436
- }
2437
-
2438
- if (data) {
2439
- const result = executor.run([data, count], PER_RECORD_TIMEOUT_MS);
2440
- if (result.error) {
2441
- // Don't block the stream on a single bad record — log,
2442
- // skip, continue. Matches the previous try/catch in the
2443
- // unsandboxed implementation.
2444
- console.log(
2445
- "[transform] mapping error on record " +
2446
- count +
2447
- ": " +
2448
- result.error,
2449
- );
2450
- return;
2451
- }
3217
+ if (data) {
3218
+ const result = executor.run([data, count], PER_RECORD_TIMEOUT_MS);
3219
+ if (result.error) {
3220
+ // Don't block the stream on a single bad record — log,
3221
+ // skip, continue. Matches the previous try/catch in the
3222
+ // unsandboxed implementation.
3223
+ console.log(
3224
+ "[transform] mapping error on record " +
3225
+ count +
3226
+ ": " +
3227
+ result.error,
3228
+ );
3229
+ return;
3230
+ }
2452
3231
 
2453
- writeStream.write(sep + JSON.stringify(result.value));
3232
+ writeStream.write(sep + JSON.stringify(result.value));
2454
3233
 
2455
- if (callbackEvent !== null && win !== null) {
2456
- win.webContents.send(callbackEvent, { count });
2457
- }
3234
+ if (callbackEvent !== null && win !== null) {
3235
+ win.webContents.send(callbackEvent, { count });
3236
+ }
2458
3237
 
2459
- count++;
2460
- }
2461
- } catch (e) {
2462
- console.log(e.message);
2463
- }
2464
- });
3238
+ count++;
3239
+ }
3240
+ } catch (e) {
3241
+ console.log(e.message);
3242
+ }
3243
+ });
2465
3244
 
2466
- readStream.on("end", () => {
2467
- writeStream.write("\n]");
2468
- writeStream.close();
2469
- executor.dispose();
2470
- resolve("Complete: wrote " + count + " objects");
2471
- });
3245
+ readStream.on("end", () => {
3246
+ writeStream.write("\n]");
3247
+ writeStream.close();
3248
+ executor.dispose();
3249
+ resolve("Complete: wrote " + count + " objects");
3250
+ });
2472
3251
 
2473
- readStream.on("error", (err) => {
2474
- console.log("read stream error transform ", err.message);
2475
- executor.dispose();
2476
- reject(err);
2477
- });
2478
- } // end startStreamingWithExecutor
2479
- });
2480
- };
3252
+ readStream.on("error", (err) => {
3253
+ console.log("read stream error transform ", err.message);
3254
+ executor.dispose();
3255
+ reject(err);
3256
+ });
3257
+ } // end startStreamingWithExecutor
3258
+ });
3259
+ };
2481
3260
 
2482
- /**
2483
- * sanitizeLine (line of file)
2484
- * @param {String} line JSON data that we wish to sanitize
2485
- * @returns String the sanitized String
2486
- */
2487
- sanitizeLine = (line) => {
2488
- try {
2489
- let newLine = line;
3261
+ /**
3262
+ * sanitizeLine (line of file)
3263
+ * @param {String} line JSON data that we wish to sanitize
3264
+ * @returns String the sanitized String
3265
+ */
3266
+ sanitizeLine = (line) => {
3267
+ try {
3268
+ let newLine = line;
2490
3269
 
2491
- if (line.slice(-1) === ",") {
2492
- newLine = newLine.slice(0, -1);
2493
- }
3270
+ if (line.slice(-1) === ",") {
3271
+ newLine = newLine.slice(0, -1);
3272
+ }
2494
3273
 
2495
- if (line.slice(0, 1) === "[") {
2496
- newLine = newLine.slice(1);
2497
- }
3274
+ if (line.slice(0, 1) === "[") {
3275
+ newLine = newLine.slice(1);
3276
+ }
2498
3277
 
2499
- return newLine;
2500
- } catch (e) {
2501
- return line;
2502
- }
2503
- };
2504
- };
3278
+ return newLine;
3279
+ } catch (e) {
3280
+ return line;
3281
+ }
3282
+ };
3283
+ }
2505
3284
 
2506
- var transform = Transform$1;
3285
+ transform = Transform;
3286
+ return transform;
3287
+ }
2507
3288
 
2508
- const { app: app$b } = require$$0$1;
2509
- var fs$d = require$$0$2;
2510
- const path$i = require$$1$2;
3289
+ const { app: app$a } = require$$0$1;
3290
+ var fs$c = require$$0$2;
3291
+ const path$h = require$$1$1;
2511
3292
  const events$5 = events$8;
2512
3293
  const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
2513
3294
  const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
3295
+ const { gateFsCall, gateFsCallWithJit } = fsGate;
3296
+ const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
3297
+
3298
+ // Reads the enforcement + JIT flags from settings.json. Mirrors the
3299
+ // helper in mcpController. The flag is shared across MCP and fs domains
3300
+ // — see Phase 2 plan for rationale (the cosmetic rename to a
3301
+ // domain-neutral name is a separate slice).
3302
+ function _loadFlags() {
3303
+ try {
3304
+ const settingsPath = path$h.join(
3305
+ app$a.getPath("userData"),
3306
+ appName$5,
3307
+ "settings.json",
3308
+ );
3309
+ if (!fs$c.existsSync(settingsPath)) return null;
3310
+ return JSON.parse(fs$c.readFileSync(settingsPath, "utf8"));
3311
+ } catch (_e) {
3312
+ return null;
3313
+ }
3314
+ }
3315
+
3316
+ /**
3317
+ * Run the fs gate before a dataController handler does its work.
3318
+ * On deny, sends an error event to the renderer and returns false so
3319
+ * the caller can early-out. On allow, returns true.
3320
+ *
3321
+ * @returns {Promise<boolean>}
3322
+ */
3323
+ async function _runFsGate(win, action, widgetId, args, errorEvent) {
3324
+ const settings = _loadFlags();
3325
+ if (!readEnforceFlag$1(settings)) return true; // gate disabled
3326
+ if (!widgetId) return true; // legacy callers without widgetId — see plan
3327
+ const gate = readJitFlag$1(settings)
3328
+ ? await gateFsCallWithJit({ widgetId, action, args }, { enableJit: true })
3329
+ : gateFsCall({ widgetId, action, args });
3330
+ if (gate.allow) return true;
3331
+ if (win && errorEvent) {
3332
+ win.webContents.send(errorEvent, {
3333
+ success: false,
3334
+ message: "fs permission gate: " + gate.reason,
3335
+ });
3336
+ }
3337
+ return false;
3338
+ }
2514
3339
 
2515
3340
  // Convert Json to Csv
2516
- const ObjectsToCsv = require$$6$1;
2517
- const Transform = transform;
2518
- const https$3 = require$$8$1;
3341
+ const ObjectsToCsv = require$$8$1;
3342
+ const Transform = requireTransform();
3343
+ const https$3 = require$$10;
2519
3344
  const appName$5 = "Dashboard";
2520
3345
 
2521
3346
  const dataController$1 = {
@@ -2532,8 +3357,8 @@ const dataController$1 = {
2532
3357
  // Validate the renderer-supplied filename is contained within
2533
3358
  // the data directory. path.join doesn't reject `..` segments;
2534
3359
  // safePath does.
2535
- const candidate = path$i.join(
2536
- app$b.getPath("userData"),
3360
+ const candidate = path$h.join(
3361
+ app$a.getPath("userData"),
2537
3362
  appName$5,
2538
3363
  appId,
2539
3364
  "data",
@@ -2691,7 +3516,7 @@ const dataController$1 = {
2691
3516
  // intent, plus realpath/symlink protection.
2692
3517
  const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
2693
3518
 
2694
- const writeStream = fs$d.createWriteStream(resolvedFilepath);
3519
+ const writeStream = fs$c.createWriteStream(resolvedFilepath);
2695
3520
 
2696
3521
  https$3
2697
3522
  .get(url, (resp) => {
@@ -2850,13 +3675,31 @@ const dataController$1 = {
2850
3675
  * @param {*} append
2851
3676
  * @param {*} returnEmpty
2852
3677
  */
2853
- saveToFile: (win, data, filename, append, returnEmpty = {}) => {
3678
+ saveToFile: async (
3679
+ win,
3680
+ data,
3681
+ filename,
3682
+ append,
3683
+ returnEmpty = {},
3684
+ widgetId = null,
3685
+ ) => {
3686
+ // Phase 2 fs gate. Runs before safePath containment so JIT can
3687
+ // prompt the user without leaking path-shape information through
3688
+ // error timing. See electron/security/fsGate.js.
3689
+ const gateOk = await _runFsGate(
3690
+ win,
3691
+ "saveToFile",
3692
+ widgetId,
3693
+ { filename },
3694
+ events$5.DATA_SAVE_TO_FILE_ERROR,
3695
+ );
3696
+ if (!gateOk) return;
2854
3697
  try {
2855
3698
  if (data) {
2856
3699
  // Validate filename is contained within the data directory.
2857
3700
  // path.join doesn't reject `..` segments; safePath does.
2858
- const candidate = path$i.join(
2859
- app$b.getPath("userData"),
3701
+ const candidate = path$h.join(
3702
+ app$a.getPath("userData"),
2860
3703
  appName$5,
2861
3704
  "data",
2862
3705
  filename,
@@ -2944,12 +3787,21 @@ const dataController$1 = {
2944
3787
  }
2945
3788
  },
2946
3789
 
2947
- readFromFile: (win, filename, returnIfEmpty = {}) => {
3790
+ readFromFile: async (win, filename, returnIfEmpty = {}, widgetId = null) => {
3791
+ // Phase 2 fs gate — same as saveToFile.
3792
+ const gateOk = await _runFsGate(
3793
+ win,
3794
+ "readFromFile",
3795
+ widgetId,
3796
+ { filename },
3797
+ events$5.DATA_READ_FROM_FILE_ERROR,
3798
+ );
3799
+ if (!gateOk) return;
2948
3800
  try {
2949
3801
  if (filename) {
2950
3802
  // filename to the pages file (live pages)
2951
- const fromFilename = path$i.join(
2952
- app$b.getPath("userData"),
3803
+ const fromFilename = path$h.join(
3804
+ app$a.getPath("userData"),
2953
3805
  appName$5,
2954
3806
  "data",
2955
3807
  filename,
@@ -3029,9 +3881,9 @@ var dataController_1 = dataController$1;
3029
3881
  * settingsController
3030
3882
  */
3031
3883
 
3032
- const { app: app$a } = require$$0$1;
3033
- const path$h = require$$1$2;
3034
- const fs$c = require$$0$2;
3884
+ const { app: app$9 } = require$$0$1;
3885
+ const path$g = require$$1$1;
3886
+ const fs$b = require$$0$2;
3035
3887
  const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
3036
3888
 
3037
3889
  const configFilename$3 = "settings.json";
@@ -3039,15 +3891,15 @@ const appName$4 = "Dashboard";
3039
3891
 
3040
3892
  // Helper function to recursively copy directory
3041
3893
  function copyDirectory(source, destination) {
3042
- if (!fs$c.existsSync(destination)) {
3043
- fs$c.mkdirSync(destination, { recursive: true });
3894
+ if (!fs$b.existsSync(destination)) {
3895
+ fs$b.mkdirSync(destination, { recursive: true });
3044
3896
  }
3045
3897
 
3046
- const files = fs$c.readdirSync(source);
3898
+ const files = fs$b.readdirSync(source);
3047
3899
  for (const file of files) {
3048
- const srcPath = path$h.join(source, file);
3049
- const destPath = path$h.join(destination, file);
3050
- const stat = fs$c.lstatSync(srcPath);
3900
+ const srcPath = path$g.join(source, file);
3901
+ const destPath = path$g.join(destination, file);
3902
+ const stat = fs$b.lstatSync(srcPath);
3051
3903
 
3052
3904
  // Skip symlinks to prevent following links to sensitive files
3053
3905
  if (stat.isSymbolicLink()) {
@@ -3058,7 +3910,7 @@ function copyDirectory(source, destination) {
3058
3910
  if (stat.isDirectory()) {
3059
3911
  copyDirectory(srcPath, destPath);
3060
3912
  } else {
3061
- fs$c.copyFileSync(srcPath, destPath);
3913
+ fs$b.copyFileSync(srcPath, destPath);
3062
3914
  }
3063
3915
  }
3064
3916
  }
@@ -3074,8 +3926,8 @@ const settingsController$4 = {
3074
3926
  try {
3075
3927
  if (data) {
3076
3928
  // <appId>/settings.json
3077
- const filename = path$h.join(
3078
- app$a.getPath("userData"),
3929
+ const filename = path$g.join(
3930
+ app$9.getPath("userData"),
3079
3931
  appName$4,
3080
3932
  configFilename$3,
3081
3933
  );
@@ -3110,8 +3962,8 @@ const settingsController$4 = {
3110
3962
  getSettingsForApplication: (win) => {
3111
3963
  try {
3112
3964
  // <appId>/settings.json
3113
- const filename = path$h.join(
3114
- app$a.getPath("userData"),
3965
+ const filename = path$g.join(
3966
+ app$9.getPath("userData"),
3115
3967
  appName$4,
3116
3968
  configFilename$3,
3117
3969
  );
@@ -3141,15 +3993,15 @@ const settingsController$4 = {
3141
3993
  */
3142
3994
  getDataDirectory: (win) => {
3143
3995
  try {
3144
- const settingsPath = path$h.join(
3145
- app$a.getPath("userData"),
3996
+ const settingsPath = path$g.join(
3997
+ app$9.getPath("userData"),
3146
3998
  appName$4,
3147
3999
  configFilename$3,
3148
4000
  );
3149
4001
  const settings = getFileContents$4(settingsPath, {});
3150
4002
  const userDataDir =
3151
4003
  settings.userDataDirectory ||
3152
- path$h.join(app$a.getPath("userData"), appName$4);
4004
+ path$g.join(app$9.getPath("userData"), appName$4);
3153
4005
 
3154
4006
  console.log("[settingsController] Data directory retrieved successfully");
3155
4007
  // Return the data for ipcMain.handle() - modern promise-based approach
@@ -3176,18 +4028,18 @@ const settingsController$4 = {
3176
4028
  setDataDirectory: (win, newPath) => {
3177
4029
  try {
3178
4030
  // Validate the path exists and is a directory
3179
- if (!fs$c.existsSync(newPath)) {
3180
- fs$c.mkdirSync(newPath, { recursive: true });
4031
+ if (!fs$b.existsSync(newPath)) {
4032
+ fs$b.mkdirSync(newPath, { recursive: true });
3181
4033
  }
3182
4034
 
3183
- const stats = fs$c.statSync(newPath);
4035
+ const stats = fs$b.statSync(newPath);
3184
4036
  if (!stats.isDirectory()) {
3185
4037
  throw new Error("Path is not a directory");
3186
4038
  }
3187
4039
 
3188
4040
  // Update settings
3189
- const settingsPath = path$h.join(
3190
- app$a.getPath("userData"),
4041
+ const settingsPath = path$g.join(
4042
+ app$9.getPath("userData"),
3191
4043
  appName$4,
3192
4044
  configFilename$3,
3193
4045
  );
@@ -3220,20 +4072,20 @@ const settingsController$4 = {
3220
4072
  migrateDataDirectory: (win, oldPath, newPath) => {
3221
4073
  try {
3222
4074
  // Resolve paths to prevent traversal
3223
- const resolvedOldPath = path$h.resolve(oldPath);
3224
- const resolvedNewPath = path$h.resolve(newPath);
4075
+ const resolvedOldPath = path$g.resolve(oldPath);
4076
+ const resolvedNewPath = path$g.resolve(newPath);
3225
4077
 
3226
4078
  // Validate oldPath is the current configured data directory
3227
- const settingsCheckPath = path$h.join(
3228
- app$a.getPath("userData"),
4079
+ const settingsCheckPath = path$g.join(
4080
+ app$9.getPath("userData"),
3229
4081
  appName$4,
3230
4082
  configFilename$3,
3231
4083
  );
3232
4084
  const currentSettings = getFileContents$4(settingsCheckPath, {});
3233
4085
  const currentDataDir =
3234
4086
  currentSettings.userDataDirectory ||
3235
- path$h.join(app$a.getPath("userData"), appName$4);
3236
- if (resolvedOldPath !== path$h.resolve(currentDataDir)) {
4087
+ path$g.join(app$9.getPath("userData"), appName$4);
4088
+ if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
3237
4089
  throw new Error("Source path must be the current data directory");
3238
4090
  }
3239
4091
 
@@ -3257,20 +4109,20 @@ const settingsController$4 = {
3257
4109
  }
3258
4110
 
3259
4111
  // Validate paths
3260
- if (!fs$c.existsSync(resolvedOldPath)) {
4112
+ if (!fs$b.existsSync(resolvedOldPath)) {
3261
4113
  throw new Error("Source directory does not exist");
3262
4114
  }
3263
4115
 
3264
- if (!fs$c.existsSync(resolvedNewPath)) {
3265
- fs$c.mkdirSync(resolvedNewPath, { recursive: true });
4116
+ if (!fs$b.existsSync(resolvedNewPath)) {
4117
+ fs$b.mkdirSync(resolvedNewPath, { recursive: true });
3266
4118
  }
3267
4119
 
3268
4120
  // Copy files
3269
4121
  copyDirectory(resolvedOldPath, resolvedNewPath);
3270
4122
 
3271
4123
  // Update settings to use new path
3272
- const settingsPath = path$h.join(
3273
- app$a.getPath("userData"),
4124
+ const settingsPath = path$g.join(
4125
+ app$9.getPath("userData"),
3274
4126
  appName$4,
3275
4127
  configFilename$3,
3276
4128
  );
@@ -3553,7 +4405,7 @@ function requireProviderController () {
3553
4405
  if (hasRequiredProviderController) return providerController_1;
3554
4406
  hasRequiredProviderController = 1;
3555
4407
  const { app, safeStorage } = require$$0$1;
3556
- const path = require$$1$2;
4408
+ const path = require$$1$1;
3557
4409
  const { writeFileSync, readFileSync, existsSync } = require$$0$2;
3558
4410
  const {
3559
4411
  ensureDirectoryExistence,
@@ -3934,8 +4786,8 @@ function requireProviderController () {
3934
4786
  return providerController_1;
3935
4787
  }
3936
4788
 
3937
- const { app: app$9 } = require$$0$1;
3938
- const path$g = require$$1$2;
4789
+ const { app: app$8 } = require$$0$1;
4790
+ const path$f = require$$1$1;
3939
4791
  const { writeFileSync: writeFileSync$1 } = require$$0$2;
3940
4792
  const events$4 = events$8;
3941
4793
  const { getFileContents: getFileContents$3 } = file;
@@ -3955,8 +4807,8 @@ const layoutController$1 = {
3955
4807
  saveLayoutForApplication: (win, appId, layoutObject) => {
3956
4808
  try {
3957
4809
  // filename to the pages file (live pages)
3958
- const filename = path$g.join(
3959
- app$9.getPath("userData"),
4810
+ const filename = path$f.join(
4811
+ app$8.getPath("userData"),
3960
4812
  appName$3,
3961
4813
  appId,
3962
4814
  configFilename$2,
@@ -3988,8 +4840,8 @@ const layoutController$1 = {
3988
4840
  */
3989
4841
  listLayoutsForApplication: (win, appId) => {
3990
4842
  try {
3991
- const filename = path$g.join(
3992
- app$9.getPath("userData"),
4843
+ const filename = path$f.join(
4844
+ app$8.getPath("userData"),
3993
4845
  appName$3,
3994
4846
  appId,
3995
4847
  configFilename$2,
@@ -21152,455 +22004,6 @@ let StreamableHTTPClientTransport$1 = class StreamableHTTPClientTransport {
21152
22004
  };
21153
22005
  streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1;
21154
22006
 
21155
- /**
21156
- * grantedPermissions.js
21157
- *
21158
- * Stores the user's actual MCP permission grants per widget. This is the
21159
- * Slice-2 enforcement source of truth — separate from the widget's declared
21160
- * `dash.permissions.mcp` block (which is just a request).
21161
- *
21162
- * The runtime gate (permissionGate.gateToolCall) reads from here only.
21163
- * A widget with a declared manifest but no grant entry has no access:
21164
- * fail-closed. The user grants permissions at install time (consent modal)
21165
- * or later in Settings → Privacy & Security.
21166
- *
21167
- * Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
21168
- *
21169
- * Shape on disk:
21170
- * {
21171
- * "@trops/notes-summarizer": {
21172
- * "servers": {
21173
- * "filesystem": {
21174
- * "tools": ["read_file"],
21175
- * "readPaths": ["/Users/jane/Documents/notes"],
21176
- * "writePaths": []
21177
- * }
21178
- * }
21179
- * }
21180
- * }
21181
- *
21182
- * Note: paths are stored as-is (already tilde-expanded by the manifest
21183
- * parser before grants are written). Tests can re-expand via
21184
- * widgetPermissions.expandHome if they store ~ literals.
21185
- *
21186
- * Public API:
21187
- * getGrant(widgetId) → grant | null
21188
- * setGrant(widgetId, perms) → boolean
21189
- * revokeGrant(widgetId) → boolean
21190
- * revokeServer(widgetId, serverName) → boolean
21191
- * listAllGrants() → [{ widgetId, granted }]
21192
- * clearCache() → void // test-only
21193
- */
21194
-
21195
- const fs$b = require$$0$2;
21196
- const path$f = require$$1$2;
21197
- const { app: app$8 } = require$$0$1;
21198
-
21199
- const FILE_NAME = "widgetMcpGrants.json";
21200
-
21201
- // In-process cache of the entire grants file. Lazily loaded; invalidated
21202
- // on every write.
21203
- let _cache$1 = null;
21204
-
21205
- function grantsFilePath() {
21206
- return path$f.join(app$8.getPath("userData"), FILE_NAME);
21207
- }
21208
-
21209
- function loadFromDisk() {
21210
- const p = grantsFilePath();
21211
- if (!fs$b.existsSync(p)) return {};
21212
- try {
21213
- const raw = fs$b.readFileSync(p, "utf8");
21214
- const parsed = JSON.parse(raw);
21215
- if (!parsed || typeof parsed !== "object") return {};
21216
- return parsed;
21217
- } catch (e) {
21218
- console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
21219
- return {};
21220
- }
21221
- }
21222
-
21223
- function ensureCache() {
21224
- if (_cache$1 === null) _cache$1 = loadFromDisk();
21225
- return _cache$1;
21226
- }
21227
-
21228
- function writeToDisk(data) {
21229
- const p = grantsFilePath();
21230
- const tmp = p + ".tmp";
21231
- // Ensure parent dir exists (userData should already, but be defensive
21232
- // for first-launch / freshly-cleared profile cases).
21233
- fs$b.mkdirSync(path$f.dirname(p), { recursive: true });
21234
- fs$b.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
21235
- fs$b.renameSync(tmp, p);
21236
- }
21237
-
21238
- // Recognized origins for a persisted grant.
21239
- // "declared" — user approved against the developer's declared
21240
- // dash.permissions.mcp block at install time.
21241
- // "discovered" — install-time scanner produced a synthetic manifest
21242
- // the user approved.
21243
- // "manual" — user typed entries themselves in
21244
- // Settings → Privacy & Security with no manifest backing.
21245
- // "live" — user approved a just-in-time consent prompt at
21246
- // runtime when a tool call hit the gate without a
21247
- // matching grant.
21248
- // Other values are dropped on persist (legacy grants stay null).
21249
- const ALLOWED_GRANT_ORIGINS = new Set([
21250
- "declared",
21251
- "discovered",
21252
- "manual",
21253
- "live",
21254
- ]);
21255
-
21256
- /**
21257
- * Sanitize a perms object before persisting. Drops unknown keys, coerces
21258
- * arrays of strings, and silently ignores malformed servers. Mirrors the
21259
- * shape produced by parseManifestPermissions so the gate reads either
21260
- * declared or granted with the same code path.
21261
- *
21262
- * Optional `grantOrigin` field is preserved when it's one of the
21263
- * recognized values; bogus values are dropped.
21264
- */
21265
- function sanitizePerms(perms) {
21266
- if (!perms || typeof perms !== "object") return null;
21267
- const rawServers =
21268
- perms.servers && typeof perms.servers === "object" ? perms.servers : null;
21269
- if (!rawServers) return null;
21270
-
21271
- const servers = {};
21272
- for (const [name, raw] of Object.entries(rawServers)) {
21273
- if (!raw || typeof raw !== "object") continue;
21274
- servers[name] = {
21275
- tools: Array.isArray(raw.tools)
21276
- ? raw.tools.filter((t) => typeof t === "string")
21277
- : [],
21278
- readPaths: Array.isArray(raw.readPaths)
21279
- ? raw.readPaths.filter((p) => typeof p === "string")
21280
- : [],
21281
- writePaths: Array.isArray(raw.writePaths)
21282
- ? raw.writePaths.filter((p) => typeof p === "string")
21283
- : [],
21284
- };
21285
- }
21286
- const out = { servers };
21287
- if (
21288
- typeof perms.grantOrigin === "string" &&
21289
- ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
21290
- ) {
21291
- out.grantOrigin = perms.grantOrigin;
21292
- }
21293
- return out;
21294
- }
21295
-
21296
- function getGrant$2(widgetId) {
21297
- if (typeof widgetId !== "string" || !widgetId) return null;
21298
- const all = ensureCache();
21299
- return all[widgetId] || null;
21300
- }
21301
-
21302
- function setGrant$2(widgetId, perms) {
21303
- if (typeof widgetId !== "string" || !widgetId) return false;
21304
- const sanitized = sanitizePerms(perms);
21305
- if (!sanitized) return false;
21306
- const all = ensureCache();
21307
- all[widgetId] = sanitized;
21308
- try {
21309
- writeToDisk(all);
21310
- return true;
21311
- } catch (e) {
21312
- console.warn(
21313
- "[grantedPermissions] failed to write grant for " +
21314
- widgetId +
21315
- ": " +
21316
- e.message,
21317
- );
21318
- // Roll back the cache entry so memory matches disk.
21319
- _cache$1 = loadFromDisk();
21320
- return false;
21321
- }
21322
- }
21323
-
21324
- function revokeGrant$1(widgetId) {
21325
- if (typeof widgetId !== "string" || !widgetId) return false;
21326
- const all = ensureCache();
21327
- if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
21328
- delete all[widgetId];
21329
- try {
21330
- writeToDisk(all);
21331
- return true;
21332
- } catch (e) {
21333
- console.warn(
21334
- "[grantedPermissions] failed to revoke grant for " +
21335
- widgetId +
21336
- ": " +
21337
- e.message,
21338
- );
21339
- _cache$1 = loadFromDisk();
21340
- return false;
21341
- }
21342
- }
21343
-
21344
- function revokeServer$1(widgetId, serverName) {
21345
- if (typeof widgetId !== "string" || !widgetId) return false;
21346
- if (typeof serverName !== "string" || !serverName) return false;
21347
- const all = ensureCache();
21348
- const widgetEntry = all[widgetId];
21349
- if (!widgetEntry || !widgetEntry.servers) return false;
21350
- if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
21351
- return false;
21352
- delete widgetEntry.servers[serverName];
21353
- try {
21354
- writeToDisk(all);
21355
- return true;
21356
- } catch (e) {
21357
- console.warn(
21358
- "[grantedPermissions] failed to revoke server " +
21359
- serverName +
21360
- " for " +
21361
- widgetId +
21362
- ": " +
21363
- e.message,
21364
- );
21365
- _cache$1 = loadFromDisk();
21366
- return false;
21367
- }
21368
- }
21369
-
21370
- function listAllGrants$1() {
21371
- const all = ensureCache();
21372
- return Object.entries(all).map(([widgetId, granted]) => ({
21373
- widgetId,
21374
- granted,
21375
- }));
21376
- }
21377
-
21378
- function clearCache$1() {
21379
- _cache$1 = null;
21380
- }
21381
-
21382
- var grantedPermissions = {
21383
- getGrant: getGrant$2,
21384
- setGrant: setGrant$2,
21385
- revokeGrant: revokeGrant$1,
21386
- revokeServer: revokeServer$1,
21387
- listAllGrants: listAllGrants$1,
21388
- clearCache: clearCache$1,
21389
- ALLOWED_GRANT_ORIGINS,
21390
- };
21391
-
21392
- /**
21393
- * jitConsent.js
21394
- *
21395
- * Just-in-time permission consent for widget→backend calls.
21396
- *
21397
- * When a widget hits a gate without an existing grant for the requested
21398
- * (domain, action, args), the gate calls `requestApproval` which:
21399
- * 1. Synchronously emits `widget:permission-required` to all
21400
- * BrowserWindows with a unique requestId.
21401
- * 2. Returns a Promise that resolves on user response or rejects on
21402
- * timeout.
21403
- * 3. Coalesces requests with the same coalescing key so a widget
21404
- * bursting identical calls produces one prompt, not many.
21405
- *
21406
- * The renderer's JitConsentModal subscribes to the event, presents the
21407
- * user with granularity options (this once / this tool / this tool +
21408
- * parent dir), and replies via `widget:permission-response` with
21409
- * `{ requestId, decision }`. main.js wires the IPC handler back to
21410
- * `_handleResponse`.
21411
- *
21412
- * The module is intentionally domain-agnostic in shape — the request
21413
- * payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
21414
- * the same machinery. Phase 1 only emits with `domain: "mcp"`.
21415
- *
21416
- * Public surface:
21417
- * requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
21418
- * _handleResponse({ requestId, decision }) → void (called from main.js IPC)
21419
- * _resetForTest() → void (test-only)
21420
- */
21421
-
21422
- const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
21423
-
21424
- const REQUEST_CHANNEL = "widget:permission-required";
21425
- const RESPONSE_CHANNEL = "widget:permission-response";
21426
- const DEFAULT_TIMEOUT_MS = 60_000;
21427
-
21428
- // requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
21429
- const _pending = new Map();
21430
- // coalesceKey → requestId (so duplicate requests join the live one)
21431
- const _coalesce = new Map();
21432
- let _idCounter = 0;
21433
-
21434
- function nextRequestId() {
21435
- _idCounter += 1;
21436
- return `jit-${Date.now()}-${_idCounter}`;
21437
- }
21438
-
21439
- /**
21440
- * Build a coalescing key from the request. Two requests share the same
21441
- * key iff they're "the same prompt" — same widget, same domain+action,
21442
- * same target server/tool. Args beyond that (e.g. exact path) DON'T
21443
- * differentiate; if the user is being asked about read_file already,
21444
- * approving handles all current paths.
21445
- */
21446
- function coalesceKeyOf(req) {
21447
- if (req.domain === "mcp") {
21448
- const innerArgs = req.args || {};
21449
- return [
21450
- req.widgetId,
21451
- "mcp",
21452
- innerArgs.serverName || "",
21453
- innerArgs.toolName || "",
21454
- ].join("::");
21455
- }
21456
- // Default: domain + action + serialized top-level args
21457
- return [
21458
- req.widgetId,
21459
- req.domain,
21460
- req.action,
21461
- JSON.stringify(req.args || {}),
21462
- ].join("::");
21463
- }
21464
-
21465
- function emitEvent(payload) {
21466
- let wins = [];
21467
- try {
21468
- wins = BrowserWindow$2.getAllWindows() || [];
21469
- } catch {
21470
- wins = [];
21471
- }
21472
- for (const w of wins) {
21473
- try {
21474
- w?.webContents?.send?.(REQUEST_CHANNEL, payload);
21475
- } catch {
21476
- // best-effort broadcast
21477
- }
21478
- }
21479
- }
21480
-
21481
- function validateRequest(req) {
21482
- if (!req || typeof req !== "object") return "invalid request: not an object";
21483
- if (typeof req.widgetId !== "string" || !req.widgetId)
21484
- return "invalid request: widgetId required";
21485
- if (typeof req.domain !== "string" || !req.domain)
21486
- return "invalid request: domain required";
21487
- if (typeof req.action !== "string" || !req.action)
21488
- return "invalid request: action required";
21489
- return null;
21490
- }
21491
-
21492
- /**
21493
- * Request user approval for an out-of-grant call. Returns a promise
21494
- * that resolves with the user's decision or rejects on timeout / bad
21495
- * input.
21496
- *
21497
- * decision shape (resolved value):
21498
- * { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
21499
- * { approve: false, reason?: string }
21500
- *
21501
- * `scope` informs the caller how to write the resulting grant.
21502
- */
21503
- function requestApproval$1(req, opts = {}) {
21504
- const validation = validateRequest(req);
21505
- if (validation) {
21506
- return Promise.reject(new Error(validation));
21507
- }
21508
-
21509
- const timeoutMs = Number.isFinite(opts.timeoutMs)
21510
- ? opts.timeoutMs
21511
- : DEFAULT_TIMEOUT_MS;
21512
-
21513
- // If a prompt for the same coalesce key is already pending, join it.
21514
- const key = coalesceKeyOf(req);
21515
- if (_coalesce.has(key)) {
21516
- const existingId = _coalesce.get(key);
21517
- const existing = _pending.get(existingId);
21518
- if (existing) {
21519
- return new Promise((resolve, reject) => {
21520
- existing.joinedResolvers.push({ resolve, reject });
21521
- });
21522
- }
21523
- // Stale coalesce entry; drop and fall through to a fresh request.
21524
- _coalesce.delete(key);
21525
- }
21526
-
21527
- return new Promise((resolve, reject) => {
21528
- const requestId = nextRequestId();
21529
- const timeout = setTimeout(() => {
21530
- const entry = _pending.get(requestId);
21531
- if (!entry) return;
21532
- _pending.delete(requestId);
21533
- _coalesce.delete(entry.coalesceKey);
21534
- const err = new Error(
21535
- `JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
21536
- );
21537
- reject(err);
21538
- for (const j of entry.joinedResolvers) j.reject(err);
21539
- }, timeoutMs);
21540
-
21541
- _pending.set(requestId, {
21542
- resolve,
21543
- reject,
21544
- timeout,
21545
- coalesceKey: key,
21546
- joinedResolvers: [],
21547
- });
21548
- _coalesce.set(key, requestId);
21549
-
21550
- emitEvent({
21551
- requestId,
21552
- widgetId: req.widgetId,
21553
- domain: req.domain,
21554
- action: req.action,
21555
- args: req.args || {},
21556
- });
21557
- });
21558
- }
21559
-
21560
- function _handleResponse({ requestId, decision } = {}) {
21561
- if (!requestId || typeof requestId !== "string") return;
21562
- const entry = _pending.get(requestId);
21563
- if (!entry) return; // unknown request — drop silently
21564
- clearTimeout(entry.timeout);
21565
- _pending.delete(requestId);
21566
- _coalesce.delete(entry.coalesceKey);
21567
- const safe =
21568
- decision && typeof decision === "object" ? decision : { approve: false };
21569
- entry.resolve(safe);
21570
- for (const j of entry.joinedResolvers) j.resolve(safe);
21571
- }
21572
-
21573
- function _resetForTest() {
21574
- for (const entry of _pending.values()) clearTimeout(entry.timeout);
21575
- _pending.clear();
21576
- _coalesce.clear();
21577
- _idCounter = 0;
21578
- }
21579
-
21580
- let _handlersRegistered = false;
21581
- /**
21582
- * Wire the renderer→main response IPC. Idempotent.
21583
- * Call once from main.js alongside other ipcMain setup.
21584
- */
21585
- function setupJitConsentHandlers() {
21586
- if (_handlersRegistered) return;
21587
- if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
21588
- ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
21589
- _handleResponse(payload);
21590
- });
21591
- _handlersRegistered = true;
21592
- }
21593
-
21594
- var jitConsent$1 = {
21595
- requestApproval: requestApproval$1,
21596
- setupJitConsentHandlers,
21597
- _handleResponse,
21598
- _resetForTest,
21599
- REQUEST_CHANNEL,
21600
- RESPONSE_CHANNEL,
21601
- DEFAULT_TIMEOUT_MS,
21602
- };
21603
-
21604
22007
  /**
21605
22008
  * permissionGate.js
21606
22009
  *
@@ -22081,37 +22484,6 @@ var mcpScopeResolver = {
22081
22484
  applyPathScopeToCredentials: applyPathScopeToCredentials$1,
22082
22485
  };
22083
22486
 
22084
- /**
22085
- * securityFlags.js
22086
- *
22087
- * Centralized readers for the two boolean security flags that gate the
22088
- * MCP allowlist stack:
22089
- * - security.enforceWidgetMcpPermissions
22090
- * - security.enableJitConsent
22091
- *
22092
- * **Default semantics: ON.** A missing settings.json, a missing
22093
- * `security` block, or an undefined field all yield `true`. Only an
22094
- * explicit `false` opts out. This is intentional — the security stack
22095
- * is on by default; users have to actively disable it. The
22096
- * Privacy & Security panel surfaces the toggles + a confirm-on-disable
22097
- * dialog so the disable path is deliberate.
22098
- *
22099
- * The readers are pure functions of a settings object so the
22100
- * default-on semantics are pinned by unit tests without touching the
22101
- * filesystem. The callers in mcpController.js wrap these with
22102
- * settings.json IO.
22103
- */
22104
-
22105
- function readEnforceFlag$1(settings) {
22106
- return settings?.security?.enforceWidgetMcpPermissions !== false;
22107
- }
22108
-
22109
- function readJitFlag$1(settings) {
22110
- return settings?.security?.enableJitConsent !== false;
22111
- }
22112
-
22113
- var securityFlags = { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 };
22114
-
22115
22487
  /**
22116
22488
  * mcpController.js
22117
22489
  *
@@ -22128,11 +22500,11 @@ var securityFlags = { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFl
22128
22500
  const { Client } = require$$0$5;
22129
22501
  const {
22130
22502
  StdioClientTransport,
22131
- } = require$$1$4;
22503
+ } = require$$1$3;
22132
22504
  const {
22133
22505
  StreamableHTTPClientTransport,
22134
22506
  } = streamableHttp$1;
22135
- const path$e = require$$1$2;
22507
+ const path$e = require$$1$1;
22136
22508
  const fs$a = require$$0$2;
22137
22509
  const os$2 = require$$2$1;
22138
22510
  const responseCache$2 = responseCache_1;
@@ -22491,7 +22863,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22491
22863
 
22492
22864
  console.log("[mcpController] Refreshing Google OAuth token...");
22493
22865
 
22494
- const https = require$$8$1;
22866
+ const https = require$$10;
22495
22867
  const postData = [
22496
22868
  `client_id=${encodeURIComponent(keyData.client_id)}`,
22497
22869
  `client_secret=${encodeURIComponent(keyData.client_secret)}`,
@@ -23288,7 +23660,7 @@ const mcpController$3 = {
23288
23660
  const sourcePath = credentials?.[from];
23289
23661
  if (sourcePath) {
23290
23662
  const destPath = to.replace(/^~/, os$2.homedir());
23291
- const destDir = require$$1$2.dirname(destPath);
23663
+ const destDir = require$$1$1.dirname(destPath);
23292
23664
  try {
23293
23665
  fs$a.mkdirSync(destDir, { recursive: true });
23294
23666
  fs$a.copyFileSync(sourcePath, destPath);
@@ -23508,7 +23880,7 @@ const REGISTRY_BASE_URL$1 =
23508
23880
  let store$3 = null;
23509
23881
  function getStore$1() {
23510
23882
  if (!store$3) {
23511
- const Store = require$$1$1;
23883
+ const Store = require$$0$6;
23512
23884
  store$3 = new Store({
23513
23885
  name: "dash-registry-auth",
23514
23886
  encryptionKey: "dash-registry-v1",
@@ -23876,7 +24248,7 @@ function commonjsRequire(path) {
23876
24248
  */
23877
24249
 
23878
24250
  const fs$9 = require$$0$2;
23879
- const path$d = require$$1$2;
24251
+ const path$d = require$$1$1;
23880
24252
 
23881
24253
  /**
23882
24254
  * Structured error thrown by compileWidget() when the underlying
@@ -24193,7 +24565,7 @@ var widgetCompiler$1 = {
24193
24565
  */
24194
24566
 
24195
24567
  const fs$8 = require$$0$2;
24196
- const path$c = require$$1$2;
24568
+ const path$c = require$$1$1;
24197
24569
  const vm = require$$2$2;
24198
24570
  const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
24199
24571
 
@@ -24484,7 +24856,7 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
24484
24856
  */
24485
24857
 
24486
24858
  const fs$7 = require$$0$2;
24487
- const path$b = require$$1$2;
24859
+ const path$b = require$$1$1;
24488
24860
  const os$1 = require$$2$1;
24489
24861
  const { app: app$6 } = require$$0$1;
24490
24862
 
@@ -24613,7 +24985,7 @@ var widgetPermissions = {
24613
24985
  */
24614
24986
 
24615
24987
  const fs$6 = require$$0$2;
24616
- const path$a = require$$1$2;
24988
+ const path$a = require$$1$1;
24617
24989
 
24618
24990
  const SOURCE_EXTENSIONS = new Set([
24619
24991
  ".js",
@@ -24793,8 +25165,8 @@ var manifestScanner = {
24793
25165
  * and dispatching task-fired events to renderer windows.
24794
25166
  */
24795
25167
 
24796
- const Store$1 = require$$1$1;
24797
- const { Cron } = require$$1$5;
25168
+ const Store$1 = require$$0$6;
25169
+ const { Cron } = require$$1$4;
24798
25170
 
24799
25171
  const store$2 = new Store$1({ name: "dash-scheduler" });
24800
25172
 
@@ -25316,7 +25688,7 @@ var schedulerController_1 = schedulerController$2;
25316
25688
 
25317
25689
  (function (module) {
25318
25690
  const fs = require$$0$2;
25319
- const path = require$$1$2;
25691
+ const path = require$$1$1;
25320
25692
  const os = require$$2$1;
25321
25693
  const AdmZip = require$$3$2;
25322
25694
  const { fileURLToPath } = require$$4$1;
@@ -26839,7 +27211,7 @@ var widgetRegistryExports = widgetRegistry$1.exports;
26839
27211
  * - Support two-level browsing: packages (bundles) and widgets within packages
26840
27212
  */
26841
27213
 
26842
- const path$9 = require$$1$2;
27214
+ const path$9 = require$$1$1;
26843
27215
  const fs$5 = require$$0$2;
26844
27216
  const os = require$$2$1;
26845
27217
  const { toPackageId } = packageId;
@@ -28009,7 +28381,7 @@ const algoliaController$1 = {
28009
28381
 
28010
28382
  var algoliaController_1 = algoliaController$1;
28011
28383
 
28012
- const OpenAI = require$$0$6;
28384
+ const OpenAI = require$$0$7;
28013
28385
  const events$2 = events$8;
28014
28386
 
28015
28387
  const openaiController$1 = {
@@ -28113,7 +28485,7 @@ function upsertMenuItem$1(items, menuItem) {
28113
28485
  var upsertMenuItem_1 = { upsertMenuItem: upsertMenuItem$1 };
28114
28486
 
28115
28487
  const { app: app$5 } = require$$0$1;
28116
- const path$7 = require$$1$2;
28488
+ const path$7 = require$$1$1;
28117
28489
  const { writeFileSync } = require$$0$2;
28118
28490
  const { getFileContents: getFileContents$2 } = file;
28119
28491
  const { upsertMenuItem } = upsertMenuItem_1;
@@ -28169,7 +28541,7 @@ const menuItemsController$1 = {
28169
28541
 
28170
28542
  var menuItemsController_1 = menuItemsController$1;
28171
28543
 
28172
- const path$6 = require$$1$2;
28544
+ const path$6 = require$$1$1;
28173
28545
  const { app: app$4 } = require$$0$1;
28174
28546
 
28175
28547
  const pluginController$1 = {
@@ -46388,13 +46760,13 @@ __export(src_exports, {
46388
46760
  var dist = __toCommonJS(src_exports);
46389
46761
 
46390
46762
  // src/server.ts
46391
- var import_node_http = require$$0$7;
46763
+ var import_node_http = require$$0$8;
46392
46764
 
46393
46765
  // src/listener.ts
46394
- var import_node_http22 = require$$1$6;
46766
+ var import_node_http22 = require$$1$5;
46395
46767
 
46396
46768
  // src/request.ts
46397
- var import_node_http2 = require$$1$6;
46769
+ var import_node_http2 = require$$1$5;
46398
46770
  var import_node_stream = require$$5;
46399
46771
  var RequestError = class extends Error {
46400
46772
  constructor(message, options) {
@@ -47972,7 +48344,7 @@ streamableHttp.StreamableHTTPServerTransport = StreamableHTTPServerTransport$1;
47972
48344
  */
47973
48345
 
47974
48346
  const fs$2 = require$$0$2;
47975
- const path$5 = require$$1$2;
48347
+ const path$5 = require$$1$1;
47976
48348
  const forge = require$$2$4;
47977
48349
 
47978
48350
  /**
@@ -48166,7 +48538,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
48166
48538
  * - Rate limiting via token bucket (60 req/min)
48167
48539
  */
48168
48540
 
48169
- const https$1 = require$$8$1;
48541
+ const https$1 = require$$10;
48170
48542
  const { randomUUID } = require$$3$4;
48171
48543
  const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
48172
48544
  const { McpServer } = mcp;
@@ -48371,7 +48743,7 @@ function saveMcpServerSettings(win, mcpSettings) {
48371
48743
  function resolveAppId() {
48372
48744
  const { app } = require$$0$1;
48373
48745
  const fs = require$$0$2;
48374
- const path = require$$1$2;
48746
+ const path = require$$1$1;
48375
48747
  const dashboardDir = path.join(app.getPath("userData"), "Dashboard");
48376
48748
  try {
48377
48749
  const entries = fs.readdirSync(dashboardDir, { withFileTypes: true });
@@ -49973,10 +50345,10 @@ var themeFromUrlErrors$1 = {
49973
50345
  * computed styles, and favicon/logo images (via node-vibrant).
49974
50346
  */
49975
50347
 
49976
- const css = require$$0$8;
49977
- const { Vibrant } = require$$1$7;
49978
- const https = require$$8$1;
49979
- const http = require$$0$7;
50348
+ const css = require$$0$9;
50349
+ const { Vibrant } = require$$1$6;
50350
+ const https = require$$10;
50351
+ const http = require$$0$8;
49980
50352
  const { URL: URL$1 } = require$$4$1;
49981
50353
  const {
49982
50354
  UrlUnreachableError,
@@ -54536,7 +54908,7 @@ var toolHandlers$1 = {
54536
54908
  * per-request, receiving the full messages array each time.
54537
54909
  */
54538
54910
 
54539
- const Anthropic = require$$0$9;
54911
+ const Anthropic = require$$0$a;
54540
54912
  const mcpController$2 = mcpControllerExports;
54541
54913
  const cliController$1 = cliController_1;
54542
54914
  const toolDefinitions = toolDefinitions$1;
@@ -56717,7 +57089,7 @@ var dashboardConfigUtils$1 = {
56717
57089
  */
56718
57090
 
56719
57091
  const fs$1 = require$$0$2;
56720
- const path$4 = require$$1$2;
57092
+ const path$4 = require$$1$1;
56721
57093
  const { getStoredToken: getStoredToken$2 } = registryAuthController$2;
56722
57094
 
56723
57095
  const REGISTRY_BASE_URL =
@@ -56992,7 +57364,7 @@ function requireWidgetPublishManifest () {
56992
57364
  * Mirrors dashboardConfigController patterns for ZIP creation, manifest generation,
56993
57365
  * and registry interaction.
56994
57366
  */
56995
- const path$3 = require$$1$2;
57367
+ const path$3 = require$$1$1;
56996
57368
  const { app: app$3, dialog: dialog$1 } = require$$0$1;
56997
57369
  const AdmZip$2 = require$$3$2;
56998
57370
 
@@ -57732,7 +58104,7 @@ var themeRegistryController$1 = {
57732
58104
  */
57733
58105
 
57734
58106
  const { app: app$2, dialog } = require$$0$1;
57735
- const path$2 = require$$1$2;
58107
+ const path$2 = require$$1$1;
57736
58108
  const AdmZip$1 = require$$3$2;
57737
58109
  const { getFileContents: getFileContents$1 } = file;
57738
58110
  const {
@@ -59701,7 +60073,7 @@ var dashboardConfigController$1 = {
59701
60073
  */
59702
60074
 
59703
60075
  const { Notification } = require$$0$1;
59704
- const Store = require$$1$1;
60076
+ const Store = require$$0$6;
59705
60077
 
59706
60078
  const store$1 = new Store({ name: "dash-notifications" });
59707
60079
 
@@ -59954,7 +60326,7 @@ var notificationController_1 = notificationController$2;
59954
60326
  * Multiple widgets referencing the same provider share a single socket.
59955
60327
  */
59956
60328
 
59957
- const WebSocket = require$$0$a;
60329
+ const WebSocket = require$$0$b;
59958
60330
 
59959
60331
  /**
59960
60332
  * Active WebSocket connections
@@ -60944,7 +61316,7 @@ clientCache$1.registerFactory("algolia", (credentials) => {
60944
61316
 
60945
61317
  // --- OpenAI ---
60946
61318
  clientCache$1.registerFactory("openai", (credentials) => {
60947
- const OpenAI = require$$0$6;
61319
+ const OpenAI = require$$0$7;
60948
61320
  return new OpenAI({ apiKey: credentials.apiKey });
60949
61321
  });
60950
61322
 
@@ -60963,7 +61335,7 @@ const MAX_RECENTS = 20;
60963
61335
  let store = null;
60964
61336
  function getStore() {
60965
61337
  if (!store) {
60966
- const Store = require$$1$1;
61338
+ const Store = require$$0$6;
60967
61339
  store = new Store({ name: "dash-session" });
60968
61340
  }
60969
61341
  return store;
@@ -61148,7 +61520,7 @@ var dashboardRatingsUtils = {
61148
61520
  */
61149
61521
 
61150
61522
  const { app: app$1 } = require$$0$1;
61151
- const path$1 = require$$1$2;
61523
+ const path$1 = require$$1$1;
61152
61524
  const { getFileContents, writeToFile } = file;
61153
61525
  const {
61154
61526
  validateAndBuildRating,
@@ -61252,7 +61624,7 @@ var dashboardRatingsController = {
61252
61624
  */
61253
61625
 
61254
61626
  const fs = require$$0$2;
61255
- const path = require$$1$2;
61627
+ const path = require$$1$1;
61256
61628
  const AdmZip = require$$3$2;
61257
61629
  const { app } = require$$0$1;
61258
61630
 
@@ -62057,11 +62429,7 @@ var widgetRegistryController = {
62057
62429
  */
62058
62430
 
62059
62431
  const { showDialog, fileChosenError } = dialogController$1;
62060
- const {
62061
- isEncryptionAvailable,
62062
- saveData,
62063
- getData,
62064
- } = secureStoreController$1;
62432
+ const { isEncryptionAvailable } = secureStoreController$1;
62065
62433
  const {
62066
62434
  listWorkspacesForApplication,
62067
62435
  saveWorkspaceForApplication,
@@ -62179,8 +62547,6 @@ var controller = {
62179
62547
  showDialog,
62180
62548
  fileChosenError,
62181
62549
  isEncryptionAvailable,
62182
- saveData,
62183
- getData,
62184
62550
  listWorkspacesForApplication,
62185
62551
  saveWorkspaceForApplication,
62186
62552
  deleteWorkspaceForApplication,
@@ -62266,21 +62632,28 @@ var controller = {
62266
62632
  };
62267
62633
 
62268
62634
  const { ipcRenderer: ipcRenderer$q } = require$$0$1;
62269
- const {
62270
- SECURE_STORE_ENCRYPTION_CHECK,
62271
- SECURE_STORE_SET_DATA,
62272
- SECURE_STORE_GET_DATA,
62273
- } = events$8;
62635
+ const { SECURE_STORE_ENCRYPTION_CHECK } = events$8;
62274
62636
  /**
62275
62637
  * secureStoreApi
62276
- * - for Apple, keychain methods
62638
+ *
62639
+ * Renderer-facing wrapper for the secure-store IPC channels. Currently
62640
+ * exposes only `isEncryptionAvailable` because that's the only channel
62641
+ * with a wired handler in dash-electron's main process.
62642
+ *
62643
+ * `saveData` / `getData` were removed in this slice — the IPC handlers
62644
+ * for `SECURE_STORE_SET_DATA` / `SECURE_STORE_GET_DATA` were never
62645
+ * registered, so the methods silently no-op'd. Worse, they appeared
62646
+ * usable on `mainApi.secureStore` but had no widgetId scoping, so
62647
+ * adding handlers later would have given every widget unscoped access
62648
+ * to every other widget's keys. If you need a widget-facing storage
62649
+ * API in the future, add a `widgetId` parameter and plumb it through a
62650
+ * per-widget gate (see `electron/security/fsGate.js` for the pattern).
62651
+ * The pin in `secureStoreApi.test.js` will fail loudly if the
62652
+ * unscoped methods reappear without a gate.
62277
62653
  */
62278
62654
  const secureStoreApi$2 = {
62279
62655
  isEncryptionAvailable: () =>
62280
62656
  ipcRenderer$q.invoke(SECURE_STORE_ENCRYPTION_CHECK, {}),
62281
- saveData: (key, value) =>
62282
- ipcRenderer$q.invoke(SECURE_STORE_SET_DATA, { key, value }),
62283
- getData: (key) => ipcRenderer$q.invoke(SECURE_STORE_GET_DATA, { key }),
62284
62657
  };
62285
62658
 
62286
62659
  var secureStoreApi_1 = secureStoreApi$2;
@@ -62431,20 +62804,30 @@ const dataApi$2 = {
62431
62804
  * @param {object} options { filename, extension }
62432
62805
  * @param {object} returnEmpty the return empty object
62433
62806
  */
62434
- saveData: (data, filename, append, returnEmpty, uuid) =>
62807
+ saveData: (data, filename, append, returnEmpty, widgetId = null) =>
62435
62808
  ipcRenderer$n.invoke(DATA_SAVE_TO_FILE, {
62436
62809
  data,
62437
62810
  filename,
62438
62811
  append,
62439
62812
  returnEmpty,
62813
+ widgetId,
62440
62814
  }),
62441
62815
 
62442
62816
  /*
62443
62817
  * readData
62444
62818
  * @param {string} filename the filename to read (not path)
62819
+ * @param {string|null} widgetId Phase 2 JIT consent — the widget's
62820
+ * package name (e.g. "@trops/notes-summarizer"). Used by the fs
62821
+ * gate to scope per-widget read access. Null disables the gate
62822
+ * for legacy callers (`enforceWidgetMcpPermissions` flag still
62823
+ * gates the gate itself).
62445
62824
  */
62446
- readData: (filename, returnEmpty = []) =>
62447
- ipcRenderer$n.invoke(DATA_READ_FROM_FILE, { filename, returnEmpty }),
62825
+ readData: (filename, returnEmpty = [], widgetId = null) =>
62826
+ ipcRenderer$n.invoke(DATA_READ_FROM_FILE, {
62827
+ filename,
62828
+ returnEmpty,
62829
+ widgetId,
62830
+ }),
62448
62831
 
62449
62832
  /**
62450
62833
  * transformFile