@trops/dash-core 0.1.524 → 0.1.526
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 +304 -0
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +6 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -3801,6 +3801,242 @@ var manifestScanner = {
|
|
|
3801
3801
|
SCAN_FILE_LIMIT,
|
|
3802
3802
|
};
|
|
3803
3803
|
|
|
3804
|
+
/**
|
|
3805
|
+
* scanWidgetPackagePermissions
|
|
3806
|
+
*
|
|
3807
|
+
* Walks a widget package directory, statically extracts MCP usage
|
|
3808
|
+
* (`useMcpProvider("type")` + `callTool("name", ...)`) from every
|
|
3809
|
+
* source file, and returns the canonical `dash.permissions.mcp` block
|
|
3810
|
+
* for embedding in the package's `package.json`.
|
|
3811
|
+
*
|
|
3812
|
+
* Used at three boundaries to keep declared permissions in sync with
|
|
3813
|
+
* the actual code:
|
|
3814
|
+
* - Widget publish flow (registry:publish-widget) — declarations
|
|
3815
|
+
* ship with the package.
|
|
3816
|
+
* - Widget install flow (installFromLocalPath) — declarations are
|
|
3817
|
+
* re-derived for already-published packages whose authors didn't
|
|
3818
|
+
* run the scanner, AND for new versions that updated their tool
|
|
3819
|
+
* set without updating the manifest.
|
|
3820
|
+
* - AI builder (widget:ai-build) — same scanner, single source of
|
|
3821
|
+
* truth.
|
|
3822
|
+
*
|
|
3823
|
+
* MERGE policy: scanner output is additive. Hand-authored entries in
|
|
3824
|
+
* `package.json.dash.permissions.mcp` are preserved; scanner-found
|
|
3825
|
+
* entries the human missed are appended. Idempotent — repeated runs
|
|
3826
|
+
* produce the same package.json (assuming source code unchanged).
|
|
3827
|
+
*
|
|
3828
|
+
* Limitations (acceptable, documented):
|
|
3829
|
+
* - Single-string regex scan. Variable-indirected calls like
|
|
3830
|
+
* `callTool(toolName, ...)` are skipped — runtime gate is the
|
|
3831
|
+
* safety net.
|
|
3832
|
+
* - Comments are stripped (line `//` only) so commented-out
|
|
3833
|
+
* examples don't pollute the declaration.
|
|
3834
|
+
* - Walks `widgets/`, `src/`, and the package root for `.js`,
|
|
3835
|
+
* `.jsx`, `.ts`, `.tsx` files. Skips `node_modules/` and `dist/`.
|
|
3836
|
+
*/
|
|
3837
|
+
|
|
3838
|
+
var scanWidgetPackagePermissions_1;
|
|
3839
|
+
var hasRequiredScanWidgetPackagePermissions;
|
|
3840
|
+
|
|
3841
|
+
function requireScanWidgetPackagePermissions () {
|
|
3842
|
+
if (hasRequiredScanWidgetPackagePermissions) return scanWidgetPackagePermissions_1;
|
|
3843
|
+
hasRequiredScanWidgetPackagePermissions = 1;
|
|
3844
|
+
|
|
3845
|
+
const fs = require$$0$2;
|
|
3846
|
+
const path = require$$1$1;
|
|
3847
|
+
|
|
3848
|
+
const SOURCE_EXTS = new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
3849
|
+
const SKIP_DIRS = new Set([
|
|
3850
|
+
"node_modules",
|
|
3851
|
+
"dist",
|
|
3852
|
+
"build",
|
|
3853
|
+
".git",
|
|
3854
|
+
"__tests__",
|
|
3855
|
+
"__mocks__",
|
|
3856
|
+
]);
|
|
3857
|
+
|
|
3858
|
+
function _stripLineComments(code) {
|
|
3859
|
+
return code.replace(/\/\/[^\n]*/g, "");
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
function _captureAll(code, pattern) {
|
|
3863
|
+
const out = [];
|
|
3864
|
+
for (const match of code.matchAll(pattern)) {
|
|
3865
|
+
out.push(match[1]);
|
|
3866
|
+
}
|
|
3867
|
+
return out;
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
/**
|
|
3871
|
+
* Scan a single file's contents and return any detected MCP usage.
|
|
3872
|
+
* @param {string} code
|
|
3873
|
+
* @returns {{providers: string[], tools: string[]}}
|
|
3874
|
+
*/
|
|
3875
|
+
function scanFileForMcpUsage(code) {
|
|
3876
|
+
if (typeof code !== "string" || !code) return { providers: [], tools: [] };
|
|
3877
|
+
const stripped = _stripLineComments(code);
|
|
3878
|
+
const providerPattern = /useMcpProvider\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
3879
|
+
const callPattern = /callTool\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
3880
|
+
return {
|
|
3881
|
+
providers: Array.from(new Set(_captureAll(stripped, providerPattern))),
|
|
3882
|
+
tools: Array.from(new Set(_captureAll(stripped, callPattern))),
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
function _walkSourceFiles(dir) {
|
|
3887
|
+
const out = [];
|
|
3888
|
+
if (!fs.existsSync(dir)) return out;
|
|
3889
|
+
let entries;
|
|
3890
|
+
try {
|
|
3891
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
3892
|
+
} catch (_) {
|
|
3893
|
+
return out;
|
|
3894
|
+
}
|
|
3895
|
+
for (const entry of entries) {
|
|
3896
|
+
if (entry.isDirectory()) {
|
|
3897
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3898
|
+
out.push(..._walkSourceFiles(path.join(dir, entry.name)));
|
|
3899
|
+
} else if (entry.isFile()) {
|
|
3900
|
+
const ext = path.extname(entry.name);
|
|
3901
|
+
if (SOURCE_EXTS.has(ext)) out.push(path.join(dir, entry.name));
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
return out;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
/**
|
|
3908
|
+
* Walk packageDir, scan every source file, return the canonical
|
|
3909
|
+
* `dash.permissions.mcp` block. Returns `{}` when no MCP usage found.
|
|
3910
|
+
*/
|
|
3911
|
+
function scanWidgetPackagePermissions(packageDir) {
|
|
3912
|
+
if (typeof packageDir !== "string" || !fs.existsSync(packageDir)) return {};
|
|
3913
|
+
|
|
3914
|
+
const allProviders = new Set();
|
|
3915
|
+
const allTools = new Set();
|
|
3916
|
+
for (const filePath of _walkSourceFiles(packageDir)) {
|
|
3917
|
+
let code;
|
|
3918
|
+
try {
|
|
3919
|
+
code = fs.readFileSync(filePath, "utf8");
|
|
3920
|
+
} catch (_) {
|
|
3921
|
+
continue;
|
|
3922
|
+
}
|
|
3923
|
+
const { providers, tools } = scanFileForMcpUsage(code);
|
|
3924
|
+
for (const p of providers) allProviders.add(p);
|
|
3925
|
+
for (const t of tools) allTools.add(t);
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
if (allProviders.size === 0 || allTools.size === 0) return {};
|
|
3929
|
+
|
|
3930
|
+
const out = {};
|
|
3931
|
+
for (const provider of allProviders) {
|
|
3932
|
+
out[provider] = { tools: Array.from(allTools).sort() };
|
|
3933
|
+
}
|
|
3934
|
+
return out;
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
/**
|
|
3938
|
+
* Merge scanner output with any hand-authored block. Additive:
|
|
3939
|
+
* - Servers in `human` are kept as-is.
|
|
3940
|
+
* - Servers in `scanned` not in `human` are added.
|
|
3941
|
+
* - When the same server is in both, the union of `tools` arrays is
|
|
3942
|
+
* written, preserving any read/write paths the human declared.
|
|
3943
|
+
*/
|
|
3944
|
+
function mergePermissions(human, scanned) {
|
|
3945
|
+
const out = {};
|
|
3946
|
+
// Start with human entries (preserves their shape including paths).
|
|
3947
|
+
if (human && typeof human === "object") {
|
|
3948
|
+
for (const [name, perms] of Object.entries(human)) {
|
|
3949
|
+
out[name] = { ...perms };
|
|
3950
|
+
if (Array.isArray(perms.tools)) out[name].tools = [...perms.tools];
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
if (scanned && typeof scanned === "object") {
|
|
3954
|
+
for (const [name, perms] of Object.entries(scanned)) {
|
|
3955
|
+
if (!out[name]) {
|
|
3956
|
+
out[name] = { tools: [...(perms.tools || [])] };
|
|
3957
|
+
} else {
|
|
3958
|
+
const existing = new Set(out[name].tools || []);
|
|
3959
|
+
for (const t of perms.tools || []) existing.add(t);
|
|
3960
|
+
out[name].tools = Array.from(existing).sort();
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
return out;
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
/**
|
|
3968
|
+
* Apply the scanner's findings to `package.json` in `packageDir` —
|
|
3969
|
+
* read, merge with any existing `dash.permissions.mcp` block, write
|
|
3970
|
+
* back. Returns the resulting block (or null if no package.json).
|
|
3971
|
+
*/
|
|
3972
|
+
function applyScanToPackageJson(packageDir) {
|
|
3973
|
+
const pkgPath = path.join(packageDir, "package.json");
|
|
3974
|
+
let existing = {};
|
|
3975
|
+
if (fs.existsSync(pkgPath)) {
|
|
3976
|
+
try {
|
|
3977
|
+
existing = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
3978
|
+
} catch (_) {
|
|
3979
|
+
existing = {};
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
const scanned = scanWidgetPackagePermissions(packageDir);
|
|
3983
|
+
const human = existing.dash?.permissions?.mcp || null;
|
|
3984
|
+
const merged = mergePermissions(human, scanned);
|
|
3985
|
+
if (Object.keys(merged).length === 0) {
|
|
3986
|
+
// Nothing to write. Don't churn the file.
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
const next = {
|
|
3990
|
+
...existing,
|
|
3991
|
+
dash: {
|
|
3992
|
+
...(existing.dash || {}),
|
|
3993
|
+
permissions: {
|
|
3994
|
+
...((existing.dash || {}).permissions || {}),
|
|
3995
|
+
mcp: merged,
|
|
3996
|
+
},
|
|
3997
|
+
},
|
|
3998
|
+
};
|
|
3999
|
+
fs.writeFileSync(pkgPath, JSON.stringify(next, null, 2), "utf8");
|
|
4000
|
+
return merged;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
/**
|
|
4004
|
+
* Run `applyScanToPackageJson` over a list of widget package paths.
|
|
4005
|
+
* Used at registry boot to backfill `dash.permissions.mcp` for widgets
|
|
4006
|
+
* installed before the publish/install scanner hooks existed (or that
|
|
4007
|
+
* have since updated their tool set without re-publishing). Idempotent
|
|
4008
|
+
* — additive merge means repeated boots produce no further writes.
|
|
4009
|
+
*
|
|
4010
|
+
* @param {string[]} packagePaths
|
|
4011
|
+
* @returns {{scanned: number, modified: number, errors: Array<{path: string, error: string}>}}
|
|
4012
|
+
*/
|
|
4013
|
+
function backfillPackagePermissions(packagePaths) {
|
|
4014
|
+
const summary = { scanned: 0, modified: 0, errors: [] };
|
|
4015
|
+
if (!Array.isArray(packagePaths)) return summary;
|
|
4016
|
+
for (const pkgPath of packagePaths) {
|
|
4017
|
+
if (typeof pkgPath !== "string" || !pkgPath) continue;
|
|
4018
|
+
if (!fs.existsSync(pkgPath)) continue;
|
|
4019
|
+
summary.scanned += 1;
|
|
4020
|
+
try {
|
|
4021
|
+
const merged = applyScanToPackageJson(pkgPath);
|
|
4022
|
+
if (merged) summary.modified += 1;
|
|
4023
|
+
} catch (e) {
|
|
4024
|
+
summary.errors.push({ path: pkgPath, error: e.message });
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
return summary;
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
scanWidgetPackagePermissions_1 = {
|
|
4031
|
+
scanFileForMcpUsage,
|
|
4032
|
+
scanWidgetPackagePermissions,
|
|
4033
|
+
mergePermissions,
|
|
4034
|
+
applyScanToPackageJson,
|
|
4035
|
+
backfillPackagePermissions,
|
|
4036
|
+
};
|
|
4037
|
+
return scanWidgetPackagePermissions_1;
|
|
4038
|
+
}
|
|
4039
|
+
|
|
3804
4040
|
/**
|
|
3805
4041
|
* schedulerController.js
|
|
3806
4042
|
*
|
|
@@ -4587,6 +4823,33 @@ var schedulerController_1 = schedulerController$2;
|
|
|
4587
4823
|
|
|
4588
4824
|
// Reconcile: re-register orphaned widget packages found on disk
|
|
4589
4825
|
this.reconcileWithDisk();
|
|
4826
|
+
|
|
4827
|
+
// Slice 13b: backfill `dash.permissions.mcp` for any registered
|
|
4828
|
+
// widget that was installed before the publish/install scanner
|
|
4829
|
+
// hooks existed (or that has since updated tools without
|
|
4830
|
+
// re-publishing). Additive merge — idempotent across boots.
|
|
4831
|
+
try {
|
|
4832
|
+
const {
|
|
4833
|
+
backfillPackagePermissions,
|
|
4834
|
+
} = requireScanWidgetPackagePermissions();
|
|
4835
|
+
const paths = Array.from(this.widgets.values())
|
|
4836
|
+
.map((w) => w.path)
|
|
4837
|
+
.filter(Boolean);
|
|
4838
|
+
const summary = backfillPackagePermissions(paths);
|
|
4839
|
+
if (summary.modified > 0) {
|
|
4840
|
+
console.log(
|
|
4841
|
+
`[WidgetRegistry] Backfilled MCP permissions for ${summary.modified}/${summary.scanned} widget(s)`,
|
|
4842
|
+
);
|
|
4843
|
+
}
|
|
4844
|
+
if (summary.errors.length) {
|
|
4845
|
+
console.warn(`[WidgetRegistry] MCP backfill errors:`, summary.errors);
|
|
4846
|
+
}
|
|
4847
|
+
} catch (e) {
|
|
4848
|
+
console.warn(
|
|
4849
|
+
"[WidgetRegistry] MCP permissions backfill failed:",
|
|
4850
|
+
e.message,
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
4590
4853
|
}
|
|
4591
4854
|
|
|
4592
4855
|
/**
|
|
@@ -4823,6 +5086,23 @@ var schedulerController_1 = schedulerController$2;
|
|
|
4823
5086
|
throw new Error(`Unsupported local source type: ${resolvedPath}`);
|
|
4824
5087
|
}
|
|
4825
5088
|
|
|
5089
|
+
// Slice 13a: scan the freshly-installed widget for MCP usage and
|
|
5090
|
+
// update the package.json's `dash.permissions.mcp` block. Always
|
|
5091
|
+
// runs (even when a manifest already exists) — catches author
|
|
5092
|
+
// forgetfulness and version updates that introduced new tools.
|
|
5093
|
+
// Merge is additive; hand-authored entries are preserved.
|
|
5094
|
+
try {
|
|
5095
|
+
const {
|
|
5096
|
+
applyScanToPackageJson,
|
|
5097
|
+
} = requireScanWidgetPackagePermissions();
|
|
5098
|
+
applyScanToPackageJson(widgetPath);
|
|
5099
|
+
} catch (e) {
|
|
5100
|
+
console.warn(
|
|
5101
|
+
`[WidgetRegistry] Permission scan failed for ${widgetName}: ${e.message}`,
|
|
5102
|
+
);
|
|
5103
|
+
// Non-fatal — the gate's runtime JIT still backs us up.
|
|
5104
|
+
}
|
|
5105
|
+
|
|
4826
5106
|
let config = await this.loadWidgetConfig(widgetName, widgetPath);
|
|
4827
5107
|
|
|
4828
5108
|
if (dashConfigPath) {
|
|
@@ -63166,6 +63446,30 @@ async function prepareWidgetForPublish$1(appId, packageId, options = {}) {
|
|
|
63166
63446
|
const parsedName = parsePackageName(pkgJson.name || "");
|
|
63167
63447
|
const resolvedScope = options.scope || callerScope;
|
|
63168
63448
|
|
|
63449
|
+
// Slice 13a: scan source for MCP usage and refresh the
|
|
63450
|
+
// `dash.permissions.mcp` block in package.json BEFORE the zip is
|
|
63451
|
+
// built. Always runs (even when a manifest exists) — catches
|
|
63452
|
+
// author forgetfulness and version updates that introduced new
|
|
63453
|
+
// tools. Merge is additive; hand-authored entries are preserved.
|
|
63454
|
+
try {
|
|
63455
|
+
const {
|
|
63456
|
+
applyScanToPackageJson,
|
|
63457
|
+
} = requireScanWidgetPackagePermissions();
|
|
63458
|
+
const merged = applyScanToPackageJson(widget.path);
|
|
63459
|
+
if (merged) {
|
|
63460
|
+
// Re-read pkgJson so subsequent steps in this function see the
|
|
63461
|
+
// updated manifest (the version bump below uses pkgJson too).
|
|
63462
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
63463
|
+
pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
63464
|
+
}
|
|
63465
|
+
}
|
|
63466
|
+
} catch (e) {
|
|
63467
|
+
console.warn(
|
|
63468
|
+
`[widgetRegistryController] Permission scan failed for ${packageId}: ${e.message}`,
|
|
63469
|
+
);
|
|
63470
|
+
// Non-fatal — runtime gate is still in place.
|
|
63471
|
+
}
|
|
63472
|
+
|
|
63169
63473
|
// 3.5 Pre-zip privacy scan. Flag any personal filesystem paths baked
|
|
63170
63474
|
// into shipped source (e.g. someone edited a `.dash.js`'s
|
|
63171
63475
|
// `defaultValue` from `~/Library/...` to `/Users/me/...` to skip
|