@trops/dash-core 0.1.487 → 0.1.490
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 +902 -191
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +244 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +244 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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$$9$1 = 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$m = 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$m.dirname(filePath);
|
|
1129
1129
|
// check if the directory exists...return true
|
|
1130
1130
|
// if not, we can pass in the dirname as the filepath
|
|
1131
1131
|
// and check each directory recursively.
|
|
@@ -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$m.join(directory, file), (err) => {
|
|
1244
1244
|
if (err) throw err;
|
|
1245
1245
|
});
|
|
1246
1246
|
}
|
|
@@ -1257,8 +1257,8 @@ var file = {
|
|
|
1257
1257
|
checkDirectory: checkDirectory$1,
|
|
1258
1258
|
};
|
|
1259
1259
|
|
|
1260
|
-
const { app: app$
|
|
1261
|
-
const path$
|
|
1260
|
+
const { app: app$f } = require$$0$1;
|
|
1261
|
+
const path$l = require$$1$2;
|
|
1262
1262
|
const { writeFileSync: writeFileSync$3 } = require$$0$2;
|
|
1263
1263
|
const { getFileContents: getFileContents$7 } = file;
|
|
1264
1264
|
|
|
@@ -1305,8 +1305,8 @@ const workspaceController$3 = {
|
|
|
1305
1305
|
saveWorkspaceForApplication: (win, appId, workspaceObject) => {
|
|
1306
1306
|
try {
|
|
1307
1307
|
// filename to the pages file (live pages)
|
|
1308
|
-
const filename = path$
|
|
1309
|
-
app$
|
|
1308
|
+
const filename = path$l.join(
|
|
1309
|
+
app$f.getPath("userData"),
|
|
1310
1310
|
appName$7,
|
|
1311
1311
|
appId,
|
|
1312
1312
|
configFilename$5,
|
|
@@ -1354,8 +1354,8 @@ const workspaceController$3 = {
|
|
|
1354
1354
|
saveMenuItemsForApplication: (win, appId, menuItems) => {
|
|
1355
1355
|
try {
|
|
1356
1356
|
// filename to the workspaces file
|
|
1357
|
-
const filename = path$
|
|
1358
|
-
app$
|
|
1357
|
+
const filename = path$l.join(
|
|
1358
|
+
app$f.getPath("userData"),
|
|
1359
1359
|
appName$7,
|
|
1360
1360
|
appId,
|
|
1361
1361
|
configFilename$5,
|
|
@@ -1403,8 +1403,8 @@ const workspaceController$3 = {
|
|
|
1403
1403
|
*/
|
|
1404
1404
|
deleteWorkspaceForApplication: (win, appId, workspaceId) => {
|
|
1405
1405
|
try {
|
|
1406
|
-
const filename = path$
|
|
1407
|
-
app$
|
|
1406
|
+
const filename = path$l.join(
|
|
1407
|
+
app$f.getPath("userData"),
|
|
1408
1408
|
appName$7,
|
|
1409
1409
|
appId,
|
|
1410
1410
|
configFilename$5,
|
|
@@ -1437,8 +1437,8 @@ const workspaceController$3 = {
|
|
|
1437
1437
|
|
|
1438
1438
|
listWorkspacesForApplication: (win, appId) => {
|
|
1439
1439
|
try {
|
|
1440
|
-
const filename = path$
|
|
1441
|
-
app$
|
|
1440
|
+
const filename = path$l.join(
|
|
1441
|
+
app$f.getPath("userData"),
|
|
1442
1442
|
appName$7,
|
|
1443
1443
|
appId,
|
|
1444
1444
|
configFilename$5,
|
|
@@ -1465,8 +1465,8 @@ const workspaceController$3 = {
|
|
|
1465
1465
|
|
|
1466
1466
|
listMenuItemsForApplication: (win, appId) => {
|
|
1467
1467
|
try {
|
|
1468
|
-
const filename = path$
|
|
1469
|
-
app$
|
|
1468
|
+
const filename = path$l.join(
|
|
1469
|
+
app$f.getPath("userData"),
|
|
1470
1470
|
appName$7,
|
|
1471
1471
|
appId,
|
|
1472
1472
|
configFilename$5,
|
|
@@ -1509,8 +1509,8 @@ const workspaceController$3 = {
|
|
|
1509
1509
|
|
|
1510
1510
|
var workspaceController_1 = workspaceController$3;
|
|
1511
1511
|
|
|
1512
|
-
const { app: app$
|
|
1513
|
-
const path$
|
|
1512
|
+
const { app: app$e } = require$$0$1;
|
|
1513
|
+
const path$k = require$$1$2;
|
|
1514
1514
|
const { writeFileSync: writeFileSync$2 } = require$$0$2;
|
|
1515
1515
|
const { getFileContents: getFileContents$6 } = file;
|
|
1516
1516
|
|
|
@@ -1530,8 +1530,8 @@ const themeController$5 = {
|
|
|
1530
1530
|
saveThemeForApplication: (win, appId, name, obj) => {
|
|
1531
1531
|
try {
|
|
1532
1532
|
// filename to the pages file (live pages)
|
|
1533
|
-
const filename = path$
|
|
1534
|
-
app$
|
|
1533
|
+
const filename = path$k.join(
|
|
1534
|
+
app$e.getPath("userData"),
|
|
1535
1535
|
appName$6,
|
|
1536
1536
|
appId,
|
|
1537
1537
|
configFilename$4,
|
|
@@ -1576,8 +1576,8 @@ const themeController$5 = {
|
|
|
1576
1576
|
*/
|
|
1577
1577
|
listThemesForApplication: (win, appId) => {
|
|
1578
1578
|
try {
|
|
1579
|
-
const filename = path$
|
|
1580
|
-
app$
|
|
1579
|
+
const filename = path$k.join(
|
|
1580
|
+
app$e.getPath("userData"),
|
|
1581
1581
|
appName$6,
|
|
1582
1582
|
appId,
|
|
1583
1583
|
configFilename$4,
|
|
@@ -1618,8 +1618,8 @@ const themeController$5 = {
|
|
|
1618
1618
|
*/
|
|
1619
1619
|
deleteThemeForApplication: (win, appId, themeKey) => {
|
|
1620
1620
|
try {
|
|
1621
|
-
const filename = path$
|
|
1622
|
-
app$
|
|
1621
|
+
const filename = path$k.join(
|
|
1622
|
+
app$e.getPath("userData"),
|
|
1623
1623
|
appName$6,
|
|
1624
1624
|
appId,
|
|
1625
1625
|
configFilename$4,
|
|
@@ -1695,9 +1695,9 @@ var themeController_1 = themeController$5;
|
|
|
1695
1695
|
* `/data/`.
|
|
1696
1696
|
*/
|
|
1697
1697
|
|
|
1698
|
-
const path$
|
|
1699
|
-
const fs$
|
|
1700
|
-
const { app: app$
|
|
1698
|
+
const path$j = require$$1$2;
|
|
1699
|
+
const fs$e = require$$0$2;
|
|
1700
|
+
const { app: app$d } = require$$0$1;
|
|
1701
1701
|
|
|
1702
1702
|
const APP_NAME = "Dashboard";
|
|
1703
1703
|
|
|
@@ -1706,10 +1706,10 @@ const APP_NAME = "Dashboard";
|
|
|
1706
1706
|
* @returns {string[]} ordered allowed roots for that category
|
|
1707
1707
|
*/
|
|
1708
1708
|
function getAllowedRoots$2(category) {
|
|
1709
|
-
const userData = app$
|
|
1709
|
+
const userData = app$d.getPath("userData");
|
|
1710
1710
|
switch (category) {
|
|
1711
1711
|
case "data": {
|
|
1712
|
-
const def = path$
|
|
1712
|
+
const def = path$j.join(userData, APP_NAME, "data");
|
|
1713
1713
|
// The user can configure a custom data directory in
|
|
1714
1714
|
// Settings → General → Data Directory. If set, that
|
|
1715
1715
|
// location is ALSO an allowed root. We don't replace the
|
|
@@ -1719,13 +1719,13 @@ function getAllowedRoots$2(category) {
|
|
|
1719
1719
|
return override ? [def, override] : [def];
|
|
1720
1720
|
}
|
|
1721
1721
|
case "themes":
|
|
1722
|
-
return [path$
|
|
1722
|
+
return [path$j.join(userData, APP_NAME, "themes")];
|
|
1723
1723
|
case "widgets":
|
|
1724
|
-
return [path$
|
|
1724
|
+
return [path$j.join(userData, "widgets")];
|
|
1725
1725
|
case "plugins":
|
|
1726
|
-
return [path$
|
|
1726
|
+
return [path$j.join(userData, "plugins")];
|
|
1727
1727
|
case "downloads":
|
|
1728
|
-
return [app$
|
|
1728
|
+
return [app$d.getPath("downloads")];
|
|
1729
1729
|
default:
|
|
1730
1730
|
throw new Error("safePath: unknown allowed-roots category: " + category);
|
|
1731
1731
|
}
|
|
@@ -1740,13 +1740,13 @@ function getAllowedRoots$2(category) {
|
|
|
1740
1740
|
*/
|
|
1741
1741
|
function readDataDirectoryFromSettings() {
|
|
1742
1742
|
try {
|
|
1743
|
-
const settingsPath = path$
|
|
1744
|
-
app$
|
|
1743
|
+
const settingsPath = path$j.join(
|
|
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$e.existsSync(settingsPath)) return undefined;
|
|
1749
|
+
const raw = fs$e.readFileSync(settingsPath, "utf8");
|
|
1750
1750
|
const settings = JSON.parse(raw);
|
|
1751
1751
|
const dir = settings && settings.dataDirectory;
|
|
1752
1752
|
if (typeof dir === "string" && dir) return dir;
|
|
@@ -1764,7 +1764,7 @@ function readDataDirectoryFromSettings() {
|
|
|
1764
1764
|
* @returns {string} validated absolute real-path
|
|
1765
1765
|
* @throws if requested is not contained within any allowed root
|
|
1766
1766
|
*/
|
|
1767
|
-
function safePath$
|
|
1767
|
+
function safePath$3(requested, allowedRoots) {
|
|
1768
1768
|
if (typeof requested !== "string" || !requested) {
|
|
1769
1769
|
throw new Error("safePath: requested must be a non-empty string");
|
|
1770
1770
|
}
|
|
@@ -1772,18 +1772,18 @@ function safePath$2(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$j.resolve(requested);
|
|
1776
1776
|
|
|
1777
1777
|
// Real-path through symlinks. If the file doesn't exist yet (a
|
|
1778
1778
|
// create-new operation), real-path the parent so a symlink in the
|
|
1779
1779
|
// parent chain can't trick us.
|
|
1780
1780
|
let real = resolved;
|
|
1781
1781
|
try {
|
|
1782
|
-
real = fs$
|
|
1782
|
+
real = fs$e.realpathSync(resolved);
|
|
1783
1783
|
} catch (_e) {
|
|
1784
1784
|
try {
|
|
1785
|
-
const parent = fs$
|
|
1786
|
-
real = path$
|
|
1785
|
+
const parent = fs$e.realpathSync(path$j.dirname(resolved));
|
|
1786
|
+
real = path$j.join(parent, path$j.basename(resolved));
|
|
1787
1787
|
} catch (_e2) {
|
|
1788
1788
|
// Parent doesn't exist either. Use the resolved-but-not-
|
|
1789
1789
|
// real path; the caller's mkdirSync will happen inside the
|
|
@@ -1795,14 +1795,14 @@ function safePath$2(requested, allowedRoots) {
|
|
|
1795
1795
|
for (const root of allowedRoots) {
|
|
1796
1796
|
let realRoot = root;
|
|
1797
1797
|
try {
|
|
1798
|
-
if (fs$
|
|
1798
|
+
if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
|
|
1799
1799
|
} catch (_e) {
|
|
1800
1800
|
// root doesn't exist or isn't reachable — keep as-is for
|
|
1801
1801
|
// the comparison below
|
|
1802
1802
|
}
|
|
1803
1803
|
// Exact match OR strictly-inside (with separator to prevent
|
|
1804
1804
|
// /data-evil/ matching /data/).
|
|
1805
|
-
if (real === realRoot || real.startsWith(realRoot + path$
|
|
1805
|
+
if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
|
|
1806
1806
|
return real;
|
|
1807
1807
|
}
|
|
1808
1808
|
}
|
|
@@ -1819,7 +1819,7 @@ function safePath$2(requested, allowedRoots) {
|
|
|
1819
1819
|
}
|
|
1820
1820
|
|
|
1821
1821
|
var safePath_1 = {
|
|
1822
|
-
safePath: safePath$
|
|
1822
|
+
safePath: safePath$3,
|
|
1823
1823
|
getAllowedRoots: getAllowedRoots$2,
|
|
1824
1824
|
};
|
|
1825
1825
|
|
|
@@ -2060,15 +2060,15 @@ var safeJsExecutor$1 = {
|
|
|
2060
2060
|
* - CSV -> JSON
|
|
2061
2061
|
*/
|
|
2062
2062
|
|
|
2063
|
-
var fs$
|
|
2063
|
+
var fs$d = 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$
|
|
2071
|
-
const { app: app$
|
|
2070
|
+
const path$i = require$$1$2;
|
|
2071
|
+
const { app: app$c } = require$$0$1;
|
|
2072
2072
|
const { ensureDirectoryExistence: ensureDirectoryExistence$1 } = file;
|
|
2073
2073
|
const safeJsExecutor = safeJsExecutor$1;
|
|
2074
2074
|
|
|
@@ -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$d.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$d.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$d.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$d.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$d
|
|
2277
2277
|
.createReadStream(filepath)
|
|
2278
2278
|
.pipe(csv({ separator: delimiter }));
|
|
2279
|
-
const writeStream = fs$
|
|
2279
|
+
const writeStream = fs$d.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$i.join(app$c.getPath("userData"), TRANSFORM_APP_NAME);
|
|
2366
|
+
const resolvedFilepath = path$i.resolve(filepath);
|
|
2367
|
+
const resolvedOutFilepath = path$i.resolve(outFilepath);
|
|
2368
2368
|
|
|
2369
|
-
if (!resolvedFilepath.startsWith(appDataDir + path$
|
|
2369
|
+
if (!resolvedFilepath.startsWith(appDataDir + path$i.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$i.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$d.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$d.createReadStream(resolvedFilepath).pipe(parser);
|
|
2393
2393
|
|
|
2394
2394
|
ensureDirectoryExistence$1(resolvedOutFilepath);
|
|
2395
2395
|
|
|
2396
|
-
var writeStream = fs$
|
|
2396
|
+
var writeStream = fs$d.createWriteStream(resolvedOutFilepath);
|
|
2397
2397
|
|
|
2398
2398
|
let sep = "";
|
|
2399
2399
|
let count = 0;
|
|
@@ -2505,12 +2505,12 @@ let Transform$1 = class Transform {
|
|
|
2505
2505
|
|
|
2506
2506
|
var transform = Transform$1;
|
|
2507
2507
|
|
|
2508
|
-
const { app: app$
|
|
2509
|
-
var fs$
|
|
2510
|
-
const path$
|
|
2508
|
+
const { app: app$b } = require$$0$1;
|
|
2509
|
+
var fs$c = require$$0$2;
|
|
2510
|
+
const path$h = require$$1$2;
|
|
2511
2511
|
const events$5 = events$8;
|
|
2512
2512
|
const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
|
|
2513
|
-
const { safePath: safePath$
|
|
2513
|
+
const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
|
|
2514
2514
|
|
|
2515
2515
|
// Convert Json to Csv
|
|
2516
2516
|
const ObjectsToCsv = require$$6$1;
|
|
@@ -2532,8 +2532,8 @@ 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$
|
|
2536
|
-
app$
|
|
2535
|
+
const candidate = path$h.join(
|
|
2536
|
+
app$b.getPath("userData"),
|
|
2537
2537
|
appName$5,
|
|
2538
2538
|
appId,
|
|
2539
2539
|
"data",
|
|
@@ -2541,7 +2541,7 @@ const dataController$1 = {
|
|
|
2541
2541
|
);
|
|
2542
2542
|
let filename;
|
|
2543
2543
|
try {
|
|
2544
|
-
filename = safePath$
|
|
2544
|
+
filename = safePath$2(candidate, getAllowedRoots$1("data"));
|
|
2545
2545
|
} catch (pathErr) {
|
|
2546
2546
|
win.webContents.send(events$5.DATA_JSON_TO_CSV_FILE_ERROR, {
|
|
2547
2547
|
error: pathErr.message,
|
|
@@ -2607,7 +2607,7 @@ const dataController$1 = {
|
|
|
2607
2607
|
try {
|
|
2608
2608
|
let validated;
|
|
2609
2609
|
try {
|
|
2610
|
-
validated = safePath$
|
|
2610
|
+
validated = safePath$2(filepath, getAllowedRoots$1("data"));
|
|
2611
2611
|
} catch (pathErr) {
|
|
2612
2612
|
win.webContents.send(events$5.READ_LINES_ERROR, {
|
|
2613
2613
|
error: pathErr.message,
|
|
@@ -2641,7 +2641,7 @@ const dataController$1 = {
|
|
|
2641
2641
|
try {
|
|
2642
2642
|
let validated;
|
|
2643
2643
|
try {
|
|
2644
|
-
validated = safePath$
|
|
2644
|
+
validated = safePath$2(filepath, getAllowedRoots$1("data"));
|
|
2645
2645
|
} catch (pathErr) {
|
|
2646
2646
|
win.webContents.send(events$5.READ_JSON_ERROR, {
|
|
2647
2647
|
error: pathErr.message,
|
|
@@ -2689,9 +2689,9 @@ const dataController$1 = {
|
|
|
2689
2689
|
// Validate toFilepath is within the app data directory.
|
|
2690
2690
|
// safePath replaces the previous inline check; same containment
|
|
2691
2691
|
// intent, plus realpath/symlink protection.
|
|
2692
|
-
const resolvedFilepath = safePath$
|
|
2692
|
+
const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
|
|
2693
2693
|
|
|
2694
|
-
const writeStream = fs$
|
|
2694
|
+
const writeStream = fs$c.createWriteStream(resolvedFilepath);
|
|
2695
2695
|
|
|
2696
2696
|
https$3
|
|
2697
2697
|
.get(url, (resp) => {
|
|
@@ -2739,8 +2739,8 @@ const dataController$1 = {
|
|
|
2739
2739
|
let validatedIn, validatedOut;
|
|
2740
2740
|
try {
|
|
2741
2741
|
const roots = getAllowedRoots$1("data");
|
|
2742
|
-
validatedIn = safePath$
|
|
2743
|
-
validatedOut = safePath$
|
|
2742
|
+
validatedIn = safePath$2(filepath, roots);
|
|
2743
|
+
validatedOut = safePath$2(outpath, roots);
|
|
2744
2744
|
} catch (pathErr) {
|
|
2745
2745
|
win.webContents.send(events$5.PARSE_XML_STREAM_ERROR, {
|
|
2746
2746
|
error: pathErr.message,
|
|
@@ -2799,8 +2799,8 @@ const dataController$1 = {
|
|
|
2799
2799
|
let validatedIn, validatedOut;
|
|
2800
2800
|
try {
|
|
2801
2801
|
const roots = getAllowedRoots$1("data");
|
|
2802
|
-
validatedIn = safePath$
|
|
2803
|
-
validatedOut = safePath$
|
|
2802
|
+
validatedIn = safePath$2(filepath, roots);
|
|
2803
|
+
validatedOut = safePath$2(outpath, roots);
|
|
2804
2804
|
} catch (pathErr) {
|
|
2805
2805
|
win.webContents.send(events$5.PARSE_CSV_STREAM_ERROR, {
|
|
2806
2806
|
error: pathErr.message,
|
|
@@ -2855,15 +2855,15 @@ 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$
|
|
2859
|
-
app$
|
|
2858
|
+
const candidate = path$h.join(
|
|
2859
|
+
app$b.getPath("userData"),
|
|
2860
2860
|
appName$5,
|
|
2861
2861
|
"data",
|
|
2862
2862
|
filename,
|
|
2863
2863
|
);
|
|
2864
2864
|
let toFilename;
|
|
2865
2865
|
try {
|
|
2866
|
-
toFilename = safePath$
|
|
2866
|
+
toFilename = safePath$2(candidate, getAllowedRoots$1("data"));
|
|
2867
2867
|
} catch (pathErr) {
|
|
2868
2868
|
win.webContents.send(events$5.DATA_SAVE_TO_FILE_ERROR, {
|
|
2869
2869
|
success: false,
|
|
@@ -2948,8 +2948,8 @@ const dataController$1 = {
|
|
|
2948
2948
|
try {
|
|
2949
2949
|
if (filename) {
|
|
2950
2950
|
// filename to the pages file (live pages)
|
|
2951
|
-
const fromFilename = path$
|
|
2952
|
-
app$
|
|
2951
|
+
const fromFilename = path$h.join(
|
|
2952
|
+
app$b.getPath("userData"),
|
|
2953
2953
|
appName$5,
|
|
2954
2954
|
"data",
|
|
2955
2955
|
filename,
|
|
@@ -3029,9 +3029,9 @@ var dataController_1 = dataController$1;
|
|
|
3029
3029
|
* settingsController
|
|
3030
3030
|
*/
|
|
3031
3031
|
|
|
3032
|
-
const { app: app$
|
|
3033
|
-
const path$
|
|
3034
|
-
const fs$
|
|
3032
|
+
const { app: app$a } = require$$0$1;
|
|
3033
|
+
const path$g = require$$1$2;
|
|
3034
|
+
const fs$b = 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$b.existsSync(destination)) {
|
|
3043
|
+
fs$b.mkdirSync(destination, { recursive: true });
|
|
3044
3044
|
}
|
|
3045
3045
|
|
|
3046
|
-
const files = fs$
|
|
3046
|
+
const files = fs$b.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$g.join(source, file);
|
|
3049
|
+
const destPath = path$g.join(destination, file);
|
|
3050
|
+
const stat = fs$b.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$b.copyFileSync(srcPath, destPath);
|
|
3062
3062
|
}
|
|
3063
3063
|
}
|
|
3064
3064
|
}
|
|
@@ -3074,8 +3074,8 @@ const settingsController$4 = {
|
|
|
3074
3074
|
try {
|
|
3075
3075
|
if (data) {
|
|
3076
3076
|
// <appId>/settings.json
|
|
3077
|
-
const filename = path$
|
|
3078
|
-
app$
|
|
3077
|
+
const filename = path$g.join(
|
|
3078
|
+
app$a.getPath("userData"),
|
|
3079
3079
|
appName$4,
|
|
3080
3080
|
configFilename$3,
|
|
3081
3081
|
);
|
|
@@ -3110,8 +3110,8 @@ const settingsController$4 = {
|
|
|
3110
3110
|
getSettingsForApplication: (win) => {
|
|
3111
3111
|
try {
|
|
3112
3112
|
// <appId>/settings.json
|
|
3113
|
-
const filename = path$
|
|
3114
|
-
app$
|
|
3113
|
+
const filename = path$g.join(
|
|
3114
|
+
app$a.getPath("userData"),
|
|
3115
3115
|
appName$4,
|
|
3116
3116
|
configFilename$3,
|
|
3117
3117
|
);
|
|
@@ -3141,15 +3141,15 @@ const settingsController$4 = {
|
|
|
3141
3141
|
*/
|
|
3142
3142
|
getDataDirectory: (win) => {
|
|
3143
3143
|
try {
|
|
3144
|
-
const settingsPath = path$
|
|
3145
|
-
app$
|
|
3144
|
+
const settingsPath = path$g.join(
|
|
3145
|
+
app$a.getPath("userData"),
|
|
3146
3146
|
appName$4,
|
|
3147
3147
|
configFilename$3,
|
|
3148
3148
|
);
|
|
3149
3149
|
const settings = getFileContents$4(settingsPath, {});
|
|
3150
3150
|
const userDataDir =
|
|
3151
3151
|
settings.userDataDirectory ||
|
|
3152
|
-
path$
|
|
3152
|
+
path$g.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,18 +3176,18 @@ 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$b.existsSync(newPath)) {
|
|
3180
|
+
fs$b.mkdirSync(newPath, { recursive: true });
|
|
3181
3181
|
}
|
|
3182
3182
|
|
|
3183
|
-
const stats = fs$
|
|
3183
|
+
const stats = fs$b.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$
|
|
3190
|
-
app$
|
|
3189
|
+
const settingsPath = path$g.join(
|
|
3190
|
+
app$a.getPath("userData"),
|
|
3191
3191
|
appName$4,
|
|
3192
3192
|
configFilename$3,
|
|
3193
3193
|
);
|
|
@@ -3220,20 +3220,20 @@ 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$g.resolve(oldPath);
|
|
3224
|
+
const resolvedNewPath = path$g.resolve(newPath);
|
|
3225
3225
|
|
|
3226
3226
|
// Validate oldPath is the current configured data directory
|
|
3227
|
-
const settingsCheckPath = path$
|
|
3228
|
-
app$
|
|
3227
|
+
const settingsCheckPath = path$g.join(
|
|
3228
|
+
app$a.getPath("userData"),
|
|
3229
3229
|
appName$4,
|
|
3230
3230
|
configFilename$3,
|
|
3231
3231
|
);
|
|
3232
3232
|
const currentSettings = getFileContents$4(settingsCheckPath, {});
|
|
3233
3233
|
const currentDataDir =
|
|
3234
3234
|
currentSettings.userDataDirectory ||
|
|
3235
|
-
path$
|
|
3236
|
-
if (resolvedOldPath !== path$
|
|
3235
|
+
path$g.join(app$a.getPath("userData"), appName$4);
|
|
3236
|
+
if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
|
|
3237
3237
|
throw new Error("Source path must be the current data directory");
|
|
3238
3238
|
}
|
|
3239
3239
|
|
|
@@ -3257,20 +3257,20 @@ const settingsController$4 = {
|
|
|
3257
3257
|
}
|
|
3258
3258
|
|
|
3259
3259
|
// Validate paths
|
|
3260
|
-
if (!fs$
|
|
3260
|
+
if (!fs$b.existsSync(resolvedOldPath)) {
|
|
3261
3261
|
throw new Error("Source directory does not exist");
|
|
3262
3262
|
}
|
|
3263
3263
|
|
|
3264
|
-
if (!fs$
|
|
3265
|
-
fs$
|
|
3264
|
+
if (!fs$b.existsSync(resolvedNewPath)) {
|
|
3265
|
+
fs$b.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$
|
|
3273
|
-
app$
|
|
3272
|
+
const settingsPath = path$g.join(
|
|
3273
|
+
app$a.getPath("userData"),
|
|
3274
3274
|
appName$4,
|
|
3275
3275
|
configFilename$3,
|
|
3276
3276
|
);
|
|
@@ -3934,8 +3934,8 @@ function requireProviderController () {
|
|
|
3934
3934
|
return providerController_1;
|
|
3935
3935
|
}
|
|
3936
3936
|
|
|
3937
|
-
const { app: app$
|
|
3938
|
-
const path$
|
|
3937
|
+
const { app: app$9 } = require$$0$1;
|
|
3938
|
+
const path$f = 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,8 +3955,8 @@ 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$
|
|
3959
|
-
app$
|
|
3958
|
+
const filename = path$f.join(
|
|
3959
|
+
app$9.getPath("userData"),
|
|
3960
3960
|
appName$3,
|
|
3961
3961
|
appId,
|
|
3962
3962
|
configFilename$2,
|
|
@@ -3988,8 +3988,8 @@ const layoutController$1 = {
|
|
|
3988
3988
|
*/
|
|
3989
3989
|
listLayoutsForApplication: (win, appId) => {
|
|
3990
3990
|
try {
|
|
3991
|
-
const filename = path$
|
|
3992
|
-
app$
|
|
3991
|
+
const filename = path$f.join(
|
|
3992
|
+
app$9.getPath("userData"),
|
|
3993
3993
|
appName$3,
|
|
3994
3994
|
appId,
|
|
3995
3995
|
configFilename$2,
|
|
@@ -21152,6 +21152,376 @@ let StreamableHTTPClientTransport$1 = class StreamableHTTPClientTransport {
|
|
|
21152
21152
|
};
|
|
21153
21153
|
streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1;
|
|
21154
21154
|
|
|
21155
|
+
/**
|
|
21156
|
+
* grantedPermissions.js
|
|
21157
|
+
*
|
|
21158
|
+
* Stores the user's actual MCP permission grants per widget. This is the
|
|
21159
|
+
* Slice-2 enforcement source of truth — separate from the widget's declared
|
|
21160
|
+
* `dash.permissions.mcp` block (which is just a request).
|
|
21161
|
+
*
|
|
21162
|
+
* The runtime gate (permissionGate.gateToolCall) reads from here only.
|
|
21163
|
+
* A widget with a declared manifest but no grant entry has no access:
|
|
21164
|
+
* fail-closed. The user grants permissions at install time (consent modal)
|
|
21165
|
+
* or later in Settings → Privacy & Security.
|
|
21166
|
+
*
|
|
21167
|
+
* Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
|
|
21168
|
+
*
|
|
21169
|
+
* Shape on disk:
|
|
21170
|
+
* {
|
|
21171
|
+
* "@trops/notes-summarizer": {
|
|
21172
|
+
* "servers": {
|
|
21173
|
+
* "filesystem": {
|
|
21174
|
+
* "tools": ["read_file"],
|
|
21175
|
+
* "readPaths": ["/Users/jane/Documents/notes"],
|
|
21176
|
+
* "writePaths": []
|
|
21177
|
+
* }
|
|
21178
|
+
* }
|
|
21179
|
+
* }
|
|
21180
|
+
* }
|
|
21181
|
+
*
|
|
21182
|
+
* Note: paths are stored as-is (already tilde-expanded by the manifest
|
|
21183
|
+
* parser before grants are written). Tests can re-expand via
|
|
21184
|
+
* widgetPermissions.expandHome if they store ~ literals.
|
|
21185
|
+
*
|
|
21186
|
+
* Public API:
|
|
21187
|
+
* getGrant(widgetId) → grant | null
|
|
21188
|
+
* setGrant(widgetId, perms) → boolean
|
|
21189
|
+
* revokeGrant(widgetId) → boolean
|
|
21190
|
+
* revokeServer(widgetId, serverName) → boolean
|
|
21191
|
+
* listAllGrants() → [{ widgetId, granted }]
|
|
21192
|
+
* clearCache() → void // test-only
|
|
21193
|
+
*/
|
|
21194
|
+
|
|
21195
|
+
const fs$a = require$$0$2;
|
|
21196
|
+
const path$e = require$$1$2;
|
|
21197
|
+
const { app: app$8 } = require$$0$1;
|
|
21198
|
+
|
|
21199
|
+
const FILE_NAME = "widgetMcpGrants.json";
|
|
21200
|
+
|
|
21201
|
+
// In-process cache of the entire grants file. Lazily loaded; invalidated
|
|
21202
|
+
// on every write.
|
|
21203
|
+
let _cache$1 = null;
|
|
21204
|
+
|
|
21205
|
+
function grantsFilePath() {
|
|
21206
|
+
return path$e.join(app$8.getPath("userData"), FILE_NAME);
|
|
21207
|
+
}
|
|
21208
|
+
|
|
21209
|
+
function loadFromDisk() {
|
|
21210
|
+
const p = grantsFilePath();
|
|
21211
|
+
if (!fs$a.existsSync(p)) return {};
|
|
21212
|
+
try {
|
|
21213
|
+
const raw = fs$a.readFileSync(p, "utf8");
|
|
21214
|
+
const parsed = JSON.parse(raw);
|
|
21215
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
21216
|
+
return parsed;
|
|
21217
|
+
} catch (e) {
|
|
21218
|
+
console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
|
|
21219
|
+
return {};
|
|
21220
|
+
}
|
|
21221
|
+
}
|
|
21222
|
+
|
|
21223
|
+
function ensureCache() {
|
|
21224
|
+
if (_cache$1 === null) _cache$1 = loadFromDisk();
|
|
21225
|
+
return _cache$1;
|
|
21226
|
+
}
|
|
21227
|
+
|
|
21228
|
+
function writeToDisk(data) {
|
|
21229
|
+
const p = grantsFilePath();
|
|
21230
|
+
const tmp = p + ".tmp";
|
|
21231
|
+
// Ensure parent dir exists (userData should already, but be defensive
|
|
21232
|
+
// for first-launch / freshly-cleared profile cases).
|
|
21233
|
+
fs$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);
|
|
21236
|
+
}
|
|
21237
|
+
|
|
21238
|
+
/**
|
|
21239
|
+
* Sanitize a perms object before persisting. Drops unknown keys, coerces
|
|
21240
|
+
* arrays of strings, and silently ignores malformed servers. Mirrors the
|
|
21241
|
+
* shape produced by parseManifestPermissions so the gate reads either
|
|
21242
|
+
* declared or granted with the same code path.
|
|
21243
|
+
*/
|
|
21244
|
+
function sanitizePerms(perms) {
|
|
21245
|
+
if (!perms || typeof perms !== "object") return null;
|
|
21246
|
+
const rawServers =
|
|
21247
|
+
perms.servers && typeof perms.servers === "object" ? perms.servers : null;
|
|
21248
|
+
if (!rawServers) return null;
|
|
21249
|
+
|
|
21250
|
+
const servers = {};
|
|
21251
|
+
for (const [name, raw] of Object.entries(rawServers)) {
|
|
21252
|
+
if (!raw || typeof raw !== "object") continue;
|
|
21253
|
+
servers[name] = {
|
|
21254
|
+
tools: Array.isArray(raw.tools)
|
|
21255
|
+
? raw.tools.filter((t) => typeof t === "string")
|
|
21256
|
+
: [],
|
|
21257
|
+
readPaths: Array.isArray(raw.readPaths)
|
|
21258
|
+
? raw.readPaths.filter((p) => typeof p === "string")
|
|
21259
|
+
: [],
|
|
21260
|
+
writePaths: Array.isArray(raw.writePaths)
|
|
21261
|
+
? raw.writePaths.filter((p) => typeof p === "string")
|
|
21262
|
+
: [],
|
|
21263
|
+
};
|
|
21264
|
+
}
|
|
21265
|
+
return { servers };
|
|
21266
|
+
}
|
|
21267
|
+
|
|
21268
|
+
function getGrant$2(widgetId) {
|
|
21269
|
+
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
21270
|
+
const all = ensureCache();
|
|
21271
|
+
return all[widgetId] || null;
|
|
21272
|
+
}
|
|
21273
|
+
|
|
21274
|
+
function setGrant$1(widgetId, perms) {
|
|
21275
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21276
|
+
const sanitized = sanitizePerms(perms);
|
|
21277
|
+
if (!sanitized) return false;
|
|
21278
|
+
const all = ensureCache();
|
|
21279
|
+
all[widgetId] = sanitized;
|
|
21280
|
+
try {
|
|
21281
|
+
writeToDisk(all);
|
|
21282
|
+
return true;
|
|
21283
|
+
} catch (e) {
|
|
21284
|
+
console.warn(
|
|
21285
|
+
"[grantedPermissions] failed to write grant for " +
|
|
21286
|
+
widgetId +
|
|
21287
|
+
": " +
|
|
21288
|
+
e.message,
|
|
21289
|
+
);
|
|
21290
|
+
// Roll back the cache entry so memory matches disk.
|
|
21291
|
+
_cache$1 = loadFromDisk();
|
|
21292
|
+
return false;
|
|
21293
|
+
}
|
|
21294
|
+
}
|
|
21295
|
+
|
|
21296
|
+
function revokeGrant$1(widgetId) {
|
|
21297
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21298
|
+
const all = ensureCache();
|
|
21299
|
+
if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
|
|
21300
|
+
delete all[widgetId];
|
|
21301
|
+
try {
|
|
21302
|
+
writeToDisk(all);
|
|
21303
|
+
return true;
|
|
21304
|
+
} catch (e) {
|
|
21305
|
+
console.warn(
|
|
21306
|
+
"[grantedPermissions] failed to revoke grant for " +
|
|
21307
|
+
widgetId +
|
|
21308
|
+
": " +
|
|
21309
|
+
e.message,
|
|
21310
|
+
);
|
|
21311
|
+
_cache$1 = loadFromDisk();
|
|
21312
|
+
return false;
|
|
21313
|
+
}
|
|
21314
|
+
}
|
|
21315
|
+
|
|
21316
|
+
function revokeServer$1(widgetId, serverName) {
|
|
21317
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21318
|
+
if (typeof serverName !== "string" || !serverName) return false;
|
|
21319
|
+
const all = ensureCache();
|
|
21320
|
+
const widgetEntry = all[widgetId];
|
|
21321
|
+
if (!widgetEntry || !widgetEntry.servers) return false;
|
|
21322
|
+
if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
|
|
21323
|
+
return false;
|
|
21324
|
+
delete widgetEntry.servers[serverName];
|
|
21325
|
+
try {
|
|
21326
|
+
writeToDisk(all);
|
|
21327
|
+
return true;
|
|
21328
|
+
} catch (e) {
|
|
21329
|
+
console.warn(
|
|
21330
|
+
"[grantedPermissions] failed to revoke server " +
|
|
21331
|
+
serverName +
|
|
21332
|
+
" for " +
|
|
21333
|
+
widgetId +
|
|
21334
|
+
": " +
|
|
21335
|
+
e.message,
|
|
21336
|
+
);
|
|
21337
|
+
_cache$1 = loadFromDisk();
|
|
21338
|
+
return false;
|
|
21339
|
+
}
|
|
21340
|
+
}
|
|
21341
|
+
|
|
21342
|
+
function listAllGrants$1() {
|
|
21343
|
+
const all = ensureCache();
|
|
21344
|
+
return Object.entries(all).map(([widgetId, granted]) => ({
|
|
21345
|
+
widgetId,
|
|
21346
|
+
granted,
|
|
21347
|
+
}));
|
|
21348
|
+
}
|
|
21349
|
+
|
|
21350
|
+
function clearCache$1() {
|
|
21351
|
+
_cache$1 = null;
|
|
21352
|
+
}
|
|
21353
|
+
|
|
21354
|
+
var grantedPermissions = {
|
|
21355
|
+
getGrant: getGrant$2,
|
|
21356
|
+
setGrant: setGrant$1,
|
|
21357
|
+
revokeGrant: revokeGrant$1,
|
|
21358
|
+
revokeServer: revokeServer$1,
|
|
21359
|
+
listAllGrants: listAllGrants$1,
|
|
21360
|
+
clearCache: clearCache$1,
|
|
21361
|
+
};
|
|
21362
|
+
|
|
21363
|
+
/**
|
|
21364
|
+
* permissionGate.js
|
|
21365
|
+
*
|
|
21366
|
+
* Per-widget gating for MCP tool calls.
|
|
21367
|
+
*
|
|
21368
|
+
* When `gateToolCall` is invoked with a widget identity, server name,
|
|
21369
|
+
* tool name, and tool arguments, it consults the widget's GRANTED
|
|
21370
|
+
* permissions (electron/mcp/grantedPermissions.js) and either permits
|
|
21371
|
+
* the call or returns a clear denial reason.
|
|
21372
|
+
*
|
|
21373
|
+
* **Granted vs declared (Slice 2):** the widget's package.json
|
|
21374
|
+
* `dash.permissions.mcp` block is the *request* — read by
|
|
21375
|
+
* widgetPermissions.js and shown to the user at install time. The
|
|
21376
|
+
* *grant* is what the user actually approved. The runtime gate reads
|
|
21377
|
+
* grants only. A widget with a declared manifest but no grant entry
|
|
21378
|
+
* has no MCP access — fail-closed. The user grants permissions via
|
|
21379
|
+
* the install consent modal or Settings → Privacy & Security.
|
|
21380
|
+
*
|
|
21381
|
+
* Two layers:
|
|
21382
|
+
*
|
|
21383
|
+
* 1. **Tool-name allowlist** — the granted `tools[]` array for the
|
|
21384
|
+
* target server determines which tool names this widget may
|
|
21385
|
+
* invoke. Anything outside the list is rejected.
|
|
21386
|
+
*
|
|
21387
|
+
* 2. **Path-argument containment** — for tools whose arguments
|
|
21388
|
+
* include a path-shaped key (`path`, `uri`, `filepath`, `file`,
|
|
21389
|
+
* `directory`), the supplied path is validated with safePath()
|
|
21390
|
+
* against the widget's granted `readPaths` or `writePaths` for
|
|
21391
|
+
* the target server. The read/write distinction is heuristic
|
|
21392
|
+
* based on the tool name (e.g. `write_file` is treated as a
|
|
21393
|
+
* write).
|
|
21394
|
+
*
|
|
21395
|
+
* This is the runtime enforcement layer. Install-time consent UI is
|
|
21396
|
+
* Slice 2. Per-dashboard MCP-server scope reconfiguration is Slice 3.
|
|
21397
|
+
* When the feature flag is OFF (default), this gate is bypassed
|
|
21398
|
+
* entirely; mcpController behaves as before. When ON, every callTool
|
|
21399
|
+
* dispatch goes through this gate.
|
|
21400
|
+
*/
|
|
21401
|
+
|
|
21402
|
+
const { getGrant: getGrant$1 } = grantedPermissions;
|
|
21403
|
+
const { safePath: safePath$1 } = safePath_1;
|
|
21404
|
+
|
|
21405
|
+
// Argument keys that look like paths. Different MCP servers use
|
|
21406
|
+
// different conventions; this list covers the common filesystem-style
|
|
21407
|
+
// servers. Extensible — add as new patterns surface.
|
|
21408
|
+
const PATH_ARG_KEYS = ["path", "uri", "filepath", "file", "directory"];
|
|
21409
|
+
|
|
21410
|
+
// Heuristic: tool names matching this regex are treated as writes for
|
|
21411
|
+
// purposes of choosing readPaths vs writePaths. The match is intentionally
|
|
21412
|
+
// broad — we'd rather treat an ambiguous tool as a write (stricter) than
|
|
21413
|
+
// as a read.
|
|
21414
|
+
const WRITE_TOOL_PATTERN =
|
|
21415
|
+
/(^|_)(write|create|edit|delete|remove|append|move|rename|chmod|chown|mkdir)/i;
|
|
21416
|
+
|
|
21417
|
+
function isWriteTool(toolName) {
|
|
21418
|
+
if (typeof toolName !== "string") return false;
|
|
21419
|
+
return WRITE_TOOL_PATTERN.test(toolName);
|
|
21420
|
+
}
|
|
21421
|
+
|
|
21422
|
+
/**
|
|
21423
|
+
* @returns {{ allow: true } | { allow: false, reason: string }}
|
|
21424
|
+
*/
|
|
21425
|
+
function gateToolCall$1({ widgetId, serverName, toolName, args }) {
|
|
21426
|
+
if (!widgetId) {
|
|
21427
|
+
return {
|
|
21428
|
+
allow: false,
|
|
21429
|
+
reason: "no widgetId supplied; cannot determine permissions",
|
|
21430
|
+
};
|
|
21431
|
+
}
|
|
21432
|
+
|
|
21433
|
+
const perms = getGrant$1(widgetId);
|
|
21434
|
+
if (!perms) {
|
|
21435
|
+
return {
|
|
21436
|
+
allow: false,
|
|
21437
|
+
reason:
|
|
21438
|
+
"widget '" +
|
|
21439
|
+
widgetId +
|
|
21440
|
+
"' has no MCP permissions granted; user must approve at install time or in Settings → Privacy & Security",
|
|
21441
|
+
};
|
|
21442
|
+
}
|
|
21443
|
+
|
|
21444
|
+
const serverPerms = perms.servers && perms.servers[serverName];
|
|
21445
|
+
if (!serverPerms) {
|
|
21446
|
+
return {
|
|
21447
|
+
allow: false,
|
|
21448
|
+
reason:
|
|
21449
|
+
"widget '" +
|
|
21450
|
+
widgetId +
|
|
21451
|
+
"' is not authorized to call '" +
|
|
21452
|
+
serverName +
|
|
21453
|
+
"'",
|
|
21454
|
+
};
|
|
21455
|
+
}
|
|
21456
|
+
|
|
21457
|
+
if (!serverPerms.tools.includes(toolName)) {
|
|
21458
|
+
return {
|
|
21459
|
+
allow: false,
|
|
21460
|
+
reason:
|
|
21461
|
+
"tool '" +
|
|
21462
|
+
toolName +
|
|
21463
|
+
"' is not in the allowlist for widget '" +
|
|
21464
|
+
widgetId +
|
|
21465
|
+
"' on server '" +
|
|
21466
|
+
serverName +
|
|
21467
|
+
"'",
|
|
21468
|
+
};
|
|
21469
|
+
}
|
|
21470
|
+
|
|
21471
|
+
// Path-argument containment. Only checked when the tool's args
|
|
21472
|
+
// include a path-shaped key.
|
|
21473
|
+
const isWrite = isWriteTool(toolName);
|
|
21474
|
+
// Write tools must use writePaths; read tools may use either
|
|
21475
|
+
// readPaths or writePaths (write access implies read access).
|
|
21476
|
+
const allowedPaths = isWrite
|
|
21477
|
+
? serverPerms.writePaths
|
|
21478
|
+
: [...serverPerms.readPaths, ...serverPerms.writePaths];
|
|
21479
|
+
|
|
21480
|
+
if (args && typeof args === "object") {
|
|
21481
|
+
for (const key of PATH_ARG_KEYS) {
|
|
21482
|
+
const v = args[key];
|
|
21483
|
+
if (typeof v !== "string" || !v) continue;
|
|
21484
|
+
if (allowedPaths.length === 0) {
|
|
21485
|
+
return {
|
|
21486
|
+
allow: false,
|
|
21487
|
+
reason:
|
|
21488
|
+
"tool '" +
|
|
21489
|
+
toolName +
|
|
21490
|
+
"' uses path argument '" +
|
|
21491
|
+
key +
|
|
21492
|
+
"' but widget '" +
|
|
21493
|
+
widgetId +
|
|
21494
|
+
"' has no " +
|
|
21495
|
+
(isWrite ? "writePaths" : "readPaths or writePaths") +
|
|
21496
|
+
" declared for server '" +
|
|
21497
|
+
serverName +
|
|
21498
|
+
"'",
|
|
21499
|
+
};
|
|
21500
|
+
}
|
|
21501
|
+
try {
|
|
21502
|
+
safePath$1(v, allowedPaths);
|
|
21503
|
+
} catch (e) {
|
|
21504
|
+
return {
|
|
21505
|
+
allow: false,
|
|
21506
|
+
reason:
|
|
21507
|
+
"path argument '" +
|
|
21508
|
+
key +
|
|
21509
|
+
"' rejected: " +
|
|
21510
|
+
(e && e.message ? e.message : String(e)),
|
|
21511
|
+
};
|
|
21512
|
+
}
|
|
21513
|
+
}
|
|
21514
|
+
}
|
|
21515
|
+
|
|
21516
|
+
return { allow: true };
|
|
21517
|
+
}
|
|
21518
|
+
|
|
21519
|
+
var permissionGate = {
|
|
21520
|
+
gateToolCall: gateToolCall$1,
|
|
21521
|
+
isWriteTool,
|
|
21522
|
+
PATH_ARG_KEYS,
|
|
21523
|
+
};
|
|
21524
|
+
|
|
21155
21525
|
/**
|
|
21156
21526
|
* mcpController.js
|
|
21157
21527
|
*
|
|
@@ -21172,10 +21542,32 @@ const {
|
|
|
21172
21542
|
const {
|
|
21173
21543
|
StreamableHTTPClientTransport,
|
|
21174
21544
|
} = streamableHttp$1;
|
|
21175
|
-
const path$
|
|
21176
|
-
const fs$
|
|
21177
|
-
const os$
|
|
21545
|
+
const path$d = require$$1$2;
|
|
21546
|
+
const fs$9 = require$$0$2;
|
|
21547
|
+
const os$2 = require$$2$1;
|
|
21178
21548
|
const responseCache$2 = responseCache_1;
|
|
21549
|
+
const { gateToolCall } = permissionGate;
|
|
21550
|
+
const { app: app$7 } = require$$0$1;
|
|
21551
|
+
|
|
21552
|
+
// Read the widget-MCP-enforcement feature flag from settings.json.
|
|
21553
|
+
// Default is OFF — flipping ON activates per-widget gating in
|
|
21554
|
+
// permissionGate.gateToolCall(). See docs/security/ipc-filesystem-audit.md
|
|
21555
|
+
// and electron/mcp/permissionGate.js for context.
|
|
21556
|
+
function isWidgetPermissionEnforcementEnabled() {
|
|
21557
|
+
try {
|
|
21558
|
+
const settingsPath = path$d.join(
|
|
21559
|
+
app$7.getPath("userData"),
|
|
21560
|
+
"Dashboard",
|
|
21561
|
+
"settings.json",
|
|
21562
|
+
);
|
|
21563
|
+
if (!fs$9.existsSync(settingsPath)) return false;
|
|
21564
|
+
const raw = fs$9.readFileSync(settingsPath, "utf8");
|
|
21565
|
+
const settings = JSON.parse(raw);
|
|
21566
|
+
return Boolean(settings?.security?.enforceWidgetMcpPermissions);
|
|
21567
|
+
} catch (_e) {
|
|
21568
|
+
return false;
|
|
21569
|
+
}
|
|
21570
|
+
}
|
|
21179
21571
|
|
|
21180
21572
|
/**
|
|
21181
21573
|
* Tool name prefixes considered safe to cache (read-only).
|
|
@@ -21311,18 +21703,18 @@ function getShellPath$1() {
|
|
|
21311
21703
|
return _shellPath$1;
|
|
21312
21704
|
}
|
|
21313
21705
|
|
|
21314
|
-
const { execSync } = require$$
|
|
21706
|
+
const { execSync } = require$$9$1;
|
|
21315
21707
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
21316
21708
|
|
|
21317
21709
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
21318
|
-
const home = os$
|
|
21710
|
+
const home = os$2.homedir();
|
|
21319
21711
|
let compatibleNvmBin = null;
|
|
21320
21712
|
if (home) {
|
|
21321
21713
|
fallbackDirs.push(`${home}/.volta/bin`);
|
|
21322
21714
|
fallbackDirs.push(`${home}/.nodenv/shims`);
|
|
21323
21715
|
try {
|
|
21324
21716
|
const nvmDir = `${home}/.nvm/versions/node`;
|
|
21325
|
-
const versions = fs$
|
|
21717
|
+
const versions = fs$9.readdirSync(nvmDir).sort();
|
|
21326
21718
|
if (versions.length > 0) {
|
|
21327
21719
|
// Find the highest compatible version (v18/v20/v22)
|
|
21328
21720
|
for (let i = versions.length - 1; i >= 0; i--) {
|
|
@@ -21454,19 +21846,19 @@ function interpolate$1(template, credentials) {
|
|
|
21454
21846
|
* @param {object} tokenRefresh { credentialsPath, oauthKeysPath }
|
|
21455
21847
|
*/
|
|
21456
21848
|
async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
21457
|
-
const home = os$
|
|
21849
|
+
const home = os$2.homedir();
|
|
21458
21850
|
const credPath = tokenRefresh.credentialsPath.replace(/^~/, home);
|
|
21459
21851
|
const keysPath = tokenRefresh.oauthKeysPath.replace(/^~/, home);
|
|
21460
21852
|
|
|
21461
|
-
if (!fs$
|
|
21853
|
+
if (!fs$9.existsSync(credPath) || !fs$9.existsSync(keysPath)) {
|
|
21462
21854
|
console.log(
|
|
21463
21855
|
"[mcpController] Token refresh skipped: credential files not found",
|
|
21464
21856
|
);
|
|
21465
21857
|
return;
|
|
21466
21858
|
}
|
|
21467
21859
|
|
|
21468
|
-
const credentials = JSON.parse(fs$
|
|
21469
|
-
const keysFile = JSON.parse(fs$
|
|
21860
|
+
const credentials = JSON.parse(fs$9.readFileSync(credPath, "utf8"));
|
|
21861
|
+
const keysFile = JSON.parse(fs$9.readFileSync(keysPath, "utf8"));
|
|
21470
21862
|
const keyData = keysFile.installed || keysFile.web;
|
|
21471
21863
|
|
|
21472
21864
|
if (
|
|
@@ -21535,7 +21927,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
|
21535
21927
|
credentials.refresh_token = body.refresh_token;
|
|
21536
21928
|
}
|
|
21537
21929
|
|
|
21538
|
-
fs$
|
|
21930
|
+
fs$9.writeFileSync(credPath, JSON.stringify(credentials, null, 2));
|
|
21539
21931
|
console.log("[mcpController] Google OAuth token refreshed successfully");
|
|
21540
21932
|
}
|
|
21541
21933
|
|
|
@@ -21650,7 +22042,7 @@ const mcpController$3 = {
|
|
|
21650
22042
|
// Merge static env vars from mcpConfig (with ~ expansion)
|
|
21651
22043
|
if (mcpConfig.staticEnv) {
|
|
21652
22044
|
Object.entries(mcpConfig.staticEnv).forEach(([envVar, value]) => {
|
|
21653
|
-
env[envVar] = value.replace(/^~/, os$
|
|
22045
|
+
env[envVar] = value.replace(/^~/, os$2.homedir());
|
|
21654
22046
|
});
|
|
21655
22047
|
}
|
|
21656
22048
|
|
|
@@ -21689,7 +22081,7 @@ const mcpController$3 = {
|
|
|
21689
22081
|
}
|
|
21690
22082
|
|
|
21691
22083
|
// Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
|
|
21692
|
-
const mcpDir = path$
|
|
22084
|
+
const mcpDir = path$d.join(__dirname, "..", "mcp");
|
|
21693
22085
|
for (let i = 0; i < args.length; i++) {
|
|
21694
22086
|
if (
|
|
21695
22087
|
typeof args[i] === "string" &&
|
|
@@ -21880,14 +22272,41 @@ const mcpController$3 = {
|
|
|
21880
22272
|
* @param {Array<string>} allowedTools optional whitelist of allowed tool names
|
|
21881
22273
|
* @returns {{ result } | { error, message }}
|
|
21882
22274
|
*/
|
|
21883
|
-
callTool: async (
|
|
22275
|
+
callTool: async (
|
|
22276
|
+
win,
|
|
22277
|
+
serverName,
|
|
22278
|
+
toolName,
|
|
22279
|
+
args,
|
|
22280
|
+
allowedTools = null,
|
|
22281
|
+
widgetId = null,
|
|
22282
|
+
) => {
|
|
21884
22283
|
try {
|
|
21885
22284
|
const server = activeServers.get(serverName);
|
|
21886
22285
|
if (!server || !server.client) {
|
|
21887
22286
|
throw new Error(`Server not connected: ${serverName}`);
|
|
21888
22287
|
}
|
|
21889
22288
|
|
|
21890
|
-
//
|
|
22289
|
+
// Per-widget manifest gate. Activated by the
|
|
22290
|
+
// security.enforceWidgetMcpPermissions setting. When enabled
|
|
22291
|
+
// and a widgetId is supplied, the widget's installed
|
|
22292
|
+
// package.json's dash.permissions.mcp block determines what
|
|
22293
|
+
// tools and paths are allowed.
|
|
22294
|
+
if (isWidgetPermissionEnforcementEnabled() && widgetId) {
|
|
22295
|
+
const gate = gateToolCall({
|
|
22296
|
+
widgetId,
|
|
22297
|
+
serverName,
|
|
22298
|
+
toolName,
|
|
22299
|
+
args,
|
|
22300
|
+
});
|
|
22301
|
+
if (!gate.allow) {
|
|
22302
|
+
throw new Error(`Widget permission gate: ${gate.reason}`);
|
|
22303
|
+
}
|
|
22304
|
+
}
|
|
22305
|
+
|
|
22306
|
+
// Legacy renderer-supplied allowedTools whitelist. Kept for
|
|
22307
|
+
// backward compatibility with callers that pre-date the
|
|
22308
|
+
// manifest-based gate. Once the manifest gate is enforced
|
|
22309
|
+
// everywhere, this can be removed.
|
|
21891
22310
|
if (allowedTools && !allowedTools.includes(toolName)) {
|
|
21892
22311
|
throw new Error(
|
|
21893
22312
|
`Tool "${toolName}" is not in the allowed tools list for this widget. Allowed: ${allowedTools.join(
|
|
@@ -22079,20 +22498,20 @@ const mcpController$3 = {
|
|
|
22079
22498
|
*/
|
|
22080
22499
|
getCatalog: (win) => {
|
|
22081
22500
|
try {
|
|
22082
|
-
const catalogPath = path$
|
|
22501
|
+
const catalogPath = path$d.join(
|
|
22083
22502
|
__dirname,
|
|
22084
22503
|
"..",
|
|
22085
22504
|
"mcp",
|
|
22086
22505
|
"mcpServerCatalog.json",
|
|
22087
22506
|
);
|
|
22088
22507
|
|
|
22089
|
-
if (!fs$
|
|
22508
|
+
if (!fs$9.existsSync(catalogPath)) {
|
|
22090
22509
|
return {
|
|
22091
22510
|
catalog: [],
|
|
22092
22511
|
};
|
|
22093
22512
|
}
|
|
22094
22513
|
|
|
22095
|
-
const catalogData = fs$
|
|
22514
|
+
const catalogData = fs$9.readFileSync(catalogPath, "utf8");
|
|
22096
22515
|
const catalog = JSON.parse(catalogData);
|
|
22097
22516
|
|
|
22098
22517
|
return {
|
|
@@ -22120,18 +22539,18 @@ const mcpController$3 = {
|
|
|
22120
22539
|
*/
|
|
22121
22540
|
getKnownExternalCatalog: () => {
|
|
22122
22541
|
try {
|
|
22123
|
-
const catalogPath = path$
|
|
22542
|
+
const catalogPath = path$d.join(
|
|
22124
22543
|
__dirname,
|
|
22125
22544
|
"..",
|
|
22126
22545
|
"mcp",
|
|
22127
22546
|
"knownExternalMcpServers.json",
|
|
22128
22547
|
);
|
|
22129
22548
|
|
|
22130
|
-
if (!fs$
|
|
22549
|
+
if (!fs$9.existsSync(catalogPath)) {
|
|
22131
22550
|
return { success: true, servers: [] };
|
|
22132
22551
|
}
|
|
22133
22552
|
|
|
22134
|
-
const catalogData = fs$
|
|
22553
|
+
const catalogData = fs$9.readFileSync(catalogPath, "utf8");
|
|
22135
22554
|
const catalog = JSON.parse(catalogData);
|
|
22136
22555
|
|
|
22137
22556
|
return {
|
|
@@ -22184,7 +22603,7 @@ const mcpController$3 = {
|
|
|
22184
22603
|
* @returns {{ success } | { error, message }}
|
|
22185
22604
|
*/
|
|
22186
22605
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
22187
|
-
const { spawn } = require$$
|
|
22606
|
+
const { spawn } = require$$9$1;
|
|
22188
22607
|
|
|
22189
22608
|
const env = cleanEnvForChildProcess();
|
|
22190
22609
|
|
|
@@ -22193,11 +22612,11 @@ const mcpController$3 = {
|
|
|
22193
22612
|
const { from, to } = authCommand.setup.copyCredential;
|
|
22194
22613
|
const sourcePath = credentials?.[from];
|
|
22195
22614
|
if (sourcePath) {
|
|
22196
|
-
const destPath = to.replace(/^~/, os$
|
|
22615
|
+
const destPath = to.replace(/^~/, os$2.homedir());
|
|
22197
22616
|
const destDir = require$$1$2.dirname(destPath);
|
|
22198
22617
|
try {
|
|
22199
|
-
fs$
|
|
22200
|
-
fs$
|
|
22618
|
+
fs$9.mkdirSync(destDir, { recursive: true });
|
|
22619
|
+
fs$9.copyFileSync(sourcePath, destPath);
|
|
22201
22620
|
} catch (err) {
|
|
22202
22621
|
return {
|
|
22203
22622
|
error: true,
|
|
@@ -22228,12 +22647,12 @@ const mcpController$3 = {
|
|
|
22228
22647
|
// Merge static env vars from authCommand with ~ expansion
|
|
22229
22648
|
if (authCommand.staticEnv) {
|
|
22230
22649
|
Object.entries(authCommand.staticEnv).forEach(([key, value]) => {
|
|
22231
|
-
env[key] = value.replace(/^~/, os$
|
|
22650
|
+
env[key] = value.replace(/^~/, os$2.homedir());
|
|
22232
22651
|
});
|
|
22233
22652
|
}
|
|
22234
22653
|
|
|
22235
22654
|
// Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
|
|
22236
|
-
const mcpDir = path$
|
|
22655
|
+
const mcpDir = path$d.join(__dirname, "..", "mcp");
|
|
22237
22656
|
const resolvedArgs = (authCommand.args || []).map((arg) =>
|
|
22238
22657
|
typeof arg === "string" && arg.includes("{{MCP_DIR}}")
|
|
22239
22658
|
? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
|
|
@@ -22754,8 +23173,8 @@ function commonjsRequire(path) {
|
|
|
22754
23173
|
* Runs in the Electron main process at widget install time.
|
|
22755
23174
|
*/
|
|
22756
23175
|
|
|
22757
|
-
const fs$
|
|
22758
|
-
const path$
|
|
23176
|
+
const fs$8 = require$$0$2;
|
|
23177
|
+
const path$c = require$$1$2;
|
|
22759
23178
|
|
|
22760
23179
|
/**
|
|
22761
23180
|
* Structured error thrown by compileWidget() when the underlying
|
|
@@ -22790,7 +23209,7 @@ function getEsbuildDiagnostics() {
|
|
|
22790
23209
|
|
|
22791
23210
|
try {
|
|
22792
23211
|
const pkgJsonPath = require.resolve("esbuild/package.json");
|
|
22793
|
-
diagnostics.esbuildPackageDir = path$
|
|
23212
|
+
diagnostics.esbuildPackageDir = path$c.dirname(pkgJsonPath);
|
|
22794
23213
|
diagnostics.esbuildVersion = commonjsRequire(pkgJsonPath).version;
|
|
22795
23214
|
} catch (err) {
|
|
22796
23215
|
diagnostics.esbuildResolveError = err.message;
|
|
@@ -22800,15 +23219,15 @@ function getEsbuildDiagnostics() {
|
|
|
22800
23219
|
const archPkgJson = require.resolve(
|
|
22801
23220
|
`${diagnostics.archPackage}/package.json`,
|
|
22802
23221
|
);
|
|
22803
|
-
const archDir = path$
|
|
23222
|
+
const archDir = path$c.dirname(archPkgJson);
|
|
22804
23223
|
// esbuild's native binary on macOS/Linux is bin/esbuild;
|
|
22805
23224
|
// on Windows it's esbuild.exe at the package root.
|
|
22806
23225
|
const candidate =
|
|
22807
23226
|
process.platform === "win32"
|
|
22808
|
-
? path$
|
|
22809
|
-
: path$
|
|
23227
|
+
? path$c.join(archDir, "esbuild.exe")
|
|
23228
|
+
: path$c.join(archDir, "bin", "esbuild");
|
|
22810
23229
|
diagnostics.nativeBinaryPath = candidate;
|
|
22811
|
-
diagnostics.nativeBinaryExists = fs$
|
|
23230
|
+
diagnostics.nativeBinaryExists = fs$8.existsSync(candidate);
|
|
22812
23231
|
} catch (err) {
|
|
22813
23232
|
diagnostics.archResolveError = err.message;
|
|
22814
23233
|
}
|
|
@@ -22854,26 +23273,26 @@ async function healthCheck() {
|
|
|
22854
23273
|
* @returns {string|null} Path to the widgets/ directory, or null
|
|
22855
23274
|
*/
|
|
22856
23275
|
function findWidgetsDir$2(widgetPath) {
|
|
22857
|
-
const direct = path$
|
|
22858
|
-
if (fs$
|
|
23276
|
+
const direct = path$c.join(widgetPath, "widgets");
|
|
23277
|
+
if (fs$8.existsSync(direct)) {
|
|
22859
23278
|
return direct;
|
|
22860
23279
|
}
|
|
22861
23280
|
|
|
22862
23281
|
// Check configs/widgets/ (packageZip.js nests .dash.js files here)
|
|
22863
|
-
const configsWidgets = path$
|
|
22864
|
-
if (fs$
|
|
23282
|
+
const configsWidgets = path$c.join(widgetPath, "configs", "widgets");
|
|
23283
|
+
if (fs$8.existsSync(configsWidgets)) {
|
|
22865
23284
|
return configsWidgets;
|
|
22866
23285
|
}
|
|
22867
23286
|
|
|
22868
23287
|
// Check configs/ directory (used by packageZip.js for distributed widgets)
|
|
22869
|
-
const configs = path$
|
|
22870
|
-
if (fs$
|
|
23288
|
+
const configs = path$c.join(widgetPath, "configs");
|
|
23289
|
+
if (fs$8.existsSync(configs)) {
|
|
22871
23290
|
return configs;
|
|
22872
23291
|
}
|
|
22873
23292
|
|
|
22874
23293
|
// Check one level deeper for nested ZIP extraction
|
|
22875
23294
|
try {
|
|
22876
|
-
const entries = fs$
|
|
23295
|
+
const entries = fs$8.readdirSync(widgetPath, { withFileTypes: true });
|
|
22877
23296
|
const subdirs = entries.filter(
|
|
22878
23297
|
(e) =>
|
|
22879
23298
|
e.isDirectory() &&
|
|
@@ -22883,8 +23302,8 @@ function findWidgetsDir$2(widgetPath) {
|
|
|
22883
23302
|
);
|
|
22884
23303
|
|
|
22885
23304
|
for (const subdir of subdirs) {
|
|
22886
|
-
const nested = path$
|
|
22887
|
-
if (fs$
|
|
23305
|
+
const nested = path$c.join(widgetPath, subdir.name, "widgets");
|
|
23306
|
+
if (fs$8.existsSync(nested)) {
|
|
22888
23307
|
console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
|
|
22889
23308
|
return nested;
|
|
22890
23309
|
}
|
|
@@ -22918,7 +23337,7 @@ async function compileWidget$1(widgetPath) {
|
|
|
22918
23337
|
}
|
|
22919
23338
|
|
|
22920
23339
|
// Discover .dash.js config files
|
|
22921
|
-
const files = fs$
|
|
23340
|
+
const files = fs$8.readdirSync(widgetsDir);
|
|
22922
23341
|
const dashFiles = files.filter((f) => f.endsWith(".dash.js"));
|
|
22923
23342
|
|
|
22924
23343
|
if (dashFiles.length === 0) {
|
|
@@ -22932,15 +23351,15 @@ async function compileWidget$1(widgetPath) {
|
|
|
22932
23351
|
// Compute relative path from the entry file (in widgetPath) to widgetsDir,
|
|
22933
23352
|
// since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
|
|
22934
23353
|
const relWidgetsDir =
|
|
22935
|
-
"./" + path$
|
|
23354
|
+
"./" + path$c.relative(widgetPath, widgetsDir).split(path$c.sep).join("/");
|
|
22936
23355
|
const imports = [];
|
|
22937
23356
|
const exportParts = [];
|
|
22938
23357
|
|
|
22939
23358
|
for (const dashFile of dashFiles) {
|
|
22940
23359
|
const componentName = dashFile.replace(".dash.js", "");
|
|
22941
23360
|
const componentFile = `${componentName}.js`;
|
|
22942
|
-
const componentFilePath = path$
|
|
22943
|
-
const hasComponent = fs$
|
|
23361
|
+
const componentFilePath = path$c.join(widgetsDir, componentFile);
|
|
23362
|
+
const hasComponent = fs$8.existsSync(componentFilePath);
|
|
22944
23363
|
|
|
22945
23364
|
// Import the config (always)
|
|
22946
23365
|
imports.push(
|
|
@@ -22972,17 +23391,17 @@ async function compileWidget$1(widgetPath) {
|
|
|
22972
23391
|
const entryContent = [...imports, "", ...exportParts, ""].join("\n");
|
|
22973
23392
|
|
|
22974
23393
|
// Write temporary entry file in the widget root
|
|
22975
|
-
const entryPath = path$
|
|
22976
|
-
const distDir = path$
|
|
22977
|
-
const outPath = path$
|
|
23394
|
+
const entryPath = path$c.join(widgetPath, "__compile_entry.js");
|
|
23395
|
+
const distDir = path$c.join(widgetPath, "dist");
|
|
23396
|
+
const outPath = path$c.join(distDir, "index.cjs.js");
|
|
22978
23397
|
|
|
22979
23398
|
try {
|
|
22980
23399
|
// Ensure dist/ directory exists
|
|
22981
|
-
if (!fs$
|
|
22982
|
-
fs$
|
|
23400
|
+
if (!fs$8.existsSync(distDir)) {
|
|
23401
|
+
fs$8.mkdirSync(distDir, { recursive: true });
|
|
22983
23402
|
}
|
|
22984
23403
|
|
|
22985
|
-
fs$
|
|
23404
|
+
fs$8.writeFileSync(entryPath, entryContent, "utf8");
|
|
22986
23405
|
|
|
22987
23406
|
console.log(
|
|
22988
23407
|
`[WidgetCompiler] Compiling ${dashFiles.length} component(s) from ${widgetPath}`,
|
|
@@ -23038,8 +23457,8 @@ async function compileWidget$1(widgetPath) {
|
|
|
23038
23457
|
} finally {
|
|
23039
23458
|
// Clean up temporary entry file
|
|
23040
23459
|
try {
|
|
23041
|
-
if (fs$
|
|
23042
|
-
fs$
|
|
23460
|
+
if (fs$8.existsSync(entryPath)) {
|
|
23461
|
+
fs$8.unlinkSync(entryPath);
|
|
23043
23462
|
}
|
|
23044
23463
|
} catch (cleanupError) {
|
|
23045
23464
|
// Non-fatal
|
|
@@ -23071,8 +23490,8 @@ var widgetCompiler$1 = {
|
|
|
23071
23490
|
* Integrates with ComponentManager for automatic registration
|
|
23072
23491
|
*/
|
|
23073
23492
|
|
|
23074
|
-
const fs$
|
|
23075
|
-
const path$
|
|
23493
|
+
const fs$7 = require$$0$2;
|
|
23494
|
+
const path$b = require$$1$2;
|
|
23076
23495
|
const vm = require$$2$2;
|
|
23077
23496
|
const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
|
|
23078
23497
|
|
|
@@ -23160,14 +23579,14 @@ class DynamicWidgetLoader {
|
|
|
23160
23579
|
);
|
|
23161
23580
|
|
|
23162
23581
|
const widgetsDir =
|
|
23163
|
-
findWidgetsDir$1(widgetPath) || path$
|
|
23164
|
-
const componentPath = path$
|
|
23165
|
-
const configPath = path$
|
|
23582
|
+
findWidgetsDir$1(widgetPath) || path$b.join(widgetPath, "widgets");
|
|
23583
|
+
const componentPath = path$b.join(widgetsDir, `${componentName}.js`);
|
|
23584
|
+
const configPath = path$b.join(widgetsDir, `${componentName}.dash.js`);
|
|
23166
23585
|
|
|
23167
|
-
if (!fs$
|
|
23586
|
+
if (!fs$7.existsSync(componentPath)) {
|
|
23168
23587
|
throw new Error(`Component file not found: ${componentPath}`);
|
|
23169
23588
|
}
|
|
23170
|
-
if (!fs$
|
|
23589
|
+
if (!fs$7.existsSync(configPath)) {
|
|
23171
23590
|
throw new Error(`Config file not found: ${configPath}`);
|
|
23172
23591
|
}
|
|
23173
23592
|
|
|
@@ -23218,7 +23637,7 @@ class DynamicWidgetLoader {
|
|
|
23218
23637
|
*/
|
|
23219
23638
|
async loadConfigFile(configPath) {
|
|
23220
23639
|
try {
|
|
23221
|
-
const source = fs$
|
|
23640
|
+
const source = fs$7.readFileSync(configPath, "utf8");
|
|
23222
23641
|
|
|
23223
23642
|
let exportMatch = source.match(/export\s+default\s+({[\s\S]*});?\s*$/);
|
|
23224
23643
|
|
|
@@ -23286,7 +23705,7 @@ class DynamicWidgetLoader {
|
|
|
23286
23705
|
return [];
|
|
23287
23706
|
}
|
|
23288
23707
|
|
|
23289
|
-
const files = fs$
|
|
23708
|
+
const files = fs$7.readdirSync(widgetsDir);
|
|
23290
23709
|
const widgets = new Set();
|
|
23291
23710
|
|
|
23292
23711
|
files.forEach((file) => {
|
|
@@ -23319,6 +23738,157 @@ dynamicWidgetLoader$3.exports.dynamicWidgetLoader = dynamicWidgetLoader$2;
|
|
|
23319
23738
|
|
|
23320
23739
|
var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
|
|
23321
23740
|
|
|
23741
|
+
/**
|
|
23742
|
+
* widgetPermissions.js
|
|
23743
|
+
*
|
|
23744
|
+
* Read and parse the `dash.permissions.mcp` block from an installed
|
|
23745
|
+
* widget's package.json.
|
|
23746
|
+
*
|
|
23747
|
+
* Manifest format (declared by widget authors in their package.json):
|
|
23748
|
+
*
|
|
23749
|
+
* {
|
|
23750
|
+
* "name": "@trops/notes-summarizer",
|
|
23751
|
+
* "dash": {
|
|
23752
|
+
* "permissions": {
|
|
23753
|
+
* "mcp": {
|
|
23754
|
+
* "filesystem": {
|
|
23755
|
+
* "tools": ["read_file", "list_directory"],
|
|
23756
|
+
* "readPaths": ["~/Documents/notes"],
|
|
23757
|
+
* "writePaths": []
|
|
23758
|
+
* },
|
|
23759
|
+
* "github": {
|
|
23760
|
+
* "tools": ["search_repositories", "get_file_contents"]
|
|
23761
|
+
* }
|
|
23762
|
+
* }
|
|
23763
|
+
* }
|
|
23764
|
+
* }
|
|
23765
|
+
* }
|
|
23766
|
+
*
|
|
23767
|
+
* Path strings beginning with `~` are expanded to the user's home
|
|
23768
|
+
* directory at parse time. Tool-only servers (no path I/O, e.g.
|
|
23769
|
+
* github) omit the `readPaths`/`writePaths` keys.
|
|
23770
|
+
*
|
|
23771
|
+
* Public API:
|
|
23772
|
+
*
|
|
23773
|
+
* getWidgetMcpPermissions(widgetId) → permissions | null
|
|
23774
|
+
* Returns the parsed permissions for a widget, or null if the
|
|
23775
|
+
* widget is unmanifested. Cached per process.
|
|
23776
|
+
*
|
|
23777
|
+
* parseManifestPermissions(packageJson) → permissions | null
|
|
23778
|
+
* Pure function — exposed for tests.
|
|
23779
|
+
*
|
|
23780
|
+
* clearCache() → void
|
|
23781
|
+
* Test-only. Drops the in-process cache so tests can re-read.
|
|
23782
|
+
*/
|
|
23783
|
+
|
|
23784
|
+
const fs$6 = require$$0$2;
|
|
23785
|
+
const path$a = require$$1$2;
|
|
23786
|
+
const os$1 = require$$2$1;
|
|
23787
|
+
const { app: app$6 } = require$$0$1;
|
|
23788
|
+
|
|
23789
|
+
// Cache: widgetId → permissions | null. Populated lazily on first
|
|
23790
|
+
// lookup; invalidated when a widget is installed/uninstalled (the
|
|
23791
|
+
// install/uninstall paths call clearCache()).
|
|
23792
|
+
const _cache = new Map();
|
|
23793
|
+
|
|
23794
|
+
/**
|
|
23795
|
+
* Expand a leading "~" to the user's home directory. Other paths are
|
|
23796
|
+
* returned as-is.
|
|
23797
|
+
*/
|
|
23798
|
+
function expandHome(p) {
|
|
23799
|
+
if (typeof p !== "string" || !p) return p;
|
|
23800
|
+
if (p === "~") return os$1.homedir();
|
|
23801
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
23802
|
+
return path$a.join(os$1.homedir(), p.slice(2));
|
|
23803
|
+
}
|
|
23804
|
+
return p;
|
|
23805
|
+
}
|
|
23806
|
+
|
|
23807
|
+
/**
|
|
23808
|
+
* Parse a widget's package.json contents into a normalized permissions
|
|
23809
|
+
* object. Returns null if no `dash.permissions.mcp` block exists.
|
|
23810
|
+
*/
|
|
23811
|
+
function parseManifestPermissions(packageJson) {
|
|
23812
|
+
if (!packageJson || typeof packageJson !== "object") return null;
|
|
23813
|
+
const mcp = packageJson?.dash?.permissions?.mcp;
|
|
23814
|
+
if (!mcp || typeof mcp !== "object") return null;
|
|
23815
|
+
|
|
23816
|
+
const servers = {};
|
|
23817
|
+
for (const [serverName, raw] of Object.entries(mcp)) {
|
|
23818
|
+
if (!raw || typeof raw !== "object") continue;
|
|
23819
|
+
const tools = Array.isArray(raw.tools)
|
|
23820
|
+
? raw.tools.filter((t) => typeof t === "string")
|
|
23821
|
+
: [];
|
|
23822
|
+
const readPaths = Array.isArray(raw.readPaths)
|
|
23823
|
+
? raw.readPaths.filter((p) => typeof p === "string").map(expandHome)
|
|
23824
|
+
: [];
|
|
23825
|
+
const writePaths = Array.isArray(raw.writePaths)
|
|
23826
|
+
? raw.writePaths.filter((p) => typeof p === "string").map(expandHome)
|
|
23827
|
+
: [];
|
|
23828
|
+
servers[serverName] = { tools, readPaths, writePaths };
|
|
23829
|
+
}
|
|
23830
|
+
|
|
23831
|
+
return { servers };
|
|
23832
|
+
}
|
|
23833
|
+
|
|
23834
|
+
/**
|
|
23835
|
+
* Find a widget's installed package.json on disk. Widgets live under
|
|
23836
|
+
* userData/widgets/<scope>/<name>/ for scoped packages or
|
|
23837
|
+
* userData/widgets/<name>/ for unscoped. The widgetId is the npm
|
|
23838
|
+
* package name (e.g. "@trops/notes-summarizer" or "notes-summarizer").
|
|
23839
|
+
*/
|
|
23840
|
+
function resolveWidgetPackagePath(widgetId) {
|
|
23841
|
+
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
23842
|
+
const widgetsRoot = path$a.join(app$6.getPath("userData"), "widgets");
|
|
23843
|
+
// Split scope from name for "@scope/name" form.
|
|
23844
|
+
const parts = widgetId.startsWith("@") ? widgetId.split("/") : [widgetId];
|
|
23845
|
+
return path$a.join(widgetsRoot, ...parts, "package.json");
|
|
23846
|
+
}
|
|
23847
|
+
|
|
23848
|
+
/**
|
|
23849
|
+
* Read and parse a widget's MCP permissions. Returns null if:
|
|
23850
|
+
* - the widget directory doesn't exist
|
|
23851
|
+
* - package.json is unreadable / malformed
|
|
23852
|
+
* - the widget hasn't declared dash.permissions.mcp
|
|
23853
|
+
*
|
|
23854
|
+
* Result is cached per widgetId for the lifetime of this process.
|
|
23855
|
+
*/
|
|
23856
|
+
function getWidgetMcpPermissions$1(widgetId) {
|
|
23857
|
+
if (_cache.has(widgetId)) return _cache.get(widgetId);
|
|
23858
|
+
const pkgPath = resolveWidgetPackagePath(widgetId);
|
|
23859
|
+
if (!pkgPath || !fs$6.existsSync(pkgPath)) {
|
|
23860
|
+
_cache.set(widgetId, null);
|
|
23861
|
+
return null;
|
|
23862
|
+
}
|
|
23863
|
+
try {
|
|
23864
|
+
const raw = fs$6.readFileSync(pkgPath, "utf8");
|
|
23865
|
+
const pkg = JSON.parse(raw);
|
|
23866
|
+
const perms = parseManifestPermissions(pkg);
|
|
23867
|
+
_cache.set(widgetId, perms);
|
|
23868
|
+
return perms;
|
|
23869
|
+
} catch (e) {
|
|
23870
|
+
console.warn(
|
|
23871
|
+
"[widgetPermissions] failed to read package.json for " +
|
|
23872
|
+
widgetId +
|
|
23873
|
+
": " +
|
|
23874
|
+
e.message,
|
|
23875
|
+
);
|
|
23876
|
+
_cache.set(widgetId, null);
|
|
23877
|
+
return null;
|
|
23878
|
+
}
|
|
23879
|
+
}
|
|
23880
|
+
|
|
23881
|
+
function clearCache() {
|
|
23882
|
+
_cache.clear();
|
|
23883
|
+
}
|
|
23884
|
+
|
|
23885
|
+
var widgetPermissions = {
|
|
23886
|
+
getWidgetMcpPermissions: getWidgetMcpPermissions$1,
|
|
23887
|
+
parseManifestPermissions,
|
|
23888
|
+
expandHome,
|
|
23889
|
+
clearCache,
|
|
23890
|
+
};
|
|
23891
|
+
|
|
23322
23892
|
/**
|
|
23323
23893
|
* schedulerController.js
|
|
23324
23894
|
*
|
|
@@ -23859,6 +24429,10 @@ var schedulerController_1 = schedulerController$2;
|
|
|
23859
24429
|
const { compileWidget, findWidgetsDir } = widgetCompiler$1;
|
|
23860
24430
|
const { toPackageId, parsePackageId } = packageId;
|
|
23861
24431
|
const { getStoredToken } = registryAuthController$2;
|
|
24432
|
+
const {
|
|
24433
|
+
getWidgetMcpPermissions,
|
|
24434
|
+
clearCache: clearWidgetPermsCache,
|
|
24435
|
+
} = widgetPermissions;
|
|
23862
24436
|
|
|
23863
24437
|
let WIDGETS_CACHE_DIR = null;
|
|
23864
24438
|
let REGISTRY_CONFIG_FILE = null;
|
|
@@ -24933,6 +25507,38 @@ var schedulerController_1 = schedulerController$2;
|
|
|
24933
25507
|
return null;
|
|
24934
25508
|
}
|
|
24935
25509
|
|
|
25510
|
+
/**
|
|
25511
|
+
* If a freshly-installed widget declares dash.permissions.mcp, ping the
|
|
25512
|
+
* renderer so the consent modal can pop up. The renderer subscribes via
|
|
25513
|
+
* `widget:mcp-consent-required` and either calls widget-mcp:set-grant
|
|
25514
|
+
* with the user's selections (Slice 2) or quietly drops the message
|
|
25515
|
+
* (older renderer pre-Slice-2 — the gate still fail-closes).
|
|
25516
|
+
*
|
|
25517
|
+
* Cache invalidation: widgetPermissions caches per-process, so an upgrade
|
|
25518
|
+
* over a stale cached entry would otherwise keep the old manifest. Drop
|
|
25519
|
+
* the whole cache here — cheap, infrequent.
|
|
25520
|
+
*/
|
|
25521
|
+
function maybeEmitMcpConsentRequired(widgetName) {
|
|
25522
|
+
try {
|
|
25523
|
+
clearWidgetPermsCache();
|
|
25524
|
+
const declared = getWidgetMcpPermissions(widgetName);
|
|
25525
|
+
if (!declared) return;
|
|
25526
|
+
BrowserWindow.getAllWindows().forEach((win) => {
|
|
25527
|
+
win.webContents.send("widget:mcp-consent-required", {
|
|
25528
|
+
widgetId: widgetName,
|
|
25529
|
+
declared,
|
|
25530
|
+
});
|
|
25531
|
+
});
|
|
25532
|
+
} catch (e) {
|
|
25533
|
+
console.warn(
|
|
25534
|
+
"[widgetRegistry] mcp consent emit failed for " +
|
|
25535
|
+
widgetName +
|
|
25536
|
+
": " +
|
|
25537
|
+
e.message,
|
|
25538
|
+
);
|
|
25539
|
+
}
|
|
25540
|
+
}
|
|
25541
|
+
|
|
24936
25542
|
/**
|
|
24937
25543
|
* Setup IPC handlers for widget management (use in main.js)
|
|
24938
25544
|
*/
|
|
@@ -24959,6 +25565,8 @@ var schedulerController_1 = schedulerController$2;
|
|
|
24959
25565
|
});
|
|
24960
25566
|
});
|
|
24961
25567
|
|
|
25568
|
+
maybeEmitMcpConsentRequired(widgetName);
|
|
25569
|
+
|
|
24962
25570
|
return config;
|
|
24963
25571
|
},
|
|
24964
25572
|
);
|
|
@@ -24980,6 +25588,8 @@ var schedulerController_1 = schedulerController$2;
|
|
|
24980
25588
|
});
|
|
24981
25589
|
});
|
|
24982
25590
|
|
|
25591
|
+
maybeEmitMcpConsentRequired(widgetName);
|
|
25592
|
+
|
|
24983
25593
|
return config;
|
|
24984
25594
|
},
|
|
24985
25595
|
);
|
|
@@ -47133,7 +47743,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
47133
47743
|
* can use the Chat widget without a separate API key.
|
|
47134
47744
|
*/
|
|
47135
47745
|
|
|
47136
|
-
const { spawn, execSync } = require$$
|
|
47746
|
+
const { spawn, execSync } = require$$9$1;
|
|
47137
47747
|
const {
|
|
47138
47748
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
47139
47749
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -50442,7 +51052,7 @@ async function handleGetAppStats$1() {
|
|
|
50442
51052
|
|
|
50443
51053
|
const registryController$2 = registryController$3;
|
|
50444
51054
|
const registryAuthController$1 = registryAuthController$2;
|
|
50445
|
-
const { getWidgetRegistry } = widgetRegistryExports;
|
|
51055
|
+
const { getWidgetRegistry: getWidgetRegistry$1 } = widgetRegistryExports;
|
|
50446
51056
|
|
|
50447
51057
|
/**
|
|
50448
51058
|
* Helper: find a workspace by ID or return the first (active) one.
|
|
@@ -50970,7 +51580,7 @@ async function handleConfigureWidget$1({ dashboardId, widgetId, config }) {
|
|
|
50970
51580
|
function getInstalledPackageNames() {
|
|
50971
51581
|
const installed = new Set();
|
|
50972
51582
|
try {
|
|
50973
|
-
const registry = getWidgetRegistry();
|
|
51583
|
+
const registry = getWidgetRegistry$1();
|
|
50974
51584
|
const widgets = registry.getWidgets();
|
|
50975
51585
|
for (const w of widgets) {
|
|
50976
51586
|
const name = w.name || w.packageId || "";
|
|
@@ -51251,7 +51861,7 @@ async function handleInstallWidget$1({ packageName }) {
|
|
|
51251
51861
|
}
|
|
51252
51862
|
|
|
51253
51863
|
// Download and install
|
|
51254
|
-
const registry = getWidgetRegistry();
|
|
51864
|
+
const registry = getWidgetRegistry$1();
|
|
51255
51865
|
const config = await registry.downloadWidget(pkg.name, pkg.downloadUrl);
|
|
51256
51866
|
|
|
51257
51867
|
// Notify all renderer windows
|
|
@@ -59210,6 +59820,93 @@ const webSocketController$1 = {
|
|
|
59210
59820
|
|
|
59211
59821
|
var webSocketController_1 = webSocketController$1;
|
|
59212
59822
|
|
|
59823
|
+
/**
|
|
59824
|
+
* widgetMcpGrantsController.js
|
|
59825
|
+
*
|
|
59826
|
+
* IPC handlers for the Slice-2 user-grant store. The renderer reads/writes
|
|
59827
|
+
* grants via these channels: install consent modal calls `widget-mcp:set-grant`
|
|
59828
|
+
* with the user's selections, Settings → Privacy & Security calls
|
|
59829
|
+
* `widget-mcp:list-all` to render the grant audit, and either entry point can
|
|
59830
|
+
* call `widget-mcp:revoke` / `widget-mcp:revoke-server`.
|
|
59831
|
+
*
|
|
59832
|
+
* The list-all handler joins each grant with its declared manifest so the UI
|
|
59833
|
+
* can show declared-vs-granted diffs (declared paths the user did NOT grant
|
|
59834
|
+
* are rendered struck through, etc.). Widgets that have a manifest but no
|
|
59835
|
+
* grant are also surfaced — those are the install-consent retroactive prompts.
|
|
59836
|
+
*/
|
|
59837
|
+
|
|
59838
|
+
const { ipcMain: ipcMain$1 } = require$$0$1;
|
|
59839
|
+
const {
|
|
59840
|
+
getGrant,
|
|
59841
|
+
setGrant,
|
|
59842
|
+
revokeGrant,
|
|
59843
|
+
revokeServer,
|
|
59844
|
+
listAllGrants,
|
|
59845
|
+
} = grantedPermissions;
|
|
59846
|
+
const { getWidgetMcpPermissions } = widgetPermissions;
|
|
59847
|
+
const { getWidgetRegistry } = widgetRegistryExports;
|
|
59848
|
+
|
|
59849
|
+
function setupWidgetMcpGrantsHandlers() {
|
|
59850
|
+
ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
|
|
59851
|
+
return getGrant(widgetId);
|
|
59852
|
+
});
|
|
59853
|
+
|
|
59854
|
+
ipcMain$1.handle("widget-mcp:set-grant", (event, widgetId, perms) => {
|
|
59855
|
+
return setGrant(widgetId, perms);
|
|
59856
|
+
});
|
|
59857
|
+
|
|
59858
|
+
ipcMain$1.handle("widget-mcp:revoke", (event, widgetId) => {
|
|
59859
|
+
return revokeGrant(widgetId);
|
|
59860
|
+
});
|
|
59861
|
+
|
|
59862
|
+
ipcMain$1.handle("widget-mcp:revoke-server", (event, widgetId, serverName) => {
|
|
59863
|
+
return revokeServer(widgetId, serverName);
|
|
59864
|
+
});
|
|
59865
|
+
|
|
59866
|
+
// Joins all installed widgets with their declared and granted permission
|
|
59867
|
+
// blocks. Returns one row per widget that has a declared manifest OR a
|
|
59868
|
+
// grant — this surfaces both "freshly installed, awaiting consent" and
|
|
59869
|
+
// "previously granted, now reviewable" cases in Settings.
|
|
59870
|
+
ipcMain$1.handle("widget-mcp:list-all", () => {
|
|
59871
|
+
const grantsByWidget = new Map();
|
|
59872
|
+
for (const { widgetId, granted } of listAllGrants()) {
|
|
59873
|
+
grantsByWidget.set(widgetId, granted);
|
|
59874
|
+
}
|
|
59875
|
+
|
|
59876
|
+
const rows = [];
|
|
59877
|
+
const seen = new Set();
|
|
59878
|
+
|
|
59879
|
+
let installedWidgets = [];
|
|
59880
|
+
try {
|
|
59881
|
+
installedWidgets = getWidgetRegistry().getWidgets() || [];
|
|
59882
|
+
} catch (_e) {
|
|
59883
|
+
// Registry not initialized yet; fall back to grants-only listing.
|
|
59884
|
+
}
|
|
59885
|
+
|
|
59886
|
+
for (const w of installedWidgets) {
|
|
59887
|
+
const widgetId = w?.name;
|
|
59888
|
+
if (!widgetId || seen.has(widgetId)) continue;
|
|
59889
|
+
seen.add(widgetId);
|
|
59890
|
+
const declared = getWidgetMcpPermissions(widgetId);
|
|
59891
|
+
const granted = grantsByWidget.get(widgetId) || null;
|
|
59892
|
+
// Skip widgets with neither — they have nothing to show.
|
|
59893
|
+
if (!declared && !granted) continue;
|
|
59894
|
+
rows.push({ widgetId, declared, granted });
|
|
59895
|
+
}
|
|
59896
|
+
|
|
59897
|
+
// Surface any grants whose widget is no longer installed (rare, but
|
|
59898
|
+
// possible if uninstall didn't revoke). The user can still revoke them.
|
|
59899
|
+
for (const [widgetId, granted] of grantsByWidget) {
|
|
59900
|
+
if (seen.has(widgetId)) continue;
|
|
59901
|
+
rows.push({ widgetId, declared: null, granted });
|
|
59902
|
+
}
|
|
59903
|
+
|
|
59904
|
+
return rows;
|
|
59905
|
+
});
|
|
59906
|
+
}
|
|
59907
|
+
|
|
59908
|
+
var widgetMcpGrantsController$1 = { setupWidgetMcpGrantsHandlers };
|
|
59909
|
+
|
|
59213
59910
|
/**
|
|
59214
59911
|
* clientFactories.js
|
|
59215
59912
|
*
|
|
@@ -61470,15 +62167,27 @@ const mcpApi$2 = {
|
|
|
61470
62167
|
* @param {string} serverName the server name
|
|
61471
62168
|
* @param {string} toolName the tool to call
|
|
61472
62169
|
* @param {object} args tool arguments
|
|
61473
|
-
* @param {Array<string>} allowedTools optional whitelist of allowed tool names
|
|
62170
|
+
* @param {Array<string>} allowedTools optional whitelist of allowed tool names (legacy — prefer per-widget manifest)
|
|
62171
|
+
* @param {string} widgetId optional widget identity. When the
|
|
62172
|
+
* security.enforceWidgetMcpPermissions setting is enabled, this is
|
|
62173
|
+
* used to look up the widget's MCP permission manifest and gate
|
|
62174
|
+
* the call accordingly. Should be the npm package name of the
|
|
62175
|
+
* calling widget (e.g. "@trops/notes-summarizer").
|
|
61474
62176
|
* @returns {Promise<{ result } | { error, message }>}
|
|
61475
62177
|
*/
|
|
61476
|
-
callTool: (
|
|
62178
|
+
callTool: (
|
|
62179
|
+
serverName,
|
|
62180
|
+
toolName,
|
|
62181
|
+
args,
|
|
62182
|
+
allowedTools = null,
|
|
62183
|
+
widgetId = null,
|
|
62184
|
+
) =>
|
|
61477
62185
|
ipcRenderer$i.invoke(MCP_CALL_TOOL, {
|
|
61478
62186
|
serverName,
|
|
61479
62187
|
toolName,
|
|
61480
62188
|
args,
|
|
61481
62189
|
allowedTools,
|
|
62190
|
+
widgetId,
|
|
61482
62191
|
}),
|
|
61483
62192
|
|
|
61484
62193
|
/**
|
|
@@ -64247,6 +64956,7 @@ const paletteToThemeMapper = paletteToThemeMapper_1;
|
|
|
64247
64956
|
const webSocketController = webSocketController_1;
|
|
64248
64957
|
const extractionCacheController = extractionCacheController_1;
|
|
64249
64958
|
const mcpDashServerController = mcpDashServerController_1;
|
|
64959
|
+
const widgetMcpGrantsController = widgetMcpGrantsController$1;
|
|
64250
64960
|
|
|
64251
64961
|
// --- Errors ---
|
|
64252
64962
|
const themeFromUrlErrors = themeFromUrlErrors$1;
|
|
@@ -64351,6 +65061,7 @@ var electron = {
|
|
|
64351
65061
|
webSocketController,
|
|
64352
65062
|
extractionCacheController,
|
|
64353
65063
|
mcpDashServerController,
|
|
65064
|
+
widgetMcpGrantsController,
|
|
64354
65065
|
|
|
64355
65066
|
// Controller functions (flat) — spread for convenient destructuring
|
|
64356
65067
|
...controllers,
|