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.
- package/README.md +1 -0
- package/dist/client/index.d.mts +18 -1
- package/dist/client/index.mjs +14 -1
- package/dist/farm.d.mts +1 -1
- package/dist/farm.mjs +1 -1
- package/dist/{index-Csy16I0Z.d.mts → index-BE1u_wvJ.d.mts} +135 -1
- package/dist/{index-WH3owjvn.d.mts → index-D6IYERGx.d.mts} +17 -7
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/{plugin-B1Afr0m3.mjs → plugin-4eB8Zx4B.mjs} +11 -11
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.mjs +1 -1
- package/dist/rspack.d.mts +1 -1
- package/dist/rspack.mjs +1 -1
- package/dist/{src-AEXLBOF9.mjs → src-BHdaOKo9.mjs} +719 -193
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +1 -1
- package/dist/webpack.d.mts +1 -1
- package/dist/webpack.mjs +1 -1
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
20421
|
-
if (isDirectoryPath(
|
|
20422
|
-
return
|
|
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
|
-
|
|
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")
|
|
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
|
|
20940
|
+
* Generate and write the core skill files to all configured paths
|
|
20478
20941
|
*/
|
|
20479
20942
|
async function generateCoreSkill(options, isDev) {
|
|
20480
|
-
if (!options.
|
|
20481
|
-
const skillFilePath = getCoreSkillFilePath(options.skillCorePath);
|
|
20943
|
+
if (!options.skillPaths || options.skillPaths.length === 0) return;
|
|
20482
20944
|
const content = generateCoreSkillContent(options, isDev);
|
|
20483
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
20492
|
-
|
|
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
|
-
|
|
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 {
|
|
24941
|
+
export { storage as a, getPluginStorage as i, unpluginDevpilot as n, clientManager as o, resolveSkillModule as r, src_default as t };
|