@hangox/mg-cli 1.0.0 → 1.0.2

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 (42) hide show
  1. package/dist/cli.js +1580 -0
  2. package/dist/cli.js.map +1 -0
  3. package/dist/daemon-runner.js +794 -0
  4. package/dist/daemon-runner.js.map +1 -0
  5. package/dist/index-DNrszrq9.d.ts +568 -0
  6. package/dist/index.d.ts +129 -0
  7. package/dist/index.js +950 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +689 -0
  11. package/dist/server.js.map +1 -0
  12. package/package.json +5 -1
  13. package/.eslintrc.cjs +0 -26
  14. package/CLAUDE.md +0 -43
  15. package/src/cli/client.ts +0 -266
  16. package/src/cli/commands/execute-code.ts +0 -59
  17. package/src/cli/commands/export-image.ts +0 -193
  18. package/src/cli/commands/get-all-nodes.ts +0 -81
  19. package/src/cli/commands/get-all-pages.ts +0 -118
  20. package/src/cli/commands/get-node-by-id.ts +0 -83
  21. package/src/cli/commands/get-node-by-link.ts +0 -105
  22. package/src/cli/commands/server.ts +0 -130
  23. package/src/cli/index.ts +0 -33
  24. package/src/index.ts +0 -9
  25. package/src/server/connection-manager.ts +0 -211
  26. package/src/server/daemon-runner.ts +0 -22
  27. package/src/server/daemon.ts +0 -211
  28. package/src/server/index.ts +0 -8
  29. package/src/server/logger.ts +0 -117
  30. package/src/server/request-handler.ts +0 -192
  31. package/src/server/websocket-server.ts +0 -297
  32. package/src/shared/constants.ts +0 -90
  33. package/src/shared/errors.ts +0 -131
  34. package/src/shared/index.ts +0 -8
  35. package/src/shared/types.ts +0 -227
  36. package/src/shared/utils.ts +0 -352
  37. package/tests/unit/shared/constants.test.ts +0 -66
  38. package/tests/unit/shared/errors.test.ts +0 -82
  39. package/tests/unit/shared/utils.test.ts +0 -208
  40. package/tsconfig.json +0 -22
  41. package/tsup.config.ts +0 -33
  42. package/vitest.config.ts +0 -22
