@trops/dash-core 0.1.491 → 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.
- package/dist/electron/index.js +664 -181
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +644 -190
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +644 -190
- package/dist/index.js.map +1 -1
- package/package.json +118 -114
- package/scripts/scanWidgetManifestCli.js +202 -0
package/dist/electron/index.js
CHANGED
|
@@ -17,7 +17,7 @@ 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');
|
|
19
19
|
var require$$2$1 = require('os');
|
|
20
|
-
var require$$
|
|
20
|
+
var require$$11 = require('child_process');
|
|
21
21
|
var require$$3$2 = require('adm-zip');
|
|
22
22
|
var require$$4$1 = require('url');
|
|
23
23
|
var require$$2$2 = require('vm');
|
|
@@ -1107,7 +1107,7 @@ var secureStoreController$1 = {
|
|
|
1107
1107
|
getData: getData$1,
|
|
1108
1108
|
};
|
|
1109
1109
|
|
|
1110
|
-
const path$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1699
|
-
const fs$
|
|
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$
|
|
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$
|
|
1722
|
+
return [path$k.join(userData, APP_NAME, "themes")];
|
|
1723
1723
|
case "widgets":
|
|
1724
|
-
return [path$
|
|
1724
|
+
return [path$k.join(userData, "widgets")];
|
|
1725
1725
|
case "plugins":
|
|
1726
|
-
return [path$
|
|
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$
|
|
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$
|
|
1749
|
-
const raw = fs$
|
|
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$
|
|
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$
|
|
1782
|
+
real = fs$f.realpathSync(resolved);
|
|
1783
1783
|
} catch (_e) {
|
|
1784
1784
|
try {
|
|
1785
|
-
const parent = fs$
|
|
1786
|
-
real = path$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2276
|
+
const readStream = fs$e
|
|
2277
2277
|
.createReadStream(filepath)
|
|
2278
2278
|
.pipe(csv({ separator: delimiter }));
|
|
2279
|
-
const writeStream = fs$
|
|
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$
|
|
2366
|
-
const resolvedFilepath = path$
|
|
2367
|
-
const resolvedOutFilepath = path$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2392
|
+
var readStream = fs$e.createReadStream(resolvedFilepath).pipe(parser);
|
|
2393
2393
|
|
|
2394
2394
|
ensureDirectoryExistence$1(resolvedOutFilepath);
|
|
2395
2395
|
|
|
2396
|
-
var writeStream = fs$
|
|
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$
|
|
2510
|
-
const path$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3034
|
-
const fs$
|
|
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$
|
|
3043
|
-
fs$
|
|
3042
|
+
if (!fs$c.existsSync(destination)) {
|
|
3043
|
+
fs$c.mkdirSync(destination, { recursive: true });
|
|
3044
3044
|
}
|
|
3045
3045
|
|
|
3046
|
-
const files = fs$
|
|
3046
|
+
const files = fs$c.readdirSync(source);
|
|
3047
3047
|
for (const file of files) {
|
|
3048
|
-
const srcPath = path$
|
|
3049
|
-
const destPath = path$
|
|
3050
|
-
const stat = fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3180
|
-
fs$
|
|
3179
|
+
if (!fs$c.existsSync(newPath)) {
|
|
3180
|
+
fs$c.mkdirSync(newPath, { recursive: true });
|
|
3181
3181
|
}
|
|
3182
3182
|
|
|
3183
|
-
const stats = fs$
|
|
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$
|
|
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$
|
|
3224
|
-
const resolvedNewPath = path$
|
|
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$
|
|
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$
|
|
3236
|
-
if (resolvedOldPath !== path$
|
|
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$
|
|
3260
|
+
if (!fs$c.existsSync(resolvedOldPath)) {
|
|
3261
3261
|
throw new Error("Source directory does not exist");
|
|
3262
3262
|
}
|
|
3263
3263
|
|
|
3264
|
-
if (!fs$
|
|
3265
|
-
fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
21196
|
-
const path$
|
|
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$
|
|
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$
|
|
21211
|
+
if (!fs$b.existsSync(p)) return {};
|
|
21212
21212
|
try {
|
|
21213
|
-
const raw = fs$
|
|
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$
|
|
21234
|
-
fs$
|
|
21235
|
-
fs$
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -21585,6 +21604,120 @@ var mcpServerKey = {
|
|
|
21585
21604
|
SEP,
|
|
21586
21605
|
};
|
|
21587
21606
|
|
|
21607
|
+
/**
|
|
21608
|
+
* mcpScopeResolver.js
|
|
21609
|
+
*
|
|
21610
|
+
* Slice 3b: per-workspace path scope reconfiguration.
|
|
21611
|
+
*
|
|
21612
|
+
* The Slice-3a process isolation key (`workspaceId::serverName`) is the
|
|
21613
|
+
* lifecycle handle. This module computes WHAT the spawned process is
|
|
21614
|
+
* configured to see — the union of granted paths from widgets on the
|
|
21615
|
+
* active workspace, applied as credential overrides at spawn time.
|
|
21616
|
+
*
|
|
21617
|
+
* Design:
|
|
21618
|
+
* - The renderer enumerates widgets on the active workspace, looks up
|
|
21619
|
+
* each widget's grant via window.mainApi.widgetMcp.getGrant, and
|
|
21620
|
+
* hands the array to unionPathScope() to compute the workspace
|
|
21621
|
+
* scope for a given server (e.g. "filesystem").
|
|
21622
|
+
* - The renderer passes the resulting scope as `pathScope` to
|
|
21623
|
+
* mcpStartServer.
|
|
21624
|
+
* - mcpController applies the scope to credentials (replacing
|
|
21625
|
+
* allowedPaths etc.) before its existing argsMapping spreads them
|
|
21626
|
+
* into the spawn args. Servers that don't declare argsMapping for
|
|
21627
|
+
* the path keys are unaffected.
|
|
21628
|
+
*
|
|
21629
|
+
* Feature flag: the controller only applies the override when
|
|
21630
|
+
* `security.enforceWidgetMcpPermissions` is on. When off, server starts
|
|
21631
|
+
* with credentials as-configured (pre-3b behavior).
|
|
21632
|
+
*
|
|
21633
|
+
* Out of scope here:
|
|
21634
|
+
* - Hot-respawn on widget add/remove (see Slice 3b plan, deferred).
|
|
21635
|
+
* - Catalog schema for new path-scoped servers (filesystem already
|
|
21636
|
+
* has argsMapping.allowedPaths; others added as discovered).
|
|
21637
|
+
*/
|
|
21638
|
+
|
|
21639
|
+
/**
|
|
21640
|
+
* Compute the workspace-scoped path union for a given server.
|
|
21641
|
+
*
|
|
21642
|
+
* @param {Array<{widgetId, granted}>} grants - widgets-on-workspace + their grants
|
|
21643
|
+
* @param {string} serverName - the MCP server name (e.g. "filesystem")
|
|
21644
|
+
* @returns {{ readPaths: string[], writePaths: string[], allowedPaths: string[] }}
|
|
21645
|
+
*
|
|
21646
|
+
* `allowedPaths` is the dedup union of read+write — used by
|
|
21647
|
+
* filesystem-style servers that take a single allowed-list. Servers
|
|
21648
|
+
* that distinguish read-only vs read-write can use the readPaths /
|
|
21649
|
+
* writePaths arrays directly.
|
|
21650
|
+
*/
|
|
21651
|
+
function unionPathScope(grants, serverName) {
|
|
21652
|
+
const reads = new Set();
|
|
21653
|
+
const writes = new Set();
|
|
21654
|
+
|
|
21655
|
+
if (!Array.isArray(grants)) {
|
|
21656
|
+
return { readPaths: [], writePaths: [], allowedPaths: [] };
|
|
21657
|
+
}
|
|
21658
|
+
|
|
21659
|
+
for (const entry of grants) {
|
|
21660
|
+
if (!entry || typeof entry !== "object") continue;
|
|
21661
|
+
const granted = entry.granted;
|
|
21662
|
+
if (!granted || typeof granted !== "object") continue;
|
|
21663
|
+
const servers = granted.servers;
|
|
21664
|
+
if (!servers || typeof servers !== "object") continue;
|
|
21665
|
+
const serverPerms = servers[serverName];
|
|
21666
|
+
if (!serverPerms || typeof serverPerms !== "object") continue;
|
|
21667
|
+
|
|
21668
|
+
if (Array.isArray(serverPerms.readPaths)) {
|
|
21669
|
+
for (const p of serverPerms.readPaths) {
|
|
21670
|
+
if (typeof p === "string" && p) reads.add(p);
|
|
21671
|
+
}
|
|
21672
|
+
}
|
|
21673
|
+
if (Array.isArray(serverPerms.writePaths)) {
|
|
21674
|
+
for (const p of serverPerms.writePaths) {
|
|
21675
|
+
if (typeof p === "string" && p) writes.add(p);
|
|
21676
|
+
}
|
|
21677
|
+
}
|
|
21678
|
+
}
|
|
21679
|
+
|
|
21680
|
+
const readPaths = [...reads];
|
|
21681
|
+
const writePaths = [...writes];
|
|
21682
|
+
const allowedPaths = [...new Set([...reads, ...writes])];
|
|
21683
|
+
|
|
21684
|
+
return { readPaths, writePaths, allowedPaths };
|
|
21685
|
+
}
|
|
21686
|
+
|
|
21687
|
+
/**
|
|
21688
|
+
* Override credential keys with values derived from a path scope.
|
|
21689
|
+
*
|
|
21690
|
+
* Filesystem-style servers expect `allowedPaths` as a comma-separated
|
|
21691
|
+
* string (the catalog's `argsMapping.allowedPaths.split` then expands
|
|
21692
|
+
* it back into positional args at spawn time). This helper joins the
|
|
21693
|
+
* scope's allowedPaths to match that convention.
|
|
21694
|
+
*
|
|
21695
|
+
* Returns a NEW credentials object — does not mutate the input.
|
|
21696
|
+
*
|
|
21697
|
+
* If pathScope is empty (no granted paths at all), the existing
|
|
21698
|
+
* credentials are returned unchanged so the user's globally-configured
|
|
21699
|
+
* allowedPaths still works for the LLM tool path / NO_WORKSPACE bucket.
|
|
21700
|
+
*/
|
|
21701
|
+
function applyPathScopeToCredentials$1(credentials, pathScope) {
|
|
21702
|
+
const base =
|
|
21703
|
+
credentials && typeof credentials === "object" ? { ...credentials } : {};
|
|
21704
|
+
|
|
21705
|
+
if (!pathScope || typeof pathScope !== "object") return base;
|
|
21706
|
+
|
|
21707
|
+
const allowed = Array.isArray(pathScope.allowedPaths)
|
|
21708
|
+
? pathScope.allowedPaths
|
|
21709
|
+
: [];
|
|
21710
|
+
if (allowed.length === 0) return base;
|
|
21711
|
+
|
|
21712
|
+
base.allowedPaths = allowed.join(",");
|
|
21713
|
+
return base;
|
|
21714
|
+
}
|
|
21715
|
+
|
|
21716
|
+
var mcpScopeResolver = {
|
|
21717
|
+
unionPathScope,
|
|
21718
|
+
applyPathScopeToCredentials: applyPathScopeToCredentials$1,
|
|
21719
|
+
};
|
|
21720
|
+
|
|
21588
21721
|
/**
|
|
21589
21722
|
* mcpController.js
|
|
21590
21723
|
*
|
|
@@ -21605,12 +21738,13 @@ const {
|
|
|
21605
21738
|
const {
|
|
21606
21739
|
StreamableHTTPClientTransport,
|
|
21607
21740
|
} = streamableHttp$1;
|
|
21608
|
-
const path$
|
|
21609
|
-
const fs$
|
|
21741
|
+
const path$e = require$$1$2;
|
|
21742
|
+
const fs$a = require$$0$2;
|
|
21610
21743
|
const os$2 = require$$2$1;
|
|
21611
21744
|
const responseCache$2 = responseCache_1;
|
|
21612
21745
|
const { gateToolCall } = permissionGate;
|
|
21613
21746
|
const { serverKey, parseServerKey } = mcpServerKey;
|
|
21747
|
+
const { applyPathScopeToCredentials } = mcpScopeResolver;
|
|
21614
21748
|
const { app: app$7 } = require$$0$1;
|
|
21615
21749
|
|
|
21616
21750
|
// Read the widget-MCP-enforcement feature flag from settings.json.
|
|
@@ -21619,13 +21753,13 @@ const { app: app$7 } = require$$0$1;
|
|
|
21619
21753
|
// and electron/mcp/permissionGate.js for context.
|
|
21620
21754
|
function isWidgetPermissionEnforcementEnabled() {
|
|
21621
21755
|
try {
|
|
21622
|
-
const settingsPath = path$
|
|
21756
|
+
const settingsPath = path$e.join(
|
|
21623
21757
|
app$7.getPath("userData"),
|
|
21624
21758
|
"Dashboard",
|
|
21625
21759
|
"settings.json",
|
|
21626
21760
|
);
|
|
21627
|
-
if (!fs$
|
|
21628
|
-
const raw = fs$
|
|
21761
|
+
if (!fs$a.existsSync(settingsPath)) return false;
|
|
21762
|
+
const raw = fs$a.readFileSync(settingsPath, "utf8");
|
|
21629
21763
|
const settings = JSON.parse(raw);
|
|
21630
21764
|
return Boolean(settings?.security?.enforceWidgetMcpPermissions);
|
|
21631
21765
|
} catch (_e) {
|
|
@@ -21767,7 +21901,7 @@ function getShellPath$1() {
|
|
|
21767
21901
|
return _shellPath$1;
|
|
21768
21902
|
}
|
|
21769
21903
|
|
|
21770
|
-
const { execSync } = require$$
|
|
21904
|
+
const { execSync } = require$$11;
|
|
21771
21905
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
21772
21906
|
|
|
21773
21907
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
@@ -21778,7 +21912,7 @@ function getShellPath$1() {
|
|
|
21778
21912
|
fallbackDirs.push(`${home}/.nodenv/shims`);
|
|
21779
21913
|
try {
|
|
21780
21914
|
const nvmDir = `${home}/.nvm/versions/node`;
|
|
21781
|
-
const versions = fs$
|
|
21915
|
+
const versions = fs$a.readdirSync(nvmDir).sort();
|
|
21782
21916
|
if (versions.length > 0) {
|
|
21783
21917
|
// Find the highest compatible version (v18/v20/v22)
|
|
21784
21918
|
for (let i = versions.length - 1; i >= 0; i--) {
|
|
@@ -21914,15 +22048,15 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
|
21914
22048
|
const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
|
|
21915
22049
|
const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
|
|
21916
22050
|
|
|
21917
|
-
if (!fs$
|
|
22051
|
+
if (!fs$a.existsSync(credPath) || !fs$a.existsSync(keysPath)) {
|
|
21918
22052
|
console.log(
|
|
21919
22053
|
"[mcpController] Token refresh skipped: credential files not found",
|
|
21920
22054
|
);
|
|
21921
22055
|
return;
|
|
21922
22056
|
}
|
|
21923
22057
|
|
|
21924
|
-
const credentials = JSON.parse(fs$
|
|
21925
|
-
const keysFile = JSON.parse(fs$
|
|
22058
|
+
const credentials = JSON.parse(fs$a.readFileSync(credPath, "utf8"));
|
|
22059
|
+
const keysFile = JSON.parse(fs$a.readFileSync(keysPath, "utf8"));
|
|
21926
22060
|
const keyData = keysFile.installed || keysFile.web;
|
|
21927
22061
|
|
|
21928
22062
|
if (
|
|
@@ -21991,7 +22125,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
|
21991
22125
|
credentials.refresh_token = body.refresh_token;
|
|
21992
22126
|
}
|
|
21993
22127
|
|
|
21994
|
-
fs$
|
|
22128
|
+
fs$a.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
|
|
21995
22129
|
console.log("[mcpController] Google OAuth token refreshed successfully");
|
|
21996
22130
|
}
|
|
21997
22131
|
|
|
@@ -22005,15 +22139,41 @@ const mcpController$3 = {
|
|
|
22005
22139
|
* Pass `null`/`undefined` workspaceId to land on the legacy
|
|
22006
22140
|
* NO_WORKSPACE bucket (e.g. dash MCP server tools, AI Builder previews).
|
|
22007
22141
|
*
|
|
22142
|
+
* Slice 3b: when `security.enforceWidgetMcpPermissions` is on AND a
|
|
22143
|
+
* non-empty `pathScope` is supplied, the scope's allowed paths
|
|
22144
|
+
* override the server's existing path-style credentials before the
|
|
22145
|
+
* catalog's argsMapping spreads them into spawn args. This scopes
|
|
22146
|
+
* the server's OS-level capability to the union of widget grants on
|
|
22147
|
+
* the active workspace.
|
|
22148
|
+
*
|
|
22008
22149
|
* @param {BrowserWindow} win the main window
|
|
22009
22150
|
* @param {string} serverName unique name for this server instance
|
|
22010
22151
|
* @param {object} mcpConfig { transport, command, args, envMapping }
|
|
22011
22152
|
* @param {object} credentials decrypted credentials object
|
|
22012
22153
|
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22154
|
+
* @param {object|null} pathScope { readPaths, writePaths, allowedPaths } (Slice 3b)
|
|
22013
22155
|
* @returns {{ success, serverName, tools, status } | { error, message }}
|
|
22014
22156
|
*/
|
|
22015
|
-
startServer: async (
|
|
22157
|
+
startServer: async (
|
|
22158
|
+
win,
|
|
22159
|
+
serverName,
|
|
22160
|
+
mcpConfig,
|
|
22161
|
+
credentials,
|
|
22162
|
+
workspaceId,
|
|
22163
|
+
pathScope = null,
|
|
22164
|
+
) => {
|
|
22016
22165
|
const key = serverKey(workspaceId, serverName);
|
|
22166
|
+
// Slice 3b: when the gate is enforced, override credentials with
|
|
22167
|
+
// the workspace's union of granted paths so the spawned process
|
|
22168
|
+
// can't see anything broader than the user has consented to.
|
|
22169
|
+
if (
|
|
22170
|
+
isWidgetPermissionEnforcementEnabled() &&
|
|
22171
|
+
pathScope &&
|
|
22172
|
+
Array.isArray(pathScope.allowedPaths) &&
|
|
22173
|
+
pathScope.allowedPaths.length > 0
|
|
22174
|
+
) {
|
|
22175
|
+
credentials = applyPathScopeToCredentials(credentials, pathScope);
|
|
22176
|
+
}
|
|
22017
22177
|
// 1. Already connected? Return existing connection
|
|
22018
22178
|
const existing = activeServers.get(key);
|
|
22019
22179
|
if (existing && existing.status === STATUS$1.CONNECTED && existing.client) {
|
|
@@ -22152,7 +22312,7 @@ const mcpController$3 = {
|
|
|
22152
22312
|
}
|
|
22153
22313
|
|
|
22154
22314
|
// Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
|
|
22155
|
-
const mcpDir = path$
|
|
22315
|
+
const mcpDir = path$e.join(__dirname, "..", "mcp");
|
|
22156
22316
|
for (let i = 0; i < args.length; i++) {
|
|
22157
22317
|
if (
|
|
22158
22318
|
typeof args[i] === "string" &&
|
|
@@ -22590,20 +22750,20 @@ const mcpController$3 = {
|
|
|
22590
22750
|
*/
|
|
22591
22751
|
getCatalog: (win) => {
|
|
22592
22752
|
try {
|
|
22593
|
-
const catalogPath = path$
|
|
22753
|
+
const catalogPath = path$e.join(
|
|
22594
22754
|
__dirname,
|
|
22595
22755
|
"..",
|
|
22596
22756
|
"mcp",
|
|
22597
22757
|
"mcpServerCatalog.json",
|
|
22598
22758
|
);
|
|
22599
22759
|
|
|
22600
|
-
if (!fs$
|
|
22760
|
+
if (!fs$a.existsSync(catalogPath)) {
|
|
22601
22761
|
return {
|
|
22602
22762
|
catalog: [],
|
|
22603
22763
|
};
|
|
22604
22764
|
}
|
|
22605
22765
|
|
|
22606
|
-
const catalogData = fs$
|
|
22766
|
+
const catalogData = fs$a.readFileSync(catalogPath, "utf8");
|
|
22607
22767
|
const catalog = JSON.parse(catalogData);
|
|
22608
22768
|
|
|
22609
22769
|
return {
|
|
@@ -22631,18 +22791,18 @@ const mcpController$3 = {
|
|
|
22631
22791
|
*/
|
|
22632
22792
|
getKnownExternalCatalog: () => {
|
|
22633
22793
|
try {
|
|
22634
|
-
const catalogPath = path$
|
|
22794
|
+
const catalogPath = path$e.join(
|
|
22635
22795
|
__dirname,
|
|
22636
22796
|
"..",
|
|
22637
22797
|
"mcp",
|
|
22638
22798
|
"knownExternalMcpServers.json",
|
|
22639
22799
|
);
|
|
22640
22800
|
|
|
22641
|
-
if (!fs$
|
|
22801
|
+
if (!fs$a.existsSync(catalogPath)) {
|
|
22642
22802
|
return { success: true, servers: [] };
|
|
22643
22803
|
}
|
|
22644
22804
|
|
|
22645
|
-
const catalogData = fs$
|
|
22805
|
+
const catalogData = fs$a.readFileSync(catalogPath, "utf8");
|
|
22646
22806
|
const catalog = JSON.parse(catalogData);
|
|
22647
22807
|
|
|
22648
22808
|
return {
|
|
@@ -22695,7 +22855,7 @@ const mcpController$3 = {
|
|
|
22695
22855
|
* @returns {{ success } | { error, message }}
|
|
22696
22856
|
*/
|
|
22697
22857
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
22698
|
-
const { spawn } = require$$
|
|
22858
|
+
const { spawn } = require$$11;
|
|
22699
22859
|
|
|
22700
22860
|
const env = cleanEnvForChildProcess();
|
|
22701
22861
|
|
|
@@ -22707,8 +22867,8 @@ const mcpController$3 = {
|
|
|
22707
22867
|
const destPath = to.replace(/^~/, os$2.homedir());
|
|
22708
22868
|
const destDir = require$$1$2.dirname(destPath);
|
|
22709
22869
|
try {
|
|
22710
|
-
fs$
|
|
22711
|
-
fs$
|
|
22870
|
+
fs$a.mkdirSync(destDir, { recursive: true });
|
|
22871
|
+
fs$a.copyFileSync(sourcePath, destPath);
|
|
22712
22872
|
} catch (err) {
|
|
22713
22873
|
return {
|
|
22714
22874
|
error: true,
|
|
@@ -22744,7 +22904,7 @@ const mcpController$3 = {
|
|
|
22744
22904
|
}
|
|
22745
22905
|
|
|
22746
22906
|
// Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
|
|
22747
|
-
const mcpDir = path$
|
|
22907
|
+
const mcpDir = path$e.join(__dirname, "..", "mcp");
|
|
22748
22908
|
const resolvedArgs = (authCommand.args || []).map((arg) =>
|
|
22749
22909
|
typeof arg === "string" && arg.includes("{{MCP_DIR}}")
|
|
22750
22910
|
? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
|
|
@@ -23292,8 +23452,8 @@ function commonjsRequire(path) {
|
|
|
23292
23452
|
* Runs in the Electron main process at widget install time.
|
|
23293
23453
|
*/
|
|
23294
23454
|
|
|
23295
|
-
const fs$
|
|
23296
|
-
const path$
|
|
23455
|
+
const fs$9 = require$$0$2;
|
|
23456
|
+
const path$d = require$$1$2;
|
|
23297
23457
|
|
|
23298
23458
|
/**
|
|
23299
23459
|
* Structured error thrown by compileWidget() when the underlying
|
|
@@ -23328,7 +23488,7 @@ function getEsbuildDiagnostics() {
|
|
|
23328
23488
|
|
|
23329
23489
|
try {
|
|
23330
23490
|
const pkgJsonPath = require.resolve("esbuild/package.json");
|
|
23331
|
-
diagnostics.esbuildPackageDir = path$
|
|
23491
|
+
diagnostics.esbuildPackageDir = path$d.dirname(pkgJsonPath);
|
|
23332
23492
|
diagnostics.esbuildVersion = commonjsRequire(pkgJsonPath).version;
|
|
23333
23493
|
} catch (err) {
|
|
23334
23494
|
diagnostics.esbuildResolveError = err.message;
|
|
@@ -23338,15 +23498,15 @@ function getEsbuildDiagnostics() {
|
|
|
23338
23498
|
const archPkgJson = require.resolve(
|
|
23339
23499
|
`${diagnostics.archPackage}/package.json`,
|
|
23340
23500
|
);
|
|
23341
|
-
const archDir = path$
|
|
23501
|
+
const archDir = path$d.dirname(archPkgJson);
|
|
23342
23502
|
// esbuild's native binary on macOS/Linux is bin/esbuild;
|
|
23343
23503
|
// on Windows it's esbuild.exe at the package root.
|
|
23344
23504
|
const candidate =
|
|
23345
23505
|
process.platform === "win32"
|
|
23346
|
-
? path$
|
|
23347
|
-
: path$
|
|
23506
|
+
? path$d.join(archDir, "esbuild.exe")
|
|
23507
|
+
: path$d.join(archDir, "bin", "esbuild");
|
|
23348
23508
|
diagnostics.nativeBinaryPath = candidate;
|
|
23349
|
-
diagnostics.nativeBinaryExists = fs$
|
|
23509
|
+
diagnostics.nativeBinaryExists = fs$9.existsSync(candidate);
|
|
23350
23510
|
} catch (err) {
|
|
23351
23511
|
diagnostics.archResolveError = err.message;
|
|
23352
23512
|
}
|
|
@@ -23392,26 +23552,26 @@ async function healthCheck() {
|
|
|
23392
23552
|
* @returns {string|null} Path to the widgets/ directory, or null
|
|
23393
23553
|
*/
|
|
23394
23554
|
function findWidgetsDir$2(widgetPath) {
|
|
23395
|
-
const direct = path$
|
|
23396
|
-
if (fs$
|
|
23555
|
+
const direct = path$d.join(widgetPath, "widgets");
|
|
23556
|
+
if (fs$9.existsSync(direct)) {
|
|
23397
23557
|
return direct;
|
|
23398
23558
|
}
|
|
23399
23559
|
|
|
23400
23560
|
// Check configs/widgets/ (packageZip.js nests .dash.js files here)
|
|
23401
|
-
const configsWidgets = path$
|
|
23402
|
-
if (fs$
|
|
23561
|
+
const configsWidgets = path$d.join(widgetPath, "configs", "widgets");
|
|
23562
|
+
if (fs$9.existsSync(configsWidgets)) {
|
|
23403
23563
|
return configsWidgets;
|
|
23404
23564
|
}
|
|
23405
23565
|
|
|
23406
23566
|
// Check configs/ directory (used by packageZip.js for distributed widgets)
|
|
23407
|
-
const configs = path$
|
|
23408
|
-
if (fs$
|
|
23567
|
+
const configs = path$d.join(widgetPath, "configs");
|
|
23568
|
+
if (fs$9.existsSync(configs)) {
|
|
23409
23569
|
return configs;
|
|
23410
23570
|
}
|
|
23411
23571
|
|
|
23412
23572
|
// Check one level deeper for nested ZIP extraction
|
|
23413
23573
|
try {
|
|
23414
|
-
const entries = fs$
|
|
23574
|
+
const entries = fs$9.readdirSync(widgetPath, { withFileTypes: true });
|
|
23415
23575
|
const subdirs = entries.filter(
|
|
23416
23576
|
(e) =>
|
|
23417
23577
|
e.isDirectory() &&
|
|
@@ -23421,8 +23581,8 @@ function findWidgetsDir$2(widgetPath) {
|
|
|
23421
23581
|
);
|
|
23422
23582
|
|
|
23423
23583
|
for (const subdir of subdirs) {
|
|
23424
|
-
const nested = path$
|
|
23425
|
-
if (fs$
|
|
23584
|
+
const nested = path$d.join(widgetPath, subdir.name, "widgets");
|
|
23585
|
+
if (fs$9.existsSync(nested)) {
|
|
23426
23586
|
console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
|
|
23427
23587
|
return nested;
|
|
23428
23588
|
}
|
|
@@ -23456,7 +23616,7 @@ async function compileWidget$1(widgetPath) {
|
|
|
23456
23616
|
}
|
|
23457
23617
|
|
|
23458
23618
|
// Discover .dash.js config files
|
|
23459
|
-
const files = fs$
|
|
23619
|
+
const files = fs$9.readdirSync(widgetsDir);
|
|
23460
23620
|
const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
|
|
23461
23621
|
|
|
23462
23622
|
if (dashFiles.length === 0) {
|
|
@@ -23470,15 +23630,15 @@ async function compileWidget$1(widgetPath) {
|
|
|
23470
23630
|
// Compute relative path from the entry file (in widgetPath) to widgetsDir,
|
|
23471
23631
|
// since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
|
|
23472
23632
|
const relWidgetsDir =
|
|
23473
|
-
"./" + path$
|
|
23633
|
+
"./" + path$d.relative(widgetPath, widgetsDir).split(path$d.sep).join("/");
|
|
23474
23634
|
const imports = [];
|
|
23475
23635
|
const exportParts = [];
|
|
23476
23636
|
|
|
23477
23637
|
for (const dashFile of dashFiles) {
|
|
23478
23638
|
const componentName = dashFile.replace(".dash.js", "");
|
|
23479
23639
|
const componentFile = `${componentName}.js`;
|
|
23480
|
-
const componentFilePath = path$
|
|
23481
|
-
const hasComponent = fs$
|
|
23640
|
+
const componentFilePath = path$d.join(widgetsDir, componentFile);
|
|
23641
|
+
const hasComponent = fs$9.existsSync(componentFilePath);
|
|
23482
23642
|
|
|
23483
23643
|
// Import the config (always)
|
|
23484
23644
|
imports.push(
|
|
@@ -23510,17 +23670,17 @@ async function compileWidget$1(widgetPath) {
|
|
|
23510
23670
|
const entryContent = [...imports, "", ...exportParts, ""].join("\n");
|
|
23511
23671
|
|
|
23512
23672
|
// Write temporary entry file in the widget root
|
|
23513
|
-
const entryPath = path$
|
|
23514
|
-
const distDir = path$
|
|
23515
|
-
const outPath = path$
|
|
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");
|
|
23516
23676
|
|
|
23517
23677
|
try {
|
|
23518
23678
|
// Ensure dist/ directory exists
|
|
23519
|
-
if (!fs$
|
|
23520
|
-
fs$
|
|
23679
|
+
if (!fs$9.existsSync(distDir)) {
|
|
23680
|
+
fs$9.mkdirSync(distDir, { recursive: true });
|
|
23521
23681
|
}
|
|
23522
23682
|
|
|
23523
|
-
fs$
|
|
23683
|
+
fs$9.writeFileSync(entryPath, entryContent, "utf8");
|
|
23524
23684
|
|
|
23525
23685
|
console.log(
|
|
23526
23686
|
`[WidgetCompiler] Compiling ${dashFiles.length} component(s) from ${widgetPath}`,
|
|
@@ -23576,8 +23736,8 @@ async function compileWidget$1(widgetPath) {
|
|
|
23576
23736
|
} finally {
|
|
23577
23737
|
// Clean up temporary entry file
|
|
23578
23738
|
try {
|
|
23579
|
-
if (fs$
|
|
23580
|
-
fs$
|
|
23739
|
+
if (fs$9.existsSync(entryPath)) {
|
|
23740
|
+
fs$9.unlinkSync(entryPath);
|
|
23581
23741
|
}
|
|
23582
23742
|
} catch (cleanupError) {
|
|
23583
23743
|
// Non-fatal
|
|
@@ -23609,8 +23769,8 @@ var widgetCompiler$1 = {
|
|
|
23609
23769
|
* Integrates with ComponentManager for automatic registration
|
|
23610
23770
|
*/
|
|
23611
23771
|
|
|
23612
|
-
const fs$
|
|
23613
|
-
const path$
|
|
23772
|
+
const fs$8 = require$$0$2;
|
|
23773
|
+
const path$c = require$$1$2;
|
|
23614
23774
|
const vm = require$$2$2;
|
|
23615
23775
|
const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
|
|
23616
23776
|
|
|
@@ -23698,14 +23858,14 @@ class DynamicWidgetLoader {
|
|
|
23698
23858
|
);
|
|
23699
23859
|
|
|
23700
23860
|
const widgetsDir =
|
|
23701
|
-
findWidgetsDir$1(widgetPath) || path$
|
|
23702
|
-
const componentPath = path$
|
|
23703
|
-
const configPath = path$
|
|
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`);
|
|
23704
23864
|
|
|
23705
|
-
if (!fs$
|
|
23865
|
+
if (!fs$8.existsSync(componentPath)) {
|
|
23706
23866
|
throw new Error(`Component file not found: ${componentPath}`);
|
|
23707
23867
|
}
|
|
23708
|
-
if (!fs$
|
|
23868
|
+
if (!fs$8.existsSync(configPath)) {
|
|
23709
23869
|
throw new Error(`Config file not found: ${configPath}`);
|
|
23710
23870
|
}
|
|
23711
23871
|
|
|
@@ -23756,7 +23916,7 @@ class DynamicWidgetLoader {
|
|
|
23756
23916
|
*/
|
|
23757
23917
|
async loadConfigFile(configPath) {
|
|
23758
23918
|
try {
|
|
23759
|
-
const source = fs$
|
|
23919
|
+
const source = fs$8.readFileSync(configPath, "utf8");
|
|
23760
23920
|
|
|
23761
23921
|
let exportMatch = source.match(/export\s+default\s+({[\s\S]*});?\s*$/);
|
|
23762
23922
|
|
|
@@ -23824,7 +23984,7 @@ class DynamicWidgetLoader {
|
|
|
23824
23984
|
return [];
|
|
23825
23985
|
}
|
|
23826
23986
|
|
|
23827
|
-
const files = fs$
|
|
23987
|
+
const files = fs$8.readdirSync(widgetsDir);
|
|
23828
23988
|
const widgets = new Set();
|
|
23829
23989
|
|
|
23830
23990
|
files.forEach((file) => {
|
|
@@ -23900,8 +24060,8 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
|
|
|
23900
24060
|
* Test-only. Drops the in-process cache so tests can re-read.
|
|
23901
24061
|
*/
|
|
23902
24062
|
|
|
23903
|
-
const fs$
|
|
23904
|
-
const path$
|
|
24063
|
+
const fs$7 = require$$0$2;
|
|
24064
|
+
const path$b = require$$1$2;
|
|
23905
24065
|
const os$1 = require$$2$1;
|
|
23906
24066
|
const { app: app$6 } = require$$0$1;
|
|
23907
24067
|
|
|
@@ -23918,7 +24078,7 @@ function expandHome(p) {
|
|
|
23918
24078
|
if (typeof p !== "string" || !p) return p;
|
|
23919
24079
|
if (p === "~") return os$1.homedir();
|
|
23920
24080
|
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
23921
|
-
return path$
|
|
24081
|
+
return path$b.join(os$1.homedir(), p.slice(2));
|
|
23922
24082
|
}
|
|
23923
24083
|
return p;
|
|
23924
24084
|
}
|
|
@@ -23958,10 +24118,10 @@ function parseManifestPermissions(packageJson) {
|
|
|
23958
24118
|
*/
|
|
23959
24119
|
function resolveWidgetPackagePath(widgetId) {
|
|
23960
24120
|
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
23961
|
-
const widgetsRoot = path$
|
|
24121
|
+
const widgetsRoot = path$b.join(app$6.getPath("userData"), "widgets");
|
|
23962
24122
|
// Split scope from name for "@scope/name" form.
|
|
23963
24123
|
const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
|
|
23964
|
-
return path$
|
|
24124
|
+
return path$b.join(widgetsRoot, ...parts, "package.json");
|
|
23965
24125
|
}
|
|
23966
24126
|
|
|
23967
24127
|
/**
|
|
@@ -23975,12 +24135,12 @@ function resolveWidgetPackagePath(widgetId) {
|
|
|
23975
24135
|
function getWidgetMcpPermissions$1(widgetId) {
|
|
23976
24136
|
if (_cache.has(widgetId)) return _cache.get(widgetId);
|
|
23977
24137
|
const pkgPath = resolveWidgetPackagePath(widgetId);
|
|
23978
|
-
if (!pkgPath || !fs$
|
|
24138
|
+
if (!pkgPath || !fs$7.existsSync(pkgPath)) {
|
|
23979
24139
|
_cache.set(widgetId, null);
|
|
23980
24140
|
return null;
|
|
23981
24141
|
}
|
|
23982
24142
|
try {
|
|
23983
|
-
const raw = fs$
|
|
24143
|
+
const raw = fs$7.readFileSync(pkgPath, "utf8");
|
|
23984
24144
|
const pkg = JSON.parse(raw);
|
|
23985
24145
|
const perms = parseManifestPermissions(pkg);
|
|
23986
24146
|
_cache.set(widgetId, perms);
|
|
@@ -24008,6 +24168,200 @@ var widgetPermissions = {
|
|
|
24008
24168
|
clearCache,
|
|
24009
24169
|
};
|
|
24010
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
|
+
|
|
24011
24365
|
/**
|
|
24012
24366
|
* schedulerController.js
|
|
24013
24367
|
*
|
|
@@ -24552,6 +24906,7 @@ var schedulerController_1 = schedulerController$2;
|
|
|
24552
24906
|
getWidgetMcpPermissions,
|
|
24553
24907
|
clearCache: clearWidgetPermsCache,
|
|
24554
24908
|
} = widgetPermissions;
|
|
24909
|
+
const { scanForMcpUsage } = manifestScanner;
|
|
24555
24910
|
|
|
24556
24911
|
let WIDGETS_CACHE_DIR = null;
|
|
24557
24912
|
let REGISTRY_CONFIG_FILE = null;
|
|
@@ -25633,6 +25988,14 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25633
25988
|
* with the user's selections (Slice 2) or quietly drops the message
|
|
25634
25989
|
* (older renderer pre-Slice-2 — the gate still fail-closes).
|
|
25635
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
|
+
*
|
|
25636
25999
|
* Cache invalidation: widgetPermissions caches per-process, so an upgrade
|
|
25637
26000
|
* over a stale cached entry would otherwise keep the old manifest. Drop
|
|
25638
26001
|
* the whole cache here — cheap, infrequent.
|
|
@@ -25641,11 +26004,41 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25641
26004
|
try {
|
|
25642
26005
|
clearWidgetPermsCache();
|
|
25643
26006
|
const declared = getWidgetMcpPermissions(widgetName);
|
|
25644
|
-
if (
|
|
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
|
+
}
|
|
25645
26036
|
BrowserWindow.getAllWindows().forEach((win) => {
|
|
25646
26037
|
win.webContents.send("widget:mcp-consent-required", {
|
|
25647
26038
|
widgetId: widgetName,
|
|
25648
|
-
declared,
|
|
26039
|
+
declared: { servers: syntheticServers },
|
|
26040
|
+
discovered: true,
|
|
26041
|
+
warnings: scanResult.warnings,
|
|
25649
26042
|
});
|
|
25650
26043
|
});
|
|
25651
26044
|
} catch (e) {
|
|
@@ -47862,7 +48255,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
47862
48255
|
* can use the Chat widget without a separate API key.
|
|
47863
48256
|
*/
|
|
47864
48257
|
|
|
47865
|
-
const { spawn, execSync } = require$$
|
|
48258
|
+
const { spawn, execSync } = require$$11;
|
|
47866
48259
|
const {
|
|
47867
48260
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
47868
48261
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -59939,6 +60332,91 @@ const webSocketController$1 = {
|
|
|
59939
60332
|
|
|
59940
60333
|
var webSocketController_1 = webSocketController$1;
|
|
59941
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
|
+
|
|
59942
60420
|
/**
|
|
59943
60421
|
* widgetMcpGrantsController.js
|
|
59944
60422
|
*
|
|
@@ -59964,6 +60442,7 @@ const {
|
|
|
59964
60442
|
} = grantedPermissions;
|
|
59965
60443
|
const { getWidgetMcpPermissions } = widgetPermissions;
|
|
59966
60444
|
const { getWidgetRegistry } = widgetRegistryExports;
|
|
60445
|
+
const { buildGrantsListing } = widgetMcpGrantsListing;
|
|
59967
60446
|
|
|
59968
60447
|
function setupWidgetMcpGrantsHandlers() {
|
|
59969
60448
|
ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
|
|
@@ -59982,19 +60461,18 @@ function setupWidgetMcpGrantsHandlers() {
|
|
|
59982
60461
|
return revokeServer(widgetId, serverName);
|
|
59983
60462
|
});
|
|
59984
60463
|
|
|
59985
|
-
// Joins all installed widgets with their declared
|
|
59986
|
-
//
|
|
59987
|
-
// grant —
|
|
59988
|
-
// "
|
|
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.
|
|
59989
60470
|
ipcMain$1.handle("widget-mcp:list-all", () => {
|
|
59990
60471
|
const grantsByWidget = new Map();
|
|
59991
60472
|
for (const { widgetId, granted } of listAllGrants()) {
|
|
59992
60473
|
grantsByWidget.set(widgetId, granted);
|
|
59993
60474
|
}
|
|
59994
60475
|
|
|
59995
|
-
const rows = [];
|
|
59996
|
-
const seen = new Set();
|
|
59997
|
-
|
|
59998
60476
|
let installedWidgets = [];
|
|
59999
60477
|
try {
|
|
60000
60478
|
installedWidgets = getWidgetRegistry().getWidgets() || [];
|
|
@@ -60002,25 +60480,19 @@ function setupWidgetMcpGrantsHandlers() {
|
|
|
60002
60480
|
// Registry not initialized yet; fall back to grants-only listing.
|
|
60003
60481
|
}
|
|
60004
60482
|
|
|
60483
|
+
const declaredByWidget = new Map();
|
|
60005
60484
|
for (const w of installedWidgets) {
|
|
60006
60485
|
const widgetId = w?.name;
|
|
60007
|
-
if (!widgetId
|
|
60008
|
-
seen.add(widgetId);
|
|
60486
|
+
if (!widgetId) continue;
|
|
60009
60487
|
const declared = getWidgetMcpPermissions(widgetId);
|
|
60010
|
-
|
|
60011
|
-
// Skip widgets with neither — they have nothing to show.
|
|
60012
|
-
if (!declared && !granted) continue;
|
|
60013
|
-
rows.push({ widgetId, declared, granted });
|
|
60488
|
+
if (declared) declaredByWidget.set(widgetId, declared);
|
|
60014
60489
|
}
|
|
60015
60490
|
|
|
60016
|
-
|
|
60017
|
-
|
|
60018
|
-
|
|
60019
|
-
|
|
60020
|
-
|
|
60021
|
-
}
|
|
60022
|
-
|
|
60023
|
-
return rows;
|
|
60491
|
+
return buildGrantsListing(
|
|
60492
|
+
installedWidgets,
|
|
60493
|
+
grantsByWidget,
|
|
60494
|
+
declaredByWidget,
|
|
60495
|
+
);
|
|
60024
60496
|
});
|
|
60025
60497
|
}
|
|
60026
60498
|
|
|
@@ -62253,14 +62725,25 @@ const mcpApi$2 = {
|
|
|
62253
62725
|
* @param {object} credentials decrypted credentials object
|
|
62254
62726
|
* @param {string|null} workspaceId active workspace id (Slice 3a) —
|
|
62255
62727
|
* server processes are keyed per workspace.
|
|
62728
|
+
* @param {object|null} pathScope (Slice 3b) — when provided, the
|
|
62729
|
+
* workspace's union of granted paths overrides the server's
|
|
62730
|
+
* path-style credentials at spawn time. Shape:
|
|
62731
|
+
* `{ readPaths, writePaths, allowedPaths }`.
|
|
62256
62732
|
* @returns {Promise<{ success, serverName, tools, status } | { error, message }>}
|
|
62257
62733
|
*/
|
|
62258
|
-
startServer: (
|
|
62734
|
+
startServer: (
|
|
62735
|
+
serverName,
|
|
62736
|
+
mcpConfig,
|
|
62737
|
+
credentials,
|
|
62738
|
+
workspaceId = null,
|
|
62739
|
+
pathScope = null,
|
|
62740
|
+
) =>
|
|
62259
62741
|
ipcRenderer$i.invoke(MCP_START_SERVER, {
|
|
62260
62742
|
serverName,
|
|
62261
62743
|
mcpConfig,
|
|
62262
62744
|
credentials,
|
|
62263
62745
|
workspaceId,
|
|
62746
|
+
pathScope,
|
|
62264
62747
|
}),
|
|
62265
62748
|
|
|
62266
62749
|
/**
|