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.
Files changed (66) hide show
  1. package/.github/workflows/build.yml +85 -0
  2. package/.kiro/steering/dev-workflow.md +166 -0
  3. package/AGENTS.md +247 -0
  4. package/CLAUDE.md +162 -0
  5. package/DOCKER.md +85 -0
  6. package/Dockerfile +46 -0
  7. package/README.md +720 -0
  8. package/TODO-anti-detection.md +326 -0
  9. package/bin/cicy +176 -0
  10. package/bin/preinstall.sh +32 -0
  11. package/copy-to-desktop.sh +26 -0
  12. package/docs/AUTOMATION-API.md +342 -0
  13. package/docs/REQUEST_MONITORING.md +435 -0
  14. package/docs/REST-API-FEATURE.md +155 -0
  15. package/docs/REST-API.md +319 -0
  16. package/docs/feature-distributed-multi-agent.md +555 -0
  17. package/docs/yaml.md +255 -0
  18. package/electron-mcp-fixed.command +134 -0
  19. package/electron-mcp-simple.command +135 -0
  20. package/electron-mcp.command +92 -0
  21. package/generate-openapi.js +158 -0
  22. package/jest.config.js +10 -0
  23. package/jest.setup.global.js +13 -0
  24. package/jest.teardown.global.js +7 -0
  25. package/package.json +75 -0
  26. package/service.sh +164 -0
  27. package/src/config.js +8 -0
  28. package/src/extension/inject.js +135 -0
  29. package/src/main-old.js +837 -0
  30. package/src/main.js +403 -0
  31. package/src/preload-rpc.js +4 -0
  32. package/src/server/args-parser.js +37 -0
  33. package/src/server/electron-setup.js +33 -0
  34. package/src/server/express-app.js +166 -0
  35. package/src/server/logging.js +58 -0
  36. package/src/server/mcp-server.js +53 -0
  37. package/src/server/tool-registry.js +77 -0
  38. package/src/server/ui-routes.js +81 -0
  39. package/src/swagger-ui.html +41 -0
  40. package/src/tools/account-tools.js +194 -0
  41. package/src/tools/automation-tools.js +297 -0
  42. package/src/tools/cdp-tools.js +444 -0
  43. package/src/tools/clipboard-tools.js +180 -0
  44. package/src/tools/download-tools.js +57 -0
  45. package/src/tools/exec-js.js +297 -0
  46. package/src/tools/exec-tools.js +139 -0
  47. package/src/tools/file-tools.js +212 -0
  48. package/src/tools/hook-chatgpt.js +489 -0
  49. package/src/tools/hook-gemini.js +454 -0
  50. package/src/tools/index.js +19 -0
  51. package/src/tools/ipc-bridge.js +31 -0
  52. package/src/tools/ping.js +60 -0
  53. package/src/tools/r-reset.js +28 -0
  54. package/src/tools/screenshot-tools.js +28 -0
  55. package/src/tools/system-tools.js +531 -0
  56. package/src/tools/window-tools.js +882 -0
  57. package/src/ui.html +914 -0
  58. package/src/utils/auth.js +81 -0
  59. package/src/utils/cdp-utils.js +8 -0
  60. package/src/utils/download-manager.js +41 -0
  61. package/src/utils/process-utils.js +185 -0
  62. package/src/utils/snapshot-utils.js +56 -0
  63. package/src/utils/window-monitor.js +605 -0
  64. package/src/utils/window-state.js +137 -0
  65. package/src/utils/window-utils.js +336 -0
  66. package/update-desktop.sh +33 -0