package/dist/cli.js ADDED
@@ -0,0 +1,1580 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command as Command8 } from "commander";
5
+
6
+ // src/cli/commands/server.ts
7
+ import { Command } from "commander";
8
+
9
+ // src/server/daemon.ts
10
+ import { spawn } from "child_process";
11
+ import { fileURLToPath } from "url";
12
+ import { dirname as dirname3, join as join2 } from "path";
13
+
14
+ // src/shared/utils.ts
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
16
+ import { dirname, resolve, isAbsolute } from "path";
17
+
18
+ // src/shared/constants.ts
19
+ import { homedir } from "os";
20
+ import { join } from "path";
21
+ var DEFAULT_PORT = 9527;
22
+ var PORT_RANGE_START = 9527;
23
+ var PORT_RANGE_END = 9536;
24
+ var PORT_SCAN_TIMEOUT = 500;
25
+ var CONFIG_DIR = join(homedir(), ".mg-plugin");
26
+ var SERVER_INFO_FILE = join(CONFIG_DIR, "server.json");
27
+ var LOG_DIR = join(CONFIG_DIR, "logs");
28
+ var SERVER_LOG_FILE = join(LOG_DIR, "server.log");
29
+ var HEARTBEAT_INTERVAL = 3e4;
30
+ var HEARTBEAT_TIMEOUT = 9e4;
31
+ var REQUEST_TIMEOUT = 3e4;
32
+ var SERVER_START_TIMEOUT = 5e3;
33
+ var RETRY_INTERVALS = [1e3, 2e3, 4e3];
34
+ var MAX_RETRY_COUNT = 3;
35
+
36
+ // src/shared/utils.ts
37
+ function ensureDir(dir) {
38
+ if (!existsSync(dir)) {
39
+ mkdirSync(dir, { recursive: true });
40
+ }
41
+ }
42
+ function ensureConfigDir() {
43
+ ensureDir(CONFIG_DIR);
44
+ ensureDir(LOG_DIR);
45
+ }
46
+ function readServerInfo() {
47
+ try {
48
+ if (!existsSync(SERVER_INFO_FILE)) {
49
+ return null;
50
+ }
51
+ const content = readFileSync(SERVER_INFO_FILE, "utf-8");
52
+ return JSON.parse(content);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function writeServerInfo(info) {
58
+ ensureConfigDir();
59
+ writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2), "utf-8");
60
+ }
61
+ function deleteServerInfo() {
62
+ try {
63
+ if (existsSync(SERVER_INFO_FILE)) {
64
+ unlinkSync(SERVER_INFO_FILE);
65
+ }
66
+ } catch {
67
+ }
68
+ }
69
+ function isProcessRunning(pid) {
70
+ try {
71
+ process.kill(pid, 0);
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function killProcess(pid) {
78
+ try {
79
+ process.kill(pid, "SIGTERM");
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ function parseMgpLink(link) {
86
+ if (!link.startsWith("mgp://")) {
87
+ return null;
88
+ }
89
+ try {
90
+ const urlPart = link.slice(6);
91
+ const questionMarkIndex = urlPart.indexOf("?");
92
+ if (questionMarkIndex === -1) {
93
+ return null;
94
+ }
95
+ const pageUrl = urlPart.slice(0, questionMarkIndex);
96
+ const queryString = urlPart.slice(questionMarkIndex + 1);
97
+ const params = new URLSearchParams(queryString);
98
+ const encodedNodeId = params.get("nodeId");
99
+ if (!encodedNodeId) {
100
+ return null;
101
+ }
102
+ const nodeId = decodeURIComponent(encodedNodeId);
103
+ if (!/^(\d+:\d+)(\/\d+:\d+)*$/.test(nodeId)) {
104
+ return null;
105
+ }
106
+ const encodedNodePath = params.get("nodePath");
107
+ let nodePath;
108
+ if (encodedNodePath) {
109
+ const decodedNodePath = decodeURIComponent(encodedNodePath);
110
+ nodePath = decodedNodePath.split("/").filter(Boolean);
111
+ if (!nodePath.every((segment) => /^\d+:\d+$/.test(segment))) {
112
+ return null;
113
+ }
114
+ }
115
+ return {
116
+ pageUrl,
117
+ nodeId,
118
+ nodePath
119
+ };
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+ function formatDuration(ms) {
125
+ const seconds = Math.floor(ms / 1e3);
126
+ const minutes = Math.floor(seconds / 60);
127
+ const hours = Math.floor(minutes / 60);
128
+ const days = Math.floor(hours / 24);
129
+ if (days > 0) {
130
+ return `${days} \u5929 ${hours % 24} \u5C0F\u65F6`;
131
+ }
132
+ if (hours > 0) {
133
+ return `${hours} \u5C0F\u65F6 ${minutes % 60} \u5206\u949F`;
134
+ }
135
+ if (minutes > 0) {
136
+ return `${minutes} \u5206\u949F ${seconds % 60} \u79D2`;
137
+ }
138
+ return `${seconds} \u79D2`;
139
+ }
140
+ function generateId() {
141
+ return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
142
+ }
143
+ function getCurrentISOTime() {
144
+ return (/* @__PURE__ */ new Date()).toISOString();
145
+ }
146
+ function formatLogTime(date = /* @__PURE__ */ new Date()) {
147
+ const year = date.getFullYear();
148
+ const month = String(date.getMonth() + 1).padStart(2, "0");
149
+ const day = String(date.getDate()).padStart(2, "0");
150
+ const hours = String(date.getHours()).padStart(2, "0");
151
+ const minutes = String(date.getMinutes()).padStart(2, "0");
152
+ const seconds = String(date.getSeconds()).padStart(2, "0");
153
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
154
+ }
155
+ function extractFileIdFromUrl(url) {
156
+ const match = url.match(/\/file\/(\d+)/);
157
+ return match ? match[1] : null;
158
+ }
159
+ function extractFileIdFromMgpLink(link) {
160
+ if (!link.startsWith("mgp://")) {
161
+ return null;
162
+ }
163
+ return extractFileIdFromUrl(link);
164
+ }
165
+ function extractFileId(input) {
166
+ const trimmed = input.trim();
167
+ if (/^\d+$/.test(trimmed)) {
168
+ return trimmed;
169
+ }
170
+ if (trimmed.startsWith("mgp://")) {
171
+ return extractFileIdFromMgpLink(trimmed);
172
+ }
173
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
174
+ return extractFileIdFromUrl(trimmed);
175
+ }
176
+ return extractFileIdFromUrl(trimmed);
177
+ }
178
+
179
+ // src/shared/errors.ts
180
+ var ErrorNames = {
181
+ ["E001" /* CONNECTION_FAILED */]: "CONNECTION_FAILED",
182
+ ["E002" /* CONNECTION_TIMEOUT */]: "CONNECTION_TIMEOUT",
183
+ ["E003" /* NO_PAGE_CONNECTED */]: "NO_PAGE_CONNECTED",
184
+ ["E004" /* PAGE_NOT_FOUND */]: "PAGE_NOT_FOUND",
185
+ ["E005" /* NODE_NOT_FOUND */]: "NODE_NOT_FOUND",
186
+ ["E006" /* NO_SELECTION */]: "NO_SELECTION",
187
+ ["E007" /* MG_UNAVAILABLE */]: "MG_UNAVAILABLE",
188
+ ["E008" /* EXPORT_FAILED */]: "EXPORT_FAILED",
189
+ ["E009" /* FILE_WRITE_FAILED */]: "FILE_WRITE_FAILED",
190
+ ["E010" /* INVALID_LINK */]: "INVALID_LINK",
191
+ ["E011" /* INVALID_PARAMS */]: "INVALID_PARAMS",
192
+ ["E012" /* REQUEST_TIMEOUT */]: "REQUEST_TIMEOUT",
193
+ ["E013" /* PORT_EXHAUSTED */]: "PORT_EXHAUSTED",
194
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "SERVER_DISCOVERY_FAILED",
195
+ ["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
196
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
197
+ ["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
198
+ ["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
199
+ };
200
+ var ErrorMessages = {
201
+ ["E001" /* CONNECTION_FAILED */]: "\u65E0\u6CD5\u8FDE\u63A5\u5230 MG Server",
202
+ ["E002" /* CONNECTION_TIMEOUT */]: "\u8FDE\u63A5\u8D85\u65F6",
203
+ ["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server",
204
+ ["E004" /* PAGE_NOT_FOUND */]: "\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762",
205
+ ["E005" /* NODE_NOT_FOUND */]: "\u8282\u70B9\u4E0D\u5B58\u5728",
206
+ ["E006" /* NO_SELECTION */]: "\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9",
207
+ ["E007" /* MG_UNAVAILABLE */]: "mg \u5BF9\u8C61\u4E0D\u53EF\u7528",
208
+ ["E008" /* EXPORT_FAILED */]: "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25",
209
+ ["E009" /* FILE_WRITE_FAILED */]: "\u6587\u4EF6\u5199\u5165\u5931\u8D25",
210
+ ["E010" /* INVALID_LINK */]: "\u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F",
211
+ ["E011" /* INVALID_PARAMS */]: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
212
+ ["E012" /* REQUEST_TIMEOUT */]: "\u8BF7\u6C42\u8D85\u65F6",
213
+ ["E013" /* PORT_EXHAUSTED */]: "\u6240\u6709\u5907\u9009\u7AEF\u53E3\u5747\u88AB\u5360\u7528",
214
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "\u65E0\u6CD5\u53D1\u73B0 Server (\u7AEF\u53E3\u626B\u63CF\u5931\u8D25)",
215
+ ["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
216
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
217
+ ["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
218
+ ["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
219
+ };
220
+ var MGError = class extends Error {
221
+ /** 错误码 */
222
+ code;
223
+ /** 错误名称 */
224
+ errorName;
225
+ /** 额外详情 */
226
+ details;
227
+ constructor(code, message, details) {
228
+ super(message || ErrorMessages[code]);
229
+ this.name = "MGError";
230
+ this.code = code;
231
+ this.errorName = ErrorNames[code];
232
+ this.details = details;
233
+ }
234
+ /** 转换为 JSON 格式 */
235
+ toJSON() {
236
+ return {
237
+ code: this.code,
238
+ name: this.errorName,
239
+ message: this.message,
240
+ details: this.details
241
+ };
242
+ }
243
+ /** 格式化输出 */
244
+ toString() {
245
+ return `\u9519\u8BEF [${this.code}]: ${this.message}`;
246
+ }
247
+ };
248
+
249
+ // src/server/websocket-server.ts
250
+ import { WebSocketServer } from "ws";
251
+
252
+ // src/server/logger.ts
253
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
254
+ import { dirname as dirname2 } from "path";
255
+ var levelPriority = {
256
+ ["DEBUG" /* DEBUG */]: 0,
257
+ ["INFO" /* INFO */]: 1,
258
+ ["WARN" /* WARN */]: 2,
259
+ ["ERROR" /* ERROR */]: 3
260
+ };
261
+ var Logger = class {
262
+ options;
263
+ constructor(options = {}) {
264
+ this.options = {
265
+ console: options.console ?? true,
266
+ file: options.file ?? false,
267
+ filePath: options.filePath ?? SERVER_LOG_FILE,
268
+ minLevel: options.minLevel ?? "INFO" /* INFO */
269
+ };
270
+ if (this.options.file) {
271
+ const dir = dirname2(this.options.filePath);
272
+ if (!existsSync2(dir)) {
273
+ mkdirSync2(dir, { recursive: true });
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * 记录日志
279
+ */
280
+ log(level, message, ...args) {
281
+ if (levelPriority[level] < levelPriority[this.options.minLevel]) {
282
+ return;
283
+ }
284
+ const timestamp = formatLogTime();
285
+ const formattedMessage = `[${timestamp}] [${level}] ${message}`;
286
+ if (this.options.console) {
287
+ const consoleMethod = level === "ERROR" /* ERROR */ ? console.error : console.log;
288
+ if (args.length > 0) {
289
+ consoleMethod(formattedMessage, ...args);
290
+ } else {
291
+ consoleMethod(formattedMessage);
292
+ }
293
+ }
294
+ if (this.options.file) {
295
+ try {
296
+ const fileMessage = args.length > 0 ? `${formattedMessage} ${JSON.stringify(args)}
297
+ ` : `${formattedMessage}
298
+ `;
299
+ appendFileSync(this.options.filePath, fileMessage);
300
+ } catch (error) {
301
+ if (this.options.console) {
302
+ console.error("\u65E5\u5FD7\u6587\u4EF6\u5199\u5165\u5931\u8D25:", error);
303
+ }
304
+ }
305
+ }
306
+ }
307
+ debug(message, ...args) {
308
+ this.log("DEBUG" /* DEBUG */, message, ...args);
309
+ }
310
+ info(message, ...args) {
311
+ this.log("INFO" /* INFO */, message, ...args);
312
+ }
313
+ warn(message, ...args) {
314
+ this.log("WARN" /* WARN */, message, ...args);
315
+ }
316
+ error(message, ...args) {
317
+ this.log("ERROR" /* ERROR */, message, ...args);
318
+ }
319
+ };
320
+ function createLogger(options) {
321
+ return new Logger(options);
322
+ }
323
+
324
+ // src/server/connection-manager.ts
325
+ var ConnectionManager = class {
326
+ logger;
327
+ /** Provider 连接(按页面 URL 索引) */
328
+ providers = /* @__PURE__ */ new Map();
329
+ /** Consumer 连接 */
330
+ consumers = /* @__PURE__ */ new Map();
331
+ /** 所有连接(按 ID 索引) */
332
+ allConnections = /* @__PURE__ */ new Map();
333
+ /** 心跳检查定时器 */
334
+ heartbeatTimer = null;
335
+ constructor(logger) {
336
+ this.logger = logger;
337
+ }
338
+ /**
339
+ * 启动心跳检查
340
+ */
341
+ startHeartbeatCheck(interval = 3e4) {
342
+ if (this.heartbeatTimer) {
343
+ clearInterval(this.heartbeatTimer);
344
+ }
345
+ this.heartbeatTimer = setInterval(() => {
346
+ this.checkHeartbeats();
347
+ }, interval);
348
+ }
349
+ /**
350
+ * 停止心跳检查
351
+ */
352
+ stopHeartbeatCheck() {
353
+ if (this.heartbeatTimer) {
354
+ clearInterval(this.heartbeatTimer);
355
+ this.heartbeatTimer = null;
356
+ }
357
+ }
358
+ /**
359
+ * 检查所有连接的心跳
360
+ */
361
+ checkHeartbeats() {
362
+ const now = Date.now();
363
+ for (const [id, ws] of this.allConnections) {
364
+ const lastActive = ws.connectionInfo.lastActiveAt.getTime();
365
+ const elapsed = now - lastActive;
366
+ if (elapsed > HEARTBEAT_TIMEOUT) {
367
+ this.logger.warn(`\u8FDE\u63A5 ${id} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5`);
368
+ this.removeConnection(ws);
369
+ ws.terminate();
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * 添加连接
375
+ */
376
+ addConnection(ws, type, pageUrl, pageId) {
377
+ const connectionId = generateId();
378
+ const now = /* @__PURE__ */ new Date();
379
+ const connectionInfo = {
380
+ id: connectionId,
381
+ type,
382
+ pageUrl,
383
+ pageId,
384
+ connectedAt: now,
385
+ lastActiveAt: now
386
+ };
387
+ const managedWs = ws;
388
+ managedWs.connectionId = connectionId;
389
+ managedWs.connectionInfo = connectionInfo;
390
+ managedWs.isAlive = true;
391
+ this.allConnections.set(connectionId, managedWs);
392
+ if (type === "provider" /* PROVIDER */ && pageUrl) {
393
+ const existing = this.providers.get(pageUrl);
394
+ if (existing) {
395
+ this.logger.info(`\u9875\u9762 ${pageUrl} \u5DF2\u6709\u8FDE\u63A5\uFF0C\u66FF\u6362\u4E3A\u65B0\u8FDE\u63A5`);
396
+ this.removeConnection(existing);
397
+ existing.close();
398
+ }
399
+ this.providers.set(pageUrl, managedWs);
400
+ this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl}`);
401
+ } else if (type === "consumer" /* CONSUMER */) {
402
+ this.consumers.set(connectionId, managedWs);
403
+ this.logger.info(`Consumer \u8FDE\u63A5: ${connectionId}`);
404
+ }
405
+ return managedWs;
406
+ }
407
+ /**
408
+ * 移除连接
409
+ */
410
+ removeConnection(ws) {
411
+ const { connectionId, connectionInfo } = ws;
412
+ this.allConnections.delete(connectionId);
413
+ if (connectionInfo.type === "provider" /* PROVIDER */ && connectionInfo.pageUrl) {
414
+ this.providers.delete(connectionInfo.pageUrl);
415
+ this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl}`);
416
+ } else if (connectionInfo.type === "consumer" /* CONSUMER */) {
417
+ this.consumers.delete(connectionId);
418
+ this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
419
+ }
420
+ }
421
+ /**
422
+ * 更新连接活跃时间
423
+ */
424
+ updateLastActive(ws) {
425
+ ws.connectionInfo.lastActiveAt = /* @__PURE__ */ new Date();
426
+ ws.isAlive = true;
427
+ }
428
+ /**
429
+ * 根据页面 URL 查找 Provider
430
+ */
431
+ findProviderByPageUrl(pageUrl) {
432
+ return this.providers.get(pageUrl);
433
+ }
434
+ /**
435
+ * 获取第一个可用的 Provider
436
+ */
437
+ getFirstProvider() {
438
+ const iterator = this.providers.values();
439
+ const first = iterator.next();
440
+ return first.value;
441
+ }
442
+ /**
443
+ * 获取所有 Provider 信息
444
+ */
445
+ getAllProviders() {
446
+ return Array.from(this.providers.values()).map((ws) => ws.connectionInfo);
447
+ }
448
+ /**
449
+ * 获取连接统计
450
+ */
451
+ getStats() {
452
+ return {
453
+ providers: this.providers.size,
454
+ consumers: this.consumers.size,
455
+ total: this.allConnections.size
456
+ };
457
+ }
458
+ /**
459
+ * 获取所有已连接的页面 URL
460
+ */
461
+ getConnectedPageUrls() {
462
+ return Array.from(this.providers.keys());
463
+ }
464
+ /**
465
+ * 关闭所有连接
466
+ */
467
+ closeAll() {
468
+ this.stopHeartbeatCheck();
469
+ for (const ws of this.allConnections.values()) {
470
+ ws.close();
471
+ }
472
+ this.providers.clear();
473
+ this.consumers.clear();
474
+ this.allConnections.clear();
475
+ }
476
+ };
477
+
478
+ // src/server/request-handler.ts
479
+ var RequestHandler = class {
480
+ logger;
481
+ connectionManager;
482
+ /** 待处理的请求 */
483
+ pendingRequests = /* @__PURE__ */ new Map();
484
+ constructor(connectionManager, logger) {
485
+ this.connectionManager = connectionManager;
486
+ this.logger = logger;
487
+ }
488
+ /**
489
+ * 处理 Consumer 请求
490
+ */
491
+ async handleRequest(consumer, message) {
492
+ const requestId = message.id || generateId();
493
+ const { type, pageUrl, params } = message;
494
+ this.logger.info(`\u6536\u5230\u8BF7\u6C42: ${type} (${requestId})`, { pageUrl });
495
+ let provider;
496
+ if (pageUrl) {
497
+ provider = this.connectionManager.findProviderByPageUrl(pageUrl);
498
+ if (!provider) {
499
+ this.sendError(consumer, requestId, "E004" /* PAGE_NOT_FOUND */, `\u672A\u627E\u5230\u9875\u9762: ${pageUrl}`);
500
+ return;
501
+ }
502
+ } else {
503
+ provider = this.connectionManager.getFirstProvider();
504
+ if (!provider) {
505
+ this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, "\u6CA1\u6709\u9875\u9762\u8FDE\u63A5\u5230 Server");
506
+ return;
507
+ }
508
+ }
509
+ const timer = setTimeout(() => {
510
+ this.handleTimeout(requestId);
511
+ }, REQUEST_TIMEOUT);
512
+ this.pendingRequests.set(requestId, {
513
+ id: requestId,
514
+ consumer,
515
+ timer,
516
+ timestamp: Date.now()
517
+ });
518
+ const forwardMessage = {
519
+ id: requestId,
520
+ type,
521
+ pageUrl: pageUrl || provider.connectionInfo.pageUrl,
522
+ params,
523
+ timestamp: Date.now()
524
+ };
525
+ try {
526
+ provider.send(JSON.stringify(forwardMessage));
527
+ this.logger.info(`\u8BF7\u6C42\u8F6C\u53D1: ${type} -> ${provider.connectionInfo.pageUrl}`);
528
+ } catch {
529
+ this.cleanupRequest(requestId);
530
+ this.sendError(consumer, requestId, "E001" /* CONNECTION_FAILED */, "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25");
531
+ }
532
+ }
533
+ /**
534
+ * 处理 Provider 响应
535
+ */
536
+ handleResponse(response) {
537
+ const { id } = response;
538
+ const pending = this.pendingRequests.get(id);
539
+ if (!pending) {
540
+ this.logger.warn(`\u6536\u5230\u672A\u77E5\u8BF7\u6C42\u7684\u54CD\u5E94: ${id}`);
541
+ return;
542
+ }
543
+ this.cleanupRequest(id);
544
+ try {
545
+ pending.consumer.send(JSON.stringify(response));
546
+ this.logger.info(
547
+ `\u54CD\u5E94\u8FD4\u56DE: ${id} (${response.success ? "\u6210\u529F" : "\u5931\u8D25"})`
548
+ );
549
+ } catch (error) {
550
+ this.logger.error(`\u54CD\u5E94\u8F6C\u53D1\u5931\u8D25: ${id}`, error);
551
+ }
552
+ }
553
+ /**
554
+ * 处理请求超时
555
+ */
556
+ handleTimeout(requestId) {
557
+ const pending = this.pendingRequests.get(requestId);
558
+ if (!pending) return;
559
+ this.logger.warn(`\u8BF7\u6C42\u8D85\u65F6: ${requestId}`);
560
+ this.cleanupRequest(requestId);
561
+ this.sendError(pending.consumer, requestId, "E012" /* REQUEST_TIMEOUT */, "\u8BF7\u6C42\u8D85\u65F6");
562
+ }
563
+ /**
564
+ * 清理请求
565
+ */
566
+ cleanupRequest(requestId) {
567
+ const pending = this.pendingRequests.get(requestId);
568
+ if (pending) {
569
+ clearTimeout(pending.timer);
570
+ this.pendingRequests.delete(requestId);
571
+ }
572
+ }
573
+ /**
574
+ * 发送错误响应
575
+ */
576
+ sendError(consumer, requestId, code, message) {
577
+ const error = new MGError(code, message);
578
+ const response = {
579
+ id: requestId,
580
+ type: "error" /* ERROR */,
581
+ success: false,
582
+ data: null,
583
+ error: error.toJSON()
584
+ };
585
+ try {
586
+ consumer.send(JSON.stringify(response));
587
+ } catch (err) {
588
+ this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
589
+ }
590
+ }
591
+ /**
592
+ * 清理特定连接的所有待处理请求
593
+ */
594
+ cleanupConnectionRequests(connectionId) {
595
+ for (const [requestId, pending] of this.pendingRequests) {
596
+ if (pending.consumer.connectionId === connectionId) {
597
+ this.cleanupRequest(requestId);
598
+ }
599
+ }
600
+ }
601
+ /**
602
+ * 清理所有待处理请求
603
+ */
604
+ cleanupAll() {
605
+ for (const [requestId] of this.pendingRequests) {
606
+ this.cleanupRequest(requestId);
607
+ }
608
+ }
609
+ };
610
+
611
+ // src/server/websocket-server.ts
612
+ var MGServer = class {
613
+ wss = null;
614
+ logger;
615
+ connectionManager;
616
+ requestHandler;
617
+ port;
618
+ isRunning = false;
619
+ constructor(options = {}) {
620
+ this.port = options.port || DEFAULT_PORT;
621
+ this.logger = options.logger || createLogger();
622
+ this.connectionManager = new ConnectionManager(this.logger);
623
+ this.requestHandler = new RequestHandler(this.connectionManager, this.logger);
624
+ }
625
+ /**
626
+ * 启动服务器
627
+ */
628
+ async start() {
629
+ if (this.isRunning) {
630
+ throw new MGError("E016" /* SERVER_ALREADY_RUNNING */, "Server \u5DF2\u5728\u8FD0\u884C\u4E2D");
631
+ }
632
+ const port = await this.findAvailablePort();
633
+ return new Promise((resolve7, reject) => {
634
+ this.wss = new WebSocketServer({ port });
635
+ this.wss.on("listening", () => {
636
+ this.port = port;
637
+ this.isRunning = true;
638
+ this.logger.info(`Server \u542F\u52A8\u6210\u529F\uFF0C\u76D1\u542C\u7AEF\u53E3: ${port}`);
639
+ this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL);
640
+ resolve7(port);
641
+ });
642
+ this.wss.on("error", (error) => {
643
+ this.logger.error("Server \u9519\u8BEF:", error);
644
+ reject(error);
645
+ });
646
+ this.wss.on("connection", (ws, request) => {
647
+ this.handleConnection(ws, request);
648
+ });
649
+ });
650
+ }
651
+ /**
652
+ * 查找可用端口
653
+ */
654
+ async findAvailablePort() {
655
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
656
+ const available = await this.isPortAvailable(port);
657
+ if (available) {
658
+ return port;
659
+ }
660
+ this.logger.debug(`\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A`);
661
+ }
662
+ throw new MGError(
663
+ "E013" /* PORT_EXHAUSTED */,
664
+ `\u7AEF\u53E3 ${PORT_RANGE_START}-${PORT_RANGE_END} \u5747\u88AB\u5360\u7528`
665
+ );
666
+ }
667
+ /**
668
+ * 检查端口是否可用
669
+ */
670
+ isPortAvailable(port) {
671
+ return new Promise((resolve7) => {
672
+ const testServer = new WebSocketServer({ port });
673
+ testServer.on("listening", () => {
674
+ testServer.close();
675
+ resolve7(true);
676
+ });
677
+ testServer.on("error", () => {
678
+ resolve7(false);
679
+ });
680
+ });
681
+ }
682
+ /**
683
+ * 处理新连接
684
+ */
685
+ handleConnection(ws, _request) {
686
+ this.logger.info("\u65B0\u8FDE\u63A5\u5EFA\u7ACB");
687
+ const registerTimeout = setTimeout(() => {
688
+ this.logger.warn("\u8FDE\u63A5\u6CE8\u518C\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5");
689
+ ws.close();
690
+ }, 5e3);
691
+ ws.on("message", (data) => {
692
+ try {
693
+ const message = JSON.parse(data.toString());
694
+ if (message.type === "register" /* REGISTER */) {
695
+ clearTimeout(registerTimeout);
696
+ this.handleRegister(ws, message);
697
+ return;
698
+ }
699
+ const managedWs = ws;
700
+ if (!managedWs.connectionId) {
701
+ this.logger.warn("\u672A\u6CE8\u518C\u7684\u8FDE\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u5FFD\u7565");
702
+ return;
703
+ }
704
+ this.handleMessage(managedWs, message);
705
+ } catch (error) {
706
+ this.logger.error("\u6D88\u606F\u89E3\u6790\u5931\u8D25:", error);
707
+ }
708
+ });
709
+ ws.on("close", () => {
710
+ clearTimeout(registerTimeout);
711
+ const managedWs = ws;
712
+ if (managedWs.connectionId) {
713
+ this.requestHandler.cleanupConnectionRequests(managedWs.connectionId);
714
+ this.connectionManager.removeConnection(managedWs);
715
+ }
716
+ });
717
+ ws.on("error", (error) => {
718
+ this.logger.error("WebSocket \u9519\u8BEF:", error);
719
+ });
720
+ }
721
+ /**
722
+ * 处理注册消息
723
+ */
724
+ handleRegister(ws, message) {
725
+ const { connectionType, pageUrl, pageId } = message.data;
726
+ const managedWs = this.connectionManager.addConnection(
727
+ ws,
728
+ connectionType,
729
+ pageUrl,
730
+ pageId
731
+ );
732
+ const ack = {
733
+ id: message.id || "",
734
+ type: "register_ack" /* REGISTER_ACK */,
735
+ success: true,
736
+ data: {
737
+ connectionId: managedWs.connectionId,
738
+ pageUrl
739
+ }
740
+ };
741
+ ws.send(JSON.stringify(ack));
742
+ }
743
+ /**
744
+ * 处理消息
745
+ */
746
+ handleMessage(ws, message) {
747
+ this.connectionManager.updateLastActive(ws);
748
+ switch (message.type) {
749
+ case "ping" /* PING */:
750
+ this.handlePing(ws, message);
751
+ break;
752
+ case "response" /* RESPONSE */:
753
+ case "error" /* ERROR */:
754
+ this.requestHandler.handleResponse(message);
755
+ break;
756
+ default:
757
+ if (ws.connectionInfo.type === "consumer" /* CONSUMER */) {
758
+ this.requestHandler.handleRequest(ws, message);
759
+ }
760
+ break;
761
+ }
762
+ }
763
+ /**
764
+ * 处理心跳
765
+ */
766
+ handlePing(ws, message) {
767
+ const pong = {
768
+ type: "pong" /* PONG */,
769
+ timestamp: message.timestamp || Date.now()
770
+ };
771
+ ws.send(JSON.stringify(pong));
772
+ }
773
+ /**
774
+ * 停止服务器
775
+ */
776
+ async stop() {
777
+ if (!this.isRunning || !this.wss) {
778
+ return;
779
+ }
780
+ this.logger.info("\u6B63\u5728\u505C\u6B62 Server...");
781
+ this.requestHandler.cleanupAll();
782
+ this.connectionManager.closeAll();
783
+ return new Promise((resolve7) => {
784
+ this.wss.close(() => {
785
+ this.isRunning = false;
786
+ this.wss = null;
787
+ this.logger.info("Server \u5DF2\u505C\u6B62");
788
+ resolve7();
789
+ });
790
+ });
791
+ }
792
+ /**
793
+ * 获取运行状态
794
+ */
795
+ getStatus() {
796
+ return {
797
+ running: this.isRunning,
798
+ port: this.port,
799
+ stats: this.connectionManager.getStats(),
800
+ connectedPages: this.connectionManager.getConnectedPageUrls()
801
+ };
802
+ }
803
+ /**
804
+ * 获取端口
805
+ */
806
+ getPort() {
807
+ return this.port;
808
+ }
809
+ /**
810
+ * 是否运行中
811
+ */
812
+ isServerRunning() {
813
+ return this.isRunning;
814
+ }
815
+ };
816
+ function createServer(options) {
817
+ return new MGServer(options);
818
+ }
819
+
820
+ // src/server/daemon.ts
821
+ function isServerRunning() {
822
+ const info = readServerInfo();
823
+ if (!info) {
824
+ return { running: false, info: null };
825
+ }
826
+ if (!isProcessRunning(info.pid)) {
827
+ deleteServerInfo();
828
+ return { running: false, info: null };
829
+ }
830
+ return { running: true, info };
831
+ }
832
+ async function startServerForeground(port) {
833
+ const { running, info } = isServerRunning();
834
+ if (running && info) {
835
+ throw new MGError(
836
+ "E016" /* SERVER_ALREADY_RUNNING */,
837
+ `Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
838
+ );
839
+ }
840
+ ensureConfigDir();
841
+ const logger = createLogger({
842
+ console: true,
843
+ file: true,
844
+ minLevel: "INFO" /* INFO */
845
+ });
846
+ const server = createServer({
847
+ port: port || DEFAULT_PORT,
848
+ logger
849
+ });
850
+ const cleanup = async () => {
851
+ console.log("\n\u6B63\u5728\u505C\u6B62 Server...");
852
+ await server.stop();
853
+ deleteServerInfo();
854
+ process.exit(0);
855
+ };
856
+ process.on("SIGINT", cleanup);
857
+ process.on("SIGTERM", cleanup);
858
+ try {
859
+ const actualPort = await server.start();
860
+ writeServerInfo({
861
+ port: actualPort,
862
+ pid: process.pid,
863
+ startedAt: getCurrentISOTime()
864
+ });
865
+ console.log(`
866
+ MG Server \u542F\u52A8\u6210\u529F`);
867
+ console.log(`\u76D1\u542C\u7AEF\u53E3: ${actualPort}`);
868
+ console.log(`\u8FDB\u7A0B PID: ${process.pid}`);
869
+ console.log(`\u8FD0\u884C\u6A21\u5F0F: \u524D\u53F0`);
870
+ console.log(`
871
+ \u6309 Ctrl+C \u505C\u6B62...`);
872
+ } catch (error) {
873
+ logger.error("Server \u542F\u52A8\u5931\u8D25:", error);
874
+ throw error;
875
+ }
876
+ }
877
+ async function startServerDaemon(port) {
878
+ const { running, info } = isServerRunning();
879
+ if (running && info) {
880
+ throw new MGError(
881
+ "E016" /* SERVER_ALREADY_RUNNING */,
882
+ `Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
883
+ );
884
+ }
885
+ ensureConfigDir();
886
+ const currentFile = fileURLToPath(import.meta.url);
887
+ const currentDir = dirname3(currentFile);
888
+ const serverScript = join2(currentDir, "daemon-runner.js");
889
+ const args = ["--foreground"];
890
+ if (port) {
891
+ args.push("--port", String(port));
892
+ }
893
+ const child = spawn(process.execPath, [serverScript, ...args], {
894
+ detached: true,
895
+ stdio: "ignore",
896
+ env: {
897
+ ...process.env,
898
+ MG_DAEMON: "1"
899
+ }
900
+ });
901
+ child.unref();
902
+ const startTime = Date.now();
903
+ while (Date.now() - startTime < SERVER_START_TIMEOUT) {
904
+ await new Promise((resolve7) => setTimeout(resolve7, 200));
905
+ const { running: running2, info: info2 } = isServerRunning();
906
+ if (running2 && info2) {
907
+ return info2;
908
+ }
909
+ }
910
+ throw new MGError("E015" /* SERVER_START_FAILED */, "Server \u542F\u52A8\u8D85\u65F6");
911
+ }
912
+ function stopServer() {
913
+ const { running, info } = isServerRunning();
914
+ if (!running || !info) {
915
+ return { stopped: false, info: null };
916
+ }
917
+ const killed = killProcess(info.pid);
918
+ if (killed) {
919
+ deleteServerInfo();
920
+ }
921
+ return { stopped: killed, info };
922
+ }
923
+ async function restartServer(port) {
924
+ const { info: oldInfo } = stopServer();
925
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
926
+ return startServerDaemon(port || oldInfo?.port);
927
+ }
928
+ function getServerStatus() {
929
+ const { running, info } = isServerRunning();
930
+ if (!running || !info) {
931
+ return { running: false };
932
+ }
933
+ const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
934
+ return {
935
+ running: true,
936
+ port: info.port,
937
+ pid: info.pid,
938
+ startedAt: info.startedAt,
939
+ uptime: formatDuration(uptimeMs)
940
+ };
941
+ }
942
+
943
+ // src/cli/commands/server.ts
944
+ function createServerCommand() {
945
+ const serverCmd = new Command("server").description("Server \u7BA1\u7406\u547D\u4EE4");
946
+ serverCmd.command("start").description("\u542F\u52A8 MG Server").option("--port <number>", "\u6307\u5B9A\u542F\u52A8\u7AEF\u53E3", (value) => parseInt(value, 10)).option("--foreground", "\u524D\u53F0\u6A21\u5F0F\u8FD0\u884C\uFF08\u4E0D\u4F5C\u4E3A\u5B88\u62A4\u8FDB\u7A0B\uFF09", false).action(async (options) => {
947
+ try {
948
+ if (options.foreground) {
949
+ await startServerForeground(options.port);
950
+ } else {
951
+ const info = await startServerDaemon(options.port);
952
+ console.log("MG Server \u542F\u52A8\u6210\u529F");
953
+ console.log(`\u76D1\u542C\u7AEF\u53E3: ${info.port}`);
954
+ console.log(`\u8FDB\u7A0B PID: ${info.pid}`);
955
+ console.log(`\u8FD0\u884C\u6A21\u5F0F: \u5B88\u62A4\u8FDB\u7A0B`);
956
+ }
957
+ } catch (error) {
958
+ console.error(`\u9519\u8BEF: ${error.message}`);
959
+ process.exit(1);
960
+ }
961
+ });
962
+ serverCmd.command("stop").description("\u505C\u6B62 MG Server").action(() => {
963
+ try {
964
+ const { stopped, info } = stopServer();
965
+ if (stopped && info) {
966
+ console.log("MG Server \u5DF2\u505C\u6B62");
967
+ console.log(`PID: ${info.pid}`);
968
+ const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
969
+ const seconds = Math.floor(uptimeMs / 1e3);
970
+ const minutes = Math.floor(seconds / 60);
971
+ const hours = Math.floor(minutes / 60);
972
+ let uptime = "";
973
+ if (hours > 0) {
974
+ uptime = `${hours} \u5C0F\u65F6 ${minutes % 60} \u5206\u949F`;
975
+ } else if (minutes > 0) {
976
+ uptime = `${minutes} \u5206\u949F ${seconds % 60} \u79D2`;
977
+ } else {
978
+ uptime = `${seconds} \u79D2`;
979
+ }
980
+ console.log(`\u8FD0\u884C\u65F6\u957F: ${uptime}`);
981
+ } else {
982
+ console.log("MG Server \u672A\u8FD0\u884C");
983
+ }
984
+ } catch (error) {
985
+ console.error(`\u9519\u8BEF: ${error.message}`);
986
+ process.exit(1);
987
+ }
988
+ });
989
+ serverCmd.command("restart").description("\u91CD\u542F MG Server").option("--port <number>", "\u91CD\u542F\u540E\u4F7F\u7528\u7684\u7AEF\u53E3", (value) => parseInt(value, 10)).action(async (options) => {
990
+ try {
991
+ const status = getServerStatus();
992
+ if (status.running) {
993
+ console.log(`\u6B63\u5728\u505C\u6B62 MG Server (PID: ${status.pid})...`);
994
+ }
995
+ const info = await restartServer(options.port);
996
+ console.log("MG Server \u5DF2\u91CD\u542F");
997
+ console.log(`\u76D1\u542C\u7AEF\u53E3: ${info.port}`);
998
+ console.log(`\u65B0\u8FDB\u7A0B PID: ${info.pid}`);
999
+ } catch (error) {
1000
+ console.error(`\u9519\u8BEF: ${error.message}`);
1001
+ process.exit(1);
1002
+ }
1003
+ });
1004
+ serverCmd.command("status").description("\u67E5\u770B Server \u8FD0\u884C\u72B6\u6001").action(() => {
1005
+ try {
1006
+ const status = getServerStatus();
1007
+ if (status.running) {
1008
+ console.log("MG Server \u72B6\u6001: \u8FD0\u884C\u4E2D \u2713");
1009
+ console.log(`\u76D1\u542C\u7AEF\u53E3: ${status.port}`);
1010
+ console.log(`\u8FDB\u7A0B PID: ${status.pid}`);
1011
+ console.log(`\u542F\u52A8\u65F6\u95F4: ${status.startedAt}`);
1012
+ console.log(`\u8FD0\u884C\u65F6\u957F: ${status.uptime}`);
1013
+ } else {
1014
+ console.log("MG Server \u72B6\u6001: \u672A\u8FD0\u884C \u2717");
1015
+ console.log("\u63D0\u793A: \u4F7F\u7528 'mg-cli server start' \u542F\u52A8 Server");
1016
+ }
1017
+ } catch (error) {
1018
+ console.error(`\u9519\u8BEF: ${error.message}`);
1019
+ process.exit(1);
1020
+ }
1021
+ });
1022
+ return serverCmd;
1023
+ }
1024
+
1025
+ // src/cli/commands/get-node-by-id.ts
1026
+ import { Command as Command2 } from "commander";
1027
+ import { writeFileSync as writeFileSync2 } from "fs";
1028
+ import { resolve as resolve2, dirname as dirname4 } from "path";
1029
+ import { mkdirSync as mkdirSync3 } from "fs";
1030
+
1031
+ // src/cli/client.ts
1032
+ import WebSocket2 from "ws";
1033
+ var MGClient = class {
1034
+ ws = null;
1035
+ options;
1036
+ constructor(options = {}) {
1037
+ this.options = options;
1038
+ }
1039
+ /**
1040
+ * 连接到 Server
1041
+ */
1042
+ async connect() {
1043
+ const serverInfo = readServerInfo();
1044
+ if (serverInfo) {
1045
+ try {
1046
+ await this.tryConnect(serverInfo.port);
1047
+ return;
1048
+ } catch {
1049
+ }
1050
+ }
1051
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
1052
+ try {
1053
+ await this.tryConnect(port);
1054
+ return;
1055
+ } catch {
1056
+ }
1057
+ }
1058
+ if (!this.options.noAutoStart) {
1059
+ console.log("Server \u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u81EA\u52A8\u542F\u52A8...");
1060
+ try {
1061
+ const info = await startServerDaemon();
1062
+ console.log(`Server \u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3: ${info.port}`);
1063
+ await this.waitForServer(info.port);
1064
+ return;
1065
+ } catch (error) {
1066
+ throw new MGError(
1067
+ "E015" /* SERVER_START_FAILED */,
1068
+ `\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25: ${error instanceof Error ? error.message : error}`
1069
+ );
1070
+ }
1071
+ }
1072
+ throw new MGError("E001" /* CONNECTION_FAILED */, ErrorMessages["E001" /* CONNECTION_FAILED */]);
1073
+ }
1074
+ /**
1075
+ * 尝试连接指定端口
1076
+ */
1077
+ tryConnect(port) {
1078
+ return new Promise((resolve7, reject) => {
1079
+ const ws = new WebSocket2(`ws://localhost:${port}`);
1080
+ const timer = setTimeout(() => {
1081
+ ws.close();
1082
+ reject(new Error("\u8FDE\u63A5\u8D85\u65F6"));
1083
+ }, PORT_SCAN_TIMEOUT);
1084
+ ws.on("open", () => {
1085
+ clearTimeout(timer);
1086
+ this.ws = ws;
1087
+ this.register();
1088
+ resolve7();
1089
+ });
1090
+ ws.on("error", (error) => {
1091
+ clearTimeout(timer);
1092
+ reject(error);
1093
+ });
1094
+ });
1095
+ }
1096
+ /**
1097
+ * 等待 Server 就绪
1098
+ */
1099
+ async waitForServer(port) {
1100
+ const startTime = Date.now();
1101
+ const interval = 500;
1102
+ while (Date.now() - startTime < SERVER_START_TIMEOUT) {
1103
+ try {
1104
+ await this.tryConnect(port);
1105
+ return;
1106
+ } catch {
1107
+ await new Promise((r) => setTimeout(r, interval));
1108
+ }
1109
+ }
1110
+ throw new Error("\u7B49\u5F85 Server \u542F\u52A8\u8D85\u65F6");
1111
+ }
1112
+ /**
1113
+ * 注册为 Consumer
1114
+ */
1115
+ register() {
1116
+ if (!this.ws) return;
1117
+ const message = {
1118
+ type: "register" /* REGISTER */,
1119
+ data: {
1120
+ connectionType: "consumer" /* CONSUMER */
1121
+ },
1122
+ timestamp: Date.now()
1123
+ };
1124
+ this.ws.send(JSON.stringify(message));
1125
+ }
1126
+ /**
1127
+ * 发送请求并等待响应
1128
+ */
1129
+ async request(type, params, pageUrl) {
1130
+ if (!this.ws) {
1131
+ throw new MGError("E001" /* CONNECTION_FAILED */, "\u672A\u8FDE\u63A5\u5230 Server");
1132
+ }
1133
+ const requestId = generateId();
1134
+ const message = {
1135
+ id: requestId,
1136
+ type,
1137
+ params,
1138
+ pageUrl,
1139
+ timestamp: Date.now()
1140
+ };
1141
+ return new Promise((resolve7, reject) => {
1142
+ const timer = setTimeout(() => {
1143
+ reject(new MGError("E012" /* REQUEST_TIMEOUT */, ErrorMessages["E012" /* REQUEST_TIMEOUT */]));
1144
+ }, REQUEST_TIMEOUT);
1145
+ const messageHandler = (data) => {
1146
+ try {
1147
+ const response = JSON.parse(data.toString());
1148
+ if (response.id === requestId) {
1149
+ clearTimeout(timer);
1150
+ this.ws?.off("message", messageHandler);
1151
+ if (response.success) {
1152
+ resolve7(response.data);
1153
+ } else {
1154
+ const error = response.error;
1155
+ reject(
1156
+ new MGError(
1157
+ error?.code || "E099" /* UNKNOWN_ERROR */,
1158
+ error?.message || "\u672A\u77E5\u9519\u8BEF"
1159
+ )
1160
+ );
1161
+ }
1162
+ }
1163
+ } catch {
1164
+ }
1165
+ };
1166
+ this.ws.on("message", messageHandler);
1167
+ this.ws.send(JSON.stringify(message));
1168
+ });
1169
+ }
1170
+ /**
1171
+ * 带重试的请求
1172
+ */
1173
+ async requestWithRetry(type, params, pageUrl) {
1174
+ if (this.options.noRetry) {
1175
+ return this.request(type, params, pageUrl);
1176
+ }
1177
+ let lastError = null;
1178
+ for (let attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++) {
1179
+ try {
1180
+ return await this.request(type, params, pageUrl);
1181
+ } catch (error) {
1182
+ lastError = error instanceof Error ? error : new Error(String(error));
1183
+ if (error instanceof MGError) {
1184
+ const retryable = ["E017" /* CONNECTION_LOST */, "E012" /* REQUEST_TIMEOUT */];
1185
+ if (!retryable.includes(error.code)) {
1186
+ throw error;
1187
+ }
1188
+ }
1189
+ if (attempt < MAX_RETRY_COUNT) {
1190
+ const delay = RETRY_INTERVALS[attempt] || RETRY_INTERVALS[RETRY_INTERVALS.length - 1];
1191
+ await new Promise((r) => setTimeout(r, delay));
1192
+ try {
1193
+ await this.connect();
1194
+ } catch {
1195
+ }
1196
+ }
1197
+ }
1198
+ }
1199
+ throw lastError || new MGError("E099" /* UNKNOWN_ERROR */, "\u8BF7\u6C42\u5931\u8D25");
1200
+ }
1201
+ /**
1202
+ * 关闭连接
1203
+ */
1204
+ close() {
1205
+ if (this.ws) {
1206
+ this.ws.close();
1207
+ this.ws = null;
1208
+ }
1209
+ }
1210
+ };
1211
+
1212
+ // src/cli/commands/get-node-by-id.ts
1213
+ function createGetNodeByIdCommand() {
1214
+ return new Command2("get_node_by_id").description("\u6839\u636E\u8282\u70B9 ID \u83B7\u53D6\u8282\u70B9\u8BE6\u7EC6\u4FE1\u606F\u3002\u6570\u636E\u4FDD\u5B58\u5230\u6307\u5B9A JSON \u6587\u4EF6\uFF0C\u8FD4\u56DE\u6587\u4EF6\u8DEF\u5F84\u548C\u5927\u5C0F\u4FE1\u606F\u3002\u5982\u9700\u901A\u8FC7\u94FE\u63A5\u83B7\u53D6\uFF0C\u8BF7\u4F7F\u7528 get_node_by_link \u547D\u4EE4").requiredOption("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u53EF\u4ECE MasterGo \u6D6E\u7A97\u94FE\u63A5\u4E2D\u83B7\u53D6").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com\u3002\u4E0E --fileId \u914D\u5408\u4F7F\u7528", "mastergo.netease.com").option("--fileId <id>", "\u6587\u4EF6 ID\uFF08\u7EAF\u6570\u5B57\uFF09\uFF0C\u4E0E --domain \u914D\u5408\u6307\u5B9A\u76EE\u6807\u9875\u9762").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 1\u3002\u589E\u52A0\u6DF1\u5EA6\u4F1A\u663E\u8457\u589E\u52A0\u6570\u636E\u91CF", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9\uFF08visible: false\uFF09\uFF0C\u9ED8\u8BA4\u4E0D\u5305\u542B", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1215
+ await handleGetNodeById(options);
1216
+ });
1217
+ }
1218
+ async function handleGetNodeById(options) {
1219
+ const client = new MGClient({
1220
+ noAutoStart: options.noAutoStart,
1221
+ noRetry: options.noRetry
1222
+ });
1223
+ try {
1224
+ await client.connect();
1225
+ let pageUrl;
1226
+ if (options.fileId) {
1227
+ const domain = options.domain || "mastergo.netease.com";
1228
+ pageUrl = `${domain}/file/${options.fileId}`;
1229
+ }
1230
+ const params = {
1231
+ nodeId: options.nodeId,
1232
+ maxDepth: parseInt(options.maxDepth || "1", 10),
1233
+ includeInvisible: options.includeInvisible || false
1234
+ };
1235
+ const data = await client.requestWithRetry("get_node_by_id" /* GET_NODE_BY_ID */, params, pageUrl);
1236
+ const outputPath = resolve2(options.output);
1237
+ const outputDir = dirname4(outputPath);
1238
+ mkdirSync3(outputDir, { recursive: true });
1239
+ const jsonContent = JSON.stringify(data, null, 2);
1240
+ writeFileSync2(outputPath, jsonContent, "utf-8");
1241
+ const size = jsonContent.length;
1242
+ const sizeKB = (size / 1024).toFixed(2);
1243
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1244
+ console.log(`\u8282\u70B9 ID: ${options.nodeId}`);
1245
+ console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1246
+ console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
1247
+ } catch (error) {
1248
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1249
+ process.exit(1);
1250
+ } finally {
1251
+ client.close();
1252
+ }
1253
+ }
1254
+
1255
+ // src/cli/commands/get-node-by-link.ts
1256
+ import { Command as Command3 } from "commander";
1257
+ import { writeFileSync as writeFileSync3 } from "fs";
1258
+ import { resolve as resolve3, dirname as dirname5 } from "path";
1259
+ import { mkdirSync as mkdirSync4 } from "fs";
1260
+ function createGetNodeByLinkCommand() {
1261
+ return new Command3("get_node_by_link").description("\u89E3\u6790 mgp:// \u534F\u8BAE\u94FE\u63A5\u5E76\u83B7\u53D6\u8282\u70B9\u4FE1\u606F").requiredOption("--link <url>", "mgp:// \u534F\u8BAE\u94FE\u63A5").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1262
+ await handleGetNodeByLink(options);
1263
+ });
1264
+ }
1265
+ async function handleGetNodeByLink(options) {
1266
+ const parsed = parseMgpLink(options.link);
1267
+ if (!parsed) {
1268
+ console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
1269
+ console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
1270
+ console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]/nodeId`);
1271
+ process.exit(1);
1272
+ }
1273
+ const { pageUrl, nodeId } = parsed;
1274
+ const client = new MGClient({
1275
+ noAutoStart: options.noAutoStart,
1276
+ noRetry: options.noRetry
1277
+ });
1278
+ try {
1279
+ await client.connect();
1280
+ const params = {
1281
+ nodeId,
1282
+ maxDepth: parseInt(options.maxDepth || "1", 10),
1283
+ includeInvisible: options.includeInvisible || false
1284
+ };
1285
+ const data = await client.requestWithRetry(
1286
+ "get_node_by_id" /* GET_NODE_BY_ID */,
1287
+ params,
1288
+ pageUrl
1289
+ );
1290
+ const outputPath = resolve3(options.output);
1291
+ const outputDir = dirname5(outputPath);
1292
+ mkdirSync4(outputDir, { recursive: true });
1293
+ const jsonContent = JSON.stringify(data, null, 2);
1294
+ writeFileSync3(outputPath, jsonContent, "utf-8");
1295
+ const size = jsonContent.length;
1296
+ const sizeKB = (size / 1024).toFixed(2);
1297
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1298
+ console.log(`Link: ${options.link}`);
1299
+ console.log(`\u9875\u9762 URL: ${pageUrl}`);
1300
+ console.log(`\u8282\u70B9 ID: ${nodeId}`);
1301
+ console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1302
+ console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
1303
+ } catch (error) {
1304
+ if (error instanceof MGError) {
1305
+ console.error(`\u9519\u8BEF [${error.code}]: ${error.message}`);
1306
+ } else {
1307
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1308
+ }
1309
+ process.exit(1);
1310
+ } finally {
1311
+ client.close();
1312
+ }
1313
+ }
1314
+
1315
+ // src/cli/commands/get-all-nodes.ts
1316
+ import { Command as Command4 } from "commander";
1317
+ import { writeFileSync as writeFileSync4 } from "fs";
1318
+ import { resolve as resolve4, dirname as dirname6 } from "path";
1319
+ import { mkdirSync as mkdirSync5 } from "fs";
1320
+ function createGetAllNodesCommand() {
1321
+ return new Command4("get_all_nodes").description("\u83B7\u53D6\u5F53\u524D\u9875\u9762\u7684\u6240\u6709\u8282\u70B9\u6811\u3002\u8B66\u544A\uFF1A\u6DF1\u5EA6\u6BCF\u589E\u52A0 1\uFF0C\u6570\u636E\u91CF\u53EF\u80FD\u5448\u6307\u6570\u7EA7\u589E\u957F\u3002\u5EFA\u8BAE\u4ECE maxDepth=1 \u5F00\u59CB").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84").option("--maxDepth <number>", "\u6700\u5927\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 1\u3002\u6DF1\u5EA6 2 \u53EF\u80FD\u4EA7\u751F 100KB-500KB\uFF0C\u6DF1\u5EA6 3 \u53EF\u80FD\u8D85\u8FC7 1MB", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9\uFF08visible: false\uFF09\uFF0C\u9ED8\u8BA4\u4E0D\u5305\u542B", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1322
+ await handleGetAllNodes(options);
1323
+ });
1324
+ }
1325
+ async function handleGetAllNodes(options) {
1326
+ const client = new MGClient({
1327
+ noAutoStart: options.noAutoStart,
1328
+ noRetry: options.noRetry
1329
+ });
1330
+ try {
1331
+ await client.connect();
1332
+ const params = {
1333
+ maxDepth: parseInt(options.maxDepth || "1", 10),
1334
+ includeInvisible: options.includeInvisible || false
1335
+ };
1336
+ const data = await client.requestWithRetry("get_all_nodes" /* GET_ALL_NODES */, params);
1337
+ const outputPath = resolve4(options.output);
1338
+ const outputDir = dirname6(outputPath);
1339
+ mkdirSync5(outputDir, { recursive: true });
1340
+ const jsonContent = JSON.stringify(data, null, 2);
1341
+ writeFileSync4(outputPath, jsonContent, "utf-8");
1342
+ const size = jsonContent.length;
1343
+ const sizeKB = (size / 1024).toFixed(2);
1344
+ const nodeCount = Array.isArray(data) ? data.length : 1;
1345
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1346
+ console.log(`\u8282\u70B9\u6570\u91CF: ${nodeCount}`);
1347
+ console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1348
+ console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
1349
+ } catch (error) {
1350
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1351
+ process.exit(1);
1352
+ } finally {
1353
+ client.close();
1354
+ }
1355
+ }
1356
+
1357
+ // src/cli/commands/export-image.ts
1358
+ import { Command as Command5 } from "commander";
1359
+ import { writeFileSync as writeFileSync5 } from "fs";
1360
+ import { resolve as resolve5, dirname as dirname7, extname } from "path";
1361
+ import { mkdirSync as mkdirSync6 } from "fs";
1362
+ import { tmpdir } from "os";
1363
+ function createExportImageCommand() {
1364
+ return new Command5("export_image").description("\u5BFC\u51FA MasterGo \u8282\u70B9\u4E3A\u56FE\u7247\u6587\u4EF6\u3002\u5F3A\u70C8\u5EFA\u8BAE\u6307\u5B9A --output\uFF0C\u5426\u5219\u4FDD\u5B58\u5230\u4E34\u65F6\u76EE\u5F55\u53EF\u80FD\u88AB\u7CFB\u7EDF\u6E05\u7406").option("--output <path>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\u3002\u5F3A\u70C8\u5EFA\u8BAE\u6307\u5B9A\uFF0C\u5426\u5219\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55\u53EF\u80FD\u88AB\u6E05\u7406").option("--link <mgp-link>", "mgp:// \u534F\u8BAE\u94FE\u63A5\u3002\u4E0D\u6307\u5B9A\u5219\u5BFC\u51FA\u5F53\u524D\u9009\u4E2D\u8282\u70B9").option("--format <type>", "\u5BFC\u51FA\u683C\u5F0F\uFF1APNG\uFF08\u65E0\u635F\u900F\u660E\uFF09\u3001JPG\uFF08\u6709\u635F\uFF09\u3001SVG\uFF08\u77E2\u91CF\uFF09\u3001PDF\u3001WEBP", "PNG").option("--scale <number>", "\u7F29\u653E\u500D\u7387\uFF08\u5982 1\u30012\u30013\uFF09\u3002\u4E0E width/height \u4E92\u65A5").option("--width <number>", "\u56FA\u5B9A\u5BBD\u5EA6\uFF08\u50CF\u7D20\uFF09\u3002\u4E0E scale/height \u4E92\u65A5").option("--height <number>", "\u56FA\u5B9A\u9AD8\u5EA6\uFF08\u50CF\u7D20\uFF09\u3002\u4E0E scale/width \u4E92\u65A5").option("--useAbsoluteBounds", "\u4F7F\u7528\u5B8C\u6574\u5C3A\u5BF8\u3002true: \u5305\u542B\u88AB\u88C1\u526A\u90E8\u5206\uFF0Cfalse: \u53EA\u5BFC\u51FA\u53EF\u89C1\u533A\u57DF", false).option("--no-use-render-bounds", "\u4E0D\u5305\u542B\u7279\u6548\u548C\u5916\u63CF\u8FB9\u3002\u9ED8\u8BA4\u5305\u542B\u9634\u5F71\u3001\u5916\u63CF\u8FB9\u7B49").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1365
+ await handleExportImage(options);
1366
+ });
1367
+ }
1368
+ async function handleExportImage(options) {
1369
+ const format = options.format?.toUpperCase() || "PNG";
1370
+ const validFormats = ["PNG", "JPG", "SVG", "PDF", "WEBP"];
1371
+ if (!validFormats.includes(format)) {
1372
+ console.error(`\u9519\u8BEF: \u4E0D\u652F\u6301\u7684\u683C\u5F0F "${options.format}"`);
1373
+ console.error(`\u652F\u6301\u7684\u683C\u5F0F: ${validFormats.join(", ")}`);
1374
+ process.exit(1);
1375
+ }
1376
+ const sizeParams = [options.scale, options.width, options.height].filter(Boolean);
1377
+ if (sizeParams.length > 1) {
1378
+ console.error("\u9519\u8BEF: scale\u3001width\u3001height \u4E09\u8005\u4E92\u65A5\uFF0C\u53EA\u80FD\u6307\u5B9A\u5176\u4E2D\u4E00\u4E2A");
1379
+ process.exit(1);
1380
+ }
1381
+ let pageUrl;
1382
+ let nodeId;
1383
+ if (options.link) {
1384
+ const linkInfo = parseMgpLink(options.link);
1385
+ if (!linkInfo) {
1386
+ console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F: ${options.link}`);
1387
+ process.exit(1);
1388
+ }
1389
+ pageUrl = linkInfo.pageUrl;
1390
+ nodeId = linkInfo.nodeId;
1391
+ }
1392
+ const client = new MGClient({
1393
+ noAutoStart: options.noAutoStart,
1394
+ noRetry: options.noRetry
1395
+ });
1396
+ try {
1397
+ await client.connect();
1398
+ const params = {
1399
+ format,
1400
+ useAbsoluteBounds: options.useAbsoluteBounds || false,
1401
+ useRenderBounds: options.useRenderBounds !== false
1402
+ };
1403
+ if (nodeId) {
1404
+ params.nodeId = nodeId;
1405
+ }
1406
+ if (options.scale) {
1407
+ params.scale = parseFloat(options.scale);
1408
+ }
1409
+ if (options.width) {
1410
+ params.width = parseInt(options.width, 10);
1411
+ }
1412
+ if (options.height) {
1413
+ params.height = parseInt(options.height, 10);
1414
+ }
1415
+ const response = await client.requestWithRetry(
1416
+ "export_image" /* EXPORT_IMAGE */,
1417
+ params,
1418
+ pageUrl
1419
+ );
1420
+ const ext = getExtension(format);
1421
+ let outputPath;
1422
+ if (options.output) {
1423
+ outputPath = resolve5(options.output);
1424
+ if (!extname(outputPath)) {
1425
+ outputPath = `${outputPath}${ext}`;
1426
+ }
1427
+ } else {
1428
+ const filename = response.filename || `export_${Date.now()}${ext}`;
1429
+ outputPath = resolve5(tmpdir(), filename);
1430
+ console.log("\u8B66\u544A: \u672A\u6307\u5B9A --output\uFF0C\u6587\u4EF6\u5C06\u4FDD\u5B58\u5230\u4E34\u65F6\u76EE\u5F55\uFF0C\u53EF\u80FD\u4F1A\u88AB\u7CFB\u7EDF\u6E05\u7406");
1431
+ }
1432
+ const outputDir = dirname7(outputPath);
1433
+ mkdirSync6(outputDir, { recursive: true });
1434
+ const buffer = Buffer.from(response.data, "base64");
1435
+ writeFileSync5(outputPath, buffer);
1436
+ const sizeKB = (buffer.length / 1024).toFixed(2);
1437
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1438
+ if (options.link) {
1439
+ console.log(`Link: ${options.link}`);
1440
+ }
1441
+ if (nodeId) {
1442
+ console.log(`\u8282\u70B9 ID: ${nodeId}`);
1443
+ } else {
1444
+ console.log("\u8282\u70B9 ID: (\u9009\u4E2D\u7684\u8282\u70B9)");
1445
+ }
1446
+ console.log(`\u5BFC\u51FA\u683C\u5F0F: ${format}`);
1447
+ console.log(`\u6587\u4EF6\u5927\u5C0F: ${buffer.length.toLocaleString()} \u5B57\u8282 (\u7EA6 ${sizeKB} KB)`);
1448
+ } catch (error) {
1449
+ if (error instanceof MGError) {
1450
+ console.error(`\u9519\u8BEF [${error.code}]: ${error.message}`);
1451
+ } else {
1452
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1453
+ }
1454
+ process.exit(1);
1455
+ } finally {
1456
+ client.close();
1457
+ }
1458
+ }
1459
+ function getExtension(format) {
1460
+ const extensions = {
1461
+ PNG: ".png",
1462
+ JPG: ".jpg",
1463
+ SVG: ".svg",
1464
+ PDF: ".pdf",
1465
+ WEBP: ".webp"
1466
+ };
1467
+ return extensions[format];
1468
+ }
1469
+
1470
+ // src/cli/commands/execute-code.ts
1471
+ import { Command as Command6 } from "commander";
1472
+ function createExecuteCodeCommand() {
1473
+ return new Command6("execute_code").description("\u5728 MasterGo \u9875\u9762\u6267\u884C\u81EA\u5B9A\u4E49 JavaScript \u4EE3\u7801\u3002\u901A\u8FC7 mg \u53D8\u91CF\u8BBF\u95EE MasterGo API\uFF0C\u7ED3\u679C\u4F1A\u88AB JSON \u5E8F\u5217\u5316\u8FD4\u56DE").argument("<code>", "\u8981\u6267\u884C\u7684\u4EE3\u7801\u3002\u53EF\u4F7F\u7528 mg \u53D8\u91CF\uFF0C\u5982 mg.currentPage.name\u3001mg.currentPage.selection").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (code, options) => {
1474
+ await handleExecuteCode(code, options);
1475
+ });
1476
+ }
1477
+ async function handleExecuteCode(code, options) {
1478
+ const client = new MGClient({
1479
+ noAutoStart: options.noAutoStart,
1480
+ noRetry: options.noRetry
1481
+ });
1482
+ try {
1483
+ await client.connect();
1484
+ const result = await client.requestWithRetry("execute_code" /* EXECUTE_CODE */, { code });
1485
+ if (result === null || result === void 0) {
1486
+ console.log("\u6267\u884C\u5B8C\u6210\uFF08\u65E0\u8FD4\u56DE\u503C\uFF09");
1487
+ } else if (typeof result === "object") {
1488
+ console.log(JSON.stringify(result, null, 2));
1489
+ } else {
1490
+ console.log(result);
1491
+ }
1492
+ } catch (error) {
1493
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1494
+ process.exit(1);
1495
+ } finally {
1496
+ client.close();
1497
+ }
1498
+ }
1499
+
1500
+ // src/cli/commands/get-all-pages.ts
1501
+ import { Command as Command7 } from "commander";
1502
+ import { writeFileSync as writeFileSync6 } from "fs";
1503
+ import { resolve as resolve6, dirname as dirname8 } from "path";
1504
+ import { mkdirSync as mkdirSync7 } from "fs";
1505
+ import { tmpdir as tmpdir2 } from "os";
1506
+ function createGetAllPagesCommand() {
1507
+ return new Command7("get_all_pages").description("\u83B7\u53D6 MasterGo \u6587\u6863\u7684\u6240\u6709\u9875\u9762\u4FE1\u606F\u3002\u4E0D\u6307\u5B9A --output \u65F6\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55").option("--link <url>", "\u9875\u9762\u94FE\u63A5\u3002\u652F\u6301\u5B8C\u6574 URL \u6216 mgp:// \u534F\u8BAE").option("--fileId <id>", "\u6587\u4EF6 ID\uFF08\u7EAF\u6570\u5B57\uFF09\u3002\u4ECE URL \u4E2D /file/ \u540E\u9762\u7684\u6570\u5B57").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com\u3002\u4E0E --fileId \u914D\u5408\u4F7F\u7528", "mastergo.netease.com").option("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u4E0D\u6307\u5B9A\u5219\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1508
+ await handleGetAllPages(options);
1509
+ });
1510
+ }
1511
+ async function handleGetAllPages(options) {
1512
+ let fileId = null;
1513
+ if (options.fileId) {
1514
+ fileId = options.fileId;
1515
+ } else if (options.link) {
1516
+ fileId = extractFileId(options.link);
1517
+ if (!fileId) {
1518
+ console.error("\u9519\u8BEF: \u65E0\u6CD5\u4ECE\u94FE\u63A5\u4E2D\u63D0\u53D6 fileId");
1519
+ console.error("\u652F\u6301\u7684\u683C\u5F0F:");
1520
+ console.error(" - \u5B8C\u6574 URL: https://mastergo.netease.com/file/174875497054651");
1521
+ console.error(" - mgp \u534F\u8BAE: mgp://mastergo.netease.com/file/174875497054651");
1522
+ console.error(" - \u7EAF fileId: 174875497054651");
1523
+ process.exit(1);
1524
+ }
1525
+ } else {
1526
+ console.log("\u672A\u63D0\u4F9B --link \u6216 --fileId\uFF0C\u5C06\u83B7\u53D6\u5F53\u524D\u8FDE\u63A5\u9875\u9762\u7684\u6240\u6709\u9875\u9762\u4FE1\u606F");
1527
+ }
1528
+ const client = new MGClient({
1529
+ noAutoStart: options.noAutoStart,
1530
+ noRetry: options.noRetry
1531
+ });
1532
+ try {
1533
+ await client.connect();
1534
+ let pageUrl;
1535
+ if (fileId) {
1536
+ const domain = options.domain || "mastergo.netease.com";
1537
+ pageUrl = `${domain}/file/${fileId}`;
1538
+ }
1539
+ const data = await client.requestWithRetry(
1540
+ "get_all_pages" /* GET_ALL_PAGES */,
1541
+ {},
1542
+ pageUrl
1543
+ );
1544
+ let outputPath;
1545
+ if (options.output) {
1546
+ outputPath = resolve6(options.output);
1547
+ } else {
1548
+ const filename = `pages_${fileId || "current"}_${Date.now()}.json`;
1549
+ outputPath = resolve6(tmpdir2(), filename);
1550
+ }
1551
+ const outputDir = dirname8(outputPath);
1552
+ mkdirSync7(outputDir, { recursive: true });
1553
+ const jsonContent = JSON.stringify(data, null, 2);
1554
+ writeFileSync6(outputPath, jsonContent, "utf-8");
1555
+ const size = jsonContent.length;
1556
+ const sizeKB = (size / 1024).toFixed(2);
1557
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1558
+ console.log(`\u6587\u6863\u540D\u79F0: ${data.documentName}`);
1559
+ console.log(`\u9875\u9762\u6570\u91CF: ${data.totalCount}`);
1560
+ console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1561
+ } catch (error) {
1562
+ console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
1563
+ process.exit(1);
1564
+ } finally {
1565
+ client.close();
1566
+ }
1567
+ }
1568
+
1569
+ // src/cli/index.ts
1570
+ var program = new Command8();
1571
+ program.name("mg-cli").description("MasterGo CLI \u5DE5\u5177 - \u7528\u4E8E Claude Code \u4E0E MasterGo \u901A\u4FE1").version("1.0.0");
1572
+ program.addCommand(createServerCommand());
1573
+ program.addCommand(createGetNodeByIdCommand());
1574
+ program.addCommand(createGetNodeByLinkCommand());
1575
+ program.addCommand(createGetAllNodesCommand());
1576
+ program.addCommand(createExportImageCommand());
1577
+ program.addCommand(createExecuteCodeCommand());
1578
+ program.addCommand(createGetAllPagesCommand());
1579
+ program.parse();
1580
+ //# sourceMappingURL=cli.js.map