cicy-desktop 1.0.8
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/.github/workflows/build.yml +85 -0
- package/.kiro/steering/dev-workflow.md +166 -0
- package/AGENTS.md +247 -0
- package/CLAUDE.md +162 -0
- package/DOCKER.md +85 -0
- package/Dockerfile +46 -0
- package/README.md +720 -0
- package/TODO-anti-detection.md +326 -0
- package/bin/cicy +176 -0
- package/bin/preinstall.sh +32 -0
- package/copy-to-desktop.sh +26 -0
- package/docs/AUTOMATION-API.md +342 -0
- package/docs/REQUEST_MONITORING.md +435 -0
- package/docs/REST-API-FEATURE.md +155 -0
- package/docs/REST-API.md +319 -0
- package/docs/feature-distributed-multi-agent.md +555 -0
- package/docs/yaml.md +255 -0
- package/electron-mcp-fixed.command +134 -0
- package/electron-mcp-simple.command +135 -0
- package/electron-mcp.command +92 -0
- package/generate-openapi.js +158 -0
- package/jest.config.js +10 -0
- package/jest.setup.global.js +13 -0
- package/jest.teardown.global.js +7 -0
- package/package.json +75 -0
- package/service.sh +164 -0
- package/src/config.js +8 -0
- package/src/extension/inject.js +135 -0
- package/src/main-old.js +837 -0
- package/src/main.js +403 -0
- package/src/preload-rpc.js +4 -0
- package/src/server/args-parser.js +37 -0
- package/src/server/electron-setup.js +33 -0
- package/src/server/express-app.js +166 -0
- package/src/server/logging.js +58 -0
- package/src/server/mcp-server.js +53 -0
- package/src/server/tool-registry.js +77 -0
- package/src/server/ui-routes.js +81 -0
- package/src/swagger-ui.html +41 -0
- package/src/tools/account-tools.js +194 -0
- package/src/tools/automation-tools.js +297 -0
- package/src/tools/cdp-tools.js +444 -0
- package/src/tools/clipboard-tools.js +180 -0
- package/src/tools/download-tools.js +57 -0
- package/src/tools/exec-js.js +297 -0
- package/src/tools/exec-tools.js +139 -0
- package/src/tools/file-tools.js +212 -0
- package/src/tools/hook-chatgpt.js +489 -0
- package/src/tools/hook-gemini.js +454 -0
- package/src/tools/index.js +19 -0
- package/src/tools/ipc-bridge.js +31 -0
- package/src/tools/ping.js +60 -0
- package/src/tools/r-reset.js +28 -0
- package/src/tools/screenshot-tools.js +28 -0
- package/src/tools/system-tools.js +531 -0
- package/src/tools/window-tools.js +882 -0
- package/src/ui.html +914 -0
- package/src/utils/auth.js +81 -0
- package/src/utils/cdp-utils.js +8 -0
- package/src/utils/download-manager.js +41 -0
- package/src/utils/process-utils.js +185 -0
- package/src/utils/snapshot-utils.js +56 -0
- package/src/utils/window-monitor.js +605 -0
- package/src/utils/window-state.js +137 -0
- package/src/utils/window-utils.js +336 -0
- package/update-desktop.sh +33 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const log = require("electron-log");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
function setupLogging(config) {
|
|
6
|
+
const logsDir = path.join(require("electron").app.getPath("home"), "logs");
|
|
7
|
+
if (!fs.existsSync(logsDir)) {
|
|
8
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
config.logsDir = logsDir;
|
|
12
|
+
config.logFilePath = path.join(logsDir, `electron-mcp-${config.port}.log`);
|
|
13
|
+
|
|
14
|
+
log.transports.file.resolvePathFn = () => config.logFilePath;
|
|
15
|
+
log.transports.file.format = "{y}-{m}-{d} {h}:{i}:{s}.{ms} [{level}] {text}";
|
|
16
|
+
log.transports.console.format = "{y}-{m}-{d} {h}:{i}:{s}.{ms} [{level}] {text}";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getCallerInfo() {
|
|
20
|
+
const stack = new Error().stack.split("\n");
|
|
21
|
+
const callerLine = stack[3] || "";
|
|
22
|
+
const match = callerLine.match(/\((.+):(\d+):(\d+)\)/) || callerLine.match(/at (.+):(\d+):(\d+)/);
|
|
23
|
+
if (match) {
|
|
24
|
+
const file = match[1].split("/").pop();
|
|
25
|
+
const line = match[2];
|
|
26
|
+
return `${file}:${line}`;
|
|
27
|
+
}
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function wrapLogger() {
|
|
32
|
+
const originalInfo = log.info.bind(log);
|
|
33
|
+
const originalError = log.error.bind(log);
|
|
34
|
+
const originalWarn = log.warn.bind(log);
|
|
35
|
+
const originalDebug = log.debug.bind(log);
|
|
36
|
+
|
|
37
|
+
log.info = (...args) => {
|
|
38
|
+
const caller = getCallerInfo();
|
|
39
|
+
originalInfo(args.join(" ") + (caller ? ` (${caller})` : ""));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
log.error = (...args) => {
|
|
43
|
+
const caller = getCallerInfo();
|
|
44
|
+
originalError(args.join(" ") + (caller ? ` (${caller})` : ""));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
log.warn = (...args) => {
|
|
48
|
+
const caller = getCallerInfo();
|
|
49
|
+
originalWarn(args.join(" ") + (caller ? ` (${caller})` : ""));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
log.debug = (...args) => {
|
|
53
|
+
const caller = getCallerInfo();
|
|
54
|
+
originalDebug(args.join(" ") + (caller ? ` (${caller})` : ""));
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { setupLogging, wrapLogger };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
2
|
+
const { SSEServerTransport } = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
3
|
+
const log = require("electron-log");
|
|
4
|
+
|
|
5
|
+
const transports = new Map();
|
|
6
|
+
|
|
7
|
+
function createMcpServer() {
|
|
8
|
+
return new McpServer({
|
|
9
|
+
name: "electron-mcp",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createTransport(res, sessionId) {
|
|
15
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
16
|
+
transports.set(sessionId, transport);
|
|
17
|
+
|
|
18
|
+
res.on("close", () => {
|
|
19
|
+
transports.delete(sessionId);
|
|
20
|
+
log.debug(`[MCP] SSE connection closed: ${sessionId}`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return transport;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function setupMcpRoutes(app, mcpServer, authMiddleware) {
|
|
27
|
+
app.get("/mcp", authMiddleware, async (req, res) => {
|
|
28
|
+
const sessionId = req.query.sessionId || req.headers["x-session-id"];
|
|
29
|
+
if (!sessionId) {
|
|
30
|
+
return res.status(400).json({ error: "sessionId required" });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
log.debug(`[MCP] SSE connection established: ${sessionId}`);
|
|
34
|
+
const transport = createTransport(res, sessionId);
|
|
35
|
+
await mcpServer.connect(transport);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.post("/messages", authMiddleware, async (req, res) => {
|
|
39
|
+
const sessionId = req.body.sessionId || req.headers["x-session-id"];
|
|
40
|
+
if (!sessionId) {
|
|
41
|
+
return res.status(400).json({ error: "sessionId required" });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const transport = transports.get(sessionId);
|
|
45
|
+
if (transport) {
|
|
46
|
+
await transport.handlePostMessage(req, res);
|
|
47
|
+
} else {
|
|
48
|
+
res.status(400).json({ error: `No active SSE connection for session: ${sessionId}` });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { createMcpServer, setupMcpRoutes };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { z } = require("zod");
|
|
2
|
+
|
|
3
|
+
function zodToJsonSchema(zodSchema) {
|
|
4
|
+
const shape = zodSchema._def.shape ? zodSchema._def.shape() : {};
|
|
5
|
+
const properties = {};
|
|
6
|
+
const required = [];
|
|
7
|
+
|
|
8
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
9
|
+
let fieldDef = value._def;
|
|
10
|
+
let fieldSchema = { type: "string" };
|
|
11
|
+
|
|
12
|
+
// Unwrap ZodOptional and ZodDefault
|
|
13
|
+
while (fieldDef.typeName === "ZodOptional" || fieldDef.typeName === "ZodDefault") {
|
|
14
|
+
if (fieldDef.typeName === "ZodDefault") {
|
|
15
|
+
fieldSchema.default = fieldDef.defaultValue();
|
|
16
|
+
}
|
|
17
|
+
fieldDef = fieldDef.innerType?._def || fieldDef;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Map Zod types to JSON Schema types
|
|
21
|
+
if (fieldDef.typeName === "ZodNumber") {
|
|
22
|
+
fieldSchema.type = "integer";
|
|
23
|
+
} else if (fieldDef.typeName === "ZodString") {
|
|
24
|
+
fieldSchema.type = "string";
|
|
25
|
+
} else if (fieldDef.typeName === "ZodBoolean") {
|
|
26
|
+
fieldSchema.type = "boolean";
|
|
27
|
+
} else if (fieldDef.typeName === "ZodArray") {
|
|
28
|
+
fieldSchema.type = "array";
|
|
29
|
+
fieldSchema.items = { type: "string" };
|
|
30
|
+
} else if (fieldDef.typeName === "ZodObject") {
|
|
31
|
+
fieldSchema.type = "object";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (fieldDef.description || value._def.description) {
|
|
35
|
+
fieldSchema.description = fieldDef.description || value._def.description;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isOptional = value.isOptional && value.isOptional();
|
|
39
|
+
if (!isOptional) {
|
|
40
|
+
required.push(key);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
properties[key] = fieldSchema;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties,
|
|
49
|
+
required: required.length > 0 ? required : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function registerTool(mcpServer, tools, title, description, schema, handler, options = {}) {
|
|
54
|
+
const inputSchema = zodToJsonSchema(schema);
|
|
55
|
+
const tag = options.tag || "General";
|
|
56
|
+
|
|
57
|
+
if (!tools[tag]) tools[tag] = [];
|
|
58
|
+
tools[tag].push({
|
|
59
|
+
name: title,
|
|
60
|
+
description,
|
|
61
|
+
inputSchema,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
mcpServer.tool(title, description, inputSchema, async (args) => {
|
|
65
|
+
try {
|
|
66
|
+
const validatedArgs = schema.parse(args);
|
|
67
|
+
return await handler(validatedArgs);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { zodToJsonSchema, registerTool };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const { BrowserWindow } = require("electron");
|
|
3
|
+
const router = express.Router();
|
|
4
|
+
|
|
5
|
+
function uiAuth(req, res, next) {
|
|
6
|
+
const manager = global.authManager;
|
|
7
|
+
const queryToken = req.query.token;
|
|
8
|
+
if (queryToken && queryToken === manager.getToken()) return next();
|
|
9
|
+
if (manager.validateAuth(req)) return next();
|
|
10
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// GET /ui/windows → JSON list of all BrowserWindows
|
|
14
|
+
router.get("/windows", uiAuth, (req, res) => {
|
|
15
|
+
const wins = BrowserWindow.getAllWindows().map((w) => ({
|
|
16
|
+
win_id: w.id,
|
|
17
|
+
title: w.getTitle(),
|
|
18
|
+
url: w.webContents.getURL(),
|
|
19
|
+
bounds: w.getBounds(),
|
|
20
|
+
isVisible: w.isVisible(),
|
|
21
|
+
isDestroyed: w.isDestroyed(),
|
|
22
|
+
}));
|
|
23
|
+
res.json({ windows: wins });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// GET /ui/snapshot?win_id=X[&quality=80] → JPEG image, scaled to 50% of window size
|
|
27
|
+
router.get("/snapshot", uiAuth, async (req, res) => {
|
|
28
|
+
const winId = parseInt(req.query.win_id);
|
|
29
|
+
if (isNaN(winId)) return res.status(400).json({ error: "win_id required" });
|
|
30
|
+
const win = BrowserWindow.fromId(winId);
|
|
31
|
+
if (!win) return res.status(404).json({ error: "Window not found" });
|
|
32
|
+
const quality = Math.min(100, Math.max(1, parseInt(req.query.quality) || 80));
|
|
33
|
+
const scale = Math.min(1, Math.max(0.1, parseFloat(req.query.scale) || 0.5));
|
|
34
|
+
try {
|
|
35
|
+
const image = await win.webContents.capturePage();
|
|
36
|
+
const { width, height } = image.getSize();
|
|
37
|
+
const scaled = image.resize({
|
|
38
|
+
width: Math.max(1, Math.floor(width * scale)),
|
|
39
|
+
height: Math.max(1, Math.floor(height * scale)),
|
|
40
|
+
});
|
|
41
|
+
const buf = scaled.toJPEG(quality);
|
|
42
|
+
res.set("Content-Type", "image/jpeg");
|
|
43
|
+
res.set("Cache-Control", "no-store");
|
|
44
|
+
res.send(buf);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
res.status(500).json({ error: e.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// POST /ui/close-window { win_id }
|
|
51
|
+
router.post("/close-window", uiAuth, (req, res) => {
|
|
52
|
+
const winId = parseInt(req.body.win_id);
|
|
53
|
+
if (isNaN(winId)) return res.status(400).json({ error: "win_id required" });
|
|
54
|
+
const win = BrowserWindow.fromId(winId);
|
|
55
|
+
if (!win) return res.status(404).json({ error: "Window not found" });
|
|
56
|
+
win.destroy();
|
|
57
|
+
res.json({ ok: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// POST /ui/close-all
|
|
61
|
+
router.post("/close-all", uiAuth, (req, res) => {
|
|
62
|
+
BrowserWindow.getAllWindows().forEach((w) => w.destroy());
|
|
63
|
+
res.json({ ok: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// POST /ui/set-bounds { win_id, x, y, width, height }
|
|
67
|
+
router.post("/set-bounds", uiAuth, (req, res) => {
|
|
68
|
+
const winId = parseInt(req.body.win_id);
|
|
69
|
+
if (isNaN(winId)) return res.status(400).json({ error: "win_id required" });
|
|
70
|
+
const win = BrowserWindow.fromId(winId);
|
|
71
|
+
if (!win) return res.status(404).json({ error: "Window not found" });
|
|
72
|
+
const bounds = {};
|
|
73
|
+
if (req.body.x !== undefined) bounds.x = parseInt(req.body.x);
|
|
74
|
+
if (req.body.y !== undefined) bounds.y = parseInt(req.body.y);
|
|
75
|
+
if (req.body.width !== undefined) bounds.width = parseInt(req.body.width);
|
|
76
|
+
if (req.body.height !== undefined) bounds.height = parseInt(req.body.height);
|
|
77
|
+
win.setBounds(bounds);
|
|
78
|
+
res.json({ ok: true, bounds: win.getBounds() });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
module.exports = router;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Electron MCP API</title>
|
|
6
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
7
|
+
<style>
|
|
8
|
+
.swagger-ui .topbar { display: none }
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="swagger-ui"></div>
|
|
13
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
14
|
+
<script>
|
|
15
|
+
window.onload = function() {
|
|
16
|
+
SwaggerUIBundle({
|
|
17
|
+
url: "/openapi.json",
|
|
18
|
+
dom_id: '#swagger-ui',
|
|
19
|
+
deepLinking: true,
|
|
20
|
+
presets: [
|
|
21
|
+
SwaggerUIBundle.presets.apis,
|
|
22
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
23
|
+
],
|
|
24
|
+
requestInterceptor: function(request) {
|
|
25
|
+
// 移除默认的 accept 头,只保留 application/yaml
|
|
26
|
+
delete request.headers['accept'];
|
|
27
|
+
request.headers['Accept'] = 'application/yaml';
|
|
28
|
+
|
|
29
|
+
// 从 localStorage 读取 token 并添加到请求
|
|
30
|
+
const token = localStorage.getItem('MCP_TOKEN');
|
|
31
|
+
if (token) {
|
|
32
|
+
request.headers['Authorization'] = 'Bearer ' + token;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return request;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { z } = require("zod");
|
|
5
|
+
|
|
6
|
+
const ACCOUNT_DIR = path.join(os.homedir(), "data", "electron");
|
|
7
|
+
|
|
8
|
+
module.exports = (registerTool) => {
|
|
9
|
+
// 获取账户信息
|
|
10
|
+
registerTool(
|
|
11
|
+
"get_account",
|
|
12
|
+
"获取指定账户的配置信息,包括窗口列表、创建时间等",
|
|
13
|
+
z.object({
|
|
14
|
+
accountIdx: z.number().describe("账户索引"),
|
|
15
|
+
}),
|
|
16
|
+
async ({ accountIdx }) => {
|
|
17
|
+
try {
|
|
18
|
+
const accountFile = path.join(ACCOUNT_DIR, `account-${accountIdx}.json`);
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(accountFile)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify({ error: `Account ${accountIdx} not found` }, null, 2),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const accountData = JSON.parse(fs.readFileSync(accountFile, "utf-8"));
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify(accountData, null, 2),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{ tag: "Account" }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// 保存账户信息
|
|
53
|
+
registerTool(
|
|
54
|
+
"save_account_info",
|
|
55
|
+
"保存或更新账户配置信息",
|
|
56
|
+
z.object({
|
|
57
|
+
accountIdx: z.number().describe("账户索引"),
|
|
58
|
+
metadata: z
|
|
59
|
+
.object({
|
|
60
|
+
description: z.string().optional().describe("账户描述"),
|
|
61
|
+
tags: z.array(z.string()).optional().describe("标签"),
|
|
62
|
+
})
|
|
63
|
+
.optional(),
|
|
64
|
+
}),
|
|
65
|
+
async ({ accountIdx, metadata }) => {
|
|
66
|
+
try {
|
|
67
|
+
// 确保目录存在
|
|
68
|
+
if (!fs.existsSync(ACCOUNT_DIR)) {
|
|
69
|
+
fs.mkdirSync(ACCOUNT_DIR, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const accountFile = path.join(ACCOUNT_DIR, `account-${accountIdx}.json`);
|
|
73
|
+
|
|
74
|
+
let accountData;
|
|
75
|
+
|
|
76
|
+
if (fs.existsSync(accountFile)) {
|
|
77
|
+
// 更新现有账户
|
|
78
|
+
accountData = JSON.parse(fs.readFileSync(accountFile, "utf-8"));
|
|
79
|
+
if (metadata) {
|
|
80
|
+
accountData.metadata = { ...accountData.metadata, ...metadata };
|
|
81
|
+
}
|
|
82
|
+
accountData.updatedAt = new Date().toISOString();
|
|
83
|
+
} else {
|
|
84
|
+
// 创建新账户
|
|
85
|
+
accountData = {
|
|
86
|
+
accountIdx,
|
|
87
|
+
createdAt: new Date().toISOString(),
|
|
88
|
+
windows: [],
|
|
89
|
+
metadata: metadata || {
|
|
90
|
+
description: `Account ${accountIdx}`,
|
|
91
|
+
tags: [],
|
|
92
|
+
},
|
|
93
|
+
updatedAt: new Date().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fs.writeFileSync(accountFile, JSON.stringify(accountData, null, 2));
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: JSON.stringify({ success: true, account: accountData }, null, 2),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{ tag: "Account" }
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// 列出所有账户
|
|
118
|
+
registerTool(
|
|
119
|
+
"list_accounts",
|
|
120
|
+
"列出所有已创建的账户",
|
|
121
|
+
z.object({}),
|
|
122
|
+
async () => {
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(ACCOUNT_DIR)) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: JSON.stringify([], null, 2) }],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const files = fs.readdirSync(ACCOUNT_DIR);
|
|
131
|
+
const accounts = files
|
|
132
|
+
.filter((f) => f.startsWith("account-") && f.endsWith(".json"))
|
|
133
|
+
.map((f) => {
|
|
134
|
+
const accountFile = path.join(ACCOUNT_DIR, f);
|
|
135
|
+
return JSON.parse(fs.readFileSync(accountFile, "utf-8"));
|
|
136
|
+
})
|
|
137
|
+
.sort((a, b) => a.accountIdx - b.accountIdx);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify(accounts, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{ tag: "Account" }
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// 设置账户代理
|
|
153
|
+
registerTool(
|
|
154
|
+
"set_account_proxy",
|
|
155
|
+
"为指定账户设置代理,该账户下所有窗口都会使用此代理",
|
|
156
|
+
z.object({
|
|
157
|
+
accountIdx: z.number().describe("账户索引"),
|
|
158
|
+
proxy: z.string().optional().describe("代理地址,如 http://127.0.0.1:8888,留空则清除代理"),
|
|
159
|
+
}),
|
|
160
|
+
async ({ accountIdx, proxy }) => {
|
|
161
|
+
try {
|
|
162
|
+
const { session } = require("electron");
|
|
163
|
+
const ses = session.fromPartition(`persist:sandbox-${accountIdx}`);
|
|
164
|
+
|
|
165
|
+
if (proxy) {
|
|
166
|
+
await ses.setProxy({ proxyRules: proxy });
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: JSON.stringify({ success: true, accountIdx, proxy }, null, 2),
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
} else {
|
|
176
|
+
await ses.setProxy({ proxyRules: "" });
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: JSON.stringify({ success: true, accountIdx, proxy: "cleared" }, null, 2),
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{ tag: "Account" }
|
|
194
|
+
);};
|