@@ -0,0 +1,531 @@
1
+ const { z } = require("zod");
2
+ const { execSync } = require("child_process");
3
+
4
+ function registerTools(registerTool) {
5
+ registerTool(
6
+ "get_system_windows",
7
+ "获取系统所有窗口信息(进程名、PID、窗口位置)",
8
+ z.object({
9
+ detail: z.boolean().optional().default(false).describe("是否显示详细信息"),
10
+ }),
11
+ async ({ detail }) => {
12
+ try {
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ let windows = [];
16
+
17
+ if (process.platform === "linux") {
18
+ // 使用 wmctrl 获取窗口信息
19
+ const output = execSync("wmctrl -lGp", { encoding: "utf8" });
20
+ const lines = output.trim().split("\n");
21
+
22
+ // 获取当前活动窗口
23
+ let activeWinId = "";
24
+ try {
25
+ activeWinId = execSync("xdotool getactivewindow", { encoding: "utf8" }).trim();
26
+ activeWinId = "0x" + parseInt(activeWinId).toString(16).padStart(8, "0");
27
+ } catch (e) {
28
+ // xdotool not available
29
+ }
30
+
31
+ windows = lines.map((line) => {
32
+ const parts = line.split(/\s+/);
33
+ const winId = parts[0];
34
+ const desktop = parts[1];
35
+ const pid = parts[2];
36
+ const x = parseInt(parts[3]);
37
+ const y = parseInt(parts[4]);
38
+ const width = parseInt(parts[5]);
39
+ const height = parseInt(parts[6]);
40
+ const title = parts.slice(8).join(" ");
41
+
42
+ // 获取进程名
43
+ let processName = "";
44
+ try {
45
+ processName = execSync(`ps -p ${pid} -o comm=`, { encoding: "utf8" }).trim();
46
+ } catch (e) {
47
+ processName = "unknown";
48
+ }
49
+
50
+ const isFocused = winId === activeWinId;
51
+ const isVisible = desktop !== "-1" && width > 0 && height > 0;
52
+
53
+ if (detail) {
54
+ // 生成缩略图 URL
55
+ const baseUrl =
56
+ process.env.ELECTRON_MCP_BASE_URL || `http://localhost:${process.env.PORT || 8101}`;
57
+ const thumbUrl = `${baseUrl}/files/screenshot/sys_win_${winId.replace(/^0x/, "")}.jpeg`;
58
+
59
+ return {
60
+ windowId: winId,
61
+ pid: parseInt(pid),
62
+ processName,
63
+ title,
64
+ bounds: { x, y, width, height },
65
+ desktop: parseInt(desktop),
66
+ isVisible,
67
+ isFocused,
68
+ thumbUrl,
69
+ };
70
+ } else {
71
+ return {
72
+ windowId: winId,
73
+ title,
74
+ processName,
75
+ };
76
+ }
77
+ });
78
+ } else if (process.platform === "darwin") {
79
+ // macOS 使用 osascript
80
+ const script = `
81
+ tell application "System Events"
82
+ set windowList to {}
83
+ repeat with proc in (every process whose background only is false)
84
+ set procName to name of proc
85
+ set procPID to unix id of proc
86
+ repeat with win in (every window of proc)
87
+ set winName to name of win
88
+ set winPos to position of win
89
+ set winSize to size of win
90
+ set end of windowList to {procName, procPID, winName, item 1 of winPos, item 2 of winPos, item 1 of winSize, item 2 of winSize}
91
+ end repeat
92
+ end repeat
93
+ return windowList
94
+ end tell
95
+ `;
96
+ const output = execSync(`osascript -e '${script}'`, { encoding: "utf8" });
97
+ // Parse output and format
98
+ windows = [{ raw: output }];
99
+ } else {
100
+ throw new Error("Unsupported platform");
101
+ }
102
+
103
+ // 清理不在列表中的截图文件
104
+ const screenshotDir = path.join(
105
+ require("os").homedir(),
106
+ "electron-mcp-files",
107
+ "screenshot"
108
+ );
109
+ if (fs.existsSync(screenshotDir)) {
110
+ const validIds = new Set(windows.map((w) => w.windowId.replace(/^0x/, "")));
111
+ const files = fs.readdirSync(screenshotDir);
112
+
113
+ for (const file of files) {
114
+ if (file.startsWith("sys_win_") && file.endsWith(".jpeg")) {
115
+ const winId = file.replace("sys_win_", "").replace(".jpeg", "");
116
+ if (!validIds.has(winId)) {
117
+ fs.unlinkSync(path.join(screenshotDir, file));
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: JSON.stringify(
128
+ {
129
+ platform: process.platform,
130
+ total: windows.length,
131
+ windows,
132
+ },
133
+ null,
134
+ 2
135
+ ),
136
+ },
137
+ ],
138
+ };
139
+ } catch (error) {
140
+ return {
141
+ content: [{ type: "text", text: `Error: ${error.message}` }],
142
+ isError: true,
143
+ };
144
+ }
145
+ },
146
+ { tag: "System" }
147
+ );
148
+
149
+ registerTool(
150
+ "focus_system_window",
151
+ "聚焦指定的系统窗口",
152
+ z.object({
153
+ windowId: z.string().describe("窗口ID(从 get_system_windows 获取)"),
154
+ }),
155
+ async ({ windowId }) => {
156
+ try {
157
+ if (process.platform === "linux") {
158
+ execSync(`wmctrl -ia ${windowId}`);
159
+ return {
160
+ content: [
161
+ {
162
+ type: "text",
163
+ text: JSON.stringify(
164
+ {
165
+ success: true,
166
+ windowId,
167
+ message: "Window focused",
168
+ },
169
+ null,
170
+ 2
171
+ ),
172
+ },
173
+ ],
174
+ };
175
+ } else {
176
+ throw new Error("Unsupported platform");
177
+ }
178
+ } catch (error) {
179
+ return {
180
+ content: [{ type: "text", text: `Error: ${error.message}` }],
181
+ isError: true,
182
+ };
183
+ }
184
+ },
185
+ { tag: "System" }
186
+ );
187
+
188
+ registerTool(
189
+ "get_system_info",
190
+ "获取系统信息(CPU、内存、磁盘、负载、IP)",
191
+ z.object({}),
192
+ async () => {
193
+ try {
194
+ const os = require("os");
195
+
196
+ // CPU
197
+ const cpus = os.cpus();
198
+ const cpuModel = cpus[0].model;
199
+ const cpuCount = cpus.length;
200
+
201
+ // CPU 使用率
202
+ let cpuUsage = 0;
203
+ if (process.platform === "linux") {
204
+ const top = execSync("top -bn1 | grep 'Cpu(s)'", { encoding: "utf8" });
205
+ const match = top.match(/(\d+\.\d+)\s+id/);
206
+ if (match) {
207
+ cpuUsage = (100 - parseFloat(match[1])).toFixed(1) + "%";
208
+ }
209
+ }
210
+
211
+ // 内存
212
+ const totalMem = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
213
+ const freeMem = (os.freemem() / 1024 / 1024 / 1024).toFixed(1);
214
+ const usedMem = (totalMem - freeMem).toFixed(1);
215
+
216
+ // 负载
217
+ const loadavg = os.loadavg().map((l) => l.toFixed(2));
218
+
219
+ // 磁盘(Linux)
220
+ let disk = {};
221
+ if (process.platform === "linux") {
222
+ const df = execSync("df -h /", { encoding: "utf8" });
223
+ const lines = df.trim().split("\n");
224
+ const parts = lines[1].split(/\s+/);
225
+ disk = {
226
+ total: parts[1],
227
+ used: parts[2],
228
+ available: parts[3],
229
+ usePercent: parts[4],
230
+ };
231
+ }
232
+
233
+ // 本地 IP
234
+ const networkInterfaces = os.networkInterfaces();
235
+ const localIPs = [];
236
+ for (const name in networkInterfaces) {
237
+ for (const net of networkInterfaces[name]) {
238
+ if (net.family === "IPv4" && !net.internal) {
239
+ localIPs.push(net.address);
240
+ }
241
+ }
242
+ }
243
+
244
+ // 公网 IP
245
+ let publicIP = "";
246
+ try {
247
+ publicIP = execSync("curl -s https://api.myip.com", { encoding: "utf8", timeout: 5000 });
248
+ publicIP = JSON.parse(publicIP);
249
+ } catch (e) {
250
+ publicIP = "Failed to fetch";
251
+ }
252
+
253
+ return {
254
+ content: [
255
+ {
256
+ type: "text",
257
+ text: JSON.stringify(
258
+ {
259
+ platform: os.platform(),
260
+ arch: os.arch(),
261
+ hostname: os.hostname(),
262
+ uptime: Math.floor(os.uptime() / 60) + " minutes",
263
+ cpu: {
264
+ model: cpuModel,
265
+ cores: cpuCount,
266
+ usage: cpuUsage,
267
+ },
268
+ memory: {
269
+ total: totalMem + "G",
270
+ used: usedMem + "G",
271
+ free: freeMem + "G",
272
+ },
273
+ loadavg: {
274
+ "1min": loadavg[0],
275
+ "5min": loadavg[1],
276
+ "15min": loadavg[2],
277
+ },
278
+ disk,
279
+ network: {
280
+ localIP: localIPs,
281
+ publicIP,
282
+ },
283
+ },
284
+ null,
285
+ 2
286
+ ),
287
+ },
288
+ ],
289
+ };
290
+ } catch (error) {
291
+ return {
292
+ content: [{ type: "text", text: `Error: ${error.message}` }],
293
+ isError: true,
294
+ };
295
+ }
296
+ },
297
+ { tag: "System" }
298
+ );
299
+
300
+ registerTool(
301
+ "system_screenshot",
302
+ "截取系统屏幕并保存到文件",
303
+ z.object({
304
+ quality: z.number().optional().default(80).describe("JPEG 质量 (1-100)"),
305
+ copyToClipboard: z.boolean().optional().default(false).describe("是否复制到剪贴板"),
306
+ }),
307
+ async ({ quality, copyToClipboard }) => {
308
+ try {
309
+ const fs = require("fs");
310
+ const path = require("path");
311
+ const crypto = require("crypto");
312
+
313
+ // 创建目录
314
+ const screenshotDir = path.join(
315
+ require("os").homedir(),
316
+ "electron-mcp-files",
317
+ "screenshot"
318
+ );
319
+ if (!fs.existsSync(screenshotDir)) {
320
+ fs.mkdirSync(screenshotDir, { recursive: true });
321
+ }
322
+
323
+ // 临时文件
324
+ const tmpFile = `/tmp/screenshot-${Date.now()}.png`;
325
+
326
+ // 截图
327
+ if (process.platform === "linux") {
328
+ execSync(`import -window root ${tmpFile}`);
329
+ } else {
330
+ throw new Error("Unsupported platform");
331
+ }
332
+
333
+ // 转换为 JPEG
334
+ const jpegFile = path.join(screenshotDir, "system.jpeg");
335
+ execSync(`convert ${tmpFile} -quality ${quality} ${jpegFile}`);
336
+
337
+ // 获取文件大小和尺寸
338
+ const stats = fs.statSync(jpegFile);
339
+ const sizeKB = (stats.size / 1024).toFixed(2);
340
+ const identify = execSync(`identify -format "%wx%h" ${jpegFile}`, { encoding: "utf8" });
341
+ const [width, height] = identify.split("x").map(Number);
342
+
343
+ // 复制到剪贴板
344
+ if (copyToClipboard) {
345
+ execSync(`xclip -selection clipboard -t image/jpeg -i ${jpegFile}`);
346
+ }
347
+
348
+ // 清理临时文件
349
+ fs.unlinkSync(tmpFile);
350
+
351
+ // 返回 URL
352
+ const baseUrl =
353
+ process.env.ELECTRON_MCP_BASE_URL || `http://localhost:${process.env.PORT || 8101}`;
354
+ const url = `${baseUrl}/files/screenshot/system.jpeg`;
355
+
356
+ return {
357
+ content: [
358
+ {
359
+ type: "text",
360
+ text: JSON.stringify(
361
+ {
362
+ success: true,
363
+ path: jpegFile,
364
+ url,
365
+ size: sizeKB + "KB",
366
+ width,
367
+ height,
368
+ quality,
369
+ copiedToClipboard: copyToClipboard,
370
+ },
371
+ null,
372
+ 2
373
+ ),
374
+ },
375
+ ],
376
+ };
377
+ } catch (error) {
378
+ return {
379
+ content: [{ type: "text", text: `Error: ${error.message}` }],
380
+ isError: true,
381
+ };
382
+ }
383
+ },
384
+ { tag: "System" }
385
+ );
386
+
387
+ registerTool(
388
+ "sys_win_screenshot",
389
+ "截取指定系统窗口并保存到文件",
390
+ z.object({
391
+ windowId: z.string().describe("窗口ID(从 get_system_windows 获取)"),
392
+ quality: z.number().optional().default(80).describe("JPEG 质量 (1-100)"),
393
+ copyToClipboard: z.boolean().optional().default(false).describe("是否复制到剪贴板"),
394
+ }),
395
+ async ({ windowId, quality, copyToClipboard }) => {
396
+ try {
397
+ const fs = require("fs");
398
+ const path = require("path");
399
+
400
+ // 创建目录
401
+ const screenshotDir = path.join(
402
+ require("os").homedir(),
403
+ "electron-mcp-files",
404
+ "screenshot"
405
+ );
406
+ if (!fs.existsSync(screenshotDir)) {
407
+ fs.mkdirSync(screenshotDir, { recursive: true });
408
+ }
409
+
410
+ // 临时文件
411
+ const tmpFile = `/tmp/screenshot-${Date.now()}.png`;
412
+
413
+ // 截图
414
+ if (process.platform === "linux") {
415
+ execSync(`import -window ${windowId} ${tmpFile}`);
416
+ } else {
417
+ throw new Error("Unsupported platform");
418
+ }
419
+
420
+ // 转换为 JPEG
421
+ const filename = `sys_win_${windowId.replace(/^0x/, "")}.jpeg`;
422
+ const jpegFile = path.join(screenshotDir, filename);
423
+ execSync(`convert ${tmpFile} -quality ${quality} ${jpegFile}`);
424
+
425
+ // 获取文件大小和尺寸
426
+ const stats = fs.statSync(jpegFile);
427
+ const sizeKB = (stats.size / 1024).toFixed(2);
428
+ const identify = execSync(`identify -format "%wx%h" ${jpegFile}`, { encoding: "utf8" });
429
+ const [width, height] = identify.split("x").map(Number);
430
+
431
+ // 复制到剪贴板
432
+ if (copyToClipboard) {
433
+ execSync(`xclip -selection clipboard -t image/jpeg -i ${jpegFile}`);
434
+ }
435
+
436
+ // 清理临时文件
437
+ fs.unlinkSync(tmpFile);
438
+
439
+ // 返回 URL
440
+ const baseUrl =
441
+ process.env.ELECTRON_MCP_BASE_URL || `http://localhost:${process.env.PORT || 8101}`;
442
+ const url = `${baseUrl}/files/screenshot/${filename}`;
443
+
444
+ return {
445
+ content: [
446
+ {
447
+ type: "text",
448
+ text: JSON.stringify(
449
+ {
450
+ success: true,
451
+ windowId,
452
+ path: jpegFile,
453
+ url,
454
+ size: sizeKB + "KB",
455
+ width,
456
+ height,
457
+ quality,
458
+ copiedToClipboard: copyToClipboard,
459
+ },
460
+ null,
461
+ 2
462
+ ),
463
+ },
464
+ ],
465
+ };
466
+ } catch (error) {
467
+ return {
468
+ content: [{ type: "text", text: `Error: ${error.message}` }],
469
+ isError: true,
470
+ };
471
+ }
472
+ },
473
+ { tag: "System" }
474
+ );
475
+
476
+ registerTool(
477
+ "system_window_setbound",
478
+ "设置系统窗口的位置和大小",
479
+ z.object({
480
+ windowId: z.string().describe("窗口ID(从 get_system_windows 获取)"),
481
+ x: z.number().optional().describe("X 坐标"),
482
+ y: z.number().optional().describe("Y 坐标"),
483
+ width: z.number().optional().describe("宽度"),
484
+ height: z.number().optional().describe("高度"),
485
+ }),
486
+ async ({ windowId, x, y, width, height }) => {
487
+ try {
488
+ if (process.platform === "linux") {
489
+ // 构建 wmctrl 命令
490
+ const gravity = 0; // 左上角
491
+ const flags = [];
492
+
493
+ if (x !== undefined || y !== undefined || width !== undefined || height !== undefined) {
494
+ const posX = x !== undefined ? x : -1;
495
+ const posY = y !== undefined ? y : -1;
496
+ const w = width !== undefined ? width : -1;
497
+ const h = height !== undefined ? height : -1;
498
+ execSync(`wmctrl -ir ${windowId} -e ${gravity},${posX},${posY},${w},${h}`);
499
+ }
500
+
501
+ return {
502
+ content: [
503
+ {
504
+ type: "text",
505
+ text: JSON.stringify(
506
+ {
507
+ success: true,
508
+ windowId,
509
+ bounds: { x, y, width, height },
510
+ },
511
+ null,
512
+ 2
513
+ ),
514
+ },
515
+ ],
516
+ };
517
+ } else {
518
+ throw new Error("Unsupported platform");
519
+ }
520
+ } catch (error) {
521
+ return {
522
+ content: [{ type: "text", text: `Error: ${error.message}` }],
523
+ isError: true,
524
+ };
525
+ }
526
+ },
527
+ { tag: "System" }
528
+ );
529
+ }
530
+
531
+ module.exports = registerTools;