unplugin-devpilot 0.0.4 → 0.0.6

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.
@@ -1,4 +1,4 @@
1
- import { r as resolveModule } from "./plugin-B1Afr0m3.mjs";
1
+ import { n as resolveModule, r as defineMcpToolRegister } from "./plugin-4eB8Zx4B.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import process$1 from "node:process";
4
4
  import { createUnplugin } from "unplugin";
@@ -19799,7 +19799,7 @@ var StreamableHTTPServerTransport = class {
19799
19799
 
19800
19800
  //#endregion
19801
19801
  //#region package.json
19802
- var version = "0.0.4";
19802
+ var version = "0.0.6";
19803
19803
 
19804
19804
  //#endregion
19805
19805
  //#region ../../node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/compat/util/uniqueId.mjs
@@ -19960,6 +19960,590 @@ var ClientManager = class {
19960
19960
  };
19961
19961
  const clientManager = new ClientManager();
19962
19962
 
19963
+ //#endregion
19964
+ //#region src/core/builtin-tools.ts
19965
+ const builtinToolRegisters = [
19966
+ defineMcpToolRegister("list_clients", {
19967
+ title: "List Clients",
19968
+ description: "List all connected browser instances with optional filtering by URL, title, or clientId",
19969
+ inputSchema: {
19970
+ activeOnly: boolean().optional().default(true).describe("Only list active clients"),
19971
+ urlPattern: string().optional().describe("Filter clients by URL pattern (substring match, case-insensitive)"),
19972
+ titlePattern: string().optional().describe("Filter clients by page title pattern (substring match, case-insensitive)"),
19973
+ clientId: string().optional().describe("Filter by specific client ID"),
19974
+ groupByUrl: boolean().optional().default(false).describe("Group results by URL for easier identification")
19975
+ }
19976
+ }, async (params) => {
19977
+ const filter = {
19978
+ activeOnly: params.activeOnly,
19979
+ urlPattern: params.urlPattern,
19980
+ titlePattern: params.titlePattern,
19981
+ clientId: params.clientId
19982
+ };
19983
+ let clients;
19984
+ let grouped;
19985
+ if (params.groupByUrl) {
19986
+ grouped = clientManager.getClientsByUrl();
19987
+ clients = Object.values(grouped).flat();
19988
+ } else clients = clientManager.findClients(filter);
19989
+ const result = {
19990
+ clients,
19991
+ total: clients.length
19992
+ };
19993
+ if (params.groupByUrl) result.grouped = grouped;
19994
+ if (clients.length === 0) {
19995
+ result.suggestions = [
19996
+ "No clients found. Make sure the browser has the devpilot extension loaded.",
19997
+ "Try refreshing the browser page to reconnect.",
19998
+ "Use activeOnly=false to see recently disconnected clients."
19999
+ ];
20000
+ if (params.urlPattern) result.suggestions.push(`No clients match URL pattern: "${params.urlPattern}"`);
20001
+ }
20002
+ return { content: [{
20003
+ type: "text",
20004
+ text: JSON.stringify(result, null, 2)
20005
+ }] };
20006
+ }),
20007
+ defineMcpToolRegister("get_pending_tasks", {
20008
+ title: "Get Pending Tasks",
20009
+ description: "Get pending tasks submitted from browser",
20010
+ inputSchema: { clearAfterFetch: boolean().optional().default(true).describe("Clear tasks after fetching") }
20011
+ }, async (params) => {
20012
+ const tasks = clientManager.getPendingTasks(params.clearAfterFetch);
20013
+ return { content: [{
20014
+ type: "text",
20015
+ text: JSON.stringify({
20016
+ hasTasks: tasks.length > 0,
20017
+ tasks,
20018
+ message: tasks.length > 0 ? `Found ${tasks.length} pending task(s)` : "No pending tasks"
20019
+ }, null, 2)
20020
+ }] };
20021
+ }),
20022
+ defineMcpToolRegister("get_task_history", {
20023
+ title: "Get Task History",
20024
+ description: "Get history of tasks (including completed and failed tasks). Useful for task recovery after page refresh.",
20025
+ inputSchema: {
20026
+ clientId: string().optional().describe("Filter tasks by client ID"),
20027
+ status: _enum([
20028
+ "pending",
20029
+ "in_progress",
20030
+ "completed",
20031
+ "failed"
20032
+ ]).optional().describe("Filter by task status"),
20033
+ limit: number().optional().default(50).describe("Maximum number of tasks to return")
20034
+ }
20035
+ }, async (params) => {
20036
+ const history = clientManager.getTaskHistory({
20037
+ clientId: params.clientId,
20038
+ status: params.status,
20039
+ limit: params.limit
20040
+ });
20041
+ const result = {
20042
+ history,
20043
+ total: history.length,
20044
+ query: params
20045
+ };
20046
+ if (history.length === 0) {
20047
+ result.message = "No task history found.";
20048
+ if (params.clientId) result.suggestions = [`No tasks found for client "${params.clientId}".`, "Try without clientId filter to see all tasks."];
20049
+ }
20050
+ result.groupedByStatus = history.reduce((acc, task) => {
20051
+ acc[task.status] = (acc[task.status] || 0) + 1;
20052
+ return acc;
20053
+ }, {});
20054
+ return { content: [{
20055
+ type: "text",
20056
+ text: JSON.stringify(result, null, 2)
20057
+ }] };
20058
+ })
20059
+ ];
20060
+ function getBuiltinTools() {
20061
+ return builtinToolRegisters.map((r) => r());
20062
+ }
20063
+ function getBuiltinToolNames() {
20064
+ return builtinToolRegisters.map((r) => r().name);
20065
+ }
20066
+
20067
+ //#endregion
20068
+ //#region ../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs
20069
+ const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
20070
+ const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
20071
+ const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
20072
+ function jsonParseTransform(key, value) {
20073
+ if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
20074
+ warnKeyDropped(key);
20075
+ return;
20076
+ }
20077
+ return value;
20078
+ }
20079
+ function warnKeyDropped(key) {
20080
+ console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
20081
+ }
20082
+ function destr(value, options = {}) {
20083
+ if (typeof value !== "string") return value;
20084
+ if (value[0] === "\"" && value[value.length - 1] === "\"" && value.indexOf("\\") === -1) return value.slice(1, -1);
20085
+ const _value = value.trim();
20086
+ if (_value.length <= 9) switch (_value.toLowerCase()) {
20087
+ case "true": return true;
20088
+ case "false": return false;
20089
+ case "undefined": return;
20090
+ case "null": return null;
20091
+ case "nan": return NaN;
20092
+ case "infinity": return Number.POSITIVE_INFINITY;
20093
+ case "-infinity": return Number.NEGATIVE_INFINITY;
20094
+ }
20095
+ if (!JsonSigRx.test(value)) {
20096
+ if (options.strict) throw new SyntaxError("[destr] Invalid JSON");
20097
+ return value;
20098
+ }
20099
+ try {
20100
+ if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
20101
+ if (options.strict) throw new Error("[destr] Possible prototype pollution");
20102
+ return JSON.parse(value, jsonParseTransform);
20103
+ }
20104
+ return JSON.parse(value);
20105
+ } catch (error) {
20106
+ if (options.strict) throw error;
20107
+ return value;
20108
+ }
20109
+ }
20110
+
20111
+ //#endregion
20112
+ //#region ../../node_modules/.pnpm/unstorage@1.17.4/node_modules/unstorage/dist/shared/unstorage.zVDD2mZo.mjs
20113
+ function wrapToPromise(value) {
20114
+ if (!value || typeof value.then !== "function") return Promise.resolve(value);
20115
+ return value;
20116
+ }
20117
+ function asyncCall(function_, ...arguments_) {
20118
+ try {
20119
+ return wrapToPromise(function_(...arguments_));
20120
+ } catch (error) {
20121
+ return Promise.reject(error);
20122
+ }
20123
+ }
20124
+ function isPrimitive(value) {
20125
+ const type = typeof value;
20126
+ return value === null || type !== "object" && type !== "function";
20127
+ }
20128
+ function isPureObject(value) {
20129
+ const proto = Object.getPrototypeOf(value);
20130
+ return !proto || proto.isPrototypeOf(Object);
20131
+ }
20132
+ function stringify(value) {
20133
+ if (isPrimitive(value)) return String(value);
20134
+ if (isPureObject(value) || Array.isArray(value)) return JSON.stringify(value);
20135
+ if (typeof value.toJSON === "function") return stringify(value.toJSON());
20136
+ throw new Error("[unstorage] Cannot stringify value!");
20137
+ }
20138
+ const BASE64_PREFIX = "base64:";
20139
+ function serializeRaw(value) {
20140
+ if (typeof value === "string") return value;
20141
+ return BASE64_PREFIX + base64Encode(value);
20142
+ }
20143
+ function deserializeRaw(value) {
20144
+ if (typeof value !== "string") return value;
20145
+ if (!value.startsWith(BASE64_PREFIX)) return value;
20146
+ return base64Decode(value.slice(7));
20147
+ }
20148
+ function base64Decode(input) {
20149
+ if (globalThis.Buffer) return Buffer.from(input, "base64");
20150
+ return Uint8Array.from(globalThis.atob(input), (c) => c.codePointAt(0));
20151
+ }
20152
+ function base64Encode(input) {
20153
+ if (globalThis.Buffer) return Buffer.from(input).toString("base64");
20154
+ return globalThis.btoa(String.fromCodePoint(...input));
20155
+ }
20156
+ const storageKeyProperties = [
20157
+ "has",
20158
+ "hasItem",
20159
+ "get",
20160
+ "getItem",
20161
+ "getItemRaw",
20162
+ "set",
20163
+ "setItem",
20164
+ "setItemRaw",
20165
+ "del",
20166
+ "remove",
20167
+ "removeItem",
20168
+ "getMeta",
20169
+ "setMeta",
20170
+ "removeMeta",
20171
+ "getKeys",
20172
+ "clear",
20173
+ "mount",
20174
+ "unmount"
20175
+ ];
20176
+ function prefixStorage(storage, base) {
20177
+ base = normalizeBaseKey(base);
20178
+ if (!base) return storage;
20179
+ const nsStorage = { ...storage };
20180
+ for (const property of storageKeyProperties) nsStorage[property] = (key = "", ...args) => storage[property](base + key, ...args);
20181
+ nsStorage.getKeys = (key = "", ...arguments_) => storage.getKeys(base + key, ...arguments_).then((keys) => keys.map((key2) => key2.slice(base.length)));
20182
+ nsStorage.keys = nsStorage.getKeys;
20183
+ nsStorage.getItems = async (items, commonOptions) => {
20184
+ const prefixedItems = items.map((item) => typeof item === "string" ? base + item : {
20185
+ ...item,
20186
+ key: base + item.key
20187
+ });
20188
+ return (await storage.getItems(prefixedItems, commonOptions)).map((entry) => ({
20189
+ key: entry.key.slice(base.length),
20190
+ value: entry.value
20191
+ }));
20192
+ };
20193
+ nsStorage.setItems = async (items, commonOptions) => {
20194
+ const prefixedItems = items.map((item) => ({
20195
+ key: base + item.key,
20196
+ value: item.value,
20197
+ options: item.options
20198
+ }));
20199
+ return storage.setItems(prefixedItems, commonOptions);
20200
+ };
20201
+ return nsStorage;
20202
+ }
20203
+ function normalizeKey(key) {
20204
+ if (!key) return "";
20205
+ return key.split("?")[0]?.replace(/[/\\]/g, ":").replace(/:+/g, ":").replace(/^:|:$/g, "") || "";
20206
+ }
20207
+ function joinKeys(...keys) {
20208
+ return normalizeKey(keys.join(":"));
20209
+ }
20210
+ function normalizeBaseKey(base) {
20211
+ base = normalizeKey(base);
20212
+ return base ? base + ":" : "";
20213
+ }
20214
+ function filterKeyByDepth(key, depth) {
20215
+ if (depth === void 0) return true;
20216
+ let substrCount = 0;
20217
+ let index = key.indexOf(":");
20218
+ while (index > -1) {
20219
+ substrCount++;
20220
+ index = key.indexOf(":", index + 1);
20221
+ }
20222
+ return substrCount <= depth;
20223
+ }
20224
+ function filterKeyByBase(key, base) {
20225
+ if (base) return key.startsWith(base) && key[key.length - 1] !== "$";
20226
+ return key[key.length - 1] !== "$";
20227
+ }
20228
+
20229
+ //#endregion
20230
+ //#region ../../node_modules/.pnpm/unstorage@1.17.4/node_modules/unstorage/dist/index.mjs
20231
+ function defineDriver(factory) {
20232
+ return factory;
20233
+ }
20234
+ const DRIVER_NAME = "memory";
20235
+ const memory = defineDriver(() => {
20236
+ const data = /* @__PURE__ */ new Map();
20237
+ return {
20238
+ name: DRIVER_NAME,
20239
+ getInstance: () => data,
20240
+ hasItem(key) {
20241
+ return data.has(key);
20242
+ },
20243
+ getItem(key) {
20244
+ return data.get(key) ?? null;
20245
+ },
20246
+ getItemRaw(key) {
20247
+ return data.get(key) ?? null;
20248
+ },
20249
+ setItem(key, value) {
20250
+ data.set(key, value);
20251
+ },
20252
+ setItemRaw(key, value) {
20253
+ data.set(key, value);
20254
+ },
20255
+ removeItem(key) {
20256
+ data.delete(key);
20257
+ },
20258
+ getKeys() {
20259
+ return [...data.keys()];
20260
+ },
20261
+ clear() {
20262
+ data.clear();
20263
+ },
20264
+ dispose() {
20265
+ data.clear();
20266
+ }
20267
+ };
20268
+ });
20269
+ function createStorage(options = {}) {
20270
+ const context = {
20271
+ mounts: { "": options.driver || memory() },
20272
+ mountpoints: [""],
20273
+ watching: false,
20274
+ watchListeners: [],
20275
+ unwatch: {}
20276
+ };
20277
+ const getMount = (key) => {
20278
+ for (const base of context.mountpoints) if (key.startsWith(base)) return {
20279
+ base,
20280
+ relativeKey: key.slice(base.length),
20281
+ driver: context.mounts[base]
20282
+ };
20283
+ return {
20284
+ base: "",
20285
+ relativeKey: key,
20286
+ driver: context.mounts[""]
20287
+ };
20288
+ };
20289
+ const getMounts = (base, includeParent) => {
20290
+ return context.mountpoints.filter((mountpoint) => mountpoint.startsWith(base) || includeParent && base.startsWith(mountpoint)).map((mountpoint) => ({
20291
+ relativeBase: base.length > mountpoint.length ? base.slice(mountpoint.length) : void 0,
20292
+ mountpoint,
20293
+ driver: context.mounts[mountpoint]
20294
+ }));
20295
+ };
20296
+ const onChange = (event, key) => {
20297
+ if (!context.watching) return;
20298
+ key = normalizeKey(key);
20299
+ for (const listener of context.watchListeners) listener(event, key);
20300
+ };
20301
+ const startWatch = async () => {
20302
+ if (context.watching) return;
20303
+ context.watching = true;
20304
+ for (const mountpoint in context.mounts) context.unwatch[mountpoint] = await watch(context.mounts[mountpoint], onChange, mountpoint);
20305
+ };
20306
+ const stopWatch = async () => {
20307
+ if (!context.watching) return;
20308
+ for (const mountpoint in context.unwatch) await context.unwatch[mountpoint]();
20309
+ context.unwatch = {};
20310
+ context.watching = false;
20311
+ };
20312
+ const runBatch = (items, commonOptions, cb) => {
20313
+ const batches = /* @__PURE__ */ new Map();
20314
+ const getBatch = (mount) => {
20315
+ let batch = batches.get(mount.base);
20316
+ if (!batch) {
20317
+ batch = {
20318
+ driver: mount.driver,
20319
+ base: mount.base,
20320
+ items: []
20321
+ };
20322
+ batches.set(mount.base, batch);
20323
+ }
20324
+ return batch;
20325
+ };
20326
+ for (const item of items) {
20327
+ const isStringItem = typeof item === "string";
20328
+ const key = normalizeKey(isStringItem ? item : item.key);
20329
+ const value = isStringItem ? void 0 : item.value;
20330
+ const options2 = isStringItem || !item.options ? commonOptions : {
20331
+ ...commonOptions,
20332
+ ...item.options
20333
+ };
20334
+ const mount = getMount(key);
20335
+ getBatch(mount).items.push({
20336
+ key,
20337
+ value,
20338
+ relativeKey: mount.relativeKey,
20339
+ options: options2
20340
+ });
20341
+ }
20342
+ return Promise.all([...batches.values()].map((batch) => cb(batch))).then((r) => r.flat());
20343
+ };
20344
+ const storage = {
20345
+ hasItem(key, opts = {}) {
20346
+ key = normalizeKey(key);
20347
+ const { relativeKey, driver } = getMount(key);
20348
+ return asyncCall(driver.hasItem, relativeKey, opts);
20349
+ },
20350
+ getItem(key, opts = {}) {
20351
+ key = normalizeKey(key);
20352
+ const { relativeKey, driver } = getMount(key);
20353
+ return asyncCall(driver.getItem, relativeKey, opts).then((value) => destr(value));
20354
+ },
20355
+ getItems(items, commonOptions = {}) {
20356
+ return runBatch(items, commonOptions, (batch) => {
20357
+ if (batch.driver.getItems) return asyncCall(batch.driver.getItems, batch.items.map((item) => ({
20358
+ key: item.relativeKey,
20359
+ options: item.options
20360
+ })), commonOptions).then((r) => r.map((item) => ({
20361
+ key: joinKeys(batch.base, item.key),
20362
+ value: destr(item.value)
20363
+ })));
20364
+ return Promise.all(batch.items.map((item) => {
20365
+ return asyncCall(batch.driver.getItem, item.relativeKey, item.options).then((value) => ({
20366
+ key: item.key,
20367
+ value: destr(value)
20368
+ }));
20369
+ }));
20370
+ });
20371
+ },
20372
+ getItemRaw(key, opts = {}) {
20373
+ key = normalizeKey(key);
20374
+ const { relativeKey, driver } = getMount(key);
20375
+ if (driver.getItemRaw) return asyncCall(driver.getItemRaw, relativeKey, opts);
20376
+ return asyncCall(driver.getItem, relativeKey, opts).then((value) => deserializeRaw(value));
20377
+ },
20378
+ async setItem(key, value, opts = {}) {
20379
+ if (value === void 0) return storage.removeItem(key);
20380
+ key = normalizeKey(key);
20381
+ const { relativeKey, driver } = getMount(key);
20382
+ if (!driver.setItem) return;
20383
+ await asyncCall(driver.setItem, relativeKey, stringify(value), opts);
20384
+ if (!driver.watch) onChange("update", key);
20385
+ },
20386
+ async setItems(items, commonOptions) {
20387
+ await runBatch(items, commonOptions, async (batch) => {
20388
+ if (batch.driver.setItems) return asyncCall(batch.driver.setItems, batch.items.map((item) => ({
20389
+ key: item.relativeKey,
20390
+ value: stringify(item.value),
20391
+ options: item.options
20392
+ })), commonOptions);
20393
+ if (!batch.driver.setItem) return;
20394
+ await Promise.all(batch.items.map((item) => {
20395
+ return asyncCall(batch.driver.setItem, item.relativeKey, stringify(item.value), item.options);
20396
+ }));
20397
+ });
20398
+ },
20399
+ async setItemRaw(key, value, opts = {}) {
20400
+ if (value === void 0) return storage.removeItem(key, opts);
20401
+ key = normalizeKey(key);
20402
+ const { relativeKey, driver } = getMount(key);
20403
+ if (driver.setItemRaw) await asyncCall(driver.setItemRaw, relativeKey, value, opts);
20404
+ else if (driver.setItem) await asyncCall(driver.setItem, relativeKey, serializeRaw(value), opts);
20405
+ else return;
20406
+ if (!driver.watch) onChange("update", key);
20407
+ },
20408
+ async removeItem(key, opts = {}) {
20409
+ if (typeof opts === "boolean") opts = { removeMeta: opts };
20410
+ key = normalizeKey(key);
20411
+ const { relativeKey, driver } = getMount(key);
20412
+ if (!driver.removeItem) return;
20413
+ await asyncCall(driver.removeItem, relativeKey, opts);
20414
+ if (opts.removeMeta || opts.removeMata) await asyncCall(driver.removeItem, relativeKey + "$", opts);
20415
+ if (!driver.watch) onChange("remove", key);
20416
+ },
20417
+ async getMeta(key, opts = {}) {
20418
+ if (typeof opts === "boolean") opts = { nativeOnly: opts };
20419
+ key = normalizeKey(key);
20420
+ const { relativeKey, driver } = getMount(key);
20421
+ const meta = /* @__PURE__ */ Object.create(null);
20422
+ if (driver.getMeta) Object.assign(meta, await asyncCall(driver.getMeta, relativeKey, opts));
20423
+ if (!opts.nativeOnly) {
20424
+ const value = await asyncCall(driver.getItem, relativeKey + "$", opts).then((value_) => destr(value_));
20425
+ if (value && typeof value === "object") {
20426
+ if (typeof value.atime === "string") value.atime = new Date(value.atime);
20427
+ if (typeof value.mtime === "string") value.mtime = new Date(value.mtime);
20428
+ Object.assign(meta, value);
20429
+ }
20430
+ }
20431
+ return meta;
20432
+ },
20433
+ setMeta(key, value, opts = {}) {
20434
+ return this.setItem(key + "$", value, opts);
20435
+ },
20436
+ removeMeta(key, opts = {}) {
20437
+ return this.removeItem(key + "$", opts);
20438
+ },
20439
+ async getKeys(base, opts = {}) {
20440
+ base = normalizeBaseKey(base);
20441
+ const mounts = getMounts(base, true);
20442
+ let maskedMounts = [];
20443
+ const allKeys = [];
20444
+ let allMountsSupportMaxDepth = true;
20445
+ for (const mount of mounts) {
20446
+ if (!mount.driver.flags?.maxDepth) allMountsSupportMaxDepth = false;
20447
+ const rawKeys = await asyncCall(mount.driver.getKeys, mount.relativeBase, opts);
20448
+ for (const key of rawKeys) {
20449
+ const fullKey = mount.mountpoint + normalizeKey(key);
20450
+ if (!maskedMounts.some((p) => fullKey.startsWith(p))) allKeys.push(fullKey);
20451
+ }
20452
+ maskedMounts = [mount.mountpoint, ...maskedMounts.filter((p) => !p.startsWith(mount.mountpoint))];
20453
+ }
20454
+ const shouldFilterByDepth = opts.maxDepth !== void 0 && !allMountsSupportMaxDepth;
20455
+ return allKeys.filter((key) => (!shouldFilterByDepth || filterKeyByDepth(key, opts.maxDepth)) && filterKeyByBase(key, base));
20456
+ },
20457
+ async clear(base, opts = {}) {
20458
+ base = normalizeBaseKey(base);
20459
+ await Promise.all(getMounts(base, false).map(async (m) => {
20460
+ if (m.driver.clear) return asyncCall(m.driver.clear, m.relativeBase, opts);
20461
+ if (m.driver.removeItem) {
20462
+ const keys = await m.driver.getKeys(m.relativeBase || "", opts);
20463
+ return Promise.all(keys.map((key) => m.driver.removeItem(key, opts)));
20464
+ }
20465
+ }));
20466
+ },
20467
+ async dispose() {
20468
+ await Promise.all(Object.values(context.mounts).map((driver) => dispose(driver)));
20469
+ },
20470
+ async watch(callback) {
20471
+ await startWatch();
20472
+ context.watchListeners.push(callback);
20473
+ return async () => {
20474
+ context.watchListeners = context.watchListeners.filter((listener) => listener !== callback);
20475
+ if (context.watchListeners.length === 0) await stopWatch();
20476
+ };
20477
+ },
20478
+ async unwatch() {
20479
+ context.watchListeners = [];
20480
+ await stopWatch();
20481
+ },
20482
+ mount(base, driver) {
20483
+ base = normalizeBaseKey(base);
20484
+ if (base && context.mounts[base]) throw new Error(`already mounted at ${base}`);
20485
+ if (base) {
20486
+ context.mountpoints.push(base);
20487
+ context.mountpoints.sort((a, b) => b.length - a.length);
20488
+ }
20489
+ context.mounts[base] = driver;
20490
+ if (context.watching) Promise.resolve(watch(driver, onChange, base)).then((unwatcher) => {
20491
+ context.unwatch[base] = unwatcher;
20492
+ }).catch(console.error);
20493
+ return storage;
20494
+ },
20495
+ async unmount(base, _dispose = true) {
20496
+ base = normalizeBaseKey(base);
20497
+ if (!base || !context.mounts[base]) return;
20498
+ if (context.watching && base in context.unwatch) {
20499
+ context.unwatch[base]?.();
20500
+ delete context.unwatch[base];
20501
+ }
20502
+ if (_dispose) await dispose(context.mounts[base]);
20503
+ context.mountpoints = context.mountpoints.filter((key) => key !== base);
20504
+ delete context.mounts[base];
20505
+ },
20506
+ getMount(key = "") {
20507
+ key = normalizeKey(key) + ":";
20508
+ const m = getMount(key);
20509
+ return {
20510
+ driver: m.driver,
20511
+ base: m.base
20512
+ };
20513
+ },
20514
+ getMounts(base = "", opts = {}) {
20515
+ base = normalizeKey(base);
20516
+ return getMounts(base, opts.parents).map((m) => ({
20517
+ driver: m.driver,
20518
+ base: m.mountpoint
20519
+ }));
20520
+ },
20521
+ keys: (base, opts = {}) => storage.getKeys(base, opts),
20522
+ get: (key, opts = {}) => storage.getItem(key, opts),
20523
+ set: (key, value, opts = {}) => storage.setItem(key, value, opts),
20524
+ has: (key, opts = {}) => storage.hasItem(key, opts),
20525
+ del: (key, opts = {}) => storage.removeItem(key, opts),
20526
+ remove: (key, opts = {}) => storage.removeItem(key, opts)
20527
+ };
20528
+ return storage;
20529
+ }
20530
+ function watch(driver, onChange, base) {
20531
+ return driver.watch ? driver.watch((event, key) => onChange(event, base + key)) : () => {};
20532
+ }
20533
+ async function dispose(driver) {
20534
+ if (typeof driver.dispose === "function") await asyncCall(driver.dispose);
20535
+ }
20536
+
20537
+ //#endregion
20538
+ //#region src/core/storage.ts
20539
+ const storage = createStorage();
20540
+ function getPluginStorage(namespace) {
20541
+ return prefixStorage(storage, namespace);
20542
+ }
20543
+ async function disposeStorage() {
20544
+ await storage.dispose();
20545
+ }
20546
+
19963
20547
  //#endregion
