@weavetab/mcp 2.5.0-beta.4 → 2.5.0-beta.5
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/LICENSE +216 -631
- package/README.md +153 -271
- package/dist/cdp/connector.js +2 -2
- package/dist/cli/plugin.d.ts +1 -0
- package/dist/cli/plugin.js +60 -4
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/index.js +42 -6
- package/dist/plugin/interface.d.ts +14 -5
- package/dist/plugin/loader.d.ts +0 -3
- package/dist/plugin/loader.js +78 -15
- package/dist/plugin/registry.d.ts +2 -1
- package/dist/plugin/registry.js +21 -12
- package/dist/security/secrets.d.ts +6 -0
- package/dist/security/secrets.js +24 -0
- package/dist/server.js +36 -33
- package/dist/tools/browser_detect.js +2 -2
- package/dist/tools/captcha.js +1 -1
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +5 -5
- package/dist/tools/tabs.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/DOCUMENTATION.md +18 -11
- package/docs/PLUGINS.md +5 -5
- package/docs/TOOLS.md +54 -56
- package/docs/architecture/overview.md +1 -1
- package/docs/architecture/security.md +8 -7
- package/docs/architecture/transport.md +2 -2
- package/docs/cli/cli-commands.md +7 -1
- package/docs/cli/cli-flags.md +15 -7
- package/docs/cli/cli-scripting.md +6 -3
- package/docs/configuration/config-locations.md +3 -4
- package/docs/configuration/env-variables.md +4 -4
- package/docs/desktop-automation/electron-apps.md +3 -6
- package/docs/get-started/introduction.md +4 -4
- package/docs/get-started/setup-wizard.md +7 -6
- package/docs/tools/tools.md +1 -1
- package/docs/troubleshooting/trouble-browser.md +1 -1
- package/icon.png +0 -0
- package/mcp.json +2 -20
- package/package.json +118 -118
- package/server.json +4 -4
package/dist/index.js
CHANGED
|
@@ -98,6 +98,32 @@ function printBootBanner(port) {
|
|
|
98
98
|
async function main() {
|
|
99
99
|
assertNotRoot();
|
|
100
100
|
const args = process.argv.slice(2);
|
|
101
|
+
// Global flags: --version always works; --ws starts the WebSocket daemon.
|
|
102
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
103
|
+
const _v = createRequire(import.meta.url);
|
|
104
|
+
const ver = _v("../package.json").version || WEAVETAB_VERSION;
|
|
105
|
+
console.log(`@weavetab/mcp v${ver}`);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
if (args.includes("--ws")) {
|
|
109
|
+
let wsPort = 3000;
|
|
110
|
+
const pi = args.findIndex((a) => a === "--port");
|
|
111
|
+
const eq = args.find((a) => a.startsWith("--port="));
|
|
112
|
+
const rawPort = pi >= 0 ? args[pi + 1] : eq ? eq.slice("--port=".length) : undefined;
|
|
113
|
+
if (rawPort !== undefined) {
|
|
114
|
+
const parsed = parseInt(rawPort, 10);
|
|
115
|
+
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
116
|
+
console.error(`[Weavetab] Invalid --port value: "${rawPort}" (1-65535).`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
wsPort = parsed;
|
|
120
|
+
}
|
|
121
|
+
const config = await loadConfig();
|
|
122
|
+
global.__wt_is_server = true;
|
|
123
|
+
await printBootBanner(wsPort);
|
|
124
|
+
await startServer(config, "ws", wsPort);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
101
127
|
// If NOT in a TTY environment (e.g. spawned by Claude Code as MCP server)
|
|
102
128
|
if (!isInteractive && args.length === 0) {
|
|
103
129
|
// Singleton guard — exit immediately if another MCP server is alive.
|
|
@@ -363,6 +389,7 @@ async function main() {
|
|
|
363
389
|
S.sl("Role *", {
|
|
364
390
|
initialValue: "admin", options: [
|
|
365
391
|
{ value: "admin", label: "Admin", hint: "Full access to all tools" },
|
|
392
|
+
{ value: "operator", label: "Operator", hint: "Full access, standard agent role" },
|
|
366
393
|
{ value: "viewer", label: "Viewer", hint: "Read-only access (map, find, scrape, console)" },
|
|
367
394
|
{ value: "automation", label: "Automation", hint: "CI/CD mode (burst, plan, wait, map)" },
|
|
368
395
|
]
|
|
@@ -702,7 +729,7 @@ async function main() {
|
|
|
702
729
|
summary.push(`${pc.dim("Allowed:")} ${a.allow.join(", ")}`);
|
|
703
730
|
p.note(summary.join("\n"), "SUMMARY");
|
|
704
731
|
if (!mcpConfigured) {
|
|
705
|
-
console.log(pc.dim("\nTo configure MCP manually, run: weavetab config
|
|
732
|
+
console.log(pc.dim("\nTo configure MCP manually, run: weavetab config"));
|
|
706
733
|
}
|
|
707
734
|
p.outro(`${pc.green("Weavetab is ready!")} ${pc.dim("Run")} ${pc.cyan("weavetab help")} ${pc.dim("to see available commands.")}`);
|
|
708
735
|
}
|
|
@@ -1165,8 +1192,10 @@ async function main() {
|
|
|
1165
1192
|
console.log(` ${pc.cyan("skills")} Install or update AI agent skills (@weavetab/skills)`);
|
|
1166
1193
|
console.log(` ${pc.cyan("view")} Display the current active configuration`);
|
|
1167
1194
|
console.log(` ${pc.cyan("reset")} Purge browser profiles, lockfiles, and orphan processes`);
|
|
1168
|
-
console.log(` ${pc.cyan("secrets")} Manage blind-injected credentials (set, list, rm)`);
|
|
1195
|
+
console.log(` ${pc.cyan("secrets")} Manage blind-injected credentials (set, list, rm, init)`);
|
|
1169
1196
|
console.log(` ${pc.cyan("reload-ext")} Send a reload signal to the browser extension`);
|
|
1197
|
+
console.log(` ${pc.cyan("--ws")} Start the WebSocket daemon (default port 3000, override with --port)`);
|
|
1198
|
+
console.log(` ${pc.cyan("--version")} Print version and exit`);
|
|
1170
1199
|
console.log(` ${pc.cyan("help")} Show this help message`);
|
|
1171
1200
|
console.log();
|
|
1172
1201
|
console.log(pc.bold("AGENT SKILLS (@weavetab/skills)"));
|
|
@@ -1174,7 +1203,7 @@ async function main() {
|
|
|
1174
1203
|
console.log(` ${pc.dim("Via CLI:")} ${pc.cyan("weavetab skills [options]")}`);
|
|
1175
1204
|
console.log();
|
|
1176
1205
|
console.log(pc.bold("PLUGIN COMMANDS (User)"));
|
|
1177
|
-
console.log(` ${pc.cyan("weavetab plugin add <pkg>")} Install a plugin (
|
|
1206
|
+
console.log(` ${pc.cyan("weavetab plugin add <pkg>")} Install a plugin from npm (or a local path: ./my-plugin)`);
|
|
1178
1207
|
console.log(` ${pc.cyan("weavetab plugin browse")} Interactive terminal explorer to browse & 1-click install`);
|
|
1179
1208
|
console.log(` ${pc.cyan("weavetab plugin search <query>")} Search plugins across registry & npm`);
|
|
1180
1209
|
console.log(` ${pc.cyan("weavetab plugin list")} List all installed Weavetab plugins and tools`);
|
|
@@ -1198,7 +1227,7 @@ async function main() {
|
|
|
1198
1227
|
console.log(pc.bold("LINKS"));
|
|
1199
1228
|
console.log(` ${pc.cyan("GitHub:")} ${pc.dim("https://github.com/weavetab")}`);
|
|
1200
1229
|
console.log(` ${pc.cyan("npm:")} ${pc.dim("https://www.npmjs.com/package/@weavetab/mcp")}`);
|
|
1201
|
-
console.log(` ${pc.cyan("Website:")} ${pc.dim("https://
|
|
1230
|
+
console.log(` ${pc.cyan("Website:")} ${pc.dim("https://weavetab.dev")}`);
|
|
1202
1231
|
console.log();
|
|
1203
1232
|
}
|
|
1204
1233
|
}
|
|
@@ -1237,10 +1266,17 @@ async function runSecretsCommand(args) {
|
|
|
1237
1266
|
const { secretStore } = await import("./security/secrets.js");
|
|
1238
1267
|
const p = await import("@clack/prompts");
|
|
1239
1268
|
if (args.length === 0) {
|
|
1240
|
-
console.log(pc.red("[ERROR] Usage: weavetab secrets <set|list|rm> [key]"));
|
|
1269
|
+
console.log(pc.red("[ERROR] Usage: weavetab secrets <set|list|rm|init> [key]"));
|
|
1241
1270
|
process.exit(1);
|
|
1242
1271
|
}
|
|
1243
1272
|
const subcmd = args[0].toLowerCase();
|
|
1273
|
+
if (subcmd === "init") {
|
|
1274
|
+
const { initProjectSecrets } = await import("./security/secrets.js");
|
|
1275
|
+
const filePath = initProjectSecrets();
|
|
1276
|
+
console.log(pc.green(`[OK] Project secrets initialized: ${filePath}`));
|
|
1277
|
+
console.log(pc.dim("Keys in this file take precedence over ~/.weavetab/secrets.json when Weavetab runs from this directory."));
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1244
1280
|
if (subcmd === "list") {
|
|
1245
1281
|
const list = await secretStore.listSecrets();
|
|
1246
1282
|
console.log(boxen(pc.cyan("STORED SECRETS"), { padding: 1, borderStyle: 'round', borderColor: 'cyan' }));
|
|
@@ -1319,6 +1355,6 @@ async function runSecretsCommand(args) {
|
|
|
1319
1355
|
return;
|
|
1320
1356
|
}
|
|
1321
1357
|
console.log(pc.red(`[ERROR] Unknown secrets command: ${subcmd}`));
|
|
1322
|
-
console.log(pc.red("Usage: weavetab secrets <set|list|rm> [key]"));
|
|
1358
|
+
console.log(pc.red("Usage: weavetab secrets <set|list|rm|init> [key]"));
|
|
1323
1359
|
process.exit(1);
|
|
1324
1360
|
}
|
|
@@ -57,6 +57,7 @@ export interface WeavetabPermissions {
|
|
|
57
57
|
reason?: string;
|
|
58
58
|
[key: string]: any;
|
|
59
59
|
};
|
|
60
|
+
/** @deprecated use `filesystem` */
|
|
60
61
|
fs?: {
|
|
61
62
|
read?: string[];
|
|
62
63
|
write?: string[];
|
|
@@ -68,11 +69,19 @@ export interface WeavetabPermissions {
|
|
|
68
69
|
reason?: string;
|
|
69
70
|
};
|
|
70
71
|
tools?: {
|
|
71
|
-
/**
|
|
72
|
+
/** Must be true for the plugin to register any new MCP tools */
|
|
73
|
+
add?: boolean;
|
|
74
|
+
/** Explicit list of built-in tool names this plugin may override */
|
|
72
75
|
override?: string[];
|
|
73
|
-
/**
|
|
76
|
+
/** Explicit list of built-in tool names this plugin may remove */
|
|
74
77
|
remove?: string[];
|
|
75
|
-
|
|
78
|
+
reason?: string;
|
|
79
|
+
};
|
|
80
|
+
hooks?: {
|
|
81
|
+
onBeforeAction?: boolean;
|
|
82
|
+
onAfterAction?: boolean;
|
|
83
|
+
onDOMMutation?: boolean;
|
|
84
|
+
onPageNavigate?: boolean;
|
|
76
85
|
reason?: string;
|
|
77
86
|
};
|
|
78
87
|
[key: string]: any;
|
|
@@ -121,8 +130,6 @@ export interface WeavetabManifest {
|
|
|
121
130
|
engines?: Record<string, string>;
|
|
122
131
|
/** Supported environments */
|
|
123
132
|
environments?: string[];
|
|
124
|
-
/** Whether the plugin should be lazy-loaded on demand */
|
|
125
|
-
lazy?: boolean;
|
|
126
133
|
/** Tools exposed by this plugin to the MCP server */
|
|
127
134
|
tools?: WeavetabManifestTool[];
|
|
128
135
|
/** Default configuration template */
|
|
@@ -236,6 +243,7 @@ export interface WeavetabPluginDefinition {
|
|
|
236
243
|
overrides?: WeavetabToolOverride[];
|
|
237
244
|
removeTools?: string[];
|
|
238
245
|
defaultConfig?: Record<string, any>;
|
|
246
|
+
permissions?: WeavetabPermissions;
|
|
239
247
|
onBeforeAction?: BeforeActionHook;
|
|
240
248
|
onAfterAction?: AfterActionHook;
|
|
241
249
|
onDOMMutation?: DOMMutationHook;
|
|
@@ -246,6 +254,7 @@ export interface WeavetabPlugin {
|
|
|
246
254
|
name: string;
|
|
247
255
|
version: string;
|
|
248
256
|
description?: string;
|
|
257
|
+
permissions?: WeavetabPermissions;
|
|
249
258
|
/** Add new tools */
|
|
250
259
|
tools?: WeavetabToolDefinition[] | WeavetabPluginTool[];
|
|
251
260
|
/** Override or wrap existing tools */
|
package/dist/plugin/loader.d.ts
CHANGED
|
@@ -21,9 +21,6 @@ export declare function inspectInstalledPackage(pkgDir: string): InstalledPlugin
|
|
|
21
21
|
* Scan node_modules and return metadata for all installed Weavetab plugins.
|
|
22
22
|
*/
|
|
23
23
|
export declare function listInstalledPlugins(pluginDir?: string): InstalledPluginInfo[];
|
|
24
|
-
/**
|
|
25
|
-
* Builds the canonical rich PluginContext for a loaded plugin.
|
|
26
|
-
*/
|
|
27
24
|
export declare function buildPluginContext(plugin: WeavetabPlugin, storageManager: PluginStorageManager, extensionBridge: PluginExtensionBridge, pluginConfig: Record<string, any>, hooks: {
|
|
28
25
|
beforeToolCall: Array<(toolName: string, args: Record<string, any>, session?: any) => Promise<any> | void>;
|
|
29
26
|
afterToolCall: Array<(toolName: string, args: Record<string, any>, result: any, session?: any) => Promise<any> | void>;
|
package/dist/plugin/loader.js
CHANGED
|
@@ -27,9 +27,9 @@ function getNodeModulesDirs(customDir) {
|
|
|
27
27
|
export function normalizePermissions(perms) {
|
|
28
28
|
if (!perms)
|
|
29
29
|
return {};
|
|
30
|
-
// Handle flat string array: ["browser.evaluate", "
|
|
30
|
+
// Handle flat string array: ["browser.evaluate", "tools.add", "tools", "fs.read", ...]
|
|
31
31
|
if (Array.isArray(perms)) {
|
|
32
|
-
const normalized = { cdp: {}, fs: {}, extension: {} };
|
|
32
|
+
const normalized = { cdp: {}, fs: {}, filesystem: {}, extension: {}, tools: {}, hooks: {} };
|
|
33
33
|
for (const item of perms) {
|
|
34
34
|
if (typeof item !== "string")
|
|
35
35
|
continue;
|
|
@@ -39,10 +39,27 @@ export function normalizePermissions(perms) {
|
|
|
39
39
|
}
|
|
40
40
|
else if (cat === "fs" || cat === "filesystem") {
|
|
41
41
|
normalized.fs[sub || "read"] = ["*"];
|
|
42
|
+
normalized.filesystem[sub || "read"] = ["*"];
|
|
42
43
|
}
|
|
43
44
|
else if (cat === "extension" || cat === "hud") {
|
|
44
45
|
normalized.extension[sub || "hud"] = true;
|
|
45
46
|
}
|
|
47
|
+
else if (cat === "tools") {
|
|
48
|
+
normalized.tools = normalized.tools || {};
|
|
49
|
+
if (!sub || sub === "add") {
|
|
50
|
+
normalized.tools.add = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (cat === "hooks") {
|
|
54
|
+
normalized.hooks = normalized.hooks || {};
|
|
55
|
+
if (sub) {
|
|
56
|
+
normalized.hooks[sub] = true;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
normalized.hooks.onBeforeAction = true;
|
|
60
|
+
normalized.hooks.onAfterAction = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
}
|
|
47
64
|
return normalized;
|
|
48
65
|
}
|
|
@@ -165,12 +182,11 @@ export function listInstalledPlugins(pluginDir) {
|
|
|
165
182
|
}
|
|
166
183
|
return results;
|
|
167
184
|
}
|
|
168
|
-
/**
|
|
169
|
-
* Builds the canonical rich PluginContext for a loaded plugin.
|
|
170
|
-
*/
|
|
171
185
|
export function buildPluginContext(plugin, storageManager, extensionBridge, pluginConfig, hooks) {
|
|
186
|
+
const perms = plugin.manifest?.permissions ?? {};
|
|
187
|
+
const pname = plugin.name;
|
|
172
188
|
return {
|
|
173
|
-
name:
|
|
189
|
+
name: pname,
|
|
174
190
|
version: plugin.version,
|
|
175
191
|
pluginDir: storageManager.getDirectory(),
|
|
176
192
|
config: pluginConfig,
|
|
@@ -178,34 +194,61 @@ export function buildPluginContext(plugin, storageManager, extensionBridge, plug
|
|
|
178
194
|
extension: extensionBridge,
|
|
179
195
|
mcp: {
|
|
180
196
|
registerTool: (tool) => {
|
|
197
|
+
if (!perms.tools?.add) {
|
|
198
|
+
console.error(`[Plugin: ${pname}] blocked registerTool("${tool?.name}"): permissions.tools.add not declared`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
181
201
|
plugin.tools = plugin.tools || [];
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
plugin.tools[
|
|
202
|
+
const idx = plugin.tools.findIndex((t) => t.name === tool.name);
|
|
203
|
+
if (idx >= 0) {
|
|
204
|
+
plugin.tools[idx] = tool;
|
|
185
205
|
}
|
|
186
206
|
else {
|
|
187
207
|
plugin.tools.push(tool);
|
|
188
208
|
}
|
|
189
209
|
},
|
|
190
210
|
overrideTool: (toolName, overrideHandler) => {
|
|
211
|
+
const allowed = perms.tools?.override ?? [];
|
|
212
|
+
if (!allowed.includes(toolName)) {
|
|
213
|
+
console.error(`[Plugin: ${pname}] blocked overrideTool("${toolName}"): not listed in permissions.tools.override`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
191
216
|
plugin.overrides = plugin.overrides || [];
|
|
192
217
|
plugin.overrides.push({ name: toolName, handler: overrideHandler });
|
|
193
218
|
},
|
|
194
219
|
removeTool: (toolName) => {
|
|
220
|
+
const allowed = perms.tools?.remove ?? [];
|
|
221
|
+
if (!allowed.includes(toolName)) {
|
|
222
|
+
console.error(`[Plugin: ${pname}] blocked removeTool("${toolName}"): not listed in permissions.tools.remove`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
195
225
|
plugin.removeTools = plugin.removeTools || [];
|
|
196
226
|
plugin.removeTools.push(toolName);
|
|
197
227
|
},
|
|
198
228
|
unregisterTool: (toolName) => {
|
|
229
|
+
const allowed = perms.tools?.remove ?? [];
|
|
230
|
+
if (!allowed.includes(toolName)) {
|
|
231
|
+
console.error(`[Plugin: ${pname}] blocked unregisterTool("${toolName}"): not listed in permissions.tools.remove`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
199
234
|
plugin.removeTools = plugin.removeTools || [];
|
|
200
235
|
plugin.removeTools.push(toolName);
|
|
201
236
|
},
|
|
202
237
|
},
|
|
203
238
|
hooks: {
|
|
204
239
|
onBeforeAction: (fn) => {
|
|
240
|
+
if (!perms.hooks?.onBeforeAction) {
|
|
241
|
+
console.error(`[Plugin: ${pname}] blocked onBeforeAction hook: permissions.hooks.onBeforeAction not declared`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
205
244
|
if (typeof fn === "function")
|
|
206
245
|
hooks.beforeToolCall.push(fn);
|
|
207
246
|
},
|
|
208
247
|
onAfterAction: (fn) => {
|
|
248
|
+
if (!perms.hooks?.onAfterAction) {
|
|
249
|
+
console.error(`[Plugin: ${pname}] blocked onAfterAction hook: permissions.hooks.onAfterAction not declared`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
209
252
|
if (typeof fn === "function") {
|
|
210
253
|
hooks.afterToolCall.push(async (toolName, args, result, session) => {
|
|
211
254
|
return await fn(toolName, result, session, args);
|
|
@@ -213,18 +256,34 @@ export function buildPluginContext(plugin, storageManager, extensionBridge, plug
|
|
|
213
256
|
}
|
|
214
257
|
},
|
|
215
258
|
onDOMMutation: (fn) => {
|
|
259
|
+
if (!perms.hooks?.onDOMMutation) {
|
|
260
|
+
console.error(`[Plugin: ${pname}] blocked onDOMMutation hook: permissions.hooks.onDOMMutation not declared`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
216
263
|
if (typeof fn === "function")
|
|
217
264
|
hooks.onDOMMutation?.push(fn);
|
|
218
265
|
},
|
|
219
266
|
beforeToolCall: (fn) => {
|
|
267
|
+
if (!perms.hooks?.onBeforeAction) {
|
|
268
|
+
console.error(`[Plugin: ${pname}] blocked beforeToolCall hook: permissions.hooks.onBeforeAction not declared`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
220
271
|
if (typeof fn === "function")
|
|
221
272
|
hooks.beforeToolCall.push(fn);
|
|
222
273
|
},
|
|
223
274
|
afterToolCall: (fn) => {
|
|
275
|
+
if (!perms.hooks?.onAfterAction) {
|
|
276
|
+
console.error(`[Plugin: ${pname}] blocked afterToolCall hook: permissions.hooks.onAfterAction not declared`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
224
279
|
if (typeof fn === "function")
|
|
225
280
|
hooks.afterToolCall.push(fn);
|
|
226
281
|
},
|
|
227
282
|
onPageNavigate: (fn) => {
|
|
283
|
+
if (!perms.hooks?.onPageNavigate) {
|
|
284
|
+
console.error(`[Plugin: ${pname}] blocked onPageNavigate hook: permissions.hooks.onPageNavigate not declared`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
228
287
|
if (typeof fn === "function")
|
|
229
288
|
hooks.onPageNavigate.push(fn);
|
|
230
289
|
},
|
|
@@ -237,10 +296,10 @@ export function buildPluginContext(plugin, storageManager, extensionBridge, plug
|
|
|
237
296
|
recordQuirk: (origin, quirkKey, quirkData) => recordQuirk(origin, quirkKey, quirkData),
|
|
238
297
|
},
|
|
239
298
|
logger: {
|
|
240
|
-
info: (...args) => console.error(`[Plugin: ${
|
|
241
|
-
warn: (...args) => console.error(`[Plugin: ${
|
|
242
|
-
error: (...args) => console.error(`[Plugin: ${
|
|
243
|
-
debug: (...args) => console.error(`[Plugin: ${
|
|
299
|
+
info: (...args) => console.error(`[Plugin: ${pname}] [INFO]`, ...args),
|
|
300
|
+
warn: (...args) => console.error(`[Plugin: ${pname}] [WARN]`, ...args),
|
|
301
|
+
error: (...args) => console.error(`[Plugin: ${pname}] [ERROR]`, ...args),
|
|
302
|
+
debug: (...args) => console.error(`[Plugin: ${pname}] [DEBUG]`, ...args),
|
|
244
303
|
},
|
|
245
304
|
};
|
|
246
305
|
}
|
|
@@ -288,7 +347,10 @@ function tryLoadPlugin(pkgDir) {
|
|
|
288
347
|
}
|
|
289
348
|
const pluginModule = require(resolvedEntry);
|
|
290
349
|
const plugin = pluginModule.default || pluginModule.plugin || pluginModule;
|
|
291
|
-
const
|
|
350
|
+
const rawPerms = Array.isArray(manifest.permissions)
|
|
351
|
+
? manifest.permissions
|
|
352
|
+
: { ...(plugin?.permissions || {}), ...(manifest?.permissions || {}) };
|
|
353
|
+
const normalizedPerms = normalizePermissions(rawPerms);
|
|
292
354
|
if (plugin &&
|
|
293
355
|
(Array.isArray(plugin.tools) ||
|
|
294
356
|
Array.isArray(plugin.overrides) ||
|
|
@@ -302,9 +364,10 @@ function tryLoadPlugin(pkgDir) {
|
|
|
302
364
|
plugin.version = plugin.version || manifest.version || pkg.version || "1.0.0";
|
|
303
365
|
plugin.description = plugin.description || manifest.description || pkg.description || "";
|
|
304
366
|
plugin.manifest = manifest;
|
|
367
|
+
plugin.permissions = normalizedPerms;
|
|
368
|
+
plugin.manifest.permissions = normalizedPerms;
|
|
305
369
|
plugin.tools = Array.isArray(plugin.tools) ? plugin.tools : [];
|
|
306
370
|
plugin.overrides = Array.isArray(plugin.overrides) ? plugin.overrides : [];
|
|
307
|
-
plugin.manifest.permissions = normalizedPerms;
|
|
308
371
|
plugin.removeTools = Array.isArray(plugin.removeTools)
|
|
309
372
|
? plugin.removeTools
|
|
310
373
|
: normalizedPerms.tools?.remove || [];
|
|
@@ -19,7 +19,8 @@ export interface PluginCandidate {
|
|
|
19
19
|
description?: string;
|
|
20
20
|
}
|
|
21
21
|
export declare const OFFICIAL_PLUGIN_PREFIX = "@weavetab/plugin-";
|
|
22
|
-
export declare const REGISTRY_URL = "https://
|
|
22
|
+
export declare const REGISTRY_URL = "https://weavetab.dev/api/registry/verified.json";
|
|
23
|
+
export declare const REGISTRY_FALLBACK_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
|
|
23
24
|
/**
|
|
24
25
|
* Returns the set of verified package names.
|
|
25
26
|
* - Tries local registry/verified.json first (dev/test mode).
|
package/dist/plugin/registry.js
CHANGED
|
@@ -18,8 +18,9 @@ const __dirname = dirname(__filename);
|
|
|
18
18
|
// Verification Registry
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
export const OFFICIAL_PLUGIN_PREFIX = "@weavetab/plugin-";
|
|
21
|
-
// The production remote
|
|
22
|
-
export const REGISTRY_URL = "https://
|
|
21
|
+
// The production remote URLs — weavetab.dev (D1-backed) primary, GitHub raw fallback.
|
|
22
|
+
export const REGISTRY_URL = "https://weavetab.dev/api/registry/verified.json";
|
|
23
|
+
export const REGISTRY_FALLBACK_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
|
|
23
24
|
// Local override: when this file exists at the root of the repo, we use it instead
|
|
24
25
|
// of the remote URL. This allows full offline/local testing.
|
|
25
26
|
const LOCAL_REGISTRY_PATH = join(__dirname, "..", "..", "..", "registry", "verified.json");
|
|
@@ -59,16 +60,24 @@ function loadLocalRegistry() {
|
|
|
59
60
|
return null;
|
|
60
61
|
}
|
|
61
62
|
async function fetchRemoteRegistry() {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
for (const url of [REGISTRY_URL, REGISTRY_FALLBACK_URL]) {
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(url, {
|
|
66
|
+
signal: AbortSignal.timeout(5000),
|
|
67
|
+
headers: { "User-Agent": "Weavetab-Registry-Check/1.0" },
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok)
|
|
70
|
+
continue;
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
if (!Array.isArray(data.verified))
|
|
73
|
+
continue;
|
|
74
|
+
return extractPackageNames(data.verified);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new Error("registry fetch failed: all sources unreachable");
|
|
72
81
|
}
|
|
73
82
|
/**
|
|
74
83
|
* Returns the set of verified package names.
|
|
@@ -18,6 +18,12 @@ export interface SecretListEntry {
|
|
|
18
18
|
updatedAt: string;
|
|
19
19
|
}
|
|
20
20
|
export declare const GLOBAL_SECRETS_PATH: string;
|
|
21
|
+
export declare const PROJECT_SECRETS_DIRNAME = ".weavetab";
|
|
22
|
+
export declare const PROJECT_SECRETS_FILENAME = "secrets.json";
|
|
23
|
+
/** Absolute path of the project-scoped secrets file for a directory (default: cwd). */
|
|
24
|
+
export declare function getProjectSecretsPath(cwd?: string): string;
|
|
25
|
+
/** Creates an empty project-scoped secrets file (chmod 600). Returns its path. */
|
|
26
|
+
export declare function initProjectSecrets(cwd?: string): string;
|
|
21
27
|
declare class JsonSecretStoreImpl implements SecretProvider {
|
|
22
28
|
/**
|
|
23
29
|
* Resolve a secret value by key.
|
package/dist/security/secrets.js
CHANGED
|
@@ -86,7 +86,31 @@ function decrypt(ciphertext) {
|
|
|
86
86
|
import { GLOBAL_CONFIG_DIR } from "../config/loader.js";
|
|
87
87
|
// ─── Path resolution ──────────────────────────────────────────────────────────
|
|
88
88
|
export const GLOBAL_SECRETS_PATH = path.join(GLOBAL_CONFIG_DIR, "secrets.json");
|
|
89
|
+
export const PROJECT_SECRETS_DIRNAME = ".weavetab";
|
|
90
|
+
export const PROJECT_SECRETS_FILENAME = "secrets.json";
|
|
91
|
+
/** Absolute path of the project-scoped secrets file for a directory (default: cwd). */
|
|
92
|
+
export function getProjectSecretsPath(cwd = process.cwd()) {
|
|
93
|
+
return path.join(cwd, PROJECT_SECRETS_DIRNAME, PROJECT_SECRETS_FILENAME);
|
|
94
|
+
}
|
|
95
|
+
/** Creates an empty project-scoped secrets file (chmod 600). Returns its path. */
|
|
96
|
+
export function initProjectSecrets(cwd = process.cwd()) {
|
|
97
|
+
const filePath = getProjectSecretsPath(cwd);
|
|
98
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
99
|
+
if (!fs.existsSync(filePath)) {
|
|
100
|
+
fs.writeFileSync(filePath, JSON.stringify({ version: 1, secrets: {} }, null, 2), { mode: 0o600 });
|
|
101
|
+
}
|
|
102
|
+
return filePath;
|
|
103
|
+
}
|
|
89
104
|
function resolveSecretsPath() {
|
|
105
|
+
// Project-scoped file wins when present — lets a checkout carry its own keys.
|
|
106
|
+
try {
|
|
107
|
+
const projectPath = getProjectSecretsPath();
|
|
108
|
+
if (fs.existsSync(projectPath))
|
|
109
|
+
return projectPath;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// fall through to global
|
|
113
|
+
}
|
|
90
114
|
return GLOBAL_SECRETS_PATH;
|
|
91
115
|
}
|
|
92
116
|
// ─── Permission guard ─────────────────────────────────────────────────────────
|
package/dist/server.js
CHANGED
|
@@ -53,7 +53,7 @@ import { browserCheckpoint } from "./tools/checkpoint.js";
|
|
|
53
53
|
import { getActiveEngine } from "./cdp/connector.js";
|
|
54
54
|
import { resetLoopCounter } from "./intelligence/trail.js";
|
|
55
55
|
import { getSiteProfile, learnPattern, recordPatternExecution, recordQuirk, getPatternReliability, formatSiteProfileHint } from "./intelligence/patternCompiler.js";
|
|
56
|
-
import { discoverPlugins
|
|
56
|
+
import { discoverPlugins } from "./plugin/loader.js";
|
|
57
57
|
import { hookPipeline } from "./plugin/pipeline.js";
|
|
58
58
|
import { agentAction } from "./sensors/pacing.js";
|
|
59
59
|
import { getCachedConfig, getElectronApps } from "./config/loader.js";
|
|
@@ -1701,22 +1701,32 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1701
1701
|
}
|
|
1702
1702
|
// 1. Tool Removals (disable built-in tools)
|
|
1703
1703
|
const manifestPerms = plugin.manifest?.permissions;
|
|
1704
|
-
const
|
|
1704
|
+
const declaredRemove = Array.isArray(manifestPerms)
|
|
1705
1705
|
? []
|
|
1706
1706
|
: manifestPerms?.tools?.remove || [];
|
|
1707
1707
|
const toRemove = [
|
|
1708
1708
|
...(plugin.removeTools || []),
|
|
1709
|
-
...manifestToolsRemove,
|
|
1710
1709
|
];
|
|
1711
1710
|
for (const toolName of toRemove) {
|
|
1711
|
+
if (!declaredRemove.includes(toolName)) {
|
|
1712
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} tried to remove "${toolName}" but it's not listed in permissions.tools.remove — skipped.\x1b[0m\n`);
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1712
1715
|
if (server._registeredTools?.[toolName]) {
|
|
1713
1716
|
delete server._registeredTools[toolName];
|
|
1714
1717
|
removedNames.push(toolName);
|
|
1715
1718
|
}
|
|
1716
1719
|
}
|
|
1717
1720
|
// 2. Tool Overrides (replace or wrap built-in tools)
|
|
1721
|
+
const declaredOverride = Array.isArray(manifestPerms)
|
|
1722
|
+
? []
|
|
1723
|
+
: manifestPerms?.tools?.override || [];
|
|
1718
1724
|
const overrides = plugin.overrides || [];
|
|
1719
1725
|
for (const ov of overrides) {
|
|
1726
|
+
if (!declaredOverride.includes(ov.name)) {
|
|
1727
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} tried to override "${ov.name}" but it's not listed in permissions.tools.override — skipped.\x1b[0m\n`);
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1720
1730
|
const existingTool = server._registeredTools?.[ov.name];
|
|
1721
1731
|
const existingDesc = existingTool?.description || "";
|
|
1722
1732
|
const existingSchema = existingTool?.parameters?.shape || {};
|
|
@@ -1745,8 +1755,13 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1745
1755
|
overriddenNames.push(ov.name);
|
|
1746
1756
|
}
|
|
1747
1757
|
// 3. Tool Additions (new tools)
|
|
1758
|
+
const canAdd = !Array.isArray(manifestPerms) && manifestPerms?.tools?.add === true;
|
|
1748
1759
|
const newTools = plugin.tools || [];
|
|
1749
1760
|
for (const td of newTools) {
|
|
1761
|
+
if (!canAdd) {
|
|
1762
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} tried to add tool "${td.name}" but permissions.tools.add is not declared — skipped.\x1b[0m\n`);
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1750
1765
|
if (server._registeredTools?.[td.name]) {
|
|
1751
1766
|
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} registered tool "${td.name}" which replaced an existing tool.\x1b[0m\n`);
|
|
1752
1767
|
delete server._registeredTools[td.name];
|
|
@@ -1768,6 +1783,10 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1768
1783
|
const fullPluginCtx = plugin._context;
|
|
1769
1784
|
if (fullPluginCtx && fullPluginCtx.mcp) {
|
|
1770
1785
|
fullPluginCtx.mcp.registerTool = (tool) => {
|
|
1786
|
+
if (!canAdd) {
|
|
1787
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic registerTool("${tool?.name}"): permissions.tools.add not declared — skipped.\x1b[0m\n`);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1771
1790
|
if (server._registeredTools?.[tool.name]) {
|
|
1772
1791
|
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic tool "${tool.name}" replaced an existing tool.\x1b[0m\n`);
|
|
1773
1792
|
delete server._registeredTools[tool.name];
|
|
@@ -1780,6 +1799,10 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1780
1799
|
});
|
|
1781
1800
|
};
|
|
1782
1801
|
fullPluginCtx.mcp.overrideTool = (toolName, overrideHandler) => {
|
|
1802
|
+
if (!declaredOverride.includes(toolName)) {
|
|
1803
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic overrideTool("${toolName}"): not in permissions.tools.override — skipped.\x1b[0m\n`);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1783
1806
|
const existingTool = server._registeredTools?.[toolName];
|
|
1784
1807
|
if (existingTool) {
|
|
1785
1808
|
delete server._registeredTools[toolName];
|
|
@@ -1799,11 +1822,19 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1799
1822
|
});
|
|
1800
1823
|
};
|
|
1801
1824
|
fullPluginCtx.mcp.removeTool = (toolName) => {
|
|
1825
|
+
if (!declaredRemove.includes(toolName)) {
|
|
1826
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic removeTool("${toolName}"): not in permissions.tools.remove — skipped.\x1b[0m\n`);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1802
1829
|
if (server._registeredTools?.[toolName]) {
|
|
1803
1830
|
delete server._registeredTools[toolName];
|
|
1804
1831
|
}
|
|
1805
1832
|
};
|
|
1806
1833
|
fullPluginCtx.mcp.unregisterTool = (toolName) => {
|
|
1834
|
+
if (!declaredRemove.includes(toolName)) {
|
|
1835
|
+
process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic unregisterTool("${toolName}"): not in permissions.tools.remove — skipped.\x1b[0m\n`);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1807
1838
|
if (server._registeredTools?.[toolName]) {
|
|
1808
1839
|
delete server._registeredTools[toolName];
|
|
1809
1840
|
}
|
|
@@ -1830,38 +1861,10 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1830
1861
|
// Ignore if client doesn't support it or not connected yet
|
|
1831
1862
|
}
|
|
1832
1863
|
};
|
|
1833
|
-
//
|
|
1834
|
-
server.tool("list_plugins", "List all available Weavetab plugins that can be loaded.", {}, async () => {
|
|
1835
|
-
const installed = listInstalledPlugins();
|
|
1836
|
-
const available = installed
|
|
1837
|
-
.filter(info => !loadedPluginNames.has(info.packageName))
|
|
1838
|
-
.map(info => `- ${info.packageName}: ${info.manifest?.description || "No description"}`);
|
|
1839
|
-
if (available.length === 0) {
|
|
1840
|
-
return { content: [{ type: "text", text: "No un-loaded plugins available." }] };
|
|
1841
|
-
}
|
|
1842
|
-
return { content: [{ type: "text", text: `Available plugins:\n${available.join("\n")}\n\nUse load_plugin with the plugin package name to load one.` }] };
|
|
1843
|
-
});
|
|
1844
|
-
server.tool("load_plugin", "Load any Weavetab plugin dynamically by npm package name (e.g. '@fy2ne/wt-youtube', 'my-plugin').", {
|
|
1845
|
-
name: z.string().describe("The exact npm package name of the plugin to load (e.g. '@fy2ne/wt-youtube', 'my-plugin')")
|
|
1846
|
-
}, async ({ name }) => {
|
|
1847
|
-
if (loadedPluginNames.has(name)) {
|
|
1848
|
-
return { content: [{ type: "text", text: `Plugin ${name} is already loaded.` }] };
|
|
1849
|
-
}
|
|
1850
|
-
const plugin = loadPluginByName(name);
|
|
1851
|
-
if (!plugin) {
|
|
1852
|
-
return { content: [{ type: "text", text: `Plugin "${name}" not found. Make sure it is installed via "weavetab plugin add ${name}".` }] };
|
|
1853
|
-
}
|
|
1854
|
-
await registerPluginTools(plugin);
|
|
1855
|
-
const toolsList = (plugin.tools || []).map((t) => t.name).join(", ");
|
|
1856
|
-
return { content: [{ type: "text", text: `loaded plugin ${name}. Tools registered: ${toolsList || "(dynamic tools registered)"}` }] };
|
|
1857
|
-
});
|
|
1858
|
-
// Auto-load plugins by default unless explicitly marked with lazy: true
|
|
1864
|
+
// Auto-load all installed plugins at startup
|
|
1859
1865
|
const allPlugins = discoverPlugins();
|
|
1860
1866
|
for (const plugin of allPlugins) {
|
|
1861
|
-
|
|
1862
|
-
if (!isExplicitlyLazy) {
|
|
1863
|
-
await registerPluginTools(plugin);
|
|
1864
|
-
}
|
|
1867
|
+
await registerPluginTools(plugin);
|
|
1865
1868
|
}
|
|
1866
1869
|
let transport;
|
|
1867
1870
|
if (transportMode === "ws") {
|
|
@@ -10,7 +10,7 @@ const ENGINE_COVERAGE = {
|
|
|
10
10
|
"browser_network_intercept",
|
|
11
11
|
"browser_viewport",
|
|
12
12
|
"browser_recording",
|
|
13
|
-
"
|
|
13
|
+
"browser_thoughts",
|
|
14
14
|
"browser_highlight",
|
|
15
15
|
"browser_inspect",
|
|
16
16
|
"browser_snapshot",
|
|
@@ -24,7 +24,7 @@ const ENGINE_COVERAGE = {
|
|
|
24
24
|
"browser_network_intercept",
|
|
25
25
|
"browser_viewport",
|
|
26
26
|
"browser_recording",
|
|
27
|
-
"
|
|
27
|
+
"browser_thoughts",
|
|
28
28
|
"browser_highlight",
|
|
29
29
|
"browser_inspect",
|
|
30
30
|
"browser_snapshot",
|
package/dist/tools/captcha.js
CHANGED
|
@@ -111,7 +111,7 @@ export async function browserCaptcha(session, args, _config) {
|
|
|
111
111
|
user_instructions: solution.userInstructions,
|
|
112
112
|
next_action: isVisionAssisted
|
|
113
113
|
? "Take a screenshot to view the CAPTCHA, then use browser_click or browser_type to solve it yourself. Do not ask the user for help."
|
|
114
|
-
: `Call
|
|
114
|
+
: `Call browser_thoughts({ action: "question", message: "${solution.userInstructions}" }) to ask the user to complete it, then call browser_captcha({ action: "wait_for_user" }).`,
|
|
115
115
|
execution_ms: Date.now() - t0,
|
|
116
116
|
};
|
|
117
117
|
}
|
package/dist/tools/navigate.d.ts
CHANGED