Files
uTools-quickcommand/plugin/preload.js
T
Mango 935b943b6a 新增 AI Agent 的 MCP 工具,可增删改查快捷命令
通过 uTools 官方的 utools.registerTool 暴露 9 个 qc_* 工具,工具声明写在
plugin.json 的 tools 字段里,外部 Agent 连 uTools 的本地 MCP 服务即可调用。
数据层自包含在 plugin/lib 下(命令 CRUD 不依赖界面是否渲染),权限按
读取/写入/运行 三档控制,默认读开写开、运行关,开关放在 AI 配置界面底部。

顺带把打包后去掉 development 入口的构建钩子带过来,uTools 开发者工具加载
dist 目录需要它,否则会去连 quasar dev 的 8080 端口导致白屏。
2026-10-13 01:21:20 +08:00

390 lines
11 KiB
JavaScript

utools.getAppVersion() < "2.6.1" && alert("请升级 uTools 至最新版本");
// -----------------------------------------------------------------
const fs = require("fs");
const os = require("os");
const child_process = require("child_process");
const iconv = require("iconv-lite");
const electron = require("electron");
const path = require("path");
const axios = require("axios");
const http = require("http");
const https = require("https");
const url = require("url");
const crypto = require("crypto");
const DOMPurify = require("dompurify");
require("ses");
const md5 = (input) => {
return crypto.createHash("md5").update(input, "utf8").digest("hex");
};
window.lodashM = require("./lib/lodashMini");
window.pinyinMatch = require("pinyin-match");
window.DOMPurify = DOMPurify;
const createTerminalCommand = require("./lib/createTerminalCommand");
const shortCodes = require("./lib/shortCodes");
const { pluginInfo, getUtoolsPlugins } = require("./lib/getUtoolsPlugins");
const {
resolveFileToBase64,
getFileInfo,
getCurrentFolderPathFix,
saveFile,
getSelectFile,
convertFilePathToUtoolsPayload,
} = require("./lib/utils");
window.pluginInfo = pluginInfo;
window.getUtoolsPlugins = getUtoolsPlugins;
window.resolveFileToBase64 = resolveFileToBase64;
window.getFileInfo = getFileInfo;
window.getCurrentFolderPathFix = getCurrentFolderPathFix;
window.saveFile = saveFile;
window.getSelectFile = getSelectFile;
window.convertFilePathToUtoolsPayload = convertFilePathToUtoolsPayload;
window.getuToolsLite = require("./lib/utoolsLite");
window.quickcommand = require("./lib/quickcommand");
window.quickcomposer = require("./lib/quickcomposer");
// 注册 AI Agent 可调用的 MCP 工具(声明在 plugin.json 的 tools 字段里)
require("./lib/quickcommandMcp");
window.showUb = require("./lib/showDocs");
window.getQuickcommandTempFile =
require("./lib/getQuickcommandFile").getQuickcommandTempFile;
window.getSharedQcById = async (id) => {
const url = "https://qc.qaz.ink/home/quick/script/getScript";
const timeStamp = parseInt(new Date().getTime() / 1000);
const { data } = await axios.get(url, {
params: {
id,
},
headers: {
"verify-encrypt": md5("quickcommand666" + timeStamp),
"verify-time": timeStamp,
},
});
return JSON.stringify(data.data);
};
// 检测进程是否存在
let isProcessExits = (pid) => {
try {
return process.kill(pid, 0);
} catch (e) {
return false;
}
};
window.isAppVersion4 = () => utools.getAppVersion() >= "4.0.0";
// 多开检测
window.multiProcessDetection = () => {
let pids = JSON.parse(localStorage.getItem("processes")) || [];
if (pids.length) pids = pids.filter((x) => isProcessExits(x));
pids.push(process.pid);
localStorage.setItem("processes", JSON.stringify(pids));
if (pids.length > 1) return true;
return false;
};
/**
* 忘了为什么之前注释下面的语句了 -_-!,保留浏览器的 axios
* axios.defaults.adapter = require('axios/lib/adapters/http')
* 另外创建一个 node 的 axios
*/
const nodeAxios = axios.create({
httpAgent: new http.Agent(),
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
});
nodeAxios.defaults.adapter = "http";
if (!window.utools.isWindows())
process.env.PATH = `/usr/local/bin:/usr/local/sbin:${process.env.PATH}`;
if (window.utools.isMacOS())
process.env.PATH = `/opt/homebrew/bin:/opt/homebrew/sbin:${process.env.PATH}`;
window.htmlEncode = (value) => {
let dom = quickcommand.htmlParse().querySelector("body");
dom.innerText = value;
return dom.innerHTML;
};
window.removeHtmlTags = (value) => {
return quickcommand.htmlParse(value).querySelector("body").innerText;
};
window.hexEncode = (text) => Buffer.from(text, "utf8").toString("hex");
window.hexDecode = (text) => Buffer.from(text, "hex").toString("utf8");
window.base64Decode = (text) => Buffer.from(text, "base64").toString("utf8");
window.processPlatform = process.platform;
window.joinPath = path.join;
window.clipboardReadText = () => electron.clipboard.readText();
let getSandboxFuns = () => {
var sandbox = {
fetch: fetch.bind(window),
utools: window.getuToolsLite(),
electron,
axios: nodeAxios,
Audio,
AbortController,
AbortSignal,
Buffer,
require,
// 兼容老版本
fs,
path,
os,
child_process,
quickcomposer,
// timeout
setTimeout: quickcommand.setTimeout,
clearTimeout: quickcommand.clearTimeout,
};
Object.keys(shortCodes).forEach((f) => {
sandbox[f] = shortCodes[f];
});
return sandbox;
};
// 简化报错信息
let liteErr = (e) => {
if (!e) return;
if (typeof e === "string") return e;
return e.error
? e.error.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, "").trim()
: e.message;
};
// vm 模块将无法在渲染进程中使用,改用 ses 来执行代码
window.evalCodeInSandbox = (code, addVars = {}) => {
let sandboxWithAD = Object.assign(addVars, getSandboxFuns());
sandboxWithAD.quickcommand = window.lodashM.cloneDeep(quickcommand);
try {
return new Compartment(sandboxWithAD).evaluate(code);
} catch (error) {
throw liteErr(error);
}
};
let isWatchingError = false;
window.runCodeInSandbox = (code, callback, addVars = {}) => {
let sandbox = getSandboxFuns();
sandbox.console = {
log: (...stdout) => {
console.log("Result:", stdout);
callback(stdout, null);
},
error: (...stderr) => {
callback(null, stderr);
},
clear: () => {
callback({ __clearQuickcommandRunResult: true }, null);
},
};
let sandboxWithAD = Object.assign(addVars, sandbox);
sandboxWithAD.quickcommand = window.lodashM.cloneDeep(quickcommand);
if (addVars.enterData) {
sandboxWithAD.quickcommand.enterData = addVars.enterData;
sandboxWithAD.quickcommand.payload = addVars.enterData.payload;
}
try {
new Compartment(sandboxWithAD).evaluate(code);
} catch (e) {
console.log("Error: ", e);
callback(null, liteErr(e));
}
// 自动捕捉错误
let cbUnhandledError = (e) => {
removeAllListener();
console.log("UnhandledError: ", e);
callback(null, liteErr(e));
};
let cbUnhandledRejection = (e) => {
removeAllListener();
console.log("UnhandledRejection: ", e);
callback(null, liteErr(e.reason));
};
let removeAllListener = () => {
window.removeEventListener("error", cbUnhandledError);
window.removeEventListener("unhandledrejection", cbUnhandledRejection);
isWatchingError = false;
};
if (!isWatchingError) {
window.addEventListener("error", cbUnhandledError);
window.addEventListener("unhandledrejection", cbUnhandledRejection);
isWatchingError = true;
}
};
// 构建命令行字符串的工具函数
const buildCommandLine = (bin, argv, script, scptarg) => {
if (bin.slice(-7) === "csc.exe") {
const outFile = script.slice(0, -2) + "exe";
return `${bin} ${argv} /out:"${outFile}" "${script}" && "${outFile}" ${scptarg}`;
}
if (bin === "gcc") {
const suffix = utools.isWindows() ? ".exe" : "";
const outFile = script.slice(0, -2) + suffix;
return `${bin} ${argv} "${script.slice(
0,
-2
)}" "${script}" && "${outFile}" ${scptarg}`;
}
if (utools.isWindows() && bin === "bash") {
const wslPath = script.replace(/\\/g, "/").replace(/C:/i, "/mnt/c");
return `wsl -e ${bin} ${argv} "${wslPath}" ${scptarg}`;
}
return `${bin} ${argv} "${script}" ${scptarg}`;
};
// 处理进程输出的工具函数
const handleProcessOutput = (child, charset, callback, realTime) => {
const chunks = [];
const errChunks = [];
child.stdout.on("data", (chunk) => {
const decodedChunk = charset.outputCode
? iconv.decode(chunk, charset.outputCode)
: chunk;
realTime
? callback(decodedChunk.toString(), null)
: chunks.push(decodedChunk);
});
child.stderr.on("data", (errChunk) => {
const decodedChunk = charset.outputCode
? iconv.decode(errChunk, charset.outputCode)
: errChunk;
realTime
? callback(null, decodedChunk.toString())
: errChunks.push(decodedChunk);
});
if (!realTime) {
child.on("close", () => {
callback(chunks.join(""), errChunks.join(""));
});
}
};
window.runCodeFile = (
cmd,
option,
terminalOptions,
callback,
realTime = true
) => {
const { bin, argv, ext, charset, scptarg, envPath, alias } = option;
const script = getQuickcommandTempFile(ext, "quickcommandTempScript");
// 处理编码和换行
const processedCmd = charset.scriptCode
? iconv.encode(cmd.replace(/\n/g, "\r\n"), charset.scriptCode)
: cmd;
fs.writeFileSync(script, processedCmd);
// 构建命令行
let cmdline = buildCommandLine(bin, argv, script, scptarg);
// 处理环境变量
const processEnv = window.lodashM.cloneDeep(process.env);
if (envPath) processEnv.PATH = envPath;
if (alias) cmdline = `${alias}\n${cmdline}`;
if (!!terminalOptions) {
cmdline = createTerminalCommand(cmdline, terminalOptions);
}
// 创建子进程
const child = child_process.spawn(cmdline, {
encoding: "buffer",
shell: true,
env: processEnv,
});
console.log("Running: " + cmdline);
handleProcessOutput(child, charset, callback, realTime);
return child;
};
const dbStorage = utools.dbStorage;
let httpServer;
window.quickcommandHttpServer = () => {
let run = (port = 33442) => {
let httpResponse = (res, code, result) => {
// 只收受一次 console.log,接收后就关闭连接
if (res.finished) return;
res.writeHead(code, {
"Content-Type": "text/html",
});
if (result) res.write(result);
res.end();
};
let runUserCode = (res, userVars) => {
let cmd = dbStorage.getItem("cfg_serverCode");
// 不需要返回输出的提前关闭连接
if (!cmd.includes("console.log")) httpResponse(res, 200);
window.runCodeInSandbox(
cmd,
(stdout, stderr) => {
// 错误返回 500
if (stderr) return httpResponse(res, 500, stderr.join(" "));
return httpResponse(res, 200, stdout.join(" "));
},
userVars
);
};
httpServer = http.createServer();
httpServer.on("request", (req, res) => {
if (req.method === "GET") {
let parsedParams = window.lodashM.cloneDeep(
url.parse(req.url, true).query
);
runUserCode(res, parsedParams);
} else if (req.method === "POST") {
let data = [];
req.on("data", (chunk) => {
data.push(chunk);
});
req.on("end", () => {
let parsedParams;
let params = data.join("").toString();
// 先尝试作为 json 解析
try {
parsedParams = JSON.parse(params);
} catch (error) {
parsedParams = window.lodashM.cloneDeep(
url.parse("?" + params, true).query
);
}
runUserCode(res, parsedParams);
});
} else {
httpResponse(res, 405);
}
});
httpServer.listen(port, "localhost");
httpServer.on("error", (err) => {
utools.showNotification("快捷命令服务:", err);
});
};
let stop = () => {
if (!httpServer) return;
httpServer.close();
};
return {
run,
stop,
};
};