@trops/dash-core 0.1.492 → 0.1.493

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.
@@ -1107,7 +1107,7 @@ var secureStoreController$1 = {
1107
1107
  getData: getData$1,
1108
1108
  };
1109
1109
 
1110
- const path$m = require$$1$2;
1110
+ const path$n = require$$1$2;
1111
1111
  const {
1112
1112
  readFileSync,
1113
1113
  writeFileSync: writeFileSync$4,
@@ -1125,7 +1125,7 @@ const {
1125
1125
  function ensureDirectoryExistence$2(filePath) {
1126
1126
  try {
1127
1127
  // isDirectory
1128
- var dirname = path$m.dirname(filePath);
1128
+ var dirname = path$n.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.
@@ -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$m.join(directory, file), (err) => {
1243
+ unlinkSync(path$n.join(directory, file), (err) => {
1244
1244
  if (err) throw err;
1245
1245
  });
1246
1246
  }
@@ -1258,7 +1258,7 @@ var file = {
1258
1258
  };
1259
1259
 
1260
1260
  const { app: app$f } = require$$0$1;
1261
- const path$l = require$$1$2;
1261
+ const path$m = require$$1$2;
1262
1262
  const { writeFileSync: writeFileSync$3 } = require$$0$2;
1263
1263
  const { getFileContents: getFileContents$7 } = file;
1264
1264
 
@@ -1305,7 +1305,7 @@ 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$l.join(
1308
+ const filename = path$m.join(
1309
1309
  app$f.getPath("userData"),
1310
1310
  appName$7,
1311
1311
  appId,
@@ -1354,7 +1354,7 @@ const workspaceController$3 = {
1354
1354
  saveMenuItemsForApplication: (win, appId, menuItems) => {
1355
1355
  try {
1356
1356
  // filename to the workspaces file
1357
- const filename = path$l.join(
1357
+ const filename = path$m.join(
1358
1358
  app$f.getPath("userData"),
1359
1359
  appName$7,
1360
1360
  appId,
@@ -1403,7 +1403,7 @@ const workspaceController$3 = {
1403
1403
  */
1404
1404
  deleteWorkspaceForApplication: (win, appId, workspaceId) => {
1405
1405
  try {
1406
- const filename = path$l.join(
1406
+ const filename = path$m.join(
1407
1407
  app$f.getPath("userData"),
1408
1408
  appName$7,
1409
1409
  appId,
@@ -1437,7 +1437,7 @@ const workspaceController$3 = {
1437
1437
 
1438
1438
  listWorkspacesForApplication: (win, appId) => {
1439
1439
  try {
1440
- const filename = path$l.join(
1440
+ const filename = path$m.join(
1441
1441
  app$f.getPath("userData"),
1442
1442
  appName$7,
1443
1443
  appId,
@@ -1465,7 +1465,7 @@ const workspaceController$3 = {
1465
1465
 
1466
1466
  listMenuItemsForApplication: (win, appId) => {
1467
1467
  try {
1468
- const filename = path$l.join(
1468
+ const filename = path$m.join(
1469
1469
  app$f.getPath("userData"),
1470
1470
  appName$7,
1471
1471
  appId,
@@ -1510,7 +1510,7 @@ const workspaceController$3 = {
1510
1510
  var workspaceController_1 = workspaceController$3;
1511
1511
 
1512
1512
  const { app: app$e } = require$$0$1;
1513
- const path$k = require$$1$2;
1513
+ const path$l = require$$1$2;
1514
1514
  const { writeFileSync: writeFileSync$2 } = require$$0$2;
1515
1515
  const { getFileContents: getFileContents$6 } = file;
1516
1516
 
@@ -1530,7 +1530,7 @@ 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$k.join(
1533
+ const filename = path$l.join(
1534
1534
  app$e.getPath("userData"),
1535
1535
  appName$6,
1536
1536
  appId,
@@ -1576,7 +1576,7 @@ const themeController$5 = {
1576
1576
  */
1577
1577
  listThemesForApplication: (win, appId) => {
1578
1578
  try {
1579
- const filename = path$k.join(
1579
+ const filename = path$l.join(
1580
1580
  app$e.getPath("userData"),
1581
1581
  appName$6,
1582
1582
  appId,
@@ -1618,7 +1618,7 @@ const themeController$5 = {
1618
1618
  */
1619
1619
  deleteThemeForApplication: (win, appId, themeKey) => {
1620
1620
  try {
1621
- const filename = path$k.join(
1621
+ const filename = path$l.join(
1622
1622
  app$e.getPath("userData"),
1623
1623
  appName$6,
1624
1624
  appId,
@@ -1695,8 +1695,8 @@ var themeController_1 = themeController$5;
1695
1695
  * `/data/`.
1696
1696
  */
1697
1697
 
1698
- const path$j = require$$1$2;
1699
- const fs$e = require$$0$2;
1698
+ const path$k = require$$1$2;
1699
+ const fs$f = require$$0$2;
1700
1700
  const { app: app$d } = require$$0$1;
1701
1701
 
1702
1702
  const APP_NAME = "Dashboard";
@@ -1709,7 +1709,7 @@ function getAllowedRoots$2(category) {
1709
1709
  const userData = app$d.getPath("userData");
1710
1710
  switch (category) {
1711
1711
  case "data": {
1712
- const def = path$j.join(userData, APP_NAME, "data");
1712
+ const def = path$k.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,11 +1719,11 @@ function getAllowedRoots$2(category) {
1719
1719
  return override ? [def, override] : [def];
1720
1720
  }
1721
1721
  case "themes":
1722
- return [path$j.join(userData, APP_NAME, "themes")];
1722
+ return [path$k.join(userData, APP_NAME, "themes")];
1723
1723
  case "widgets":
1724
- return [path$j.join(userData, "widgets")];
1724
+ return [path$k.join(userData, "widgets")];
1725
1725
  case "plugins":
1726
- return [path$j.join(userData, "plugins")];
1726
+ return [path$k.join(userData, "plugins")];
1727
1727
  case "downloads":
1728
1728
  return [app$d.getPath("downloads")];
1729
1729
  default:
@@ -1740,13 +1740,13 @@ function getAllowedRoots$2(category) {
1740
1740
  */
1741
1741
  function readDataDirectoryFromSettings() {
1742
1742
  try {
1743
- const settingsPath = path$j.join(
1743
+ const settingsPath = path$k.join(
1744
1744
  app$d.getPath("userData"),
1745
1745
  APP_NAME,
1746
1746
  "settings.json",
1747
1747
  );
1748
- if (!fs$e.existsSync(settingsPath)) return undefined;
1749
- const raw = fs$e.readFileSync(settingsPath, "utf8");
1748
+ if (!fs$f.existsSync(settingsPath)) return undefined;
1749
+ const raw = fs$f.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$j.resolve(requested);
1775
+ const resolved = path$k.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$e.realpathSync(resolved);
1782
+ real = fs$f.realpathSync(resolved);
1783
1783
  } catch (_e) {
1784
1784
  try {
1785
- const parent = fs$e.realpathSync(path$j.dirname(resolved));
1786
- real = path$j.join(parent, path$j.basename(resolved));
1785
+ const parent = fs$f.realpathSync(path$k.dirname(resolved));
1786
+ real = path$k.join(parent, path$k.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$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
1798
+ if (fs$f.existsSync(root)) realRoot = fs$f.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$j.sep)) {
1805
+ if (real === realRoot || real.startsWith(realRoot + path$k.sep)) {
1806
1806
  return real;
1807
1807
  }
1808
1808
  }
@@ -2060,14 +2060,14 @@ var safeJsExecutor$1 = {
2060
2060
  * - CSV -> JSON
2061
2061
  */
2062
2062
 
2063
- var fs$d = require$$0$2;
2063
+ var fs$e = require$$0$2;
2064
2064
  var readline = require$$1$3;
2065
2065
  const xtreamer = require$$2;
2066
2066
  var xmlParser = require$$3$1;
2067
2067
  var JSONStream$1 = require$$4;
2068
2068
  const stream$1 = require$$5;
2069
2069
  var csv = require$$6;
2070
- const path$i = require$$1$2;
2070
+ const path$j = require$$1$2;
2071
2071
  const { app: app$c } = require$$0$1;
2072
2072
  const { ensureDirectoryExistence: ensureDirectoryExistence$1 } = file;
2073
2073
  const safeJsExecutor = safeJsExecutor$1;
@@ -2122,7 +2122,7 @@ let Transform$1 = class Transform {
2122
2122
  let lineObject = [];
2123
2123
 
2124
2124
  const readInterface = readline.createInterface({
2125
- input: fs$d.createReadStream(filepath),
2125
+ input: fs$e.createReadStream(filepath),
2126
2126
  output: process.stdout,
2127
2127
  console: false,
2128
2128
  });
@@ -2157,7 +2157,7 @@ let Transform$1 = class Transform {
2157
2157
  return new Promise((resolve, reject) => {
2158
2158
  try {
2159
2159
  const parser = JSONStream$1.parse("*");
2160
- const readStream = fs$d.createReadStream(file).pipe(parser);
2160
+ const readStream = fs$e.createReadStream(file).pipe(parser);
2161
2161
 
2162
2162
  let count = 0;
2163
2163
 
@@ -2210,7 +2210,7 @@ let Transform$1 = class Transform {
2210
2210
  parseXMLStream = (filepath, outpath, start) => {
2211
2211
  return new Promise((resolve, reject) => {
2212
2212
  try {
2213
- const xmlFileReadStream = fs$d.createReadStream(filepath);
2213
+ const xmlFileReadStream = fs$e.createReadStream(filepath);
2214
2214
 
2215
2215
  xmlFileReadStream.on("end", () => {
2216
2216
  writeStream.write("\n]");
@@ -2221,7 +2221,7 @@ let Transform$1 = class Transform {
2221
2221
  resolve("Read Finish");
2222
2222
  });
2223
2223
 
2224
- const writeStream = fs$d.createWriteStream(outpath);
2224
+ const writeStream = fs$e.createWriteStream(outpath);
2225
2225
  writeStream.write("[\n");
2226
2226
 
2227
2227
  const options = {
@@ -2273,10 +2273,10 @@ let Transform$1 = class Transform {
2273
2273
  ) => {
2274
2274
  return new Promise((resolve, reject) => {
2275
2275
  try {
2276
- const readStream = fs$d
2276
+ const readStream = fs$e
2277
2277
  .createReadStream(filepath)
2278
2278
  .pipe(csv({ separator: delimiter }));
2279
- const writeStream = fs$d.createWriteStream(outpath);
2279
+ const writeStream = fs$e.createWriteStream(outpath);
2280
2280
 
2281
2281
  let canParse = true;
2282
2282
 
@@ -2362,18 +2362,18 @@ let Transform$1 = class Transform {
2362
2362
  }
2363
2363
 
2364
2364
  // Validate file paths are within app data directory
2365
- const appDataDir = path$i.join(app$c.getPath("userData"), TRANSFORM_APP_NAME);
2366
- const resolvedFilepath = path$i.resolve(filepath);
2367
- const resolvedOutFilepath = path$i.resolve(outFilepath);
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);
2368
2368
 
2369
- if (!resolvedFilepath.startsWith(appDataDir + path$i.sep)) {
2369
+ if (!resolvedFilepath.startsWith(appDataDir + path$j.sep)) {
2370
2370
  return reject(
2371
2371
  new Error(
2372
2372
  "Input file path must be within the application data directory",
2373
2373
  ),
2374
2374
  );
2375
2375
  }
2376
- if (!resolvedOutFilepath.startsWith(appDataDir + path$i.sep)) {
2376
+ if (!resolvedOutFilepath.startsWith(appDataDir + path$j.sep)) {
2377
2377
  return reject(
2378
2378
  new Error(
2379
2379
  "Output file path must be within the application data directory",
@@ -2384,16 +2384,16 @@ let Transform$1 = class Transform {
2384
2384
  // JSON parser
2385
2385
  var parser = JSONStream$1.parse("*");
2386
2386
 
2387
- if (!fs$d.existsSync(resolvedFilepath)) {
2387
+ if (!fs$e.existsSync(resolvedFilepath)) {
2388
2388
  return reject(new Error("File doesnt exist"));
2389
2389
  }
2390
2390
  console.log("file exists ", resolvedFilepath);
2391
2391
  // create the readStream to parse the large file (json)
2392
- var readStream = fs$d.createReadStream(resolvedFilepath).pipe(parser);
2392
+ var readStream = fs$e.createReadStream(resolvedFilepath).pipe(parser);
2393
2393
 
2394
2394
  ensureDirectoryExistence$1(resolvedOutFilepath);
2395
2395
 
2396
- var writeStream = fs$d.createWriteStream(resolvedOutFilepath);
2396
+ var writeStream = fs$e.createWriteStream(resolvedOutFilepath);
2397
2397
 
2398
2398
  let sep = "";
2399
2399
  let count = 0;
@@ -2506,8 +2506,8 @@ let Transform$1 = class Transform {
2506
2506
  var transform = Transform$1;
2507
2507
 
2508
2508
  const { app: app$b } = require$$0$1;
2509
- var fs$c = require$$0$2;
2510
- const path$h = require$$1$2;
2509
+ var fs$d = require$$0$2;
2510
+ const path$i = require$$1$2;
2511
2511
  const events$5 = events$8;
2512
2512
  const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
2513
2513
  const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
@@ -2532,7 +2532,7 @@ const dataController$1 = {
2532
2532
  // Validate the renderer-supplied filename is contained within
2533
2533
  // the data directory. path.join doesn't reject `..` segments;
2534
2534
  // safePath does.
2535
- const candidate = path$h.join(
2535
+ const candidate = path$i.join(
2536
2536
  app$b.getPath("userData"),
2537
2537
  appName$5,
2538
2538
  appId,
@@ -2691,7 +2691,7 @@ const dataController$1 = {
2691
2691
  // intent, plus realpath/symlink protection.
2692
2692
  const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
2693
2693
 
2694
- const writeStream = fs$c.createWriteStream(resolvedFilepath);
2694
+ const writeStream = fs$d.createWriteStream(resolvedFilepath);
2695
2695
 
2696
2696
  https$3
2697
2697
  .get(url, (resp) => {
@@ -2855,7 +2855,7 @@ const dataController$1 = {
2855
2855
  if (data) {
2856
2856
  // Validate filename is contained within the data directory.
2857
2857
  // path.join doesn't reject `..` segments; safePath does.
2858
- const candidate = path$h.join(
2858
+ const candidate = path$i.join(
2859
2859
  app$b.getPath("userData"),
2860
2860
  appName$5,
2861
2861
  "data",
@@ -2948,7 +2948,7 @@ const dataController$1 = {
2948
2948
  try {
2949
2949
  if (filename) {
2950
2950
  // filename to the pages file (live pages)
2951
- const fromFilename = path$h.join(
2951
+ const fromFilename = path$i.join(
2952
2952
  app$b.getPath("userData"),
2953
2953
  appName$5,
2954
2954
  "data",
@@ -3030,8 +3030,8 @@ var dataController_1 = dataController$1;
3030
3030
  */
3031
3031
 
3032
3032
  const { app: app$a } = require$$0$1;
3033
- const path$g = require$$1$2;
3034
- const fs$b = require$$0$2;
3033
+ const path$h = require$$1$2;
3034
+ const fs$c = require$$0$2;
3035
3035
  const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
3036
3036
 
3037
3037
  const configFilename$3 = "settings.json";
@@ -3039,15 +3039,15 @@ const appName$4 = "Dashboard";
3039
3039
 
3040
3040
  // Helper function to recursively copy directory
3041
3041
  function copyDirectory(source, destination) {
3042
- if (!fs$b.existsSync(destination)) {
3043
- fs$b.mkdirSync(destination, { recursive: true });
3042
+ if (!fs$c.existsSync(destination)) {
3043
+ fs$c.mkdirSync(destination, { recursive: true });
3044
3044
  }
3045
3045
 
3046
- const files = fs$b.readdirSync(source);
3046
+ const files = fs$c.readdirSync(source);
3047
3047
  for (const file of files) {
3048
- const srcPath = path$g.join(source, file);
3049
- const destPath = path$g.join(destination, file);
3050
- const stat = fs$b.lstatSync(srcPath);
3048
+ const srcPath = path$h.join(source, file);
3049
+ const destPath = path$h.join(destination, file);
3050
+ const stat = fs$c.lstatSync(srcPath);
3051
3051
 
3052
3052
  // Skip symlinks to prevent following links to sensitive files
3053
3053
  if (stat.isSymbolicLink()) {
@@ -3058,7 +3058,7 @@ function copyDirectory(source, destination) {
3058
3058
  if (stat.isDirectory()) {
3059
3059
  copyDirectory(srcPath, destPath);
3060
3060
  } else {
3061
- fs$b.copyFileSync(srcPath, destPath);
3061
+ fs$c.copyFileSync(srcPath, destPath);
3062
3062
  }
3063
3063
  }
3064
3064
  }
@@ -3074,7 +3074,7 @@ const settingsController$4 = {
3074
3074
  try {
3075
3075
  if (data) {
3076
3076
  // <appId>/settings.json
3077
- const filename = path$g.join(
3077
+ const filename = path$h.join(
3078
3078
  app$a.getPath("userData"),
3079
3079
  appName$4,
3080
3080
  configFilename$3,
@@ -3110,7 +3110,7 @@ const settingsController$4 = {
3110
3110
  getSettingsForApplication: (win) => {
3111
3111
  try {
3112
3112
  // <appId>/settings.json
3113
- const filename = path$g.join(
3113
+ const filename = path$h.join(
3114
3114
  app$a.getPath("userData"),
3115
3115
  appName$4,
3116
3116
  configFilename$3,
@@ -3141,7 +3141,7 @@ const settingsController$4 = {
3141
3141
  */
3142
3142
  getDataDirectory: (win) => {
3143
3143
  try {
3144
- const settingsPath = path$g.join(
3144
+ const settingsPath = path$h.join(
3145
3145
  app$a.getPath("userData"),
3146
3146
  appName$4,
3147
3147
  configFilename$3,
@@ -3149,7 +3149,7 @@ const settingsController$4 = {
3149
3149
  const settings = getFileContents$4(settingsPath, {});
3150
3150
  const userDataDir =
3151
3151
  settings.userDataDirectory ||
3152
- path$g.join(app$a.getPath("userData"), appName$4);
3152
+ path$h.join(app$a.getPath("userData"), appName$4);
3153
3153
 
3154
3154
  console.log("[settingsController] Data directory retrieved successfully");
3155
3155
  // Return the data for ipcMain.handle() - modern promise-based approach
@@ -3176,17 +3176,17 @@ const settingsController$4 = {
3176
3176
  setDataDirectory: (win, newPath) => {
3177
3177
  try {
3178
3178
  // Validate the path exists and is a directory
3179
- if (!fs$b.existsSync(newPath)) {
3180
- fs$b.mkdirSync(newPath, { recursive: true });
3179
+ if (!fs$c.existsSync(newPath)) {
3180
+ fs$c.mkdirSync(newPath, { recursive: true });
3181
3181
  }
3182
3182
 
3183
- const stats = fs$b.statSync(newPath);
3183
+ const stats = fs$c.statSync(newPath);
3184
3184
  if (!stats.isDirectory()) {
3185
3185
  throw new Error("Path is not a directory");
3186
3186
  }
3187
3187
 
3188
3188
  // Update settings
3189
- const settingsPath = path$g.join(
3189
+ const settingsPath = path$h.join(
3190
3190
  app$a.getPath("userData"),
3191
3191
  appName$4,
3192
3192
  configFilename$3,
@@ -3220,11 +3220,11 @@ const settingsController$4 = {
3220
3220
  migrateDataDirectory: (win, oldPath, newPath) => {
3221
3221
  try {
3222
3222
  // Resolve paths to prevent traversal
3223
- const resolvedOldPath = path$g.resolve(oldPath);
3224
- const resolvedNewPath = path$g.resolve(newPath);
3223
+ const resolvedOldPath = path$h.resolve(oldPath);
3224
+ const resolvedNewPath = path$h.resolve(newPath);
3225
3225
 
3226
3226
  // Validate oldPath is the current configured data directory
3227
- const settingsCheckPath = path$g.join(
3227
+ const settingsCheckPath = path$h.join(
3228
3228
  app$a.getPath("userData"),
3229
3229
  appName$4,
3230
3230
  configFilename$3,
@@ -3232,8 +3232,8 @@ const settingsController$4 = {
3232
3232
  const currentSettings = getFileContents$4(settingsCheckPath, {});
3233
3233
  const currentDataDir =
3234
3234
  currentSettings.userDataDirectory ||
3235
- path$g.join(app$a.getPath("userData"), appName$4);
3236
- if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
3235
+ path$h.join(app$a.getPath("userData"), appName$4);
3236
+ if (resolvedOldPath !== path$h.resolve(currentDataDir)) {
3237
3237
  throw new Error("Source path must be the current data directory");
3238
3238
  }
3239
3239
 
@@ -3257,19 +3257,19 @@ const settingsController$4 = {
3257
3257
  }
3258
3258
 
3259
3259
  // Validate paths
3260
- if (!fs$b.existsSync(resolvedOldPath)) {
3260
+ if (!fs$c.existsSync(resolvedOldPath)) {
3261
3261
  throw new Error("Source directory does not exist");
3262
3262
  }
3263
3263
 
3264
- if (!fs$b.existsSync(resolvedNewPath)) {
3265
- fs$b.mkdirSync(resolvedNewPath, { recursive: true });
3264
+ if (!fs$c.existsSync(resolvedNewPath)) {
3265
+ fs$c.mkdirSync(resolvedNewPath, { recursive: true });
3266
3266
  }
3267
3267
 
3268
3268
  // Copy files
3269
3269
  copyDirectory(resolvedOldPath, resolvedNewPath);
3270
3270
 
3271
3271
  // Update settings to use new path
3272
- const settingsPath = path$g.join(
3272
+ const settingsPath = path$h.join(
3273
3273
  app$a.getPath("userData"),
3274
3274
  appName$4,
3275
3275
  configFilename$3,
@@ -3935,7 +3935,7 @@ function requireProviderController () {
3935
3935
  }
3936
3936
 
3937
3937
  const { app: app$9 } = require$$0$1;
3938
- const path$f = require$$1$2;
3938
+ const path$g = require$$1$2;
3939
3939
  const { writeFileSync: writeFileSync$1 } = require$$0$2;
3940
3940
  const events$4 = events$8;
3941
3941
  const { getFileContents: getFileContents$3 } = file;
@@ -3955,7 +3955,7 @@ const layoutController$1 = {
3955
3955
  saveLayoutForApplication: (win, appId, layoutObject) => {
3956
3956
  try {
3957
3957
  // filename to the pages file (live pages)
3958
- const filename = path$f.join(
3958
+ const filename = path$g.join(
3959
3959
  app$9.getPath("userData"),
3960
3960
  appName$3,
3961
3961
  appId,
@@ -3988,7 +3988,7 @@ const layoutController$1 = {
3988
3988
  */
3989
3989
  listLayoutsForApplication: (win, appId) => {
3990
3990
  try {
3991
- const filename = path$f.join(
3991
+ const filename = path$g.join(
3992
3992
  app$9.getPath("userData"),
3993
3993
  appName$3,
3994
3994
  appId,
@@ -21192,8 +21192,8 @@ streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1
21192
21192
  * clearCache() → void // test-only
21193
21193
  */
21194
21194
 
21195
- const fs$a = require$$0$2;
21196
- const path$e = require$$1$2;
21195
+ const fs$b = require$$0$2;
21196
+ const path$f = require$$1$2;
21197
21197
  const { app: app$8 } = require$$0$1;
21198
21198
 
21199
21199
  const FILE_NAME = "widgetMcpGrants.json";
@@ -21203,14 +21203,14 @@ const FILE_NAME = "widgetMcpGrants.json";
21203
21203
  let _cache$1 = null;
21204
21204
 
21205
21205
  function grantsFilePath() {
21206
- return path$e.join(app$8.getPath("userData"), FILE_NAME);
21206
+ return path$f.join(app$8.getPath("userData"), FILE_NAME);
21207
21207
  }
21208
21208
 
21209
21209
  function loadFromDisk() {
21210
21210
  const p = grantsFilePath();
21211
- if (!fs$a.existsSync(p)) return {};
21211
+ if (!fs$b.existsSync(p)) return {};
21212
21212
  try {
21213
- const raw = fs$a.readFileSync(p, "utf8");
21213
+ const raw = fs$b.readFileSync(p, "utf8");
21214
21214
  const parsed = JSON.parse(raw);
21215
21215
  if (!parsed || typeof parsed !== "object") return {};
21216
21216
  return parsed;
@@ -21230,16 +21230,27 @@ function writeToDisk(data) {
21230
21230
  const tmp = p + ".tmp";
21231
21231
  // Ensure parent dir exists (userData should already, but be defensive
21232
21232
  // for first-launch / freshly-cleared profile cases).
21233
- fs$a.mkdirSync(path$e.dirname(p), { recursive: true });
21234
- fs$a.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
21235
- fs$a.renameSync(tmp, p);
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
21236
  }
21237
21237
 
21238
+ // Recognized origins for a persisted grant. "declared" means the user
21239
+ // approved against the developer's declared dash.permissions.mcp block;
21240
+ // "discovered" means the install-time scanner produced a synthetic
21241
+ // manifest the user approved; "manual" means the user typed entries
21242
+ // themselves in Settings → Privacy & Security with no manifest backing.
21243
+ // Other values are dropped on persist (legacy grants stay null).
21244
+ const ALLOWED_GRANT_ORIGINS = new Set(["declared", "discovered", "manual"]);
21245
+
21238
21246
  /**
21239
21247
  * Sanitize a perms object before persisting. Drops unknown keys, coerces
21240
21248
  * arrays of strings, and silently ignores malformed servers. Mirrors the
21241
21249
  * shape produced by parseManifestPermissions so the gate reads either
21242
21250
  * declared or granted with the same code path.
21251
+ *
21252
+ * Optional `grantOrigin` field is preserved when it's one of the
21253
+ * recognized values; bogus values are dropped.
21243
21254
  */
21244
21255
  function sanitizePerms(perms) {
21245
21256
  if (!perms || typeof perms !== "object") return null;
@@ -21262,7 +21273,14 @@ function sanitizePerms(perms) {
21262
21273
  : [],
21263
21274
  };
21264
21275
  }
21265
- return { servers };
21276
+ const out = { servers };
21277
+ if (
21278
+ typeof perms.grantOrigin === "string" &&
21279
+ ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
21280
+ ) {
21281
+ out.grantOrigin = perms.grantOrigin;
21282
+ }
21283
+ return out;
21266
21284
  }
21267
21285
 
21268
21286
  function getGrant$2(widgetId) {
@@ -21358,6 +21376,7 @@ var grantedPermissions = {
21358
21376
  revokeServer: revokeServer$1,
21359
21377
  listAllGrants: listAllGrants$1,
21360
21378
  clearCache: clearCache$1,
21379
+ ALLOWED_GRANT_ORIGINS,
21361
21380
  };
21362
21381
 
21363
21382
  /**
@@ -21719,8 +21738,8 @@ const {
21719
21738
  const {
21720
21739
  StreamableHTTPClientTransport,
21721
21740
  } = streamableHttp$1;
21722
- const path$d = require$$1$2;
21723
- const fs$9 = require$$0$2;
21741
+ const path$e = require$$1$2;
21742
+ const fs$a = require$$0$2;
21724
21743
  const os$2 = require$$2$1;
21725
21744
  const responseCache$2 = responseCache_1;
21726
21745
  const { gateToolCall } = permissionGate;
@@ -21734,13 +21753,13 @@ const { app: app$7 } = require$$0$1;
21734
21753
  // and electron/mcp/permissionGate.js for context.
21735
21754
  function isWidgetPermissionEnforcementEnabled() {
21736
21755
  try {
21737
- const settingsPath = path$d.join(
21756
+ const settingsPath = path$e.join(
21738
21757
  app$7.getPath("userData"),
21739
21758
  "Dashboard",
21740
21759
  "settings.json",
21741
21760
  );
21742
- if (!fs$9.existsSync(settingsPath)) return false;
21743
- const raw = fs$9.readFileSync(settingsPath, "utf8");
21761
+ if (!fs$a.existsSync(settingsPath)) return false;
21762
+ const raw = fs$a.readFileSync(settingsPath, "utf8");
21744
21763
  const settings = JSON.parse(raw);
21745
21764
  return Boolean(settings?.security?.enforceWidgetMcpPermissions);
21746
21765
  } catch (_e) {
@@ -21893,7 +21912,7 @@ function getShellPath$1() {
21893
21912
  fallbackDirs.push(`${home}/.nodenv/shims`);
21894
21913
  try {
21895
21914
  const nvmDir = `${home}/.nvm/versions/node`;
21896
- const versions = fs$9.readdirSync(nvmDir).sort();
21915
+ const versions = fs$a.readdirSync(nvmDir).sort();
21897
21916
  if (versions.length > 0) {
21898
21917
  // Find the highest compatible version (v18/v20/v22)
21899
21918
  for (let i = versions.length - 1; i >= 0; i--) {
@@ -22029,15 +22048,15 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22029
22048
  const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
22030
22049
  const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
22031
22050
 
22032
- if (!fs$9.existsSync(credPath) || !fs$9.existsSync(keysPath)) {
22051
+ if (!fs$a.existsSync(credPath) || !fs$a.existsSync(keysPath)) {
22033
22052
  console.log(
22034
22053
  "[mcpController] Token refresh skipped: credential files not found",
22035
22054
  );
22036
22055
  return;
22037
22056
  }
22038
22057
 
22039
- const credentials = JSON.parse(fs$9.readFileSync(credPath, "utf8"));
22040
- const keysFile = JSON.parse(fs$9.readFileSync(keysPath, "utf8"));
22058
+ const credentials = JSON.parse(fs$a.readFileSync(credPath, "utf8"));
22059
+ const keysFile = JSON.parse(fs$a.readFileSync(keysPath, "utf8"));
22041
22060
  const keyData = keysFile.installed || keysFile.web;
22042
22061
 
22043
22062
  if (
@@ -22106,7 +22125,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
22106
22125
  credentials.refresh_token = body.refresh_token;
22107
22126
  }
22108
22127
 
22109
- fs$9.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
22128
+ fs$a.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
22110
22129
  console.log("[mcpController] Google OAuth token refreshed successfully");
22111
22130
  }
22112
22131
 
@@ -22293,7 +22312,7 @@ const mcpController$3 = {
22293
22312
  }
22294
22313
 
22295
22314
  // Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
22296
- const mcpDir = path$d.join(__dirname, "..", "mcp");
22315
+ const mcpDir = path$e.join(__dirname, "..", "mcp");
22297
22316
  for (let i = 0; i < args.length; i++) {
22298
22317
  if (
22299
22318
  typeof args[i] === "string" &&
@@ -22731,20 +22750,20 @@ const mcpController$3 = {
22731
22750
  */
22732
22751
  getCatalog: (win) => {
22733
22752
  try {
22734
- const catalogPath = path$d.join(
22753
+ const catalogPath = path$e.join(
22735
22754
  __dirname,
22736
22755
  "..",
22737
22756
  "mcp",
22738
22757
  "mcpServerCatalog.json",
22739
22758
  );
22740
22759
 
22741
- if (!fs$9.existsSync(catalogPath)) {
22760
+ if (!fs$a.existsSync(catalogPath)) {
22742
22761
  return {
22743
22762
  catalog: [],
22744
22763
  };
22745
22764
  }
22746
22765
 
22747
- const catalogData = fs$9.readFileSync(catalogPath, "utf8");
22766
+ const catalogData = fs$a.readFileSync(catalogPath, "utf8");
22748
22767
  const catalog = JSON.parse(catalogData);
22749
22768
 
22750
22769
  return {
@@ -22772,18 +22791,18 @@ const mcpController$3 = {
22772
22791
  */
22773
22792
  getKnownExternalCatalog: () => {
22774
22793
  try {
22775
- const catalogPath = path$d.join(
22794
+ const catalogPath = path$e.join(
22776
22795
  __dirname,
22777
22796
  "..",
22778
22797
  "mcp",
22779
22798
  "knownExternalMcpServers.json",
22780
22799
  );
22781
22800
 
22782
- if (!fs$9.existsSync(catalogPath)) {
22801
+ if (!fs$a.existsSync(catalogPath)) {
22783
22802
  return { success: true, servers: [] };
22784
22803
  }
22785
22804
 
22786
- const catalogData = fs$9.readFileSync(catalogPath, "utf8");
22805
+ const catalogData = fs$a.readFileSync(catalogPath, "utf8");
22787
22806
  const catalog = JSON.parse(catalogData);
22788
22807
 
22789
22808
  return {
@@ -22848,8 +22867,8 @@ const mcpController$3 = {
22848
22867
  const destPath = to.replace(/^~/, os$2.homedir());
22849
22868
  const destDir = require$$1$2.dirname(destPath);
22850
22869
  try {
22851
- fs$9.mkdirSync(destDir, { recursive: true });
22852
- fs$9.copyFileSync(sourcePath, destPath);
22870
+ fs$a.mkdirSync(destDir, { recursive: true });
22871
+ fs$a.copyFileSync(sourcePath, destPath);
22853
22872
  } catch (err) {
22854
22873
  return {
22855
22874
  error: true,
@@ -22885,7 +22904,7 @@ const mcpController$3 = {
22885
22904
  }
22886
22905
 
22887
22906
  // Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
22888
- const mcpDir = path$d.join(__dirname, "..", "mcp");
22907
+ const mcpDir = path$e.join(__dirname, "..", "mcp");
22889
22908
  const resolvedArgs = (authCommand.args || []).map((arg) =>
22890
22909
  typeof arg === "string" && arg.includes("{{MCP_DIR}}")
22891
22910
  ? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
@@ -23433,8 +23452,8 @@ function commonjsRequire(path) {
23433
23452
  * Runs in the Electron main process at widget install time.
23434
23453
  */
23435
23454
 
23436
- const fs$8 = require$$0$2;
23437
- const path$c = require$$1$2;
23455
+ const fs$9 = require$$0$2;
23456
+ const path$d = require$$1$2;
23438
23457
 
23439
23458
  /**
23440
23459
  * Structured error thrown by compileWidget() when the underlying
@@ -23469,7 +23488,7 @@ function getEsbuildDiagnostics() {
23469
23488
 
23470
23489
  try {
23471
23490
  const pkgJsonPath = require.resolve("esbuild/package.json");
23472
- diagnostics.esbuildPackageDir = path$c.dirname(pkgJsonPath);
23491
+ diagnostics.esbuildPackageDir = path$d.dirname(pkgJsonPath);
23473
23492
  diagnostics.esbuildVersion = commonjsRequire(pkgJsonPath).version;
23474
23493
  } catch (err) {
23475
23494
  diagnostics.esbuildResolveError = err.message;
@@ -23479,15 +23498,15 @@ function getEsbuildDiagnostics() {
23479
23498
  const archPkgJson = require.resolve(
23480
23499
  `${diagnostics.archPackage}/package.json`,
23481
23500
  );
23482
- const archDir = path$c.dirname(archPkgJson);
23501
+ const archDir = path$d.dirname(archPkgJson);
23483
23502
  // esbuild's native binary on macOS/Linux is bin/esbuild;
23484
23503
  // on Windows it's esbuild.exe at the package root.
23485
23504
  const candidate =
23486
23505
  process.platform === "win32"
23487
- ? path$c.join(archDir, "esbuild.exe")
23488
- : path$c.join(archDir, "bin", "esbuild");
23506
+ ? path$d.join(archDir, "esbuild.exe")
23507
+ : path$d.join(archDir, "bin", "esbuild");
23489
23508
  diagnostics.nativeBinaryPath = candidate;
23490
- diagnostics.nativeBinaryExists = fs$8.existsSync(candidate);
23509
+ diagnostics.nativeBinaryExists = fs$9.existsSync(candidate);
23491
23510
  } catch (err) {
23492
23511
  diagnostics.archResolveError = err.message;
23493
23512
  }
@@ -23533,26 +23552,26 @@ async function healthCheck() {
23533
23552
  * @returns {string|null} Path to the widgets/ directory, or null
23534
23553
  */
23535
23554
  function findWidgetsDir$2(widgetPath) {
23536
- const direct = path$c.join(widgetPath, "widgets");
23537
- if (fs$8.existsSync(direct)) {
23555
+ const direct = path$d.join(widgetPath, "widgets");
23556
+ if (fs$9.existsSync(direct)) {
23538
23557
  return direct;
23539
23558
  }
23540
23559
 
23541
23560
  // Check configs/widgets/ (packageZip.js nests .dash.js files here)
23542
- const configsWidgets = path$c.join(widgetPath, "configs", "widgets");
23543
- if (fs$8.existsSync(configsWidgets)) {
23561
+ const configsWidgets = path$d.join(widgetPath, "configs", "widgets");
23562
+ if (fs$9.existsSync(configsWidgets)) {
23544
23563
  return configsWidgets;
23545
23564
  }
23546
23565
 
23547
23566
  // Check configs/ directory (used by packageZip.js for distributed widgets)
23548
- const configs = path$c.join(widgetPath, "configs");
23549
- if (fs$8.existsSync(configs)) {
23567
+ const configs = path$d.join(widgetPath, "configs");
23568
+ if (fs$9.existsSync(configs)) {
23550
23569
  return configs;
23551
23570
  }
23552
23571
 
23553
23572
  // Check one level deeper for nested ZIP extraction
23554
23573
  try {
23555
- const entries = fs$8.readdirSync(widgetPath, { withFileTypes: true });
23574
+ const entries = fs$9.readdirSync(widgetPath, { withFileTypes: true });
23556
23575
  const subdirs = entries.filter(
23557
23576
  (e) =>
23558
23577
  e.isDirectory() &&
@@ -23562,8 +23581,8 @@ function findWidgetsDir$2(widgetPath) {
23562
23581
  );
23563
23582
 
23564
23583
  for (const subdir of subdirs) {
23565
- const nested = path$c.join(widgetPath, subdir.name, "widgets");
23566
- if (fs$8.existsSync(nested)) {
23584
+ const nested = path$d.join(widgetPath, subdir.name, "widgets");
23585
+ if (fs$9.existsSync(nested)) {
23567
23586
  console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
23568
23587
  return nested;
23569
23588
  }
@@ -23597,7 +23616,7 @@ async function compileWidget$1(widgetPath) {
23597
23616
  }
23598
23617
 
23599
23618
  // Discover .dash.js config files
23600
- const files = fs$8.readdirSync(widgetsDir);
23619
+ const files = fs$9.readdirSync(widgetsDir);
23601
23620
  const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
23602
23621
 
23603
23622
  if (dashFiles.length === 0) {
@@ -23611,15 +23630,15 @@ async function compileWidget$1(widgetPath) {
23611
23630
  // Compute relative path from the entry file (in widgetPath) to widgetsDir,
23612
23631
  // since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
23613
23632
  const relWidgetsDir =
23614
- "./" + path$c.relative(widgetPath, widgetsDir).split(path$c.sep).join("/");
23633
+ "./" + path$d.relative(widgetPath, widgetsDir).split(path$d.sep).join("/");
23615
23634
  const imports = [];
23616
23635
  const exportParts = [];
23617
23636
 
23618
23637
  for (const dashFile of dashFiles) {
23619
23638
  const componentName = dashFile.replace(".dash.js", "");
23620
23639
  const componentFile = `${componentName}.js`;
23621
- const componentFilePath = path$c.join(widgetsDir, componentFile);
23622
- const hasComponent = fs$8.existsSync(componentFilePath);
23640
+ const componentFilePath = path$d.join(widgetsDir, componentFile);
23641
+ const hasComponent = fs$9.existsSync(componentFilePath);
23623
23642
 
23624
23643
  // Import the config (always)
23625
23644
  imports.push(
@@ -23651,17 +23670,17 @@ async function compileWidget$1(widgetPath) {
23651
23670
  const entryContent = [...imports, "", ...exportParts, ""].join("\n");
23652
23671
 
23653
23672
  // Write temporary entry file in the widget root
23654
- const entryPath = path$c.join(widgetPath, "__compile_entry.js");
23655
- const distDir = path$c.join(widgetPath, "dist");
23656
- const outPath = path$c.join(distDir, "index.cjs.js");
23673
+ const entryPath = path$d.join(widgetPath, "__compile_entry.js");
23674
+ const distDir = path$d.join(widgetPath, "dist");
23675
+ const outPath = path$d.join(distDir, "index.cjs.js");
23657
23676
 
23658
23677
  try {
23659
23678
  // Ensure dist/ directory exists
23660
- if (!fs$8.existsSync(distDir)) {
23661
- fs$8.mkdirSync(distDir, { recursive: true });
23679
+ if (!fs$9.existsSync(distDir)) {
23680
+ fs$9.mkdirSync(distDir, { recursive: true });
23662
23681
  }
23663
23682
 
23664
- fs$8.writeFileSync(entryPath, entryContent, "utf8");
23683
+ fs$9.writeFileSync(entryPath, entryContent, "utf8");
23665
23684
 
23666
23685
  console.log(
23667
23686
  `[WidgetCompiler] Compiling ${dashFiles.length} component(s) from ${widgetPath}`,
@@ -23717,8 +23736,8 @@ async function compileWidget$1(widgetPath) {
23717
23736
  } finally {
23718
23737
  // Clean up temporary entry file
23719
23738
  try {
23720
- if (fs$8.existsSync(entryPath)) {
23721
- fs$8.unlinkSync(entryPath);
23739
+ if (fs$9.existsSync(entryPath)) {
23740
+ fs$9.unlinkSync(entryPath);
23722
23741
  }
23723
23742
  } catch (cleanupError) {
23724
23743
  // Non-fatal
@@ -23750,8 +23769,8 @@ var widgetCompiler$1 = {
23750
23769
  * Integrates with ComponentManager for automatic registration
23751
23770
  */
23752
23771
 
23753
- const fs$7 = require$$0$2;
23754
- const path$b = require$$1$2;
23772
+ const fs$8 = require$$0$2;
23773
+ const path$c = require$$1$2;
23755
23774
  const vm = require$$2$2;
23756
23775
  const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
23757
23776
 
@@ -23839,14 +23858,14 @@ class DynamicWidgetLoader {
23839
23858
  );
23840
23859
 
23841
23860
  const widgetsDir =
23842
- findWidgetsDir$1(widgetPath) || path$b.join(widgetPath, "widgets");
23843
- const componentPath = path$b.join(widgetsDir, `${componentName}.js`);
23844
- const configPath = path$b.join(widgetsDir, `${componentName}.dash.js`);
23861
+ findWidgetsDir$1(widgetPath) || path$c.join(widgetPath, "widgets");
23862
+ const componentPath = path$c.join(widgetsDir, `${componentName}.js`);
23863
+ const configPath = path$c.join(widgetsDir, `${componentName}.dash.js`);
23845
23864
 
23846
- if (!fs$7.existsSync(componentPath)) {
23865
+ if (!fs$8.existsSync(componentPath)) {
23847
23866
  throw new Error(`Component file not found: ${componentPath}`);
23848
23867
  }
23849
- if (!fs$7.existsSync(configPath)) {
23868
+ if (!fs$8.existsSync(configPath)) {
23850
23869
  throw new Error(`Config file not found: ${configPath}`);
23851
23870
  }
23852
23871
 
@@ -23897,7 +23916,7 @@ class DynamicWidgetLoader {
23897
23916
  */
23898
23917
  async loadConfigFile(configPath) {
23899
23918
  try {
23900
- const source = fs$7.readFileSync(configPath, "utf8");
23919
+ const source = fs$8.readFileSync(configPath, "utf8");
23901
23920
 
23902
23921
  let exportMatch = source.match(/export\s+default\s+({[\s\S]*});?\s*$/);
23903
23922
 
@@ -23965,7 +23984,7 @@ class DynamicWidgetLoader {
23965
23984
  return [];
23966
23985
  }
23967
23986
 
23968
- const files = fs$7.readdirSync(widgetsDir);
23987
+ const files = fs$8.readdirSync(widgetsDir);
23969
23988
  const widgets = new Set();
23970
23989
 
23971
23990
  files.forEach((file) => {
@@ -24041,8 +24060,8 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
24041
24060
  * Test-only. Drops the in-process cache so tests can re-read.
24042
24061
  */
24043
24062
 
24044
- const fs$6 = require$$0$2;
24045
- const path$a = require$$1$2;
24063
+ const fs$7 = require$$0$2;
24064
+ const path$b = require$$1$2;
24046
24065
  const os$1 = require$$2$1;
24047
24066
  const { app: app$6 } = require$$0$1;
24048
24067
 
@@ -24059,7 +24078,7 @@ function expandHome(p) {
24059
24078
  if (typeof p !== "string" || !p) return p;
24060
24079
  if (p === "~") return os$1.homedir();
24061
24080
  if (p.startsWith("~/") || p.startsWith("~\\")) {
24062
- return path$a.join(os$1.homedir(), p.slice(2));
24081
+ return path$b.join(os$1.homedir(), p.slice(2));
24063
24082
  }
24064
24083
  return p;
24065
24084
  }
@@ -24099,10 +24118,10 @@ function parseManifestPermissions(packageJson) {
24099
24118
  */
24100
24119
  function resolveWidgetPackagePath(widgetId) {
24101
24120
  if (typeof widgetId !== "string" || !widgetId) return null;
24102
- const widgetsRoot = path$a.join(app$6.getPath("userData"), "widgets");
24121
+ const widgetsRoot = path$b.join(app$6.getPath("userData"), "widgets");
24103
24122
  // Split scope from name for "@scope/name" form.
24104
24123
  const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
24105
- return path$a.join(widgetsRoot, ...parts, "package.json");
24124
+ return path$b.join(widgetsRoot, ...parts, "package.json");
24106
24125
  }
24107
24126
 
24108
24127
  /**
@@ -24116,12 +24135,12 @@ function resolveWidgetPackagePath(widgetId) {
24116
24135
  function getWidgetMcpPermissions$1(widgetId) {
24117
24136
  if (_cache.has(widgetId)) return _cache.get(widgetId);
24118
24137
  const pkgPath = resolveWidgetPackagePath(widgetId);
24119
- if (!pkgPath || !fs$6.existsSync(pkgPath)) {
24138
+ if (!pkgPath || !fs$7.existsSync(pkgPath)) {
24120
24139
  _cache.set(widgetId, null);
24121
24140
  return null;
24122
24141
  }
24123
24142
  try {
24124
- const raw = fs$6.readFileSync(pkgPath, "utf8");
24143
+ const raw = fs$7.readFileSync(pkgPath, "utf8");
24125
24144
  const pkg = JSON.parse(raw);
24126
24145
  const perms = parseManifestPermissions(pkg);
24127
24146
  _cache.set(widgetId, perms);
@@ -24149,6 +24168,200 @@ var widgetPermissions = {
24149
24168
  clearCache,
24150
24169
  };
24151
24170
 
24171
+ /**
24172
+ * manifestScanner.js
24173
+ *
24174
+ * Literal-only static scanner for widget MCP usage. Three callers:
24175
+ * 1. publish-time CLI (`dash-scan-manifest`) — the dev runs it before
24176
+ * shipping a widget; it diffs detected calls against the package's
24177
+ * `dash.permissions.mcp` block.
24178
+ * 2. install-time hook in widgetRegistry — when a widget arrives
24179
+ * without a manifest, the scanner is run on the installed source
24180
+ * and the result is offered as a "discovered" consent prompt.
24181
+ * 3. Future: lints during widget builds, IDE plugins, etc.
24182
+ *
24183
+ * Scope: detects literal-string `callTool("server","tool", ...)` and
24184
+ * `useMcpProvider("server")` patterns. Anything dynamic (variable,
24185
+ * template literal, function arg) is recorded as a `warnings[]` entry,
24186
+ * NOT a silent miss.
24187
+ *
24188
+ * This is a *linter*, not a security mechanism. The runtime gate
24189
+ * (Slices 1-3) is the actual boundary.
24190
+ */
24191
+
24192
+ const fs$6 = require$$0$2;
24193
+ const path$a = require$$1$2;
24194
+
24195
+ const SOURCE_EXTENSIONS = new Set([
24196
+ ".js",
24197
+ ".jsx",
24198
+ ".ts",
24199
+ ".tsx",
24200
+ ".mjs",
24201
+ ".cjs",
24202
+ ]);
24203
+ const SCAN_FILE_LIMIT = 200;
24204
+
24205
+ const CALL_TOOL_REGEX =
24206
+ /(?:mainApi\.mcp\.|window\.mainApi\.mcp\.|\b)callTool\s*\(\s*([^,)]+?)\s*,\s*([^,)]+?)\s*[,)]/g;
24207
+
24208
+ const USE_PROVIDER_REGEX = /useMcpProvider\s*\(\s*([^,)]+?)\s*[,)]/g;
24209
+
24210
+ function stripComments(src) {
24211
+ return src
24212
+ .replace(/\/\*[\s\S]*?\*\//g, "")
24213
+ .replace(/(^|[^:])\/\/.*$/gm, "$1");
24214
+ }
24215
+
24216
+ function tryLiteralString(arg) {
24217
+ if (typeof arg !== "string") return null;
24218
+ const trimmed = arg.trim();
24219
+ const m =
24220
+ /^"([^"\\]*(?:\\.[^"\\]*)*)"$/.exec(trimmed) ||
24221
+ /^'([^'\\]*(?:\\.[^'\\]*)*)'$/.exec(trimmed);
24222
+ if (!m) return null;
24223
+ return m[1];
24224
+ }
24225
+
24226
+ function lineNumberOf(src, charIndex) {
24227
+ let n = 1;
24228
+ for (let i = 0; i < charIndex && i < src.length; i++) {
24229
+ if (src.charCodeAt(i) === 10) n++;
24230
+ }
24231
+ return n;
24232
+ }
24233
+
24234
+ function readSourceFiles(dir) {
24235
+ const result = [];
24236
+ const skipDirs = new Set([
24237
+ "node_modules",
24238
+ "dist",
24239
+ "package",
24240
+ "build",
24241
+ ".git",
24242
+ ]);
24243
+ function walk(current, relBase) {
24244
+ let entries;
24245
+ try {
24246
+ entries = fs$6.readdirSync(current, { withFileTypes: true });
24247
+ } catch {
24248
+ return;
24249
+ }
24250
+ for (const entry of entries) {
24251
+ const abs = path$a.join(current, entry.name);
24252
+ const rel = relBase ? path$a.join(relBase, entry.name) : entry.name;
24253
+ if (entry.isDirectory()) {
24254
+ if (skipDirs.has(entry.name)) continue;
24255
+ walk(abs, rel);
24256
+ } else if (entry.isFile()) {
24257
+ const ext = path$a.extname(entry.name).toLowerCase();
24258
+ if (!SOURCE_EXTENSIONS.has(ext)) continue;
24259
+ if (result.length >= SCAN_FILE_LIMIT) return;
24260
+ try {
24261
+ result.push({
24262
+ relPath: rel,
24263
+ source: fs$6.readFileSync(abs, "utf8"),
24264
+ });
24265
+ } catch {
24266
+ // unreadable — skip
24267
+ }
24268
+ }
24269
+ }
24270
+ }
24271
+ walk(dir, "");
24272
+ return result;
24273
+ }
24274
+
24275
+ function scanForMcpUsage(input) {
24276
+ if (!input || typeof input !== "object") {
24277
+ return { servers: {}, warnings: [] };
24278
+ }
24279
+
24280
+ let fileList = [];
24281
+ if (input.files && typeof input.files === "object") {
24282
+ for (const [relPath, source] of Object.entries(input.files)) {
24283
+ const ext = path$a.extname(relPath).toLowerCase();
24284
+ if (!SOURCE_EXTENSIONS.has(ext)) continue;
24285
+ if (typeof source !== "string") continue;
24286
+ fileList.push({ relPath, source });
24287
+ }
24288
+ } else if (typeof input.dir === "string" && input.dir) {
24289
+ fileList = readSourceFiles(input.dir);
24290
+ }
24291
+
24292
+ const servers = {};
24293
+ const warnings = [];
24294
+
24295
+ function ensureServer(name) {
24296
+ if (!servers[name]) servers[name] = { tools: new Set() };
24297
+ return servers[name];
24298
+ }
24299
+
24300
+ for (const { relPath, source } of fileList) {
24301
+ const stripped = stripComments(source);
24302
+
24303
+ USE_PROVIDER_REGEX.lastIndex = 0;
24304
+ let m;
24305
+ while ((m = USE_PROVIDER_REGEX.exec(stripped)) !== null) {
24306
+ const lit = tryLiteralString(m[1]);
24307
+ const line = lineNumberOf(stripped, m.index);
24308
+ if (lit) {
24309
+ ensureServer(lit);
24310
+ } else {
24311
+ warnings.push({
24312
+ file: relPath,
24313
+ line,
24314
+ kind: "dynamic-server-name",
24315
+ snippet: m[0].trim(),
24316
+ });
24317
+ }
24318
+ }
24319
+
24320
+ CALL_TOOL_REGEX.lastIndex = 0;
24321
+ while ((m = CALL_TOOL_REGEX.exec(stripped)) !== null) {
24322
+ const serverLit = tryLiteralString(m[1]);
24323
+ const toolLit = tryLiteralString(m[2]);
24324
+ const line = lineNumberOf(stripped, m.index);
24325
+
24326
+ if (serverLit && toolLit) {
24327
+ ensureServer(serverLit).tools.add(toolLit);
24328
+ } else if (!serverLit && !toolLit) {
24329
+ warnings.push({
24330
+ file: relPath,
24331
+ line,
24332
+ kind: "dynamic-server-and-tool",
24333
+ snippet: m[0].trim(),
24334
+ });
24335
+ } else if (!serverLit) {
24336
+ warnings.push({
24337
+ file: relPath,
24338
+ line,
24339
+ kind: "dynamic-server-name",
24340
+ snippet: m[0].trim(),
24341
+ });
24342
+ } else {
24343
+ warnings.push({
24344
+ file: relPath,
24345
+ line,
24346
+ kind: "dynamic-tool-name",
24347
+ snippet: m[0].trim(),
24348
+ });
24349
+ }
24350
+ }
24351
+ }
24352
+
24353
+ const out = {};
24354
+ for (const [name, entry] of Object.entries(servers)) {
24355
+ out[name] = { tools: [...entry.tools].sort() };
24356
+ }
24357
+ return { servers: out, warnings };
24358
+ }
24359
+
24360
+ var manifestScanner = {
24361
+ scanForMcpUsage,
24362
+ SCAN_FILE_LIMIT,
24363
+ };
24364
+
24152
24365
  /**
24153
24366
  * schedulerController.js
24154
24367
  *
@@ -24693,6 +24906,7 @@ var schedulerController_1 = schedulerController$2;
24693
24906
  getWidgetMcpPermissions,
24694
24907
  clearCache: clearWidgetPermsCache,
24695
24908
  } = widgetPermissions;
24909
+ const { scanForMcpUsage } = manifestScanner;
24696
24910
 
24697
24911
  let WIDGETS_CACHE_DIR = null;
24698
24912
  let REGISTRY_CONFIG_FILE = null;
@@ -25774,6 +25988,14 @@ var schedulerController_1 = schedulerController$2;
25774
25988
  * with the user's selections (Slice 2) or quietly drops the message
25775
25989
  * (older renderer pre-Slice-2 — the gate still fail-closes).
25776
25990
  *
25991
+ * Fallback: if there's no manifest, run the literal-only scanner on the
25992
+ * installed source. If the scan finds any literal MCP usage, emit the
25993
+ * same event with `discovered: true` and a synthetic declared blob —
25994
+ * the consent modal renders this with amber framing so the user knows
25995
+ * they're approving a guess, not the developer's declaration. If the
25996
+ * scan finds nothing, no event fires; the widget appears in
25997
+ * Settings → Privacy & Security with a "Grant manually" button.
25998
+ *
25777
25999
  * Cache invalidation: widgetPermissions caches per-process, so an upgrade
25778
26000
  * over a stale cached entry would otherwise keep the old manifest. Drop
25779
26001
  * the whole cache here — cheap, infrequent.
@@ -25782,11 +26004,41 @@ var schedulerController_1 = schedulerController$2;
25782
26004
  try {
25783
26005
  clearWidgetPermsCache();
25784
26006
  const declared = getWidgetMcpPermissions(widgetName);
25785
- if (!declared) return;
26007
+ if (declared) {
26008
+ BrowserWindow.getAllWindows().forEach((win) => {
26009
+ win.webContents.send("widget:mcp-consent-required", {
26010
+ widgetId: widgetName,
26011
+ declared,
26012
+ });
26013
+ });
26014
+ return;
26015
+ }
26016
+
26017
+ // No manifest — try a scan of the installed source.
26018
+ if (!WIDGETS_CACHE_DIR) return;
26019
+ const widgetPath = path.join(WIDGETS_CACHE_DIR, ...widgetName.split("/"));
26020
+ if (!fs.existsSync(widgetPath)) return;
26021
+ const scanResult = scanForMcpUsage({ dir: widgetPath });
26022
+ const detectedServers = Object.keys(scanResult.servers);
26023
+ if (detectedServers.length === 0) return; // nothing actionable to prompt about
26024
+
26025
+ // Build a synthetic declared blob in the same shape parseManifestPermissions
26026
+ // produces so the consent modal can render it identically (modulo the
26027
+ // amber "discovered" framing).
26028
+ const syntheticServers = {};
26029
+ for (const [name, entry] of Object.entries(scanResult.servers)) {
26030
+ syntheticServers[name] = {
26031
+ tools: entry.tools,
26032
+ readPaths: [],
26033
+ writePaths: [],
26034
+ };
26035
+ }
25786
26036
  BrowserWindow.getAllWindows().forEach((win) => {
25787
26037
  win.webContents.send("widget:mcp-consent-required", {
25788
26038
  widgetId: widgetName,
25789
- declared,
26039
+ declared: { servers: syntheticServers },
26040
+ discovered: true,
26041
+ warnings: scanResult.warnings,
25790
26042
  });
25791
26043
  });
25792
26044
  } catch (e) {
@@ -60080,6 +60332,91 @@ const webSocketController$1 = {
60080
60332
 
60081
60333
  var webSocketController_1 = webSocketController$1;
60082
60334
 
60335
+ /**
60336
+ * widgetMcpGrantsListing.js
60337
+ *
60338
+ * Pure helper that joins three data sources into rows for the
60339
+ * Settings → Privacy & Security panel:
60340
+ * - installed widgets (from widgetRegistry.getWidgets())
60341
+ * - persisted grants (from grantedPermissions.listAllGrants())
60342
+ * - declared manifests (from widgetPermissions.getWidgetMcpPermissions())
60343
+ *
60344
+ * Output row shape:
60345
+ * {
60346
+ * widgetId: string,
60347
+ * declared: object|null, // dash.permissions.mcp block from package.json
60348
+ * granted: object|null, // user grant from widgetMcpGrants.json
60349
+ * hasManifest: boolean, // declared !== null
60350
+ * grantOrigin: string|null // "declared" | "discovered" | "manual" | null
60351
+ * }
60352
+ *
60353
+ * Includes ALL installed widgets (even unmanifested + ungranted) so the
60354
+ * panel can offer "Grant manually" for every widget. Also surfaces
60355
+ * orphan grants (granted but uninstalled — rare, only happens if
60356
+ * uninstall didn't revoke).
60357
+ */
60358
+
60359
+ function buildGrantsListing$1(
60360
+ installedWidgets,
60361
+ grantsByWidgetId,
60362
+ declaredByWidgetId,
60363
+ ) {
60364
+ const rows = [];
60365
+ const seen = new Set();
60366
+
60367
+ const installed = Array.isArray(installedWidgets) ? installedWidgets : [];
60368
+ const grants = grantsByWidgetId instanceof Map ? grantsByWidgetId : new Map();
60369
+ const declared =
60370
+ declaredByWidgetId instanceof Map ? declaredByWidgetId : new Map();
60371
+
60372
+ for (const w of installed) {
60373
+ if (!w || typeof w !== "object") continue;
60374
+ const widgetId = w.name;
60375
+ if (typeof widgetId !== "string" || !widgetId) continue;
60376
+ if (seen.has(widgetId)) continue;
60377
+ seen.add(widgetId);
60378
+
60379
+ const decl = declared.get(widgetId) || null;
60380
+ const grant = grants.get(widgetId) || null;
60381
+ const grantOrigin =
60382
+ grant &&
60383
+ typeof grant === "object" &&
60384
+ typeof grant.grantOrigin === "string"
60385
+ ? grant.grantOrigin
60386
+ : null;
60387
+
60388
+ rows.push({
60389
+ widgetId,
60390
+ declared: decl,
60391
+ granted: grant,
60392
+ hasManifest: decl !== null,
60393
+ grantOrigin,
60394
+ });
60395
+ }
60396
+
60397
+ // Orphan grants: granted but not in the installed list.
60398
+ for (const [widgetId, grant] of grants) {
60399
+ if (seen.has(widgetId)) continue;
60400
+ const grantOrigin =
60401
+ grant &&
60402
+ typeof grant === "object" &&
60403
+ typeof grant.grantOrigin === "string"
60404
+ ? grant.grantOrigin
60405
+ : null;
60406
+ rows.push({
60407
+ widgetId,
60408
+ declared: null,
60409
+ granted: grant,
60410
+ hasManifest: false,
60411
+ grantOrigin,
60412
+ });
60413
+ }
60414
+
60415
+ return rows;
60416
+ }
60417
+
60418
+ var widgetMcpGrantsListing = { buildGrantsListing: buildGrantsListing$1 };
60419
+
60083
60420
  /**
60084
60421
  * widgetMcpGrantsController.js
60085
60422
  *
@@ -60105,6 +60442,7 @@ const {
60105
60442
  } = grantedPermissions;
60106
60443
  const { getWidgetMcpPermissions } = widgetPermissions;
60107
60444
  const { getWidgetRegistry } = widgetRegistryExports;
60445
+ const { buildGrantsListing } = widgetMcpGrantsListing;
60108
60446
 
60109
60447
  function setupWidgetMcpGrantsHandlers() {
60110
60448
  ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
@@ -60123,19 +60461,18 @@ function setupWidgetMcpGrantsHandlers() {
60123
60461
  return revokeServer(widgetId, serverName);
60124
60462
  });
60125
60463
 
60126
- // Joins all installed widgets with their declared and granted permission
60127
- // blocks. Returns one row per widget that has a declared manifest OR a
60128
- // grant — this surfaces both "freshly installed, awaiting consent" and
60129
- // "previously granted, now reviewable" cases in Settings.
60464
+ // Joins all installed widgets with their declared manifests + persisted
60465
+ // grants. Returns ONE row per installed widget regardless of whether it
60466
+ // has a manifest or grant — that's how the Settings panel can offer
60467
+ // "Grant manually" for unmanifested widgets. Plus orphan-grant rows for
60468
+ // granted-but-uninstalled cases. Logic delegated to
60469
+ // widgetMcpGrantsListing.buildGrantsListing for unit-testability.
60130
60470
  ipcMain$1.handle("widget-mcp:list-all", () => {
60131
60471
  const grantsByWidget = new Map();
60132
60472
  for (const { widgetId, granted } of listAllGrants()) {
60133
60473
  grantsByWidget.set(widgetId, granted);
60134
60474
  }
60135
60475
 
60136
- const rows = [];
60137
- const seen = new Set();
60138
-
60139
60476
  let installedWidgets = [];
60140
60477
  try {
60141
60478
  installedWidgets = getWidgetRegistry().getWidgets() || [];
@@ -60143,25 +60480,19 @@ function setupWidgetMcpGrantsHandlers() {
60143
60480
  // Registry not initialized yet; fall back to grants-only listing.
60144
60481
  }
60145
60482
 
60483
+ const declaredByWidget = new Map();
60146
60484
  for (const w of installedWidgets) {
60147
60485
  const widgetId = w?.name;
60148
- if (!widgetId || seen.has(widgetId)) continue;
60149
- seen.add(widgetId);
60486
+ if (!widgetId) continue;
60150
60487
  const declared = getWidgetMcpPermissions(widgetId);
60151
- const granted = grantsByWidget.get(widgetId) || null;
60152
- // Skip widgets with neither — they have nothing to show.
60153
- if (!declared && !granted) continue;
60154
- rows.push({ widgetId, declared, granted });
60488
+ if (declared) declaredByWidget.set(widgetId, declared);
60155
60489
  }
60156
60490
 
60157
- // Surface any grants whose widget is no longer installed (rare, but
60158
- // possible if uninstall didn't revoke). The user can still revoke them.
60159
- for (const [widgetId, granted] of grantsByWidget) {
60160
- if (seen.has(widgetId)) continue;
60161
- rows.push({ widgetId, declared: null, granted });
60162
- }
60163
-
60164
- return rows;
60491
+ return buildGrantsListing(
60492
+ installedWidgets,
60493
+ grantsByWidget,
60494
+ declaredByWidget,
60495
+ );
60165
60496
  });
60166
60497
  }
60167
60498