@trops/dash-core 0.1.501 → 0.1.502

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.
@@ -4,7 +4,7 @@ var require$$0$1 = require('electron');
4
4
  var require$$1$1 = require('electron-store');
5
5
  var require$$1$2 = require('path');
6
6
  var require$$0$2 = require('fs');
7
- var require$$6$1 = require('objects-to-csv');
7
+ var require$$8$1 = require('objects-to-csv');
8
8
  var require$$1$3 = require('readline');
9
9
  var require$$2 = require('xtreamer');
10
10
  var require$$3$1 = require('xml2js');
@@ -12,7 +12,7 @@ var require$$4 = require('JSONStream');
12
12
  var require$$5 = require('stream');
13
13
  var require$$6 = require('csv-parser');
14
14
  var require$$0$3 = require('quickjs-emscripten');
15
- var require$$8$1 = require('https');
15
+ var require$$10 = require('https');
16
16
  var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
17
17
  var require$$1$4 = require('@modelcontextprotocol/sdk/client/stdio.js');
18
18
  var require$$0$4 = require('pkce-challenge');
@@ -1107,7 +1107,7 @@ var secureStoreController$1 = {
1107
1107
  getData: getData$1,
1108
1108
  };
1109
1109
 
1110
- const path$n = require$$1$2;
1110
+ const path$m = require$$1$2;
1111
1111
  const {
1112
1112
  readFileSync,
1113
1113
  writeFileSync: writeFileSync$4,
@@ -1122,10 +1122,10 @@ const {
1122
1122
  lstatSync,
1123
1123
  } = require$$0$2;
1124
1124
 
1125
- function ensureDirectoryExistence$2(filePath) {
1125
+ function ensureDirectoryExistence$1(filePath) {
1126
1126
  try {
1127
1127
  // isDirectory
1128
- var dirname = path$n.dirname(filePath);
1128
+ var dirname = path$m.dirname(filePath);
1129
1129
  // check if the directory exists...return true
1130
1130
  // if not, we can pass in the dirname as the filepath
1131
1131
  // and check each directory recursively.
@@ -1133,7 +1133,7 @@ function ensureDirectoryExistence$2(filePath) {
1133
1133
  return true;
1134
1134
  }
1135
1135
  // recursion...
1136
- ensureDirectoryExistence$2(dirname);
1136
+ ensureDirectoryExistence$1(dirname);
1137
1137
  mkdirSync(dirname);
1138
1138
  } catch (e) {
1139
1139
  console.log("ensure directory " + e.message);
@@ -1179,7 +1179,7 @@ function checkDirectory$1(dir) {
1179
1179
  function getFileContents$8(filepath, defaultReturn = []) {
1180
1180
  try {
1181
1181
  // lets first make sure all is there...
1182
- ensureDirectoryExistence$2(filepath);
1182
+ ensureDirectoryExistence$1(filepath);
1183
1183
 
1184
1184
  // and now lets read the file...
1185
1185
  let fileContents = JSON.stringify(defaultReturn);
@@ -1240,7 +1240,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1240
1240
 
1241
1241
  for (const file of files) {
1242
1242
  if (!excludeFiles.includes(file)) {
1243
- unlinkSync(path$n.join(directory, file), (err) => {
1243
+ unlinkSync(path$m.join(directory, file), (err) => {
1244
1244
  if (err) throw err;
1245
1245
  });
1246
1246
  }
@@ -1250,15 +1250,15 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1250
1250
  }
1251
1251
 
1252
1252
  var file = {
1253
- ensureDirectoryExistence: ensureDirectoryExistence$2,
1253
+ ensureDirectoryExistence: ensureDirectoryExistence$1,
1254
1254
  getFileContents: getFileContents$8,
1255
1255
  writeToFile: writeToFile$3,
1256
1256
  removeFilesFromDirectory,
1257
1257
  checkDirectory: checkDirectory$1,
1258
1258
  };
1259
1259
 
1260
- const { app: app$f } = require$$0$1;
1261
- const path$m = require$$1$2;
1260
+ const { app: app$e } = require$$0$1;
1261
+ const path$l = require$$1$2;
1262
1262
  const { writeFileSync: writeFileSync$3 } = require$$0$2;
1263
1263
  const { getFileContents: getFileContents$7 } = file;
1264
1264
 
@@ -1305,8 +1305,8 @@ const workspaceController$3 = {
1305
1305
  saveWorkspaceForApplication: (win, appId, workspaceObject) => {
1306
1306
  try {
1307
1307
  // filename to the pages file (live pages)
1308
- const filename = path$m.join(
1309
- app$f.getPath("userData"),
1308
+ const filename = path$l.join(
1309
+ app$e.getPath("userData"),
1310
1310
  appName$7,
1311
1311
  appId,
1312
1312
  configFilename$5,
@@ -1354,8 +1354,8 @@ const workspaceController$3 = {
1354
1354
  saveMenuItemsForApplication: (win, appId, menuItems) => {
1355
1355
  try {
1356
1356
  // filename to the workspaces file
1357
- const filename = path$m.join(
1358
- app$f.getPath("userData"),
1357
+ const filename = path$l.join(
1358
+ app$e.getPath("userData"),
1359
1359
  appName$7,
1360
1360
  appId,
1361
1361
  configFilename$5,
@@ -1403,8 +1403,8 @@ const workspaceController$3 = {
1403
1403
  */
1404
1404
  deleteWorkspaceForApplication: (win, appId, workspaceId) => {
1405
1405
  try {
1406
- const filename = path$m.join(
1407
- app$f.getPath("userData"),
1406
+ const filename = path$l.join(
1407
+ app$e.getPath("userData"),
1408
1408
  appName$7,
1409
1409
  appId,
1410
1410
  configFilename$5,
@@ -1437,8 +1437,8 @@ const workspaceController$3 = {
1437
1437
 
1438
1438
  listWorkspacesForApplication: (win, appId) => {
1439
1439
  try {
1440
- const filename = path$m.join(
1441
- app$f.getPath("userData"),
1440
+ const filename = path$l.join(
1441
+ app$e.getPath("userData"),
1442
1442
  appName$7,
1443
1443
  appId,
1444
1444
  configFilename$5,
@@ -1465,8 +1465,8 @@ const workspaceController$3 = {
1465
1465
 
1466
1466
  listMenuItemsForApplication: (win, appId) => {
1467
1467
  try {
1468
- const filename = path$m.join(
1469
- app$f.getPath("userData"),
1468
+ const filename = path$l.join(
1469
+ app$e.getPath("userData"),
1470
1470
  appName$7,
1471
1471
  appId,
1472
1472
  configFilename$5,
@@ -1509,8 +1509,8 @@ const workspaceController$3 = {
1509
1509
 
1510
1510
  var workspaceController_1 = workspaceController$3;
1511
1511
 
1512
- const { app: app$e } = require$$0$1;
1513
- const path$l = require$$1$2;
1512
+ const { app: app$d } = require$$0$1;
1513
+ const path$k = require$$1$2;
1514
1514
  const { writeFileSync: writeFileSync$2 } = require$$0$2;
1515
1515
  const { getFileContents: getFileContents$6 } = file;
1516
1516
 
@@ -1530,8 +1530,8 @@ const themeController$5 = {
1530
1530
  saveThemeForApplication: (win, appId, name, obj) => {
1531
1531
  try {
1532
1532
  // filename to the pages file (live pages)
1533
- const filename = path$l.join(
1534
- app$e.getPath("userData"),
1533
+ const filename = path$k.join(
1534
+ app$d.getPath("userData"),
1535
1535
  appName$6,
1536
1536
  appId,
1537
1537
  configFilename$4,
@@ -1576,8 +1576,8 @@ const themeController$5 = {
1576
1576
  */
1577
1577
  listThemesForApplication: (win, appId) => {
1578
1578
  try {
1579
- const filename = path$l.join(
1580
- app$e.getPath("userData"),
1579
+ const filename = path$k.join(
1580
+ app$d.getPath("userData"),
1581
1581
  appName$6,
1582
1582
  appId,
1583
1583
  configFilename$4,
@@ -1618,8 +1618,8 @@ const themeController$5 = {
1618
1618
  */
1619
1619
  deleteThemeForApplication: (win, appId, themeKey) => {
1620
1620
  try {
1621
- const filename = path$l.join(
1622
- app$e.getPath("userData"),
1621
+ const filename = path$k.join(
1622
+ app$d.getPath("userData"),
1623
1623
  appName$6,
1624
1624
  appId,
1625
1625
  configFilename$4,
@@ -1695,9 +1695,9 @@ var themeController_1 = themeController$5;
1695
1695
  * `/data/`.
1696
1696
  */
1697
1697
 
1698
- const path$k = require$$1$2;
1699
- const fs$f = require$$0$2;
1700
- const { app: app$d } = require$$0$1;
1698
+ const path$j = require$$1$2;
1699
+ const fs$e = require$$0$2;
1700
+ const { app: app$c } = require$$0$1;
1701
1701
 
1702
1702
  const APP_NAME = "Dashboard";
1703
1703
 
@@ -1706,10 +1706,10 @@ const APP_NAME = "Dashboard";
1706
1706
  * @returns {string[]} ordered allowed roots for that category
1707
1707
  */
1708
1708
  function getAllowedRoots$2(category) {
1709
- const userData = app$d.getPath("userData");
1709
+ const userData = app$c.getPath("userData");
1710
1710
  switch (category) {
1711
1711
  case "data": {
1712
- const def = path$k.join(userData, APP_NAME, "data");
1712
+ const def = path$j.join(userData, APP_NAME, "data");
1713
1713
  // The user can configure a custom data directory in
1714
1714
  // Settings → General → Data Directory. If set, that
1715
1715
  // location is ALSO an allowed root. We don't replace the
@@ -1719,13 +1719,13 @@ function getAllowedRoots$2(category) {
1719
1719
  return override ? [def, override] : [def];
1720
1720
  }
1721
1721
  case "themes":
1722
- return [path$k.join(userData, APP_NAME, "themes")];
1722
+ return [path$j.join(userData, APP_NAME, "themes")];
1723
1723
  case "widgets":
1724
- return [path$k.join(userData, "widgets")];
1724
+ return [path$j.join(userData, "widgets")];
1725
1725
  case "plugins":
1726
- return [path$k.join(userData, "plugins")];
1726
+ return [path$j.join(userData, "plugins")];
1727
1727
  case "downloads":
1728
- return [app$d.getPath("downloads")];
1728
+ return [app$c.getPath("downloads")];
1729
1729
  default:
1730
1730
  throw new Error("safePath: unknown allowed-roots category: " + category);
1731
1731
  }
@@ -1740,13 +1740,13 @@ function getAllowedRoots$2(category) {
1740
1740
  */
1741
1741
  function readDataDirectoryFromSettings() {
1742
1742
  try {
1743
- const settingsPath = path$k.join(
1744
- app$d.getPath("userData"),
1743
+ const settingsPath = path$j.join(
1744
+ app$c.getPath("userData"),
1745
1745
  APP_NAME,
1746
1746
  "settings.json",
1747
1747
  );
1748
- if (!fs$f.existsSync(settingsPath)) return undefined;
1749
- const raw = fs$f.readFileSync(settingsPath, "utf8");
1748
+ if (!fs$e.existsSync(settingsPath)) return undefined;
1749
+ const raw = fs$e.readFileSync(settingsPath, "utf8");
1750
1750
  const settings = JSON.parse(raw);
1751
1751
  const dir = settings && settings.dataDirectory;
1752
1752
  if (typeof dir === "string" && dir) return dir;
@@ -1772,18 +1772,18 @@ function safePath$3(requested, allowedRoots) {
1772
1772
  throw new Error("safePath: allowedRoots must be a non-empty array");
1773
1773
  }
1774
1774
 
1775
- const resolved = path$k.resolve(requested);
1775
+ const resolved = path$j.resolve(requested);
1776
1776
 
1777
1777
  // Real-path through symlinks. If the file doesn't exist yet (a
1778
1778
  // create-new operation), real-path the parent so a symlink in the
1779
1779
  // parent chain can't trick us.
1780
1780
  let real = resolved;
1781
1781
  try {
1782
- real = fs$f.realpathSync(resolved);
1782
+ real = fs$e.realpathSync(resolved);
1783
1783
  } catch (_e) {
1784
1784
  try {
1785
- const parent = fs$f.realpathSync(path$k.dirname(resolved));
1786
- real = path$k.join(parent, path$k.basename(resolved));
1785
+ const parent = fs$e.realpathSync(path$j.dirname(resolved));
1786
+ real = path$j.join(parent, path$j.basename(resolved));
1787
1787
  } catch (_e2) {
1788
1788
  // Parent doesn't exist either. Use the resolved-but-not-
1789
1789
  // real path; the caller's mkdirSync will happen inside the
@@ -1795,14 +1795,14 @@ function safePath$3(requested, allowedRoots) {
1795
1795
  for (const root of allowedRoots) {
1796
1796
  let realRoot = root;
1797
1797
  try {
1798
- if (fs$f.existsSync(root)) realRoot = fs$f.realpathSync(root);
1798
+ if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
1799
1799
  } catch (_e) {
1800
1800
  // root doesn't exist or isn't reachable — keep as-is for
1801
1801
  // the comparison below
1802
1802
  }
1803
1803
  // Exact match OR strictly-inside (with separator to prevent
1804
1804
  // /data-evil/ matching /data/).
1805
- if (real === realRoot || real.startsWith(realRoot + path$k.sep)) {
1805
+ if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
1806
1806
  return real;
1807
1807
  }
1808
1808
  }
@@ -1823,6 +1823,795 @@ var safePath_1 = {
1823
1823
  getAllowedRoots: getAllowedRoots$2,
1824
1824
  };
1825
1825
 
1826
+ /**
1827
+ * grantedPermissions.js
1828
+ *
1829
+ * Stores the user's actual MCP permission grants per widget. This is the
1830
+ * Slice-2 enforcement source of truth — separate from the widget's declared
1831
+ * `dash.permissions.mcp` block (which is just a request).
1832
+ *
1833
+ * The runtime gate (permissionGate.gateToolCall) reads from here only.
1834
+ * A widget with a declared manifest but no grant entry has no access:
1835
+ * fail-closed. The user grants permissions at install time (consent modal)
1836
+ * or later in Settings → Privacy & Security.
1837
+ *
1838
+ * Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
1839
+ *
1840
+ * Shape on disk:
1841
+ * {
1842
+ * "@trops/notes-summarizer": {
1843
+ * "servers": {
1844
+ * "filesystem": {
1845
+ * "tools": ["read_file"],
1846
+ * "readPaths": ["/Users/jane/Documents/notes"],
1847
+ * "writePaths": []
1848
+ * }
1849
+ * }
1850
+ * }
1851
+ * }
1852
+ *
1853
+ * Note: paths are stored as-is (already tilde-expanded by the manifest
1854
+ * parser before grants are written). Tests can re-expand via
1855
+ * widgetPermissions.expandHome if they store ~ literals.
1856
+ *
1857
+ * Public API:
1858
+ * getGrant(widgetId) → grant | null
1859
+ * setGrant(widgetId, perms) → boolean
1860
+ * revokeGrant(widgetId) → boolean
1861
+ * revokeServer(widgetId, serverName) → boolean
1862
+ * listAllGrants() → [{ widgetId, granted }]
1863
+ * clearCache() → void // test-only
1864
+ */
1865
+
1866
+ const fs$d = require$$0$2;
1867
+ const path$i = require$$1$2;
1868
+ const { app: app$b } = require$$0$1;
1869
+
1870
+ const FILE_NAME = "widgetMcpGrants.json";
1871
+
1872
+ // In-process cache of the entire grants file. Lazily loaded; invalidated
1873
+ // on every write.
1874
+ let _cache$1 = null;
1875
+
1876
+ function grantsFilePath() {
1877
+ return path$i.join(app$b.getPath("userData"), FILE_NAME);
1878
+ }
1879
+
1880
+ function loadFromDisk() {
1881
+ const p = grantsFilePath();
1882
+ if (!fs$d.existsSync(p)) return {};
1883
+ try {
1884
+ const raw = fs$d.readFileSync(p, "utf8");
1885
+ const parsed = JSON.parse(raw);
1886
+ if (!parsed || typeof parsed !== "object") return {};
1887
+ return parsed;
1888
+ } catch (e) {
1889
+ console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
1890
+ return {};
1891
+ }
1892
+ }
1893
+
1894
+ function ensureCache() {
1895
+ if (_cache$1 === null) _cache$1 = loadFromDisk();
1896
+ return _cache$1;
1897
+ }
1898
+
1899
+ function writeToDisk(data) {
1900
+ const p = grantsFilePath();
1901
+ const tmp = p + ".tmp";
1902
+ // Ensure parent dir exists (userData should already, but be defensive
1903
+ // for first-launch / freshly-cleared profile cases).
1904
+ fs$d.mkdirSync(path$i.dirname(p), { recursive: true });
1905
+ fs$d.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
1906
+ fs$d.renameSync(tmp, p);
1907
+ }
1908
+
1909
+ // Recognized origins for a persisted grant.
1910
+ // "declared" — user approved against the developer's declared
1911
+ // dash.permissions.mcp block at install time.
1912
+ // "discovered" — install-time scanner produced a synthetic manifest
1913
+ // the user approved.
1914
+ // "manual" — user typed entries themselves in
1915
+ // Settings → Privacy & Security with no manifest backing.
1916
+ // "live" — user approved a just-in-time consent prompt at
1917
+ // runtime when a tool call hit the gate without a
1918
+ // matching grant.
1919
+ // Other values are dropped on persist (legacy grants stay null).
1920
+ const ALLOWED_GRANT_ORIGINS = new Set([
1921
+ "declared",
1922
+ "discovered",
1923
+ "manual",
1924
+ "live",
1925
+ ]);
1926
+
1927
+ /**
1928
+ * Sanitize a perms object before persisting. Drops unknown keys, coerces
1929
+ * arrays of strings, and silently ignores malformed servers. Mirrors the
1930
+ * shape produced by parseManifestPermissions so the gate reads either
1931
+ * declared or granted with the same code path.
1932
+ *
1933
+ * Optional `grantOrigin` field is preserved when it's one of the
1934
+ * recognized values; bogus values are dropped.
1935
+ *
1936
+ * Phase 2 (JIT consent for non-MCP domains): also accepts a top-level
1937
+ * `domains` block. Each known domain has its own shape — `domains.fs`
1938
+ * has `readPaths`/`writePaths`, future domains will have their own.
1939
+ * The `servers` block (MCP) and `domains` block coexist on the same
1940
+ * grant; either or both may be present.
1941
+ *
1942
+ * Either `servers` (MCP) or any `domains.*` block is enough to make the
1943
+ * grant non-empty. If neither is present, the grant is rejected as
1944
+ * malformed.
1945
+ */
1946
+ function sanitizePerms(perms) {
1947
+ if (!perms || typeof perms !== "object") return null;
1948
+ const rawServers =
1949
+ perms.servers && typeof perms.servers === "object" ? perms.servers : null;
1950
+ const rawDomains =
1951
+ perms.domains && typeof perms.domains === "object" ? perms.domains : null;
1952
+ if (!rawServers && !rawDomains) return null;
1953
+
1954
+ const out = {};
1955
+
1956
+ if (rawServers) {
1957
+ const servers = {};
1958
+ for (const [name, raw] of Object.entries(rawServers)) {
1959
+ if (!raw || typeof raw !== "object") continue;
1960
+ servers[name] = {
1961
+ tools: Array.isArray(raw.tools)
1962
+ ? raw.tools.filter((t) => typeof t === "string")
1963
+ : [],
1964
+ readPaths: Array.isArray(raw.readPaths)
1965
+ ? raw.readPaths.filter((p) => typeof p === "string")
1966
+ : [],
1967
+ writePaths: Array.isArray(raw.writePaths)
1968
+ ? raw.writePaths.filter((p) => typeof p === "string")
1969
+ : [],
1970
+ };
1971
+ }
1972
+ out.servers = servers;
1973
+ } else {
1974
+ // Always emit `servers` so consumers don't have to null-check it.
1975
+ out.servers = {};
1976
+ }
1977
+
1978
+ if (rawDomains) {
1979
+ const domains = {};
1980
+ for (const [name, raw] of Object.entries(rawDomains)) {
1981
+ if (!raw || typeof raw !== "object") continue;
1982
+ if (name === "fs") {
1983
+ domains.fs = {
1984
+ readPaths: Array.isArray(raw.readPaths)
1985
+ ? raw.readPaths.filter((p) => typeof p === "string")
1986
+ : [],
1987
+ writePaths: Array.isArray(raw.writePaths)
1988
+ ? raw.writePaths.filter((p) => typeof p === "string")
1989
+ : [],
1990
+ };
1991
+ }
1992
+ // Future domains plug in here. Unknown domain names are dropped.
1993
+ }
1994
+ if (Object.keys(domains).length > 0) {
1995
+ out.domains = domains;
1996
+ }
1997
+ }
1998
+
1999
+ if (
2000
+ typeof perms.grantOrigin === "string" &&
2001
+ ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
2002
+ ) {
2003
+ out.grantOrigin = perms.grantOrigin;
2004
+ }
2005
+ return out;
2006
+ }
2007
+
2008
+ function getGrant$3(widgetId) {
2009
+ if (typeof widgetId !== "string" || !widgetId) return null;
2010
+ const all = ensureCache();
2011
+ return all[widgetId] || null;
2012
+ }
2013
+
2014
+ function setGrant$3(widgetId, perms) {
2015
+ if (typeof widgetId !== "string" || !widgetId) return false;
2016
+ const sanitized = sanitizePerms(perms);
2017
+ if (!sanitized) return false;
2018
+ const all = ensureCache();
2019
+ all[widgetId] = sanitized;
2020
+ try {
2021
+ writeToDisk(all);
2022
+ return true;
2023
+ } catch (e) {
2024
+ console.warn(
2025
+ "[grantedPermissions] failed to write grant for " +
2026
+ widgetId +
2027
+ ": " +
2028
+ e.message,
2029
+ );
2030
+ // Roll back the cache entry so memory matches disk.
2031
+ _cache$1 = loadFromDisk();
2032
+ return false;
2033
+ }
2034
+ }
2035
+
2036
+ function revokeGrant$1(widgetId) {
2037
+ if (typeof widgetId !== "string" || !widgetId) return false;
2038
+ const all = ensureCache();
2039
+ if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
2040
+ delete all[widgetId];
2041
+ try {
2042
+ writeToDisk(all);
2043
+ return true;
2044
+ } catch (e) {
2045
+ console.warn(
2046
+ "[grantedPermissions] failed to revoke grant for " +
2047
+ widgetId +
2048
+ ": " +
2049
+ e.message,
2050
+ );
2051
+ _cache$1 = loadFromDisk();
2052
+ return false;
2053
+ }
2054
+ }
2055
+
2056
+ function revokeServer$1(widgetId, serverName) {
2057
+ if (typeof widgetId !== "string" || !widgetId) return false;
2058
+ if (typeof serverName !== "string" || !serverName) return false;
2059
+ const all = ensureCache();
2060
+ const widgetEntry = all[widgetId];
2061
+ if (!widgetEntry || !widgetEntry.servers) return false;
2062
+ if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
2063
+ return false;
2064
+ delete widgetEntry.servers[serverName];
2065
+ try {
2066
+ writeToDisk(all);
2067
+ return true;
2068
+ } catch (e) {
2069
+ console.warn(
2070
+ "[grantedPermissions] failed to revoke server " +
2071
+ serverName +
2072
+ " for " +
2073
+ widgetId +
2074
+ ": " +
2075
+ e.message,
2076
+ );
2077
+ _cache$1 = loadFromDisk();
2078
+ return false;
2079
+ }
2080
+ }
2081
+
2082
+ function listAllGrants$1() {
2083
+ const all = ensureCache();
2084
+ return Object.entries(all).map(([widgetId, granted]) => ({
2085
+ widgetId,
2086
+ granted,
2087
+ }));
2088
+ }
2089
+
2090
+ function clearCache$1() {
2091
+ _cache$1 = null;
2092
+ }
2093
+
2094
+ var grantedPermissions = {
2095
+ getGrant: getGrant$3,
2096
+ setGrant: setGrant$3,
2097
+ revokeGrant: revokeGrant$1,
2098
+ revokeServer: revokeServer$1,
2099
+ listAllGrants: listAllGrants$1,
2100
+ clearCache: clearCache$1,
2101
+ ALLOWED_GRANT_ORIGINS,
2102
+ };
2103
+
2104
+ /**
2105
+ * jitConsent.js
2106
+ *
2107
+ * Just-in-time permission consent for widget→backend calls.
2108
+ *
2109
+ * When a widget hits a gate without an existing grant for the requested
2110
+ * (domain, action, args), the gate calls `requestApproval` which:
2111
+ * 1. Synchronously emits `widget:permission-required` to all
2112
+ * BrowserWindows with a unique requestId.
2113
+ * 2. Returns a Promise that resolves on user response or rejects on
2114
+ * timeout.
2115
+ * 3. Coalesces requests with the same coalescing key so a widget
2116
+ * bursting identical calls produces one prompt, not many.
2117
+ *
2118
+ * The renderer's JitConsentModal subscribes to the event, presents the
2119
+ * user with granularity options (this once / this tool / this tool +
2120
+ * parent dir), and replies via `widget:permission-response` with
2121
+ * `{ requestId, decision }`. main.js wires the IPC handler back to
2122
+ * `_handleResponse`.
2123
+ *
2124
+ * The module is intentionally domain-agnostic in shape — the request
2125
+ * payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
2126
+ * the same machinery. Phase 1 only emits with `domain: "mcp"`.
2127
+ *
2128
+ * Public surface:
2129
+ * requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
2130
+ * _handleResponse({ requestId, decision }) → void (called from main.js IPC)
2131
+ * _resetForTest() → void (test-only)
2132
+ */
2133
+
2134
+ const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
2135
+
2136
+ const REQUEST_CHANNEL = "widget:permission-required";
2137
+ const RESPONSE_CHANNEL = "widget:permission-response";
2138
+ const DEFAULT_TIMEOUT_MS = 60_000;
2139
+
2140
+ // requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
2141
+ const _pending = new Map();
2142
+ // coalesceKey → requestId (so duplicate requests join the live one)
2143
+ const _coalesce = new Map();
2144
+ let _idCounter = 0;
2145
+
2146
+ function nextRequestId() {
2147
+ _idCounter += 1;
2148
+ return `jit-${Date.now()}-${_idCounter}`;
2149
+ }
2150
+
2151
+ /**
2152
+ * Build a coalescing key from the request. Two requests share the same
2153
+ * key iff they're "the same prompt" — same widget, same domain+action,
2154
+ * same target server/tool. Args beyond that (e.g. exact path) DON'T
2155
+ * differentiate; if the user is being asked about read_file already,
2156
+ * approving handles all current paths.
2157
+ */
2158
+ function coalesceKeyOf(req) {
2159
+ if (req.domain === "mcp") {
2160
+ const innerArgs = req.args || {};
2161
+ return [
2162
+ req.widgetId,
2163
+ "mcp",
2164
+ innerArgs.serverName || "",
2165
+ innerArgs.toolName || "",
2166
+ ].join("::");
2167
+ }
2168
+ // Default: domain + action + serialized top-level args
2169
+ return [
2170
+ req.widgetId,
2171
+ req.domain,
2172
+ req.action,
2173
+ JSON.stringify(req.args || {}),
2174
+ ].join("::");
2175
+ }
2176
+
2177
+ function emitEvent(payload) {
2178
+ let wins = [];
2179
+ try {
2180
+ wins = BrowserWindow$2.getAllWindows() || [];
2181
+ } catch {
2182
+ wins = [];
2183
+ }
2184
+ for (const w of wins) {
2185
+ try {
2186
+ w?.webContents?.send?.(REQUEST_CHANNEL, payload);
2187
+ } catch {
2188
+ // best-effort broadcast
2189
+ }
2190
+ }
2191
+ }
2192
+
2193
+ function validateRequest(req) {
2194
+ if (!req || typeof req !== "object") return "invalid request: not an object";
2195
+ if (typeof req.widgetId !== "string" || !req.widgetId)
2196
+ return "invalid request: widgetId required";
2197
+ if (typeof req.domain !== "string" || !req.domain)
2198
+ return "invalid request: domain required";
2199
+ if (typeof req.action !== "string" || !req.action)
2200
+ return "invalid request: action required";
2201
+ return null;
2202
+ }
2203
+
2204
+ /**
2205
+ * Request user approval for an out-of-grant call. Returns a promise
2206
+ * that resolves with the user's decision or rejects on timeout / bad
2207
+ * input.
2208
+ *
2209
+ * decision shape (resolved value):
2210
+ * { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
2211
+ * { approve: false, reason?: string }
2212
+ *
2213
+ * `scope` informs the caller how to write the resulting grant.
2214
+ */
2215
+ function requestApproval$2(req, opts = {}) {
2216
+ const validation = validateRequest(req);
2217
+ if (validation) {
2218
+ return Promise.reject(new Error(validation));
2219
+ }
2220
+
2221
+ const timeoutMs = Number.isFinite(opts.timeoutMs)
2222
+ ? opts.timeoutMs
2223
+ : DEFAULT_TIMEOUT_MS;
2224
+
2225
+ // If a prompt for the same coalesce key is already pending, join it.
2226
+ const key = coalesceKeyOf(req);
2227
+ if (_coalesce.has(key)) {
2228
+ const existingId = _coalesce.get(key);
2229
+ const existing = _pending.get(existingId);
2230
+ if (existing) {
2231
+ return new Promise((resolve, reject) => {
2232
+ existing.joinedResolvers.push({ resolve, reject });
2233
+ });
2234
+ }
2235
+ // Stale coalesce entry; drop and fall through to a fresh request.
2236
+ _coalesce.delete(key);
2237
+ }
2238
+
2239
+ return new Promise((resolve, reject) => {
2240
+ const requestId = nextRequestId();
2241
+ const timeout = setTimeout(() => {
2242
+ const entry = _pending.get(requestId);
2243
+ if (!entry) return;
2244
+ _pending.delete(requestId);
2245
+ _coalesce.delete(entry.coalesceKey);
2246
+ const err = new Error(
2247
+ `JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
2248
+ );
2249
+ reject(err);
2250
+ for (const j of entry.joinedResolvers) j.reject(err);
2251
+ }, timeoutMs);
2252
+
2253
+ _pending.set(requestId, {
2254
+ resolve,
2255
+ reject,
2256
+ timeout,
2257
+ coalesceKey: key,
2258
+ joinedResolvers: [],
2259
+ });
2260
+ _coalesce.set(key, requestId);
2261
+
2262
+ emitEvent({
2263
+ requestId,
2264
+ widgetId: req.widgetId,
2265
+ domain: req.domain,
2266
+ action: req.action,
2267
+ args: req.args || {},
2268
+ });
2269
+ });
2270
+ }
2271
+
2272
+ function _handleResponse({ requestId, decision } = {}) {
2273
+ if (!requestId || typeof requestId !== "string") return;
2274
+ const entry = _pending.get(requestId);
2275
+ if (!entry) return; // unknown request — drop silently
2276
+ clearTimeout(entry.timeout);
2277
+ _pending.delete(requestId);
2278
+ _coalesce.delete(entry.coalesceKey);
2279
+ const safe =
2280
+ decision && typeof decision === "object" ? decision : { approve: false };
2281
+ entry.resolve(safe);
2282
+ for (const j of entry.joinedResolvers) j.resolve(safe);
2283
+ }
2284
+
2285
+ function _resetForTest() {
2286
+ for (const entry of _pending.values()) clearTimeout(entry.timeout);
2287
+ _pending.clear();
2288
+ _coalesce.clear();
2289
+ _idCounter = 0;
2290
+ }
2291
+
2292
+ let _handlersRegistered = false;
2293
+ /**
2294
+ * Wire the renderer→main response IPC. Idempotent.
2295
+ * Call once from main.js alongside other ipcMain setup.
2296
+ */
2297
+ function setupJitConsentHandlers() {
2298
+ if (_handlersRegistered) return;
2299
+ if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
2300
+ ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
2301
+ _handleResponse(payload);
2302
+ });
2303
+ _handlersRegistered = true;
2304
+ }
2305
+
2306
+ var jitConsent$1 = {
2307
+ requestApproval: requestApproval$2,
2308
+ setupJitConsentHandlers,
2309
+ _handleResponse,
2310
+ _resetForTest,
2311
+ REQUEST_CHANNEL,
2312
+ RESPONSE_CHANNEL,
2313
+ DEFAULT_TIMEOUT_MS,
2314
+ };
2315
+
2316
+ /**
2317
+ * fsGate.js
2318
+ *
2319
+ * Per-widget gate for `mainApi.data.*` IPC handlers (Phase 2 of JIT
2320
+ * consent). Same shape as `electron/mcp/permissionGate.js` but for the
2321
+ * filesystem domain — saveToFile/readFromFile in dataController.
2322
+ *
2323
+ * The gate evaluates against the widget's persisted grant under
2324
+ * `grant.domains.fs.{readPaths,writePaths}`. The existing `safePath()`
2325
+ * containment in dataController is unchanged — that constrains paths
2326
+ * to the userData/Dashboard/data dir; this gate adds per-widget
2327
+ * identity scoping on top, so two widgets can't read each other's
2328
+ * files even though both are inside the data dir.
2329
+ *
2330
+ * Action → read/write classification by name. Read tools may match
2331
+ * either readPaths or writePaths (write access implies read); write
2332
+ * tools must match writePaths only.
2333
+ *
2334
+ * Filename matching:
2335
+ * - Exact match
2336
+ * - "*" wildcard in the grant matches any filename (escape hatch
2337
+ * for users who want broad widget access; surfaced in the JIT
2338
+ * modal as "no path scope — risky")
2339
+ *
2340
+ * Path-traversal protection lives in safePath; the gate doesn't
2341
+ * re-check it. The gate is purely an identity+filename allowlist on
2342
+ * top of safePath's containment.
2343
+ *
2344
+ * JIT escalation: when the runtime calls `gateFsCallWithJit` and the
2345
+ * gate denies for "no fs permissions granted", a permission-required
2346
+ * IPC fires and the user's response is merged into the persisted
2347
+ * grant. Other denial reasons (filename not in allowlist, write to a
2348
+ * read-only entry) stay synchronous.
2349
+ */
2350
+
2351
+ const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
2352
+ const { requestApproval: requestApproval$1 } = jitConsent$1;
2353
+
2354
+ // Action names treated as writes. Anything not in this set is a read.
2355
+ // Conservative — when in doubt, classify as a read so write-protected
2356
+ // grants don't accidentally allow writes.
2357
+ const WRITE_ACTIONS = new Set([
2358
+ "saveToFile",
2359
+ "saveData", // future-proof: the renderer-facing API name
2360
+ "convertJsonToCsvFile",
2361
+ "parseXMLStream",
2362
+ "parseCSVStream",
2363
+ "readDataFromURL", // writes to toFilepath despite the name
2364
+ "transformFile",
2365
+ ]);
2366
+
2367
+ function isFsWriteAction(action) {
2368
+ return WRITE_ACTIONS.has(action);
2369
+ }
2370
+
2371
+ function _isNoGrantDenial$1(reason) {
2372
+ return (
2373
+ typeof reason === "string" && /no fs permissions granted/i.test(reason)
2374
+ );
2375
+ }
2376
+
2377
+ function _filenameMatches(filename, allowedList) {
2378
+ if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
2379
+ if (allowedList.includes("*")) return true;
2380
+ return allowedList.includes(filename);
2381
+ }
2382
+
2383
+ /**
2384
+ * Synchronous gate evaluation.
2385
+ * @returns {{ allow: true } | { allow: false, reason: string }}
2386
+ */
2387
+ function gateFsCall$1({ widgetId, action, args }) {
2388
+ if (!widgetId) {
2389
+ return {
2390
+ allow: false,
2391
+ reason: "no widgetId supplied; cannot determine fs permissions",
2392
+ };
2393
+ }
2394
+
2395
+ const filename = args && typeof args === "object" ? args.filename : null;
2396
+ if (typeof filename !== "string" || !filename) {
2397
+ return {
2398
+ allow: false,
2399
+ reason:
2400
+ "fs gate: action '" +
2401
+ action +
2402
+ "' requires args.filename (got: " +
2403
+ JSON.stringify(filename) +
2404
+ ")",
2405
+ };
2406
+ }
2407
+
2408
+ const grant = getGrant$2(widgetId);
2409
+ const fsPerms = grant && grant.domains && grant.domains.fs;
2410
+ if (!fsPerms) {
2411
+ return {
2412
+ allow: false,
2413
+ reason:
2414
+ "widget '" +
2415
+ widgetId +
2416
+ "' has no fs permissions granted; user must approve at runtime or in Settings → Privacy & Security",
2417
+ };
2418
+ }
2419
+
2420
+ const isWrite = isFsWriteAction(action);
2421
+
2422
+ if (isWrite) {
2423
+ if (!Array.isArray(fsPerms.writePaths) || fsPerms.writePaths.length === 0) {
2424
+ return {
2425
+ allow: false,
2426
+ reason:
2427
+ "fs gate: widget '" +
2428
+ widgetId +
2429
+ "' has no writePaths granted for action '" +
2430
+ action +
2431
+ "'",
2432
+ };
2433
+ }
2434
+ if (!_filenameMatches(filename, fsPerms.writePaths)) {
2435
+ return {
2436
+ allow: false,
2437
+ reason:
2438
+ "fs gate: filename '" +
2439
+ filename +
2440
+ "' rejected — not in allowed writePaths for widget '" +
2441
+ widgetId +
2442
+ "'",
2443
+ };
2444
+ }
2445
+ return { allow: true };
2446
+ }
2447
+
2448
+ // Read action — may use readPaths OR writePaths (write implies read)
2449
+ if (
2450
+ _filenameMatches(filename, fsPerms.readPaths) ||
2451
+ _filenameMatches(filename, fsPerms.writePaths)
2452
+ ) {
2453
+ return { allow: true };
2454
+ }
2455
+ return {
2456
+ allow: false,
2457
+ reason:
2458
+ "fs gate: filename '" +
2459
+ filename +
2460
+ "' rejected — not in allowed readPaths or writePaths for widget '" +
2461
+ widgetId +
2462
+ "'",
2463
+ };
2464
+ }
2465
+
2466
+ /**
2467
+ * Merge an approved JIT decision's grant into the widget's existing
2468
+ * grant under domains.fs. Same shape as permissionGate._mergeGrant
2469
+ * but scoped to fs.
2470
+ */
2471
+ function _mergeFsGrant(current, addition) {
2472
+ const out = {
2473
+ grantOrigin: addition.grantOrigin || current?.grantOrigin || null,
2474
+ servers: { ...(current?.servers || {}) },
2475
+ domains: { ...(current?.domains || {}) },
2476
+ };
2477
+ const additionFs = addition?.domains?.fs;
2478
+ if (additionFs) {
2479
+ const existingFs = out.domains.fs || { readPaths: [], writePaths: [] };
2480
+ out.domains.fs = {
2481
+ readPaths: [
2482
+ ...new Set([
2483
+ ...(existingFs.readPaths || []),
2484
+ ...(Array.isArray(additionFs.readPaths) ? additionFs.readPaths : []),
2485
+ ]),
2486
+ ],
2487
+ writePaths: [
2488
+ ...new Set([
2489
+ ...(existingFs.writePaths || []),
2490
+ ...(Array.isArray(additionFs.writePaths)
2491
+ ? additionFs.writePaths
2492
+ : []),
2493
+ ]),
2494
+ ],
2495
+ };
2496
+ }
2497
+ return out;
2498
+ }
2499
+
2500
+ /**
2501
+ * Async gate that escalates "no fs grant" denials to a JIT consent
2502
+ * prompt when `opts.enableJit` is true. On approval, merges the
2503
+ * decision's grant blob into the persisted grant and re-evaluates.
2504
+ */
2505
+ async function gateFsCallWithJit$1(req, opts = {}) {
2506
+ const initial = gateFsCall$1(req);
2507
+ if (initial.allow) return initial;
2508
+ if (!opts.enableJit) return initial;
2509
+ if (!_isNoGrantDenial$1(initial.reason)) return initial;
2510
+
2511
+ let decision;
2512
+ try {
2513
+ decision = await requestApproval$1(
2514
+ {
2515
+ widgetId: req.widgetId,
2516
+ domain: "fs",
2517
+ action: req.action,
2518
+ args: req.args || {},
2519
+ },
2520
+ { timeoutMs: opts.timeoutMs },
2521
+ );
2522
+ } catch (e) {
2523
+ return {
2524
+ allow: false,
2525
+ reason:
2526
+ "JIT consent " +
2527
+ (e && e.message ? e.message : "failed") +
2528
+ "; original denial: " +
2529
+ initial.reason,
2530
+ };
2531
+ }
2532
+
2533
+ if (!decision || decision.approve !== true) {
2534
+ return {
2535
+ allow: false,
2536
+ reason:
2537
+ "user declined JIT consent for widget '" +
2538
+ req.widgetId +
2539
+ "' calling fs '" +
2540
+ req.action +
2541
+ "'",
2542
+ };
2543
+ }
2544
+
2545
+ const filename = req.args?.filename || "*";
2546
+ const isWrite = isFsWriteAction(req.action);
2547
+ const addition =
2548
+ decision.granted && typeof decision.granted === "object"
2549
+ ? decision.granted
2550
+ : {
2551
+ grantOrigin: "live",
2552
+ domains: {
2553
+ fs: {
2554
+ readPaths: !isWrite ? [filename] : [],
2555
+ writePaths: isWrite ? [filename] : [],
2556
+ },
2557
+ },
2558
+ };
2559
+ addition.grantOrigin = "live";
2560
+
2561
+ try {
2562
+ const current = getGrant$2(req.widgetId);
2563
+ const merged = _mergeFsGrant(current, addition);
2564
+ setGrant$2(req.widgetId, merged);
2565
+ } catch (e) {
2566
+ return {
2567
+ allow: false,
2568
+ reason:
2569
+ "JIT consent: failed to persist fs grant: " +
2570
+ (e && e.message ? e.message : String(e)),
2571
+ };
2572
+ }
2573
+
2574
+ return gateFsCall$1(req);
2575
+ }
2576
+
2577
+ var fsGate = {
2578
+ gateFsCall: gateFsCall$1,
2579
+ gateFsCallWithJit: gateFsCallWithJit$1,
2580
+ isFsWriteAction,
2581
+ WRITE_ACTIONS,
2582
+ };
2583
+
2584
+ /**
2585
+ * securityFlags.js
2586
+ *
2587
+ * Centralized readers for the two boolean security flags that gate the
2588
+ * MCP allowlist stack:
2589
+ * - security.enforceWidgetMcpPermissions
2590
+ * - security.enableJitConsent
2591
+ *
2592
+ * **Default semantics: ON.** A missing settings.json, a missing
2593
+ * `security` block, or an undefined field all yield `true`. Only an
2594
+ * explicit `false` opts out. This is intentional — the security stack
2595
+ * is on by default; users have to actively disable it. The
2596
+ * Privacy & Security panel surfaces the toggles + a confirm-on-disable
2597
+ * dialog so the disable path is deliberate.
2598
+ *
2599
+ * The readers are pure functions of a settings object so the
2600
+ * default-on semantics are pinned by unit tests without touching the
2601
+ * filesystem. The callers in mcpController.js wrap these with
2602
+ * settings.json IO.
2603
+ */
2604
+
2605
+ function readEnforceFlag$2(settings) {
2606
+ return settings?.security?.enforceWidgetMcpPermissions !== false;
2607
+ }
2608
+
2609
+ function readJitFlag$2(settings) {
2610
+ return settings?.security?.enableJitConsent !== false;
2611
+ }
2612
+
2613
+ var securityFlags = { readEnforceFlag: readEnforceFlag$2, readJitFlag: readJitFlag$2 };
2614
+
1826
2615
  /**
1827
2616
  * safeJsExecutor.js
1828
2617
  *
@@ -1866,192 +2655,201 @@ var safePath_1 = {
1866
2655
  * while QuickJS is executing synchronously.
1867
2656
  */
1868
2657
 
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
2658
+ var safeJsExecutor;
2659
+ var hasRequiredSafeJsExecutor;
2660
+
2661
+ function requireSafeJsExecutor () {
2662
+ if (hasRequiredSafeJsExecutor) return safeJsExecutor;
2663
+ hasRequiredSafeJsExecutor = 1;
2664
+
2665
+ // quickjs-emscripten is loaded lazily inside getModule() rather than at
2666
+ // module top. Reason: when transform.js (which lives in the same dir)
2667
+ // does require("./safeJsExecutor") and rollup-plugin-commonjs sees a
2668
+ // statically-required external (`quickjs-emscripten`) inside that file,
2669
+ // it can mark the relative import as transitive-external and then fail
2670
+ // to resolve back. Deferring the require breaks the static-analysis
2671
+ // chain so the rollup build resolves cleanly.
2672
+ const DEFAULT_TIMEOUT_MS = 1000;
2673
+ const DEFAULT_MEMORY_BYTES = 32 * 1024 * 1024; // 32 MB
2674
+
2675
+ let _modulePromise = null;
2676
+ function getModule() {
2677
+ if (!_modulePromise) {
2678
+ const { getQuickJS } = require$$0$3;
2679
+ _modulePromise = getQuickJS();
2680
+ }
2681
+ return _modulePromise;
2682
+ }
1878
2683
 
1879
- let _modulePromise = null;
1880
- function getModule() {
1881
- if (!_modulePromise) {
1882
- const { getQuickJS } = require$$0$3;
1883
- _modulePromise = getQuickJS();
1884
- }
1885
- return _modulePromise;
1886
- }
2684
+ function injectInputs(vm, args, inputs) {
2685
+ if (!Array.isArray(args) || !Array.isArray(inputs)) {
2686
+ throw new Error(
2687
+ "safeJsExecutor: args and inputs must be arrays of equal length",
2688
+ );
2689
+ }
2690
+ if (args.length !== inputs.length) {
2691
+ throw new Error("safeJsExecutor: args.length must equal inputs.length");
2692
+ }
2693
+ for (let i = 0; i < args.length; i++) {
2694
+ const name = args[i];
2695
+ if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
2696
+ throw new Error("safeJsExecutor: arg names must be valid JS identifiers");
2697
+ }
2698
+ const json = JSON.stringify(inputs[i]);
2699
+ // Use evalCode to materialize the JSON-typed value inside the
2700
+ // VM. For undefined inputs, JSON.stringify returns undefined
2701
+ // (not a string) — fall through to evaluating the literal
2702
+ // `undefined`.
2703
+ const literal = json === undefined ? "undefined" : json;
2704
+ const result = vm.evalCode(`(${literal})`);
2705
+ if (result.error) {
2706
+ const err = vm.dump(result.error);
2707
+ result.error.dispose();
2708
+ throw new Error(
2709
+ `safeJsExecutor: failed to inject "${name}": ${
2710
+ err && err.message ? err.message : err
2711
+ }`,
2712
+ );
2713
+ }
2714
+ vm.setProp(vm.global, name, result.value);
2715
+ result.value.dispose();
2716
+ }
2717
+ }
1887
2718
 
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
- }
2719
+ function setDeadline(vm, timeoutMs) {
2720
+ const deadline = Date.now() + Math.max(1, timeoutMs);
2721
+ vm.runtime.setInterruptHandler(() => Date.now() > deadline);
2722
+ }
1922
2723
 
1923
- function setDeadline(vm, timeoutMs) {
1924
- const deadline = Date.now() + Math.max(1, timeoutMs);
1925
- vm.runtime.setInterruptHandler(() => Date.now() > deadline);
1926
- }
2724
+ function buildWrappedBody(args, body) {
2725
+ // Wrap the user body in an IIFE so `return` works at the body level
2726
+ // (matching the dynamic-function-constructor semantics this is
2727
+ // replacing).
2728
+ return `(function(${args.join(",")}){${body}})(${args.join(",")})`;
2729
+ }
1927
2730
 
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
- }
2731
+ async function runOnce({
2732
+ body,
2733
+ args = [],
2734
+ inputs = [],
2735
+ timeoutMs = DEFAULT_TIMEOUT_MS,
2736
+ memoryBytes = DEFAULT_MEMORY_BYTES,
2737
+ }) {
2738
+ if (typeof body !== "string" || !body.trim()) {
2739
+ return { error: "body must be a non-empty string" };
2740
+ }
2741
+ const QuickJS = await getModule();
2742
+ const vm = QuickJS.newContext();
2743
+ try {
2744
+ vm.runtime.setMemoryLimit(memoryBytes);
2745
+ vm.runtime.setMaxStackSize(1024 * 1024);
2746
+ setDeadline(vm, timeoutMs);
1934
2747
 
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);
2748
+ injectInputs(vm, args, inputs);
2749
+ const wrapped = buildWrappedBody(args, body);
2750
+ const result = vm.evalCode(wrapped);
1951
2751
 
1952
- injectInputs(vm, args, inputs);
1953
- const wrapped = buildWrappedBody(args, body);
1954
- const result = vm.evalCode(wrapped);
2752
+ if (result.error) {
2753
+ const err = vm.dump(result.error);
2754
+ result.error.dispose();
2755
+ return {
2756
+ error:
2757
+ err && err.message
2758
+ ? String(err.message)
2759
+ : typeof err === "string"
2760
+ ? err
2761
+ : JSON.stringify(err),
2762
+ };
2763
+ }
2764
+ const value = vm.dump(result.value);
2765
+ result.value.dispose();
2766
+ return { value };
2767
+ } catch (e) {
2768
+ return { error: e && e.message ? e.message : String(e) };
2769
+ } finally {
2770
+ vm.dispose();
2771
+ }
2772
+ }
1955
2773
 
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
- }
2774
+ async function createCompiled({
2775
+ body,
2776
+ args = [],
2777
+ memoryBytes = DEFAULT_MEMORY_BYTES,
2778
+ }) {
2779
+ if (typeof body !== "string" || !body.trim()) {
2780
+ throw new Error("body must be a non-empty string");
2781
+ }
2782
+ const QuickJS = await getModule();
2783
+ const vm = QuickJS.newContext();
2784
+ vm.runtime.setMemoryLimit(memoryBytes);
2785
+ vm.runtime.setMaxStackSize(1024 * 1024);
2786
+
2787
+ // Define the user function once on the VM globals so subsequent
2788
+ // run() calls can invoke it with fresh args without re-parsing.
2789
+ const define = vm.evalCode(
2790
+ `globalThis.__userFn = function(${args.join(",")}){${body}};`,
2791
+ );
2792
+ if (define.error) {
2793
+ const err = vm.dump(define.error);
2794
+ define.error.dispose();
2795
+ vm.dispose();
2796
+ throw new Error(
2797
+ "safeJsExecutor: compile failed: " +
2798
+ (err && err.message ? err.message : JSON.stringify(err)),
2799
+ );
2800
+ }
2801
+ define.value.dispose();
1977
2802
 
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();
2803
+ let disposed = false;
2006
2804
 
2007
- let disposed = false;
2805
+ return {
2806
+ run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS) {
2807
+ if (disposed) {
2808
+ return { error: "executor already disposed" };
2809
+ }
2810
+ try {
2811
+ setDeadline(vm, timeoutMs);
2812
+ const argList = inputs
2813
+ .map((v) => (v === undefined ? "undefined" : JSON.stringify(v)))
2814
+ .join(",");
2815
+ const result = vm.evalCode(`__userFn(${argList})`);
2816
+ if (result.error) {
2817
+ const err = vm.dump(result.error);
2818
+ result.error.dispose();
2819
+ return {
2820
+ error:
2821
+ err && err.message
2822
+ ? String(err.message)
2823
+ : typeof err === "string"
2824
+ ? err
2825
+ : JSON.stringify(err),
2826
+ };
2827
+ }
2828
+ const value = vm.dump(result.value);
2829
+ result.value.dispose();
2830
+ return { value };
2831
+ } catch (e) {
2832
+ return { error: e && e.message ? e.message : String(e) };
2833
+ }
2834
+ },
2835
+ dispose() {
2836
+ if (!disposed) {
2837
+ disposed = true;
2838
+ vm.dispose();
2839
+ }
2840
+ },
2841
+ };
2842
+ }
2008
2843
 
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
- };
2844
+ safeJsExecutor = {
2845
+ runOnce,
2846
+ createCompiled,
2847
+ DEFAULT_TIMEOUT_MS,
2848
+ DEFAULT_MEMORY_BYTES,
2849
+ };
2850
+ return safeJsExecutor;
2046
2851
  }
2047
2852
 
2048
- var safeJsExecutor$1 = {
2049
- runOnce,
2050
- createCompiled,
2051
- DEFAULT_TIMEOUT_MS: DEFAULT_TIMEOUT_MS$1,
2052
- DEFAULT_MEMORY_BYTES,
2053
- };
2054
-
2055
2853
  /**
2056
2854
  * Utils/tranaform
2057
2855
  * Main gial is to transform a file of data into another form (CSV -> Json for example)
@@ -2060,462 +2858,514 @@ var safeJsExecutor$1 = {
2060
2858
  * - CSV -> JSON
2061
2859
  */
2062
2860
 
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 = [];
2861
+ var transform;
2862
+ var hasRequiredTransform;
2863
+
2864
+ function requireTransform () {
2865
+ if (hasRequiredTransform) return transform;
2866
+ hasRequiredTransform = 1;
2867
+ var fs = require$$0$2;
2868
+ var readline = require$$1$3;
2869
+ const xtreamer = require$$2;
2870
+ var xmlParser = require$$3$1;
2871
+ var JSONStream = require$$4;
2872
+ const stream = require$$5;
2873
+ var csv = require$$6;
2874
+ const path = require$$1$2;
2875
+ const { app } = require$$0$1;
2876
+ const { ensureDirectoryExistence } = file;
2877
+ const safeJsExecutor = requireSafeJsExecutor();
2878
+
2879
+ const TRANSFORM_APP_NAME = "Dashboard";
2880
+ const MAX_MAPPING_BODY_SIZE = 10240; // 10KB limit for mapping function body
2881
+ // Per-record execution limit. Each mapping function call inside the
2882
+ // streaming transform must complete within this budget; a runaway loop
2883
+ // in the user's mapping triggers an "interrupted" error and the record
2884
+ // is skipped rather than blocking the whole stream.
2885
+ const PER_RECORD_TIMEOUT_MS = 1000;
2120
2886
 
2121
- // can we aggregate potentially?
2122
- let lineObject = [];
2887
+ /**
2888
+ * XtreamerClientTransform
2889
+ * Custom Transform stream to parse the JSON from the XML to String operation
2890
+ */
2891
+ class XtreamerClientTransform extends stream.Transform {
2892
+ _transform(value, encoding, callback) {
2893
+ this.push(JSON.parse(value));
2894
+ callback();
2895
+ }
2896
+ }
2123
2897
 
2124
- const readInterface = readline.createInterface({
2125
- input: fs$e.createReadStream(filepath),
2126
- output: process.stdout,
2127
- console: false,
2128
- });
2898
+ class Transform {
2899
+ constructor() {
2900
+ console.log("constructor");
2901
+ }
2129
2902
 
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
- };
2903
+ /**
2904
+ * readLinesFromFile
2905
+ *
2906
+ * If this is a json file we should check in and remove the newLines, and then
2907
+ * add commas between records potentially...
2908
+ * @param {*} win
2909
+ * @param {*} filepath
2910
+ * @param {*} linesCount
2911
+ * @param {*} callbackEvent
2912
+ * @returns
2913
+ */
2914
+ readLinesFromFile = (
2915
+ win,
2916
+ filepath,
2917
+ linesCount = 100,
2918
+ callbackEvent = null,
2919
+ ) => {
2920
+ return new Promise((resolve, reject) => {
2921
+ try {
2922
+ let count = 0;
2923
+ let lines = [];
2150
2924
 
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);
2925
+ // can we aggregate potentially?
2926
+ let lineObject = [];
2161
2927
 
2162
- let count = 0;
2928
+ const readInterface = readline.createInterface({
2929
+ input: fs.createReadStream(filepath),
2930
+ output: process.stdout,
2931
+ console: false,
2932
+ });
2163
2933
 
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
- });
2934
+ readInterface.on("line", function (line) {
2935
+ if (count < linesCount) {
2936
+ //console.log(line);
2937
+ lines.push(line);
2938
+ count++;
2939
+ if (callbackEvent !== null) {
2940
+ win.webContents.send(callbackEvent, {
2941
+ line,
2942
+ });
2943
+ }
2944
+ } else {
2945
+ readInterface.close();
2946
+ resolve(lines);
2947
+ }
2948
+ });
2949
+ } catch (e) {
2950
+ reject(e);
2951
+ }
2952
+ });
2953
+ };
2177
2954
 
2178
- readStream.on("error", (e) => {
2179
- reject(e);
2180
- });
2955
+ readJSONFromFile = (
2956
+ win,
2957
+ file = "",
2958
+ objectCount = 10,
2959
+ callbackEvent = null,
2960
+ ) => {
2961
+ return new Promise((resolve, reject) => {
2962
+ try {
2963
+ const parser = JSONStream.parse("*");
2964
+ const readStream = fs.createReadStream(file).pipe(parser);
2181
2965
 
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
- };
2966
+ let count = 0;
2191
2967
 
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
- }
2968
+ readStream.on("data", (data) => {
2969
+ //console.log(data);
2970
+ if (callbackEvent !== null) {
2971
+ win.webContents.send(callbackEvent, {
2972
+ data: JSON.stringify(data),
2973
+ });
2974
+ }
2975
+ if (objectCount !== null && count < objectCount) {
2976
+ readStream.destroy();
2977
+ resolve("complete");
2978
+ }
2979
+ count++;
2980
+ });
2209
2981
 
2210
- parseXMLStream = (filepath, outpath, start) => {
2211
- return new Promise((resolve, reject) => {
2212
- try {
2213
- const xmlFileReadStream = fs$e.createReadStream(filepath);
2982
+ readStream.on("error", (e) => {
2983
+ reject(e);
2984
+ });
2214
2985
 
2215
- xmlFileReadStream.on("end", () => {
2216
- writeStream.write("\n]");
2217
- resolve("Read End");
2218
- });
2986
+ readStream.on("end", (data) => {
2987
+ resolve("Complete");
2988
+ });
2989
+ } catch (e) {
2990
+ console.log("read json error ", e);
2991
+ reject(e);
2992
+ }
2993
+ });
2994
+ };
2219
2995
 
2220
- xmlFileReadStream.on("finished", () => {
2221
- resolve("Read Finish");
2222
- });
2996
+ async parseXMLString(data) {
2997
+ let xmlText = data.toString().replace("\ufeff", "");
2998
+ return new Promise((resolve, reject) => {
2999
+ xmlParser
3000
+ .parseStringPromise(xmlText, {
3001
+ trim: true,
3002
+ compact: true,
3003
+ ignoreComment: true,
3004
+ ignoreDoctype: true,
3005
+ })
3006
+ .then((data) => {
3007
+ // resolve with the comma
3008
+ resolve(JSON.stringify(data) + ",");
3009
+ })
3010
+ .catch((e) => reject(e));
3011
+ });
3012
+ }
2223
3013
 
2224
- const writeStream = fs$e.createWriteStream(outpath);
2225
- writeStream.write("[\n");
3014
+ parseXMLStream = (filepath, outpath, start) => {
3015
+ return new Promise((resolve, reject) => {
3016
+ try {
3017
+ const xmlFileReadStream = fs.createReadStream(filepath);
2226
3018
 
2227
- const options = {
2228
- headers: { Accept: "application/xml" },
2229
- resolveWithFullResponse: true,
2230
- json: false,
2231
- simple: false,
2232
- max_xml_size: 50000000, // 10000000
2233
- };
3019
+ xmlFileReadStream.on("end", () => {
3020
+ writeStream.write("\n]");
3021
+ resolve("Read End");
3022
+ });
2234
3023
 
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
- });
3024
+ xmlFileReadStream.on("finished", () => {
3025
+ resolve("Read Finish");
3026
+ });
2246
3027
 
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
- };
3028
+ const writeStream = fs.createWriteStream(outpath);
3029
+ writeStream.write("[\n");
2263
3030
 
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;
3031
+ const options = {
3032
+ headers: { Accept: "application/xml" },
3033
+ resolveWithFullResponse: true,
3034
+ json: false,
3035
+ simple: false,
3036
+ max_xml_size: 50000000, // 10000000
3037
+ };
2282
3038
 
2283
- // separators for JSON
2284
- let sep = "";
2285
- let count = 0;
2286
- writeStream.write("[\n");
3039
+ const xtreamerTransform = xtreamer(
3040
+ start,
3041
+ {
3042
+ transformer: this.parseXMLString, // returns Promise
3043
+ max_xml_size: 50000000,
3044
+ },
3045
+ options,
3046
+ ).on("error", (e) => {
3047
+ console.log(e);
3048
+ reject(e);
3049
+ });
2287
3050
 
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++;
3051
+ xmlFileReadStream
3052
+ .pipe(xtreamerTransform)
3053
+ .pipe(new XtreamerClientTransform())
3054
+ .pipe(writeStream)
3055
+ .on("end", () => {
3056
+ console.log("ended");
3057
+ })
3058
+ .on("finish", () => {
3059
+ console.log("finished pipe");
3060
+ });
3061
+ } catch (e) {
3062
+ console.log(e);
3063
+ reject(e);
3064
+ }
3065
+ });
3066
+ };
2303
3067
 
2304
- if (win !== null) {
2305
- //console.log("have win", count, callbackEvent);
2306
- win.webContents.send(callbackEvent, {
2307
- count,
2308
- });
2309
- }
2310
- }
2311
- });
3068
+ parseCSVStream = (
3069
+ filepath,
3070
+ outpath,
3071
+ delimiter = ",",
3072
+ objectIdKey = null,
3073
+ headers = null, // optional array of headings to grab
3074
+ win = null,
3075
+ callbackEvent = null,
3076
+ limit = null,
3077
+ ) => {
3078
+ return new Promise((resolve, reject) => {
3079
+ try {
3080
+ const readStream = fs
3081
+ .createReadStream(filepath)
3082
+ .pipe(csv({ separator: delimiter }));
3083
+ const writeStream = fs.createWriteStream(outpath);
2312
3084
 
2313
- readStream.on("end", () => {
2314
- writeStream.write("]");
2315
- readStream.destroy();
2316
- resolve("Complete");
2317
- });
2318
- } catch (e) {
2319
- reject(e);
2320
- }
2321
- });
2322
- };
3085
+ let canParse = true;
2323
3086
 
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
- }
3087
+ // separators for JSON
3088
+ let sep = "";
3089
+ let count = 0;
3090
+ writeStream.write("[\n");
2352
3091
 
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
- }
3092
+ readStream.on("data", (item) => {
3093
+ if (count > 0) {
3094
+ sep = ",\n";
3095
+ }
3096
+ // if we have specified a limit...
3097
+ if (limit !== null && limit <= count) {
3098
+ canParse = false;
3099
+ writeStream.write("]");
3100
+ readStream.destroy();
3101
+ resolve("Complete");
3102
+ }
3103
+ if (canParse === true) {
3104
+ item["objectID"] = item[objectIdKey];
3105
+ writeStream.write(sep + JSON.stringify(item));
3106
+ count++;
3107
+
3108
+ if (win !== null) {
3109
+ //console.log("have win", count, callbackEvent);
3110
+ win.webContents.send(callbackEvent, {
3111
+ count,
3112
+ });
3113
+ }
3114
+ }
3115
+ });
2363
3116
 
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);
3117
+ readStream.on("end", () => {
3118
+ writeStream.write("]");
3119
+ readStream.destroy();
3120
+ resolve("Complete");
3121
+ });
3122
+ } catch (e) {
3123
+ reject(e);
3124
+ }
3125
+ });
3126
+ };
2368
3127
 
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
- }
3128
+ /**
3129
+ * transformFileToFile
3130
+ * We want to convert a format from a file into a different format
3131
+ * based on the mapper we have provided
3132
+ *
3133
+ * @param {*} win
3134
+ * @param {*} filepath
3135
+ * @param {*} outFilepath
3136
+ * @param {*} mapping
3137
+ */
3138
+ transformFileToFile = (
3139
+ win,
3140
+ filepath,
3141
+ outFilepath,
3142
+ mappingFunctionBody,
3143
+ args = ["refObj", "index"],
3144
+ callbackEvent = null,
3145
+ ) => {
3146
+ return new Promise((resolve, reject) => {
3147
+ // Validate mappingFunctionBody is a non-empty string
3148
+ if (
3149
+ typeof mappingFunctionBody !== "string" ||
3150
+ !mappingFunctionBody.trim()
3151
+ ) {
3152
+ return reject(
3153
+ new Error("mappingFunctionBody must be a non-empty string"),
3154
+ );
3155
+ }
2383
3156
 
2384
- // JSON parser
2385
- var parser = JSONStream$1.parse("*");
3157
+ // Enforce size limit on mapping function body
3158
+ if (mappingFunctionBody.length > MAX_MAPPING_BODY_SIZE) {
3159
+ return reject(
3160
+ new Error(
3161
+ "mappingFunctionBody exceeds maximum size of " +
3162
+ MAX_MAPPING_BODY_SIZE +
3163
+ " bytes",
3164
+ ),
3165
+ );
3166
+ }
2386
3167
 
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);
3168
+ // Validate file paths are within app data directory
3169
+ const appDataDir = path.join(app.getPath("userData"), TRANSFORM_APP_NAME);
3170
+ const resolvedFilepath = path.resolve(filepath);
3171
+ const resolvedOutFilepath = path.resolve(outFilepath);
2393
3172
 
2394
- ensureDirectoryExistence$1(resolvedOutFilepath);
3173
+ if (!resolvedFilepath.startsWith(appDataDir + path.sep)) {
3174
+ return reject(
3175
+ new Error(
3176
+ "Input file path must be within the application data directory",
3177
+ ),
3178
+ );
3179
+ }
3180
+ if (!resolvedOutFilepath.startsWith(appDataDir + path.sep)) {
3181
+ return reject(
3182
+ new Error(
3183
+ "Output file path must be within the application data directory",
3184
+ ),
3185
+ );
3186
+ }
2395
3187
 
2396
- var writeStream = fs$e.createWriteStream(resolvedOutFilepath);
3188
+ // JSON parser
3189
+ var parser = JSONStream.parse("*");
2397
3190
 
2398
- let sep = "";
2399
- let count = 0;
3191
+ if (!fs.existsSync(resolvedFilepath)) {
3192
+ return reject(new Error("File doesnt exist"));
3193
+ }
3194
+ console.log("file exists ", resolvedFilepath);
3195
+ // create the readStream to parse the large file (json)
3196
+ var readStream = fs.createReadStream(resolvedFilepath).pipe(parser);
3197
+
3198
+ ensureDirectoryExistence(resolvedOutFilepath);
3199
+
3200
+ var writeStream = fs.createWriteStream(resolvedOutFilepath);
3201
+
3202
+ let sep = "";
3203
+ let count = 0;
3204
+
3205
+ // Compile the user-supplied mapping function inside a QuickJS
3206
+ // sandbox. The previous implementation compiled the body with
3207
+ // full Node.js privileges (filesystem, network, process). The
3208
+ // sandbox gives the body a tiny pure-JS surface (Math, JSON,
3209
+ // Date, primitives) and no host globals — see
3210
+ // electron/utils/safeJsExecutor.js for full rationale.
3211
+ //
3212
+ // createCompiled is async (the WASM module must be loaded). Use
3213
+ // .then/.catch instead of await because we're inside a sync
3214
+ // Promise executor.
3215
+ safeJsExecutor
3216
+ .createCompiled({
3217
+ body: mappingFunctionBody,
3218
+ args,
3219
+ })
3220
+ .then((executor) => {
3221
+ startStreamingWithExecutor(executor);
3222
+ })
3223
+ .catch((e) => {
3224
+ reject(
3225
+ new Error(
3226
+ "mappingFunctionBody failed to compile: " +
3227
+ (e && e.message ? e.message : String(e)),
3228
+ ),
3229
+ );
3230
+ });
2400
3231
 
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
- });
3232
+ function startStreamingWithExecutor(executor) {
3233
+ // begin the write stream
3234
+ writeStream.write("[\n");
2427
3235
 
2428
- function startStreamingWithExecutor(executor) {
2429
- // begin the write stream
2430
- writeStream.write("[\n");
3236
+ readStream.on("data", (data) => {
3237
+ try {
3238
+ if (count > 0) {
3239
+ sep = ",\n";
3240
+ }
2431
3241
 
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
- }
3242
+ if (data) {
3243
+ const result = executor.run([data, count], PER_RECORD_TIMEOUT_MS);
3244
+ if (result.error) {
3245
+ // Don't block the stream on a single bad record — log,
3246
+ // skip, continue. Matches the previous try/catch in the
3247
+ // unsandboxed implementation.
3248
+ console.log(
3249
+ "[transform] mapping error on record " +
3250
+ count +
3251
+ ": " +
3252
+ result.error,
3253
+ );
3254
+ return;
3255
+ }
2452
3256
 
2453
- writeStream.write(sep + JSON.stringify(result.value));
3257
+ writeStream.write(sep + JSON.stringify(result.value));
2454
3258
 
2455
- if (callbackEvent !== null && win !== null) {
2456
- win.webContents.send(callbackEvent, { count });
2457
- }
3259
+ if (callbackEvent !== null && win !== null) {
3260
+ win.webContents.send(callbackEvent, { count });
3261
+ }
2458
3262
 
2459
- count++;
2460
- }
2461
- } catch (e) {
2462
- console.log(e.message);
2463
- }
2464
- });
3263
+ count++;
3264
+ }
3265
+ } catch (e) {
3266
+ console.log(e.message);
3267
+ }
3268
+ });
2465
3269
 
2466
- readStream.on("end", () => {
2467
- writeStream.write("\n]");
2468
- writeStream.close();
2469
- executor.dispose();
2470
- resolve("Complete: wrote " + count + " objects");
2471
- });
3270
+ readStream.on("end", () => {
3271
+ writeStream.write("\n]");
3272
+ writeStream.close();
3273
+ executor.dispose();
3274
+ resolve("Complete: wrote " + count + " objects");
3275
+ });
2472
3276
 
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
- };
3277
+ readStream.on("error", (err) => {
3278
+ console.log("read stream error transform ", err.message);
3279
+ executor.dispose();
3280
+ reject(err);
3281
+ });
3282
+ } // end startStreamingWithExecutor
3283
+ });
3284
+ };
2481
3285
 
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;
3286
+ /**
3287
+ * sanitizeLine (line of file)
3288
+ * @param {String} line JSON data that we wish to sanitize
3289
+ * @returns String the sanitized String
3290
+ */
3291
+ sanitizeLine = (line) => {
3292
+ try {
3293
+ let newLine = line;
2490
3294
 
2491
- if (line.slice(-1) === ",") {
2492
- newLine = newLine.slice(0, -1);
2493
- }
3295
+ if (line.slice(-1) === ",") {
3296
+ newLine = newLine.slice(0, -1);
3297
+ }
2494
3298
 
2495
- if (line.slice(0, 1) === "[") {
2496
- newLine = newLine.slice(1);
2497
- }
3299
+ if (line.slice(0, 1) === "[") {
3300
+ newLine = newLine.slice(1);
3301
+ }
2498
3302
 
2499
- return newLine;
2500
- } catch (e) {
2501
- return line;
2502
- }
2503
- };
2504
- };
3303
+ return newLine;
3304
+ } catch (e) {
3305
+ return line;
3306
+ }
3307
+ };
3308
+ }
2505
3309
 
2506
- var transform = Transform$1;
3310
+ transform = Transform;
3311
+ return transform;
3312
+ }
2507
3313
 
2508
- const { app: app$b } = require$$0$1;
2509
- var fs$d = require$$0$2;
2510
- const path$i = require$$1$2;
3314
+ const { app: app$a } = require$$0$1;
3315
+ var fs$c = require$$0$2;
3316
+ const path$h = require$$1$2;
2511
3317
  const events$5 = events$8;
2512
3318
  const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
2513
3319
  const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
3320
+ const { gateFsCall, gateFsCallWithJit } = fsGate;
3321
+ const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
3322
+
3323
+ // Reads the enforcement + JIT flags from settings.json. Mirrors the
3324
+ // helper in mcpController. The flag is shared across MCP and fs domains
3325
+ // — see Phase 2 plan for rationale (the cosmetic rename to a
3326
+ // domain-neutral name is a separate slice).
3327
+ function _loadFlags() {
3328
+ try {
3329
+ const settingsPath = path$h.join(
3330
+ app$a.getPath("userData"),
3331
+ appName$5,
3332
+ "settings.json",
3333
+ );
3334
+ if (!fs$c.existsSync(settingsPath)) return null;
3335
+ return JSON.parse(fs$c.readFileSync(settingsPath, "utf8"));
3336
+ } catch (_e) {
3337
+ return null;
3338
+ }
3339
+ }
3340
+
3341
+ /**
3342
+ * Run the fs gate before a dataController handler does its work.
3343
+ * On deny, sends an error event to the renderer and returns false so
3344
+ * the caller can early-out. On allow, returns true.
3345
+ *
3346
+ * @returns {Promise<boolean>}
3347
+ */
3348
+ async function _runFsGate(win, action, widgetId, args, errorEvent) {
3349
+ const settings = _loadFlags();
3350
+ if (!readEnforceFlag$1(settings)) return true; // gate disabled
3351
+ if (!widgetId) return true; // legacy callers without widgetId — see plan
3352
+ const gate = readJitFlag$1(settings)
3353
+ ? await gateFsCallWithJit({ widgetId, action, args }, { enableJit: true })
3354
+ : gateFsCall({ widgetId, action, args });
3355
+ if (gate.allow) return true;
3356
+ if (win && errorEvent) {
3357
+ win.webContents.send(errorEvent, {
3358
+ success: false,
3359
+ message: "fs permission gate: " + gate.reason,
3360
+ });
3361
+ }
3362
+ return false;
3363
+ }
2514
3364
 
2515
3365
  // Convert Json to Csv
2516
- const ObjectsToCsv = require$$6$1;
2517
- const Transform = transform;
2518
- const https$3 = require$$8$1;
3366
+ const ObjectsToCsv = require$$8$1;
3367
+ const Transform = requireTransform();
3368
+ const https$3 = require$$10;
2519
3369
  const appName$5 = "Dashboard";
2520
3370
 
2521
3371
  const dataController$1 = {
@@ -2532,8 +3382,8 @@ const dataController$1 = {
2532
3382
  // Validate the renderer-supplied filename is contained within
2533
3383
  // the data directory. path.join doesn't reject `..` segments;
2534
3384
  // safePath does.
2535
- const candidate = path$i.join(
2536
- app$b.getPath("userData"),
3385
+ const candidate = path$h.join(
3386
+ app$a.getPath("userData"),
2537
3387
  appName$5,
2538
3388
  appId,
2539
3389
  "data",
@@ -2691,7 +3541,7 @@ const dataController$1 = {
2691
3541
  // intent, plus realpath/symlink protection.
2692
3542
  const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
2693
3543
 
2694
- const writeStream = fs$d.createWriteStream(resolvedFilepath);
3544
+ const writeStream = fs$c.createWriteStream(resolvedFilepath);
2695
3545
 
2696
3546
  https$3
2697
3547
  .get(url, (resp) => {
@@ -2850,13 +3700,31 @@ const dataController$1 = {
2850
3700
  * @param {*} append
2851
3701
  * @param {*} returnEmpty
2852
3702
  */
2853
- saveToFile: (win, data, filename, append, returnEmpty = {}) => {
3703
+ saveToFile: async (
3704
+ win,
3705
+ data,
3706
+ filename,
3707
+ append,
3708
+ returnEmpty = {},
3709
+ widgetId = null,
3710
+ ) => {
3711
+ // Phase 2 fs gate. Runs before safePath containment so JIT can
3712
+ // prompt the user without leaking path-shape information through
3713
+ // error timing. See electron/security/fsGate.js.
3714
+ const gateOk = await _runFsGate(
3715
+ win,
3716
+ "saveToFile",
3717
+ widgetId,
3718
+ { filename },
3719
+ events$5.DATA_SAVE_TO_FILE_ERROR,
3720
+ );
3721
+ if (!gateOk) return;
2854
3722
  try {
2855
3723
  if (data) {
2856
3724
  // Validate filename is contained within the data directory.
2857
3725
  // path.join doesn't reject `..` segments; safePath does.
2858
- const candidate = path$i.join(
2859
- app$b.getPath("userData"),
3726
+ const candidate = path$h.join(
3727
+ app$a.getPath("userData"),
2860
3728
  appName$5,
2861
3729
  "data",
2862
3730
  filename,
@@ -2944,12 +3812,21 @@ const dataController$1 = {
2944
3812
  }
2945
3813
  },
2946
3814
 
2947
- readFromFile: (win, filename, returnIfEmpty = {}) => {
3815
+ readFromFile: async (win, filename, returnIfEmpty = {}, widgetId = null) => {
3816
+ // Phase 2 fs gate — same as saveToFile.
3817
+ const gateOk = await _runFsGate(
3818
+ win,
3819
+ "readFromFile",
3820
+ widgetId,
3821
+ { filename },
3822
+ events$5.DATA_READ_FROM_FILE_ERROR,
3823
+ );
3824
+ if (!gateOk) return;
2948
3825
  try {
2949
3826
  if (filename) {
2950
3827
  // filename to the pages file (live pages)
2951
- const fromFilename = path$i.join(
2952
- app$b.getPath("userData"),
3828
+ const fromFilename = path$h.join(
3829
+ app$a.getPath("userData"),
2953
3830
  appName$5,
2954
3831
  "data",
2955
3832
  filename,
@@ -3029,9 +3906,9 @@ var dataController_1 = dataController$1;
3029
3906
  * settingsController
3030
3907
  */
3031
3908
 
3032
- const { app: app$a } = require$$0$1;
3033
- const path$h = require$$1$2;
3034
- const fs$c = require$$0$2;
3909
+ const { app: app$9 } = require$$0$1;
3910
+ const path$g = require$$1$2;
3911
+ const fs$b = require$$0$2;
3035
3912
  const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
3036
3913
 
3037
3914
  const configFilename$3 = "settings.json";
@@ -3039,15 +3916,15 @@ const appName$4 = "Dashboard";
3039
3916
 
3040
3917
  // Helper function to recursively copy directory
3041
3918
  function copyDirectory(source, destination) {
3042
- if (!fs$c.existsSync(destination)) {
3043
- fs$c.mkdirSync(destination, { recursive: true });
3919
+ if (!fs$b.existsSync(destination)) {
3920
+ fs$b.mkdirSync(destination, { recursive: true });
3044
3921
  }
3045
3922
 
3046
- const files = fs$c.readdirSync(source);
3923
+ const files = fs$b.readdirSync(source);
3047
3924
  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);
3925
+ const srcPath = path$g.join(source, file);
3926
+ const destPath = path$g.join(destination, file);
3927
+ const stat = fs$b.lstatSync(srcPath);
3051
3928
 
3052
3929
  // Skip symlinks to prevent following links to sensitive files
3053
3930
  if (stat.isSymbolicLink()) {
@@ -3058,7 +3935,7 @@ function copyDirectory(source, destination) {
3058
3935
  if (stat.isDirectory()) {
3059
3936
  copyDirectory(srcPath, destPath);
3060
3937
  } else {
3061
- fs$c.copyFileSync(srcPath, destPath);
3938
+ fs$b.copyFileSync(srcPath, destPath);
3062
3939
  }
3063
3940
  }
3064
3941
  }
@@ -3074,8 +3951,8 @@ const settingsController$4 = {
3074
3951
  try {
3075
3952
  if (data) {
3076
3953
  // <appId>/settings.json
3077
- const filename = path$h.join(
3078
- app$a.getPath("userData"),
3954
+ const filename = path$g.join(
3955
+ app$9.getPath("userData"),
3079
3956
  appName$4,
3080
3957
  configFilename$3,
3081
3958
  );
@@ -3110,8 +3987,8 @@ const settingsController$4 = {
3110
3987
  getSettingsForApplication: (win) => {
3111
3988
  try {
3112
3989
  // <appId>/settings.json
3113
- const filename = path$h.join(
3114
- app$a.getPath("userData"),
3990
+ const filename = path$g.join(
3991
+ app$9.getPath("userData"),
3115
3992
  appName$4,
3116
3993
  configFilename$3,
3117
3994
  );
@@ -3141,15 +4018,15 @@ const settingsController$4 = {
3141
4018
  */
3142
4019
  getDataDirectory: (win) => {
3143
4020
  try {
3144
- const settingsPath = path$h.join(
3145
- app$a.getPath("userData"),
4021
+ const settingsPath = path$g.join(
4022
+ app$9.getPath("userData"),
3146
4023
  appName$4,
3147
4024
  configFilename$3,
3148
4025
  );
3149
4026
  const settings = getFileContents$4(settingsPath, {});
3150
4027
  const userDataDir =
3151
4028
  settings.userDataDirectory ||
3152
- path$h.join(app$a.getPath("userData"), appName$4);
4029
+ path$g.join(app$9.getPath("userData"), appName$4);
3153
4030
 
3154
4031
  console.log("[settingsController] Data directory retrieved successfully");
3155
4032
  // Return the data for ipcMain.handle() - modern promise-based approach
@@ -3176,18 +4053,18 @@ const settingsController$4 = {
3176
4053
  setDataDirectory: (win, newPath) => {
3177
4054
  try {
3178
4055
  // Validate the path exists and is a directory
3179
- if (!fs$c.existsSync(newPath)) {
3180
- fs$c.mkdirSync(newPath, { recursive: true });
4056
+ if (!fs$b.existsSync(newPath)) {
4057
+ fs$b.mkdirSync(newPath, { recursive: true });
3181
4058
  }
3182
4059
 
3183
- const stats = fs$c.statSync(newPath);
4060
+ const stats = fs$b.statSync(newPath);
3184
4061
  if (!stats.isDirectory()) {
3185
4062
  throw new Error("Path is not a directory");
3186
4063
  }
3187
4064
 
3188
4065
  // Update settings
3189
- const settingsPath = path$h.join(
3190
- app$a.getPath("userData"),
4066
+ const settingsPath = path$g.join(
4067
+ app$9.getPath("userData"),
3191
4068
  appName$4,
3192
4069
  configFilename$3,
3193
4070
  );
@@ -3220,20 +4097,20 @@ const settingsController$4 = {
3220
4097
  migrateDataDirectory: (win, oldPath, newPath) => {
3221
4098
  try {
3222
4099
  // Resolve paths to prevent traversal
3223
- const resolvedOldPath = path$h.resolve(oldPath);
3224
- const resolvedNewPath = path$h.resolve(newPath);
4100
+ const resolvedOldPath = path$g.resolve(oldPath);
4101
+ const resolvedNewPath = path$g.resolve(newPath);
3225
4102
 
3226
4103
  // Validate oldPath is the current configured data directory
3227
- const settingsCheckPath = path$h.join(
3228
- app$a.getPath("userData"),
4104
+ const settingsCheckPath = path$g.join(
4105
+ app$9.getPath("userData"),
3229
4106
  appName$4,
3230
4107
  configFilename$3,
3231
4108
  );
3232
4109
  const currentSettings = getFileContents$4(settingsCheckPath, {});
3233
4110
  const currentDataDir =
3234
4111
  currentSettings.userDataDirectory ||
3235
- path$h.join(app$a.getPath("userData"), appName$4);
3236
- if (resolvedOldPath !== path$h.resolve(currentDataDir)) {
4112
+ path$g.join(app$9.getPath("userData"), appName$4);
4113
+ if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
3237
4114
  throw new Error("Source path must be the current data directory");
3238
4115
  }
3239
4116
 
@@ -3257,20 +4134,20 @@ const settingsController$4 = {
3257
4134
  }
3258
4135
 
3259
4136
  // Validate paths
3260
- if (!fs$c.existsSync(resolvedOldPath)) {
4137
+ if (!fs$b.existsSync(resolvedOldPath)) {
3261
4138
  throw new Error("Source directory does not exist");
3262
4139
  }
3263
4140
 
3264
- if (!fs$c.existsSync(resolvedNewPath)) {
3265
- fs$c.mkdirSync(resolvedNewPath, { recursive: true });
4141
+ if (!fs$b.existsSync(resolvedNewPath)) {
4142
+ fs$b.mkdirSync(resolvedNewPath, { recursive: true });
3266
4143
  }
3267
4144
 
3268
4145
  // Copy files
3269
4146
  copyDirectory(resolvedOldPath, resolvedNewPath);
3270
4147
 
3271
4148
  // Update settings to use new path
3272
- const settingsPath = path$h.join(
3273
- app$a.getPath("userData"),
4149
+ const settingsPath = path$g.join(
4150
+ app$9.getPath("userData"),
3274
4151
  appName$4,
3275
4152
  configFilename$3,
3276
4153
  );
@@ -3934,8 +4811,8 @@ function requireProviderController () {
3934
4811
  return providerController_1;
3935
4812
  }
3936
4813
 
3937
- const { app: app$9 } = require$$0$1;
3938
- const path$g = require$$1$2;
4814
+ const { app: app$8 } = require$$0$1;
4815
+ const path$f = require$$1$2;
3939
4816
  const { writeFileSync: writeFileSync$1 } = require$$0$2;
3940
4817
  const events$4 = events$8;
3941
4818
  const { getFileContents: getFileContents$3 } = file;
@@ -3955,8 +4832,8 @@ const layoutController$1 = {
3955
4832
  saveLayoutForApplication: (win, appId, layoutObject) => {
3956
4833
  try {
3957
4834
  // filename to the pages file (live pages)
3958
- const filename = path$g.join(
3959
- app$9.getPath("userData"),
4835
+ const filename = path$f.join(
4836
+ app$8.getPath("userData"),
3960
4837
  appName$3,
3961
4838
  appId,
3962
4839
  configFilename$2,
@@ -3988,8 +4865,8 @@ const layoutController$1 = {
3988
4865
  */
3989
4866
  listLayoutsForApplication: (win, appId) => {
3990
4867
  try {
3991
- const filename = path$g.join(
3992
- app$9.getPath("userData"),
4868
+ const filename = path$f.join(
4869
+ app$8.getPath("userData"),
3993
4870
  appName$3,
3994
4871
  appId,
3995
4872
  configFilename$2,
@@ -21152,455 +22029,6 @@ let StreamableHTTPClientTransport$1 = class StreamableHTTPClientTransport {
21152
22029
  };
21153
22030
  streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1;
21154
22031
 
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
22032
  /**
21605
22033
  * permissionGate.js
21606
22034
  *
@@ -22081,37 +22509,6 @@ var mcpScopeResolver = {
22081
22509
  applyPathScopeToCredentials: applyPathScopeToCredentials$1,
22082
22510
  };
22083
22511
 
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
22512
  /**
22116
22513
  * mcpController.js
22117
22514
  *
@@ -22491,7 +22888,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22491
22888
 
22492
22889
  console.log("[mcpController] Refreshing Google OAuth token...");
22493
22890
 
22494
- const https = require$$8$1;
22891
+ const https = require$$10;
22495
22892
  const postData = [
22496
22893
  `client_id=${encodeURIComponent(keyData.client_id)}`,
22497
22894
  `client_secret=${encodeURIComponent(keyData.client_secret)}`,
@@ -48166,7 +48563,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
48166
48563
  * - Rate limiting via token bucket (60 req/min)
48167
48564
  */
48168
48565
 
48169
- const https$1 = require$$8$1;
48566
+ const https$1 = require$$10;
48170
48567
  const { randomUUID } = require$$3$4;
48171
48568
  const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
48172
48569
  const { McpServer } = mcp;
@@ -49975,7 +50372,7 @@ var themeFromUrlErrors$1 = {
49975
50372
 
49976
50373
  const css = require$$0$8;
49977
50374
  const { Vibrant } = require$$1$7;
49978
- const https = require$$8$1;
50375
+ const https = require$$10;
49979
50376
  const http = require$$0$7;
49980
50377
  const { URL: URL$1 } = require$$4$1;
49981
50378
  const {
@@ -62431,20 +62828,30 @@ const dataApi$2 = {
62431
62828
  * @param {object} options { filename, extension }
62432
62829
  * @param {object} returnEmpty the return empty object
62433
62830
  */
62434
- saveData: (data, filename, append, returnEmpty, uuid) =>
62831
+ saveData: (data, filename, append, returnEmpty, widgetId = null) =>
62435
62832
  ipcRenderer$n.invoke(DATA_SAVE_TO_FILE, {
62436
62833
  data,
62437
62834
  filename,
62438
62835
  append,
62439
62836
  returnEmpty,
62837
+ widgetId,
62440
62838
  }),
62441
62839
 
62442
62840
  /*
62443
62841
  * readData
62444
62842
  * @param {string} filename the filename to read (not path)
62843
+ * @param {string|null} widgetId Phase 2 JIT consent — the widget's
62844
+ * package name (e.g. "@trops/notes-summarizer"). Used by the fs
62845
+ * gate to scope per-widget read access. Null disables the gate
62846
+ * for legacy callers (`enforceWidgetMcpPermissions` flag still
62847
+ * gates the gate itself).
62445
62848
  */
62446
- readData: (filename, returnEmpty = []) =>
62447
- ipcRenderer$n.invoke(DATA_READ_FROM_FILE, { filename, returnEmpty }),
62849
+ readData: (filename, returnEmpty = [], widgetId = null) =>
62850
+ ipcRenderer$n.invoke(DATA_READ_FROM_FILE, {
62851
+ filename,
62852
+ returnEmpty,
62853
+ widgetId,
62854
+ }),
62448
62855
 
62449
62856
  /**
62450
62857
  * transformFile