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
package/src/main.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
const { app: electronApp } = require("electron");
|
|
2
|
+
const { default: contextMenu } = require("electron-context-menu");
|
|
3
|
+
|
|
4
|
+
// 🎯 添加右键上下文菜单
|
|
5
|
+
contextMenu({
|
|
6
|
+
showLookUpSelection: true,
|
|
7
|
+
showSearchWithGoogle: true,
|
|
8
|
+
showCopyImage: true,
|
|
9
|
+
showCopyImageAddress: true,
|
|
10
|
+
showSaveImageAs: true,
|
|
11
|
+
showCopyVideoAddress: true,
|
|
12
|
+
showSaveVideoAs: true,
|
|
13
|
+
showCopyLink: true,
|
|
14
|
+
showSaveLinkAs: true,
|
|
15
|
+
showInspectElement: true,
|
|
16
|
+
showServices: true,
|
|
17
|
+
labels: {
|
|
18
|
+
cut: '剪切',
|
|
19
|
+
copy: '复制',
|
|
20
|
+
paste: '粘贴',
|
|
21
|
+
selectAll: '全选',
|
|
22
|
+
reload: '重新加载',
|
|
23
|
+
forceReload: '强制重新加载',
|
|
24
|
+
toggleDevTools: '切换开发者工具',
|
|
25
|
+
inspectElement: '检查元素',
|
|
26
|
+
services: '服务',
|
|
27
|
+
lookUpSelection: '查找选中内容',
|
|
28
|
+
searchWithGoogle: '用 Google 搜索',
|
|
29
|
+
copyImage: '复制图片',
|
|
30
|
+
copyImageAddress: '复制图片地址',
|
|
31
|
+
saveImage: '保存图片',
|
|
32
|
+
copyVideoAddress: '复制视频地址',
|
|
33
|
+
saveVideo: '保存视频',
|
|
34
|
+
copyLink: '复制链接',
|
|
35
|
+
saveLinkAs: '链接另存为...'
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// Setup Electron flags IMMEDIATELY after require
|
|
41
|
+
electronApp.commandLine.appendSwitch("ignore-certificate-errors");
|
|
42
|
+
if (process.platform === "linux") {
|
|
43
|
+
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
|
|
44
|
+
// electronApp.commandLine.appendSwitch("disable-setuid-sandbox");
|
|
45
|
+
electronApp.commandLine.appendSwitch("log-level", "3");
|
|
46
|
+
electronApp.commandLine.appendSwitch("disable-notifications");
|
|
47
|
+
electronApp.commandLine.appendSwitch("ignore-certificate-errors");
|
|
48
|
+
if (process.env.ELECTRON_DISABLE_HTTP_CACHE === "1") {
|
|
49
|
+
electronApp.commandLine.appendSwitch("disable-http-cache");
|
|
50
|
+
electronApp.commandLine.appendSwitch("disable-application-cache");
|
|
51
|
+
}
|
|
52
|
+
electronApp.commandLine.appendSwitch("disable-geolocation");
|
|
53
|
+
electronApp.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
54
|
+
electronApp.commandLine.appendSwitch("use-gl", "angle");
|
|
55
|
+
electronApp.commandLine.appendSwitch("use-angle", "swiftshader");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const http = require("http");
|
|
59
|
+
const log = require("electron-log");
|
|
60
|
+
const { z } = require("zod");
|
|
61
|
+
const { config } = require("./config");
|
|
62
|
+
const { createWindow } = require("./utils/window-utils");
|
|
63
|
+
const { AuthManager } = require("./utils/auth");
|
|
64
|
+
const { setupElectronFlags, setupErrorHandlers } = require("./server/electron-setup");
|
|
65
|
+
const { parseArgs } = require("./server/args-parser");
|
|
66
|
+
const { setupLogging, wrapLogger } = require("./server/logging");
|
|
67
|
+
const { createExpressApp } = require("./server/express-app");
|
|
68
|
+
const { createMcpServer, setupMcpRoutes } = require("./server/mcp-server");
|
|
69
|
+
const { registerTool } = require("./server/tool-registry");
|
|
70
|
+
|
|
71
|
+
// Setup
|
|
72
|
+
// setupElectronFlags(); // Already done above
|
|
73
|
+
setupErrorHandlers();
|
|
74
|
+
|
|
75
|
+
// Parse arguments
|
|
76
|
+
const { PORT, START_URL, PROXY, oneWindow } = parseArgs();
|
|
77
|
+
config.port = PORT;
|
|
78
|
+
if (PROXY) {
|
|
79
|
+
config.proxy = PROXY;
|
|
80
|
+
log.info(`[MCP] Global proxy enabled: ${PROXY}`);
|
|
81
|
+
}
|
|
82
|
+
if (oneWindow) {
|
|
83
|
+
config.oneWindow = true;
|
|
84
|
+
log.info("[MCP] Single window mode enabled");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Setup logging
|
|
88
|
+
setupLogging(config);
|
|
89
|
+
wrapLogger();
|
|
90
|
+
|
|
91
|
+
log.info("[MCP] Server starting at", new Date().toISOString());
|
|
92
|
+
|
|
93
|
+
// Initialize auth
|
|
94
|
+
const authManager = new AuthManager();
|
|
95
|
+
global.authManager = authManager; // Make it globally accessible
|
|
96
|
+
const authMiddleware = (req, res, next) => {
|
|
97
|
+
if (!authManager.validateAuth(req)) {
|
|
98
|
+
res.setHeader("WWW-Authenticate", 'Basic realm="Electron MCP"');
|
|
99
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
100
|
+
}
|
|
101
|
+
next();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Create servers
|
|
105
|
+
const mcpServer = createMcpServer();
|
|
106
|
+
const tools = {};
|
|
107
|
+
const app = createExpressApp(authMiddleware, tools);
|
|
108
|
+
|
|
109
|
+
// Register tools
|
|
110
|
+
const toolModules = require("./tools");
|
|
111
|
+
|
|
112
|
+
toolModules.forEach((module) => {
|
|
113
|
+
module((title, description, schema, handler, options) => {
|
|
114
|
+
registerTool(mcpServer, tools, title, description, schema, handler, options);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Setup MCP routes
|
|
119
|
+
setupMcpRoutes(app, mcpServer, authMiddleware);
|
|
120
|
+
|
|
121
|
+
// RPC endpoint with hot reload
|
|
122
|
+
app.post("/rpc/tools/call", authMiddleware, async (req, res) => {
|
|
123
|
+
let body = req.body;
|
|
124
|
+
|
|
125
|
+
// Parse YAML if Content-Type is application/yaml
|
|
126
|
+
if (req.get("Content-Type")?.includes("application/yaml")) {
|
|
127
|
+
try {
|
|
128
|
+
const yaml = require("js-yaml");
|
|
129
|
+
const rawBody = await new Promise((resolve) => {
|
|
130
|
+
let data = "";
|
|
131
|
+
req.on("data", (chunk) => (data += chunk));
|
|
132
|
+
req.on("end", () => resolve(data));
|
|
133
|
+
});
|
|
134
|
+
body = yaml.load(rawBody);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return res.status(400).json({ error: `Invalid YAML: ${error.message}` });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { name, arguments: args } = body;
|
|
141
|
+
try {
|
|
142
|
+
// Re-load tool modules
|
|
143
|
+
const toolModules = require("./tools");
|
|
144
|
+
|
|
145
|
+
let handler = null;
|
|
146
|
+
let schema = null;
|
|
147
|
+
|
|
148
|
+
toolModules.forEach((module) => {
|
|
149
|
+
module((title, description, toolSchema, toolHandler, options) => {
|
|
150
|
+
if (title === name) {
|
|
151
|
+
handler = toolHandler;
|
|
152
|
+
schema = toolSchema;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!handler) {
|
|
158
|
+
throw new Error(`Tool '${name}' not found`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const validatedArgs = schema.parse(args || {});
|
|
162
|
+
const result = await handler(validatedArgs);
|
|
163
|
+
|
|
164
|
+
// Support YAML response
|
|
165
|
+
const accept = req.headers.accept || "application/json";
|
|
166
|
+
if (accept.includes("application/yaml") || accept.includes("text/yaml")) {
|
|
167
|
+
const yaml = require("js-yaml");
|
|
168
|
+
res.type("yaml").send(yaml.dump({ result }));
|
|
169
|
+
} else {
|
|
170
|
+
res.json({ result });
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// Handle Zod validation errors
|
|
174
|
+
if (error.name === "ZodError") {
|
|
175
|
+
const errorMsg = error.errors.map((e) => e.message).join(", ");
|
|
176
|
+
return res.json({
|
|
177
|
+
result: {
|
|
178
|
+
content: [{ type: "text", text: errorMsg }],
|
|
179
|
+
isError: true,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
res.status(500).json({ error: error.message });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
app.get("/rpc/tools", authMiddleware, (req, res) => {
|
|
188
|
+
const accept = req.headers.accept || "application/json";
|
|
189
|
+
const allTools = Object.values(tools).flat();
|
|
190
|
+
|
|
191
|
+
if (accept.includes("application/yaml") || accept.includes("text/yaml")) {
|
|
192
|
+
const yaml = require("js-yaml");
|
|
193
|
+
res.type("yaml").send(yaml.dump({ tools: allTools }));
|
|
194
|
+
} else {
|
|
195
|
+
res.json({ tools: allTools });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Static file server for uploads/downloads
|
|
200
|
+
const fs = require("fs");
|
|
201
|
+
const path = require("path");
|
|
202
|
+
const serveIndex = require("serve-index");
|
|
203
|
+
const FILES_DIR = path.join(require("os").homedir(), "electron-mcp-files");
|
|
204
|
+
if (!fs.existsSync(FILES_DIR)) {
|
|
205
|
+
fs.mkdirSync(FILES_DIR, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Serve files with directory listing (auth required)
|
|
209
|
+
app.use("/files", authMiddleware, require("express").static(FILES_DIR));
|
|
210
|
+
app.use("/files", authMiddleware, serveIndex(FILES_DIR, { icons: true, view: "details" }));
|
|
211
|
+
|
|
212
|
+
// Dynamic tool endpoints: /rpc/{tool_name}
|
|
213
|
+
Object.values(tools)
|
|
214
|
+
.flat()
|
|
215
|
+
.forEach((tool) => {
|
|
216
|
+
app.post(`/rpc/${tool.name}`, authMiddleware, async (req, res) => {
|
|
217
|
+
let body = req.body;
|
|
218
|
+
|
|
219
|
+
// Parse YAML if Content-Type is application/yaml
|
|
220
|
+
if (req.get("Content-Type")?.includes("application/yaml")) {
|
|
221
|
+
try {
|
|
222
|
+
const yaml = require("js-yaml");
|
|
223
|
+
const rawBody = await new Promise((resolve) => {
|
|
224
|
+
let data = "";
|
|
225
|
+
req.on("data", (chunk) => (data += chunk));
|
|
226
|
+
req.on("end", () => resolve(data));
|
|
227
|
+
});
|
|
228
|
+
body = yaml.load(rawBody) || {};
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return res.status(400).json({ error: `Invalid YAML: ${error.message}` });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// Re-load tool modules
|
|
236
|
+
const toolModules = require("./tools");
|
|
237
|
+
|
|
238
|
+
let handler = null;
|
|
239
|
+
let schema = null;
|
|
240
|
+
|
|
241
|
+
toolModules.forEach((module) => {
|
|
242
|
+
module((name, desc, inputSchema, fn) => {
|
|
243
|
+
if (name === tool.name) {
|
|
244
|
+
handler = fn;
|
|
245
|
+
schema = inputSchema;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!handler) {
|
|
251
|
+
throw new Error(`Tool '${tool.name}' not found`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const validatedArgs = schema.parse(body || {});
|
|
255
|
+
const result = await handler(validatedArgs);
|
|
256
|
+
|
|
257
|
+
// Support YAML response
|
|
258
|
+
const accept = req.headers.accept || "application/json";
|
|
259
|
+
if (accept.includes("application/yaml") || accept.includes("text/yaml")) {
|
|
260
|
+
const yaml = require("js-yaml");
|
|
261
|
+
res.type("yaml").send(yaml.dump({ result }));
|
|
262
|
+
} else {
|
|
263
|
+
res.json({ result });
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error.name === "ZodError") {
|
|
267
|
+
const errorMsg = error.errors.map((e) => e.message).join(", ");
|
|
268
|
+
return res.json({
|
|
269
|
+
result: {
|
|
270
|
+
content: [{ type: "text", text: errorMsg }],
|
|
271
|
+
isError: true,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
res.status(500).json({ error: error.message });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
// File upload to path: curl --data-binary @local.js http://localhost:8101/rpc/upload/C:/Users/Administrator/data/file.js
|
|
283
|
+
app.post("/rpc/upload/*", authMiddleware, require("express").raw({ type: "*/*", limit: "10mb" }), (req, res) => {
|
|
284
|
+
try {
|
|
285
|
+
const filePath = req.params[0];
|
|
286
|
+
if (!filePath) return res.status(400).json({ error: "Missing path" });
|
|
287
|
+
const dir = require("path").dirname(filePath);
|
|
288
|
+
if (!require("fs").existsSync(dir)) require("fs").mkdirSync(dir, { recursive: true });
|
|
289
|
+
require("fs").writeFileSync(filePath, req.body);
|
|
290
|
+
const size = require("fs").statSync(filePath).size;
|
|
291
|
+
res.json({ success: true, path: filePath, size });
|
|
292
|
+
} catch (error) {
|
|
293
|
+
res.status(500).json({ error: error.message });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// File upload + execute: curl -X POST --data-binary @local.js http://localhost:8101/rpc/exec/node
|
|
298
|
+
// Supported types: shell, python, node, js (js = browser exec_js)
|
|
299
|
+
app.post("/rpc/exec/:type", authMiddleware, require("express").text({ type: "*/*", limit: "10mb" }), async (req, res) => {
|
|
300
|
+
const type = req.params.type;
|
|
301
|
+
const body = typeof req.body === "string" ? req.body : req.body.toString("utf-8");
|
|
302
|
+
if (!body) return res.status(400).json({ error: "Empty body" });
|
|
303
|
+
|
|
304
|
+
const TMP = require("path").join(require("os").homedir(), "tmp");
|
|
305
|
+
if (!require("fs").existsSync(TMP)) require("fs").mkdirSync(TMP, { recursive: true });
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
// Re-load tools for hot reload
|
|
309
|
+
const toolModules = require("./tools");
|
|
310
|
+
const toolName = type === "js" ? "exec_js_file" : `exec_${type}_file`;
|
|
311
|
+
let handler = null, schema = null;
|
|
312
|
+
toolModules.forEach((module) => {
|
|
313
|
+
module((name, desc, toolSchema, fn) => {
|
|
314
|
+
if (name === toolName) { handler = fn; schema = toolSchema; }
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
if (!handler) return res.status(404).json({ error: `Unknown type: ${type}` });
|
|
318
|
+
|
|
319
|
+
const result = await handler({ content: body, win_id: parseInt(req.query.win_id) || 1 });
|
|
320
|
+
res.json({ result });
|
|
321
|
+
} catch (error) {
|
|
322
|
+
res.status(500).json({ error: error.message });
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Start server
|
|
327
|
+
const server = http.createServer(app);
|
|
328
|
+
|
|
329
|
+
// 必须在 whenReady 之前设置调试端口
|
|
330
|
+
electronApp.commandLine.appendSwitch("remote-debugging-port", "9221");
|
|
331
|
+
log.info("[MCP] Remote debugging enabled on port 9221");
|
|
332
|
+
|
|
333
|
+
// IPC Bridge: expose all RPC tools to renderer via ipcMain.handle
|
|
334
|
+
const { ipcMain } = require("electron");
|
|
335
|
+
ipcMain.handle("rpc", async (event, toolName, args) => {
|
|
336
|
+
console.log("[IPC Bridge] called:", toolName, JSON.stringify(args));
|
|
337
|
+
try {
|
|
338
|
+
const toolModules = require("./tools");
|
|
339
|
+
let handler = null, schema = null;
|
|
340
|
+
toolModules.forEach((module) => {
|
|
341
|
+
module((name, desc, toolSchema, fn) => {
|
|
342
|
+
if (name === toolName) { handler = fn; schema = toolSchema; }
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
if (!handler) throw new Error("Tool '" + toolName + "' not found");
|
|
346
|
+
const validatedArgs = schema.parse(args || {});
|
|
347
|
+
const result = await handler(validatedArgs);
|
|
348
|
+
console.log("[IPC Bridge] success:", toolName);
|
|
349
|
+
return result;
|
|
350
|
+
} catch(e) { console.error("[IPC Bridge] error:", toolName, e.message); throw e; }
|
|
351
|
+
});
|
|
352
|
+
console.log("[IPC Bridge] All RPC tools available via ipcRenderer.invoke('rpc', toolName, args)");
|
|
353
|
+
|
|
354
|
+
electronApp.whenReady().then(() => {
|
|
355
|
+
|
|
356
|
+
// 为 webview partition 设置代理
|
|
357
|
+
if (config.proxy) {
|
|
358
|
+
const { session } = require("electron");
|
|
359
|
+
const mainSession = session.fromPartition("persist:main");
|
|
360
|
+
mainSession.setProxy({
|
|
361
|
+
proxyRules: config.proxy
|
|
362
|
+
}).then(() => {
|
|
363
|
+
log.info(`[Proxy] persist:main partition 已设置代理: ${config.proxy}`);
|
|
364
|
+
}).catch(err => {
|
|
365
|
+
log.error("[Proxy] persist:main partition 设置代理失败:", err);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
server.listen(PORT, () => {
|
|
369
|
+
log.info(`[MCP] Log file: ${config.logFilePath}`);
|
|
370
|
+
log.info(`[MCP] Server listening on http://localhost:${PORT}`);
|
|
371
|
+
log.info(`[MCP] SSE endpoint: http://localhost:${PORT}/mcp`);
|
|
372
|
+
log.info(`[MCP] REST API docs: http://localhost:${PORT}/docs`);
|
|
373
|
+
log.info(`[MCP] Remote debugger: http://localhost:9221`);
|
|
374
|
+
|
|
375
|
+
createWindow({ url: START_URL ||"https://ide.cicy.de5.net"}, 0);
|
|
376
|
+
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
electronApp.on("window-all-closed", () => {
|
|
381
|
+
// Keep app running
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
function cleanup() {
|
|
385
|
+
log.info("[MCP] Server shutting down");
|
|
386
|
+
server.close();
|
|
387
|
+
electronApp.quit();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
process.on("SIGTERM", cleanup);
|
|
391
|
+
|
|
392
|
+
// 为所有 session(包括 webview partition)设置代理
|
|
393
|
+
electronApp.on('session-created', (session) => {
|
|
394
|
+
if (config.proxy) {
|
|
395
|
+
session.setProxy({
|
|
396
|
+
proxyRules: config.proxy
|
|
397
|
+
}).then(() => {
|
|
398
|
+
log.info(`[Proxy] Session ${session.partition || 'default'} 已设置代理: ${config.proxy}`);
|
|
399
|
+
}).catch(err => {
|
|
400
|
+
log.error(`[Proxy] Session ${session.partition || 'default'} 设置代理失败:`, err);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function parseArgs() {
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
|
|
4
|
+
let PORT = args.find((arg) => arg.startsWith("--port="))?.split("=")[1];
|
|
5
|
+
if (!PORT) {
|
|
6
|
+
const portIndex = args.indexOf("--port");
|
|
7
|
+
if (portIndex !== -1 && args[portIndex + 1]) {
|
|
8
|
+
PORT = args[portIndex + 1];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (!PORT) {
|
|
12
|
+
PORT = process.env.PORT;
|
|
13
|
+
}
|
|
14
|
+
PORT = parseInt(PORT) || 8101;
|
|
15
|
+
|
|
16
|
+
let START_URL = args.find((arg) => arg.startsWith("--url="))?.split("=")[1];
|
|
17
|
+
if (!START_URL) {
|
|
18
|
+
const urlIndex = args.indexOf("--url");
|
|
19
|
+
if (urlIndex !== -1 && args[urlIndex + 1]) {
|
|
20
|
+
START_URL = args[urlIndex + 1];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let PROXY = args.find((arg) => arg.startsWith("--proxy="))?.split("=")[1];
|
|
25
|
+
if (!PROXY) {
|
|
26
|
+
const proxyIndex = args.indexOf("--proxy");
|
|
27
|
+
if (proxyIndex !== -1 && args[proxyIndex + 1]) {
|
|
28
|
+
PROXY = args[proxyIndex + 1];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const oneWindow = args.includes("--one-window");
|
|
33
|
+
|
|
34
|
+
return { PORT, START_URL, PROXY, oneWindow };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { parseArgs };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { app: electronApp } = require("electron");
|
|
2
|
+
const log = require("electron-log");
|
|
3
|
+
|
|
4
|
+
function setupElectronFlags() {
|
|
5
|
+
// Must be called before app.whenReady()
|
|
6
|
+
electronApp.commandLine.appendSwitch("no-sandbox");
|
|
7
|
+
electronApp.commandLine.appendSwitch("disable-setuid-sandbox");
|
|
8
|
+
|
|
9
|
+
if (process.platform === "linux") {
|
|
10
|
+
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
|
|
11
|
+
electronApp.commandLine.appendSwitch("log-level", "3");
|
|
12
|
+
electronApp.commandLine.appendSwitch("disable-notifications");
|
|
13
|
+
electronApp.commandLine.appendSwitch("disable-geolocation");
|
|
14
|
+
electronApp.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
15
|
+
electronApp.commandLine.appendSwitch("disable-gpu");
|
|
16
|
+
electronApp.commandLine.appendSwitch("disable-software-rasterizer");
|
|
17
|
+
electronApp.commandLine.appendSwitch("disable-gpu-compositing");
|
|
18
|
+
electronApp.commandLine.appendSwitch("disable-gpu-rasterization");
|
|
19
|
+
electronApp.commandLine.appendSwitch("use-gl", "swiftshader");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function setupErrorHandlers() {
|
|
24
|
+
process.on("uncaughtException", (error) => {
|
|
25
|
+
log.error("[Uncaught Exception]", error);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
29
|
+
log.error("[Unhandled Rejection]", reason);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { setupElectronFlags, setupErrorHandlers };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const cors = require("cors");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const uiRoutes = require("./ui-routes");
|
|
5
|
+
|
|
6
|
+
function createExpressApp(authMiddleware, tools = {}) {
|
|
7
|
+
const app = express();
|
|
8
|
+
|
|
9
|
+
app.use(
|
|
10
|
+
cors({
|
|
11
|
+
origin: "*",
|
|
12
|
+
methods: ["GET", "POST", "OPTIONS"],
|
|
13
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
app.use(express.text());
|
|
19
|
+
|
|
20
|
+
// Health check - no auth
|
|
21
|
+
app.get("/ping", (req, res) => {
|
|
22
|
+
res.json({ ping: "pong", ts: Date.now() });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Swagger UI
|
|
26
|
+
app.get("/docs", (req, res) => {
|
|
27
|
+
const htmlPath = path.join(__dirname, "../swagger-ui.html");
|
|
28
|
+
res.sendFile(htmlPath);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// OpenAPI spec - dynamic generation, no auth
|
|
32
|
+
app.get("/openapi.json", (req, res) => {
|
|
33
|
+
const acceptHeader = req.get("Accept") || "application/json";
|
|
34
|
+
const useYaml = acceptHeader.includes("application/yaml");
|
|
35
|
+
|
|
36
|
+
const allTools = Object.entries(tools).flatMap(([tag, toolList]) =>
|
|
37
|
+
toolList.map((tool) => ({ ...tool, tag }))
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const openapi = {
|
|
41
|
+
openapi: "3.0.0",
|
|
42
|
+
info: {
|
|
43
|
+
title: "Electron MCP REST API",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
description: `REST API for Electron MCP tools - ${allTools.length} tools available`,
|
|
46
|
+
},
|
|
47
|
+
servers: [
|
|
48
|
+
{
|
|
49
|
+
url: "https://g-electron.cicy.de5.net",
|
|
50
|
+
description: "Remote server",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
components: {
|
|
54
|
+
securitySchemes: {
|
|
55
|
+
bearerAuth: {
|
|
56
|
+
type: "http",
|
|
57
|
+
scheme: "bearer",
|
|
58
|
+
bearerFormat: "token",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
security: [{ bearerAuth: [] }],
|
|
63
|
+
paths: {
|
|
64
|
+
"/rpc/tools": {
|
|
65
|
+
get: {
|
|
66
|
+
summary: "List all available tools",
|
|
67
|
+
tags: ["Tools"],
|
|
68
|
+
security: [{ bearerAuth: [] }],
|
|
69
|
+
responses: {
|
|
70
|
+
200: {
|
|
71
|
+
description: "List of tools",
|
|
72
|
+
content: {
|
|
73
|
+
"application/json": {
|
|
74
|
+
schema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
tools: {
|
|
78
|
+
type: "array",
|
|
79
|
+
items: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
name: { type: "string" },
|
|
83
|
+
description: { type: "string" },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Generate endpoint for each tool
|
|
99
|
+
allTools.forEach((tool) => {
|
|
100
|
+
openapi.paths[`/rpc/${tool.name}`] = {
|
|
101
|
+
post: {
|
|
102
|
+
summary: tool.name,
|
|
103
|
+
description: tool.description,
|
|
104
|
+
tags: [tool.tag],
|
|
105
|
+
security: [{ bearerAuth: [] }],
|
|
106
|
+
requestBody: {
|
|
107
|
+
required: true,
|
|
108
|
+
content: {
|
|
109
|
+
"application/yaml": {
|
|
110
|
+
schema: tool.inputSchema,
|
|
111
|
+
},
|
|
112
|
+
"application/json": {
|
|
113
|
+
schema: tool.inputSchema,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
responses: {
|
|
118
|
+
200: {
|
|
119
|
+
description: "Tool execution result",
|
|
120
|
+
content: {
|
|
121
|
+
"application/json": {
|
|
122
|
+
schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
content: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
type: { type: "string" },
|
|
131
|
+
text: { type: "string" },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
404: { description: "Tool not found" },
|
|
141
|
+
500: { description: "Execution error" },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (useYaml) {
|
|
148
|
+
const yaml = require("js-yaml");
|
|
149
|
+
res.type("application/yaml").send(yaml.dump(openapi));
|
|
150
|
+
} else {
|
|
151
|
+
res.json(openapi);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Serve UI page (no auth — page handles token itself)
|
|
156
|
+
app.get("/ui", (req, res) => {
|
|
157
|
+
res.sendFile(path.join(__dirname, "../ui.html"));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Mount UI API routes
|
|
161
|
+
app.use("/ui", uiRoutes);
|
|
162
|
+
|
|
163
|
+
return app;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { createExpressApp };
|