19964
20548
  //#region src/core/mcp-server.ts
19965
20549
  let httpServer = null;
@@ -19968,9 +20552,12 @@ let mcpRegeisterMethods = {};
19968
20552
  * Register plugin server methods
19969
20553
  */
19970
20554
  function registerPluginMcpRegisterMethods(plugins) {
19971
- const ctx = { wsPort: 0 };
19972
20555
  mcpRegeisterMethods = {};
19973
20556
  for (const plugin of plugins) if (plugin.mcpSetup) try {
20557
+ const ctx = {
20558
+ wsPort: 0,
20559
+ storage: getPluginStorage(plugin.namespace)
20560
+ };
19974
20561
  const mcps = plugin.mcpSetup(ctx);
19975
20562
  mcpRegeisterMethods[plugin.namespace] = mcps;
19976
20563
  } catch (error) {
@@ -19987,159 +20574,7 @@ async function startMcpServer(port) {
19987
20574
  name: "unplugin-devpilot",
19988
20575
  version
19989
20576
  });
19990
- mcpServer.registerTool("list_clients", {
19991
- title: "List Clients",
19992
- description: "List all connected browser instances with optional filtering by URL, title, or clientId",
19993
- inputSchema: {
19994
- activeOnly: boolean().optional().default(true).describe("Only list active clients"),
19995
- urlPattern: string().optional().describe("Filter clients by URL pattern (substring match, case-insensitive)"),
19996
- titlePattern: string().optional().describe("Filter clients by page title pattern (substring match, case-insensitive)"),
19997
- clientId: string().optional().describe("Filter by specific client ID"),
19998
- groupByUrl: boolean().optional().default(false).describe("Group results by URL for easier identification")
19999
- }
20000
- }, async (params) => {
20001
- const filter = {
20002
- activeOnly: params.activeOnly,
20003
- urlPattern: params.urlPattern,
20004
- titlePattern: params.titlePattern,
20005
- clientId: params.clientId
20006
- };
20007
- let clients;
20008
- let grouped;
20009
- if (params.groupByUrl) {
20010
- grouped = clientManager.getClientsByUrl();
20011
- clients = Object.values(grouped).flat();
20012
- } else clients = clientManager.findClients(filter);
20013
- const result = {
20014
- clients,
20015
- total: clients.length
20016
- };
20017
- if (params.groupByUrl) result.grouped = grouped;
20018
- if (clients.length === 0) {
20019
- result.suggestions = [
20020
- "No clients found. Make sure the browser has the devpilot extension loaded.",
20021
- "Try refreshing the browser page to reconnect.",
20022
- "Use activeOnly=false to see recently disconnected clients."
20023
- ];
20024
- if (params.urlPattern) result.suggestions.push(`No clients match URL pattern: "${params.urlPattern}"`);
20025
- }
20026
- return { content: [{
20027
- type: "text",
20028
- text: JSON.stringify(result, null, 2)
20029
- }] };
20030
- });
20031
- mcpServer.registerTool("get_pending_tasks", {
20032
- title: "Get Pending Tasks",
20033
- description: "Get pending tasks submitted from browser",
20034
- inputSchema: { clearAfterFetch: boolean().optional().default(true).describe("Clear tasks after fetching") }
20035
- }, async (params) => {
20036
- const tasks = clientManager.getPendingTasks(params.clearAfterFetch);
20037
- return { content: [{
20038
- type: "text",
20039
- text: JSON.stringify({
20040
- hasTasks: tasks.length > 0,
20041
- tasks,
20042
- message: tasks.length > 0 ? `Found ${tasks.length} pending task(s)` : "No pending tasks"
20043
- }, null, 2)
20044
- }] };
20045
- });
20046
- mcpServer.registerTool("find_clients_by_url", {
20047
- title: "Find Clients by URL",
20048
- description: "Find browser clients matching a URL pattern. Useful for reconnecting to specific pages after refresh.",
20049
- inputSchema: {
20050
- urlPattern: string().describe("URL pattern to search for (substring match, case-insensitive)"),
20051
- exactMatch: boolean().optional().default(false).describe("Require exact URL match instead of substring")
20052
- }
20053
- }, async (params) => {
20054
- const { urlPattern, exactMatch } = params;
20055
- const allClients = clientManager.getAllClients(true);
20056
- let matchedClients;
20057
- if (exactMatch) matchedClients = allClients.filter((c) => c.url === urlPattern);
20058
- else {
20059
- const patternLower = urlPattern.toLowerCase();
20060
- matchedClients = allClients.filter((c) => c.url.toLowerCase().includes(patternLower));
20061
- }
20062
- const result = {
20063
- matchedClients,
20064
- total: matchedClients.length,
20065
- query: {
20066
- urlPattern,
20067
- exactMatch
20068
- }
20069
- };
20070
- if (matchedClients.length === 0) result.suggestions = [
20071
- "No clients found matching this URL.",
20072
- "Try refreshing the browser page to reconnect.",
20073
- "Available URLs:",
20074
- ...allClients.map((c) => c.url).filter(Boolean).slice(0, 5).map((u) => ` - ${u}`)
20075
- ];
20076
- else if (matchedClients.length === 1) result.suggestion = `Use clientId "${matchedClients[0].clientId}" to target this client in other tools.`;
20077
- else result.suggestion = "Multiple clients found. Use clientId parameter to specify which one to target.";
20078
- return { content: [{
20079
- type: "text",
20080
- text: JSON.stringify(result, null, 2)
20081
- }] };
20082
- });
20083
- mcpServer.registerTool("get_task_history", {
20084
- title: "Get Task History",
20085
- description: "Get history of tasks (including completed and failed tasks). Useful for task recovery after page refresh.",
20086
- inputSchema: {
20087
- clientId: string().optional().describe("Filter tasks by client ID"),
20088
- status: _enum([
20089
- "pending",
20090
- "in_progress",
20091
- "completed",
20092
- "failed"
20093
- ]).optional().describe("Filter by task status"),
20094
- limit: number().optional().default(50).describe("Maximum number of tasks to return")
20095
- }
20096
- }, async (params) => {
20097
- const history = clientManager.getTaskHistory({
20098
- clientId: params.clientId,
20099
- status: params.status,
20100
- limit: params.limit
20101
- });
20102
- const result = {
20103
- history,
20104
- total: history.length,
20105
- query: params
20106
- };
20107
- if (history.length === 0) {
20108
- result.message = "No task history found.";
20109
- if (params.clientId) result.suggestions = [`No tasks found for client "${params.clientId}".`, "Try without clientId filter to see all tasks."];
20110
- }
20111
- result.groupedByStatus = history.reduce((acc, task) => {
20112
- acc[task.status] = (acc[task.status] || 0) + 1;
20113
- return acc;
20114
- }, {});
20115
- return { content: [{
20116
- type: "text",
20117
- text: JSON.stringify(result, null, 2)
20118
- }] };
20119
- });
20120
- mcpServer.registerTool("find_clients_by_title", {
20121
- title: "Find Clients by Title",
20122
- description: "Find browser clients by page title. Useful when URL is not unique or helpful.",
20123
- inputSchema: { titlePattern: string().describe("Title pattern to search for (substring match, case-insensitive)") }
20124
- }, async (params) => {
20125
- const { titlePattern } = params;
20126
- const patternLower = titlePattern.toLowerCase();
20127
- const matchedClients = clientManager.getAllClients(true).filter((c) => c.title.toLowerCase().includes(patternLower));
20128
- const result = {
20129
- matchedClients,
20130
- total: matchedClients.length,
20131
- query: { titlePattern }
20132
- };
20133
- if (matchedClients.length === 0) result.suggestions = [
20134
- "No clients found matching this title.",
20135
- "Available titles:",
20136
- ...clientManager.getAllClients(true).map((c) => c.title).filter(Boolean).slice(0, 5).map((t) => ` - ${t}`)
20137
- ];
20138
- return { content: [{
20139
- type: "text",
20140
- text: JSON.stringify(result, null, 2)
20141
- }] };
20142
- });
20577
+ for (const { name, config, cb } of getBuiltinTools()) mcpServer.registerTool(name, config, cb);
20143
20578
  Object.entries(mcpRegeisterMethods).forEach(([namespace, mcpRegeisters]) => {
20144
20579
  mcpRegeisters.forEach((mcpRegeister) => {
20145
20580
  const { name: _name, config, cb } = mcpRegeister();
@@ -20363,7 +20798,7 @@ async function resolveOptions(options) {
20363
20798
  wsPort,
20364
20799
  mcpPort,
20365
20800
  plugins: options.plugins || [],
20366
- skillCorePath: options.skillCorePath
20801
+ skillPaths: options.skillPaths || []
20367
20802
  };
20368
20803
  }
20369
20804
  async function resolveWsPort(preferred) {
@@ -20414,19 +20849,22 @@ function isDirectoryPath(path) {
20414
20849
  }
20415
20850
  /**
20416
20851
  * Get the core skill file path, handling both directory and file paths
20417
- * @param skillCorePath - The configured skill core path (directory or file)
20852
+ * @param skillPath - The configured skill core path (directory or file)
20418
20853
  * @returns The actual file path to use for the core skill file
20419
20854
  */
20420
- function getCoreSkillFilePath(skillCorePath) {
20421
- if (isDirectoryPath(skillCorePath)) return join(skillCorePath, "SKILL.md");
20422
- return skillCorePath;
20855
+ function getCoreSkillFilePath(skillPath) {
20856
+ if (isDirectoryPath(skillPath)) return join(skillPath, "SKILL.md");
20857
+ return skillPath;
20423
20858
  }
20424
20859
  /**
20425
20860
  * Get all plugin skill modules
20426
20861
  */
20427
20862
  function getPluginSkillModules(plugins, options) {
20428
- const ctx = { wsPort: options.wsPort };
20429
20863
  return plugins.filter((p) => p.skillModule).map((p) => {
20864
+ const ctx = {
20865
+ wsPort: options.wsPort,
20866
+ storage: getPluginStorage(p.namespace)
20867
+ };
20430
20868
  const mod = typeof p.skillModule === "function" ? p.skillModule(ctx) : p.skillModule;
20431
20869
  let skillPath;
20432
20870
  if (mod.startsWith("file://")) skillPath = fileURLToPath(mod);
@@ -20439,18 +20877,35 @@ function getPluginSkillModules(plugins, options) {
20439
20877
  };
20440
20878
  });
20441
20879
  }
20880
+ function collectAllowedTools(plugins, options) {
20881
+ const tools = [...getBuiltinToolNames()];
20882
+ for (const plugin of plugins) if (plugin.mcpSetup) try {
20883
+ const ctx = {
20884
+ wsPort: options.wsPort,
20885
+ storage: getPluginStorage(plugin.namespace)
20886
+ };
20887
+ const mcps = plugin.mcpSetup(ctx);
20888
+ for (const register of mcps) {
20889
+ const result = register();
20890
+ if (result.name) tools.push(result.name);
20891
+ }
20892
+ } catch {}
20893
+ return tools;
20894
+ }
20895
+ function generateFrontmatter(options) {
20896
+ const allowedTools = collectAllowedTools(options.plugins, options);
20897
+ return `---
20898
+ name: devpilot
20899
+ description: Devpilot core skill that aggregates all plugin skills for web application interaction and debugging.
20900
+ ${allowedTools.length > 0 ? `allowed-tools: [\n${allowedTools.map((t) => ` "${t}"`).join(",\n")}\n]` : "allowed-tools: []"}
20901
+ ---`;
20902
+ }
20442
20903
  /**
20443
20904
  * Generate the core skill markdown content
20444
20905
  */
20445
20906
  function generateCoreSkillContent(options, isDev) {
20446
20907
  if (!isDev) return "";
20447
- return `# Devpilot Core Skills
20448
-
20449
- This is the core skill file that aggregates all plugin skills.
20450
-
20451
- ## Available Skills
20452
-
20453
- ${getPluginSkillModules(options.plugins, options).map((skill) => {
20908
+ const skillList = getPluginSkillModules(options.plugins, options).map((skill) => {
20454
20909
  if (skill.originalSkillModule.startsWith("file://")) {
20455
20910
  const linkPath = `./${skill.namespace}.md`;
20456
20911
  return `- [${skill.namespace}](${linkPath}) - ${skill.namespace} capabilities`;
@@ -20459,7 +20914,16 @@ ${getPluginSkillModules(options.plugins, options).map((skill) => {
20459
20914
  const linkPath = `./${skill.namespace}.md`;
20460
20915
  return `- [${skill.namespace}](${linkPath}) - ${skill.namespace} capabilities`;
20461
20916
  }
20462
- }).join("\n") || "No plugin skills configured"}
20917
+ }).join("\n");
20918
+ return `${generateFrontmatter(options)}
20919
+
20920
+ # Devpilot Core Skills
20921
+
20922
+ This is the core skill file that aggregates all plugin skills.
20923
+
20924
+ ## Available Skills
20925
+
20926
+ ${skillList || "No plugin skills configured"}
20463
20927
 
20464
20928
  ## Usage
20465
20929
 
@@ -20467,42 +20931,79 @@ These skills can be used with Claude Agent to interact with web applications.
20467
20931
 
20468
20932
  ## Configuration
20469
20933
 
20470
- - **Core Skill Path**: ${options.skillCorePath || "Not configured"}
20471
20934
  - **Plugins**: ${options.plugins.length}
20472
20935
  - **WebSocket Port**: ${options.wsPort}
20473
20936
  - **MCP Port**: ${options.mcpPort}
20474
20937
  `;
20475
20938
  }
20476
20939
  /**
20477
- * Generate and write the core skill file
20940
+ * Generate and write the core skill files to all configured paths
20478
20941
  */
20479
20942
  async function generateCoreSkill(options, isDev) {
20480
- if (!options.skillCorePath) return;
20481
- const skillFilePath = getCoreSkillFilePath(options.skillCorePath);
20943
+ if (!options.skillPaths || options.skillPaths.length === 0) return;
20482
20944
  const content = generateCoreSkillContent(options, isDev);
20483
- if (!isDev || !content) {
20945
+ const pluginSkills = getPluginSkillModules(options.plugins, options);
20946
+ for (const skillPath of options.skillPaths) {
20947
+ const skillFilePath = getCoreSkillFilePath(skillPath);
20948
+ const dir = dirname(skillFilePath);
20949
+ if (!isDev || !content) {
20950
+ for (const skill of pluginSkills) if (skill.originalSkillModule.startsWith("file://")) try {
20951
+ await promises.unlink(join(dir, `${skill.namespace}.md`));
20952
+ } catch {}
20953
+ try {
20954
+ await promises.unlink(skillFilePath);
20955
+ } catch {}
20956
+ continue;
20957
+ }
20958
+ await promises.mkdir(dir, { recursive: true });
20959
+ for (const skill of pluginSkills) if (skill.originalSkillModule.startsWith("file://")) await copyPluginSkillFile(skill.path, join(dir, `${skill.namespace}.md`));
20960
+ let existingContent;
20484
20961
  try {
20485
- await promises.unlink(skillFilePath);
20962
+ existingContent = await promises.readFile(skillFilePath, "utf-8");
20963
+ } catch {}
20964
+ if (existingContent !== content) await promises.writeFile(skillFilePath, content, "utf-8");
20965
+ await setPermissionsRecursive(dir).catch(() => {});
20966
+ }
20967
+ }
20968
+ async function setPermissionsRecursive(dirPath) {
20969
+ const isRoot = process$1.getuid?.() === 0;
20970
+ const sudoUid = Number(process$1.env.SUDO_UID);
20971
+ const sudoGid = Number(process$1.env.SUDO_GID);
20972
+ const shouldChown = isRoot && !Number.isNaN(sudoUid) && !Number.isNaN(sudoGid);
20973
+ async function applyPermissions(targetPath, mode) {
20974
+ try {
20975
+ await promises.chmod(targetPath, mode);
20976
+ if (shouldChown) await promises.chown(targetPath, sudoUid, sudoGid);
20486
20977
  } catch {}
20487
- return;
20488
20978
  }
20489
- let existingContent;
20979
+ async function walk(currentPath) {
20980
+ await applyPermissions(currentPath, 511);
20981
+ try {
20982
+ const entries = await promises.readdir(currentPath, { withFileTypes: true });
20983
+ for (const entry of entries) {
20984
+ const fullPath = join(currentPath, entry.name);
20985
+ if (entry.isDirectory()) await walk(fullPath);
20986
+ else await applyPermissions(fullPath, 438);
20987
+ }
20988
+ } catch {}
20989
+ }
20990
+ await walk(dirPath);
20991
+ }
20992
+ async function copyPluginSkillFile(sourcePath, destPath) {
20490
20993
  try {
20491
- existingContent = await promises.readFile(skillFilePath, "utf-8");
20492
- } catch {}
20493
- if (existingContent === content) return;
20494
- const dir = dirname(skillFilePath);
20495
- await promises.mkdir(dir, { recursive: true });
20496
- const pluginSkills = getPluginSkillModules(options.plugins, options);
20497
- for (const skill of pluginSkills) if (skill.originalSkillModule.startsWith("file://")) {
20498
- const sourcePath = skill.path;
20499
- const destPath = join(dir, `${skill.namespace}.md`);
20994
+ const skillContent = await promises.readFile(sourcePath, "utf-8");
20995
+ let existingDest;
20500
20996
  try {
20501
- const skillContent = await promises.readFile(sourcePath, "utf-8");
20502
- await promises.writeFile(destPath, skillContent, "utf-8");
20997
+ existingDest = await promises.readFile(destPath, "utf-8");
20503
20998
  } catch {}
20999
+ if (existingDest !== skillContent) {
21000
+ await promises.writeFile(destPath, skillContent, "utf-8");
21001
+ return true;
21002
+ }
21003
+ return false;
21004
+ } catch {
21005
+ return false;
20504
21006
  }
20505
- await promises.writeFile(skillFilePath, content, "utf-8");
20506
21007
  }
20507
21008
 
20508
21009
  //#endregion
@@ -24241,9 +24742,12 @@ let pluginServerMethods = {};
24241
24742
  * Register plugin server methods
24242
24743
  */
24243
24744
  function registerPluginServerMethods(plugins) {
24244
- const ctx = { wsPort: 0 };
24245
24745
  pluginServerMethods = {};
24246
24746
  for (const plugin of plugins) if (plugin.serverSetup) try {
24747
+ const ctx = {
24748
+ wsPort: 0,
24749
+ storage: getPluginStorage(plugin.namespace)
24750
+ };
24247
24751
  const methods = plugin.serverSetup(ctx);
24248
24752
  pluginServerMethods[plugin.namespace] = methods;
24249
24753
  } catch (error) {
@@ -24264,6 +24768,24 @@ function startWebSocketServer(port) {
24264
24768
  updateClientInfo(info) {
24265
24769
  clientManager.updateClientInfo(clientId, info);
24266
24770
  },
24771
+ async storageGetItem(namespace, key) {
24772
+ return getPluginStorage(namespace).getItem(key);
24773
+ },
24774
+ async storageSetItem(namespace, key, value) {
24775
+ await getPluginStorage(namespace).setItem(key, value);
24776
+ },
24777
+ async storageRemoveItem(namespace, key) {
24778
+ await getPluginStorage(namespace).removeItem(key);
24779
+ },
24780
+ async storageGetKeys(namespace, base) {
24781
+ return getPluginStorage(namespace).getKeys(base);
24782
+ },
24783
+ async storageHasItem(namespace, key) {
24784
+ return getPluginStorage(namespace).hasItem(key);
24785
+ },
24786
+ async storageClear(namespace, base) {
24787
+ await getPluginStorage(namespace).clear(base);
24788
+ },
24267
24789
  ...allPluginMethods
24268
24790
  }, {
24269
24791
  post: (data) => ws.send(data),
@@ -24303,8 +24825,11 @@ function stopWebSocketServer() {
24303
24825
  const VIRTUAL_MODULE_ID = "virtual:devpilot-client";
24304
24826
  const RESOLVED_VIRTUAL_MODULE_ID = "\0virtual:devpilot-client";
24305
24827
  function getPluginClientModules(plugins, options) {
24306
- const ctx = { wsPort: options.wsPort };
24307
24828
  return plugins.filter((p) => p.clientModule).map((p) => {
24829
+ const ctx = {
24830
+ wsPort: options.wsPort,
24831
+ storage: getPluginStorage(p.namespace)
24832
+ };
24308
24833
  return typeof p.clientModule === "function" ? p.clientModule(ctx) : p.clientModule;
24309
24834
  });
24310
24835
  }
@@ -24346,6 +24871,7 @@ async function stopServers() {
24346
24871
  serversStarted = false;
24347
24872
  await Promise.all([stopWebSocketServer(), stopMcpServer()]);
24348
24873
  if (lastOptions) await generateCoreSkill(lastOptions, false);
24874
+ await disposeStorage();
24349
24875
  }
24350
24876
  const unpluginDevpilot = createUnplugin((rawOptions = {}) => {
24351
24877
  let options = null;
@@ -24412,4 +24938,4 @@ process$1.on("beforeExit", () => {
24412
24938
  var src_default = unpluginDevpilot;
24413
24939
 
24414
24940
  //#endregion
24415
- export { clientManager as i, unpluginDevpilot as n, resolveSkillModule as r, src_default as t };
24941
+ export { storage as a, getPluginStorage as i, unpluginDevpilot as n, clientManager as o, resolveSkillModule as r, src_default as t };