@hangox/mg-cli 1.0.7 → 1.0.9

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/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var MAX_RETRY_COUNT = 3;
26
26
  var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
27
27
  ConnectionType2["CONSUMER"] = "consumer";
28
28
  ConnectionType2["PROVIDER"] = "provider";
29
+ ConnectionType2["CHROME_EXTENSION"] = "chrome_extension";
29
30
  return ConnectionType2;
30
31
  })(ConnectionType || {});
31
32
  var MessageType = /* @__PURE__ */ ((MessageType2) => {
@@ -40,6 +41,9 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
40
41
  MessageType2["EXPORT_IMAGE"] = "export_image";
41
42
  MessageType2["EXECUTE_CODE"] = "execute_code";
42
43
  MessageType2["GET_ALL_PAGES"] = "get_all_pages";
44
+ MessageType2["LIST_EXTENSIONS"] = "list_extensions";
45
+ MessageType2["OPEN_PAGE"] = "open_page";
46
+ MessageType2["NAVIGATE_TO_NODE"] = "navigate_to_node";
43
47
  MessageType2["GET_SERVER_STATUS"] = "get_server_status";
44
48
  MessageType2["RESPONSE"] = "response";
45
49
  MessageType2["ERROR"] = "error";
@@ -65,6 +69,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
65
69
  ErrorCode2["SERVER_START_FAILED"] = "E015";
66
70
  ErrorCode2["SERVER_ALREADY_RUNNING"] = "E016";
67
71
  ErrorCode2["CONNECTION_LOST"] = "E017";
72
+ ErrorCode2["NO_EXTENSION_CONNECTED"] = "E018";
68
73
  ErrorCode2["UNKNOWN_ERROR"] = "E099";
69
74
  return ErrorCode2;
70
75
  })(ErrorCode || {});
@@ -86,6 +91,7 @@ var ErrorNames = {
86
91
  ["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
87
92
  ["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
88
93
  ["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
94
+ ["E018" /* NO_EXTENSION_CONNECTED */]: "NO_EXTENSION_CONNECTED",
89
95
  ["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
90
96
  };
91
97
  var ErrorMessages = {
@@ -106,6 +112,7 @@ var ErrorMessages = {
106
112
  ["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
107
113
  ["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
108
114
  ["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
115
+ ["E018" /* NO_EXTENSION_CONNECTED */]: "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin",
109
116
  ["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
110
117
  };
111
118
  var MGError = class extends Error {
@@ -489,6 +496,12 @@ function extractSpaceInfo(node) {
489
496
  width: roundToOneDecimal(typeof node.width === "number" ? node.width : 0),
490
497
  height: roundToOneDecimal(typeof node.height === "number" ? node.height : 0)
491
498
  };
499
+ if (typeof node.absoluteX === "number") {
500
+ result.absoluteX = roundToOneDecimal(node.absoluteX);
501
+ }
502
+ if (typeof node.absoluteY === "number") {
503
+ result.absoluteY = roundToOneDecimal(node.absoluteY);
504
+ }
492
505
  if (node.children && Array.isArray(node.children) && node.children.length > 0) {
493
506
  result.children = node.children.map((child) => extractSpaceInfo(child));
494
507
  }
@@ -584,6 +597,12 @@ var ConnectionManager = class {
584
597
  providers = /* @__PURE__ */ new Map();
585
598
  /** Consumer 连接 */
586
599
  consumers = /* @__PURE__ */ new Map();
600
+ /** Chrome 扩展连接(按扩展 ID 索引) */
601
+ chromeExtensions = /* @__PURE__ */ new Map();
602
+ /** 扩展序号映射(扩展 ID → 序号) */
603
+ extensionIndexMap = /* @__PURE__ */ new Map();
604
+ /** 下一个可用的扩展序号 */
605
+ nextExtensionIndex = 1;
587
606
  /** 所有连接(按 ID 索引) */
588
607
  allConnections = /* @__PURE__ */ new Map();
589
608
  /** 心跳检查定时器 */
@@ -678,6 +697,16 @@ var ConnectionManager = class {
678
697
  } else if (connectionInfo.type === "consumer" /* CONSUMER */) {
679
698
  this.consumers.delete(connectionId);
680
699
  this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
700
+ } else if (connectionInfo.type === "chrome_extension" /* CHROME_EXTENSION */) {
701
+ for (const [extensionId, extWs] of this.chromeExtensions) {
702
+ if (extWs.connectionId === connectionId) {
703
+ const index = this.extensionIndexMap.get(extensionId);
704
+ this.chromeExtensions.delete(extensionId);
705
+ this.extensionIndexMap.delete(extensionId);
706
+ this.logger.info(`Chrome \u6269\u5C55\u65AD\u5F00: ${extensionId} (\u5E8F\u53F7: #${index})`);
707
+ break;
708
+ }
709
+ }
681
710
  }
682
711
  }
683
712
  /**
@@ -731,6 +760,7 @@ var ConnectionManager = class {
731
760
  return {
732
761
  providers: providerCount,
733
762
  consumers: this.consumers.size,
763
+ extensions: this.chromeExtensions.size,
734
764
  total: this.allConnections.size
735
765
  };
736
766
  }
@@ -740,6 +770,48 @@ var ConnectionManager = class {
740
770
  getConnectedPageUrls() {
741
771
  return Array.from(this.providers.keys());
742
772
  }
773
+ /**
774
+ * 添加 Chrome 扩展连接
775
+ */
776
+ addChromeExtension(ws, extensionId) {
777
+ const managedWs = this.addConnection(ws, "chrome_extension" /* CHROME_EXTENSION */);
778
+ const index = this.nextExtensionIndex++;
779
+ this.extensionIndexMap.set(extensionId, index);
780
+ this.chromeExtensions.set(extensionId, managedWs);
781
+ this.logger.info(`Chrome \u6269\u5C55\u8FDE\u63A5: ${extensionId} (\u5206\u914D\u5E8F\u53F7: #${index})`);
782
+ return managedWs;
783
+ }
784
+ /**
785
+ * 根据序号查找扩展
786
+ */
787
+ findExtensionByIndex(index) {
788
+ for (const [extensionId, extIndex] of this.extensionIndexMap) {
789
+ if (extIndex === index) {
790
+ return this.chromeExtensions.get(extensionId);
791
+ }
792
+ }
793
+ return void 0;
794
+ }
795
+ /**
796
+ * 获取第一个扩展
797
+ */
798
+ getFirstExtension() {
799
+ return this.chromeExtensions.values().next().value;
800
+ }
801
+ /**
802
+ * 获取所有扩展信息
803
+ */
804
+ getAllExtensions() {
805
+ const result = [];
806
+ for (const [extensionId, ws] of this.chromeExtensions) {
807
+ const index = this.extensionIndexMap.get(extensionId);
808
+ if (index !== void 0) {
809
+ result.push({ index, extensionId, ws });
810
+ }
811
+ }
812
+ result.sort((a, b) => a.index - b.index);
813
+ return result;
814
+ }
743
815
  /**
744
816
  * 关闭所有连接
745
817
  */
@@ -751,6 +823,8 @@ var ConnectionManager = class {
751
823
  }
752
824
  this.providers.clear();
753
825
  this.consumers.clear();
826
+ this.chromeExtensions.clear();
827
+ this.extensionIndexMap.clear();
754
828
  this.allConnections.clear();
755
829
  }
756
830
  };
@@ -868,6 +942,24 @@ var RequestHandler = class {
868
942
  this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
869
943
  }
870
944
  }
945
+ /**
946
+ * 注册待处理请求(供外部使用,如 OPEN_PAGE)
947
+ * @param requestId 请求 ID
948
+ * @param consumer 发起请求的 Consumer
949
+ * @param sendCallback 发送请求的回调函数
950
+ */
951
+ registerPendingRequest(requestId, consumer, sendCallback) {
952
+ const timer = setTimeout(() => {
953
+ this.handleTimeout(requestId);
954
+ }, REQUEST_TIMEOUT);
955
+ this.pendingRequests.set(requestId, {
956
+ id: requestId,
957
+ consumer,
958
+ timer,
959
+ timestamp: Date.now()
960
+ });
961
+ sendCallback();
962
+ }
871
963
  /**
872
964
  * 清理特定连接的所有待处理请求
873
965
  */
@@ -922,6 +1014,13 @@ function getVersion() {
922
1014
  return cachedVersion;
923
1015
  }
924
1016
  }
1017
+ var DEV_VERSION = "9.9.9";
1018
+ function isVersionMatch(version1, version2) {
1019
+ if (version1 === DEV_VERSION || version2 === DEV_VERSION) {
1020
+ return true;
1021
+ }
1022
+ return version1 === version2;
1023
+ }
925
1024
 
926
1025
  // src/server/websocket-server.ts
927
1026
  var MGServer = class {
@@ -1040,12 +1139,22 @@ var MGServer = class {
1040
1139
  */
1041
1140
  handleRegister(ws, message) {
1042
1141
  const { connectionType, pageUrl, pageId } = message.data;
1043
- const managedWs = this.connectionManager.addConnection(
1044
- ws,
1045
- connectionType,
1046
- pageUrl,
1047
- pageId
1048
- );
1142
+ const extensionId = message.data.extensionId;
1143
+ let managedWs;
1144
+ let extensionIndex;
1145
+ if (connectionType === "chrome_extension" /* CHROME_EXTENSION */ && extensionId) {
1146
+ managedWs = this.connectionManager.addChromeExtension(ws, extensionId);
1147
+ const exts = this.connectionManager.getAllExtensions();
1148
+ const ext = exts.find((e) => e.extensionId === extensionId);
1149
+ extensionIndex = ext?.index;
1150
+ } else {
1151
+ managedWs = this.connectionManager.addConnection(
1152
+ ws,
1153
+ connectionType,
1154
+ pageUrl,
1155
+ pageId
1156
+ );
1157
+ }
1049
1158
  const ack = {
1050
1159
  id: message.id || "",
1051
1160
  type: "register_ack" /* REGISTER_ACK */,
@@ -1053,7 +1162,8 @@ var MGServer = class {
1053
1162
  data: {
1054
1163
  connectionId: managedWs.connectionId,
1055
1164
  pageUrl,
1056
- serverVersion: getVersion()
1165
+ serverVersion: getVersion(),
1166
+ ...extensionIndex !== void 0 && { extensionIndex }
1057
1167
  }
1058
1168
  };
1059
1169
  ws.send(JSON.stringify(ack));
@@ -1070,6 +1180,15 @@ var MGServer = class {
1070
1180
  case "get_server_status" /* GET_SERVER_STATUS */:
1071
1181
  this.handleGetServerStatus(ws, message);
1072
1182
  break;
1183
+ case "list_extensions" /* LIST_EXTENSIONS */:
1184
+ this.handleListExtensions(ws, message);
1185
+ break;
1186
+ case "open_page" /* OPEN_PAGE */:
1187
+ this.handleOpenPage(ws, message);
1188
+ break;
1189
+ case "navigate_to_node" /* NAVIGATE_TO_NODE */:
1190
+ this.handleNavigateToNode(ws, message);
1191
+ break;
1073
1192
  case "response" /* RESPONSE */:
1074
1193
  case "error" /* ERROR */:
1075
1194
  this.requestHandler.handleResponse(message);
@@ -1102,6 +1221,13 @@ var MGServer = class {
1102
1221
  connectedAt: info.connectedAt.toISOString(),
1103
1222
  lastActiveAt: info.lastActiveAt.toISOString()
1104
1223
  }));
1224
+ const extensions = this.connectionManager.getAllExtensions();
1225
+ const connectedExtensions = extensions.map((ext) => ({
1226
+ index: ext.index,
1227
+ extensionId: ext.extensionId,
1228
+ connectedAt: ext.ws.connectionInfo.connectedAt.toISOString(),
1229
+ lastActiveAt: ext.ws.connectionInfo.lastActiveAt.toISOString()
1230
+ }));
1105
1231
  const uptimeMs = this.startedAt ? Date.now() - this.startedAt.getTime() : 0;
1106
1232
  const statusData = {
1107
1233
  running: this.isRunning,
@@ -1111,7 +1237,8 @@ var MGServer = class {
1111
1237
  uptime: formatDuration(uptimeMs),
1112
1238
  version: getVersion(),
1113
1239
  stats,
1114
- connectedPages
1240
+ connectedPages,
1241
+ connectedExtensions
1115
1242
  };
1116
1243
  const response = {
1117
1244
  id: message.id || "",
@@ -1122,6 +1249,156 @@ var MGServer = class {
1122
1249
  ws.send(JSON.stringify(response));
1123
1250
  this.logger.info("\u8FD4\u56DE Server \u72B6\u6001\u4FE1\u606F");
1124
1251
  }
1252
+ /**
1253
+ * 处理扩展列表查询
1254
+ */
1255
+ handleListExtensions(ws, message) {
1256
+ const extensions = this.connectionManager.getAllExtensions();
1257
+ const extensionList = extensions.map((ext) => ({
1258
+ index: ext.index,
1259
+ extensionId: ext.extensionId,
1260
+ connectedAt: ext.ws.connectionInfo.connectedAt.toISOString(),
1261
+ lastActiveAt: ext.ws.connectionInfo.lastActiveAt.toISOString()
1262
+ }));
1263
+ const response = {
1264
+ id: message.id || "",
1265
+ type: "response" /* RESPONSE */,
1266
+ success: true,
1267
+ data: {
1268
+ extensions: extensionList,
1269
+ totalCount: extensionList.length
1270
+ }
1271
+ };
1272
+ ws.send(JSON.stringify(response));
1273
+ this.logger.info(`\u8FD4\u56DE\u6269\u5C55\u5217\u8868\uFF0C\u5171 ${extensionList.length} \u4E2A\u6269\u5C55`);
1274
+ }
1275
+ /**
1276
+ * 处理打开页面请求
1277
+ */
1278
+ handleOpenPage(ws, message) {
1279
+ const params = message.params;
1280
+ const requestId = message.id || "";
1281
+ if (!params?.url) {
1282
+ const errorResponse = {
1283
+ id: requestId,
1284
+ type: "error" /* ERROR */,
1285
+ success: false,
1286
+ error: {
1287
+ code: "E011" /* INVALID_PARAMS */,
1288
+ name: "INVALID_PARAMS",
1289
+ message: "\u7F3A\u5C11 url \u53C2\u6570"
1290
+ }
1291
+ };
1292
+ ws.send(JSON.stringify(errorResponse));
1293
+ return;
1294
+ }
1295
+ const targetIndex = params.extensionIndex || 1;
1296
+ const targetExtension = this.connectionManager.findExtensionByIndex(targetIndex);
1297
+ if (!targetExtension) {
1298
+ const errorResponse = {
1299
+ id: requestId,
1300
+ type: "error" /* ERROR */,
1301
+ success: false,
1302
+ error: {
1303
+ code: "E018" /* NO_EXTENSION_CONNECTED */,
1304
+ name: "NO_EXTENSION_CONNECTED",
1305
+ message: targetIndex === 1 ? "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin" : `\u672A\u627E\u5230\u5E8F\u53F7\u4E3A #${targetIndex} \u7684\u6269\u5C55\u5B9E\u4F8B`
1306
+ }
1307
+ };
1308
+ ws.send(JSON.stringify(errorResponse));
1309
+ this.logger.warn(`\u672A\u627E\u5230\u6269\u5C55\u5B9E\u4F8B #${targetIndex}`);
1310
+ return;
1311
+ }
1312
+ const forwardMessage = {
1313
+ id: requestId,
1314
+ type: "open_page" /* OPEN_PAGE */,
1315
+ params: { url: params.url }
1316
+ };
1317
+ this.requestHandler.registerPendingRequest(requestId, ws, () => {
1318
+ try {
1319
+ targetExtension.send(JSON.stringify(forwardMessage));
1320
+ this.logger.info(`\u8F6C\u53D1 OPEN_PAGE \u8BF7\u6C42\u5230\u6269\u5C55 #${targetIndex}: ${params.url}`);
1321
+ } catch (error) {
1322
+ this.logger.error(`\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25: ${error}`);
1323
+ const errorResponse = {
1324
+ id: requestId,
1325
+ type: "error" /* ERROR */,
1326
+ success: false,
1327
+ error: {
1328
+ code: "E001" /* CONNECTION_FAILED */,
1329
+ name: "CONNECTION_FAILED",
1330
+ message: "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25"
1331
+ }
1332
+ };
1333
+ ws.send(JSON.stringify(errorResponse));
1334
+ }
1335
+ });
1336
+ }
1337
+ /**
1338
+ * 处理节点导航请求
1339
+ */
1340
+ handleNavigateToNode(ws, message) {
1341
+ const params = message.params;
1342
+ const requestId = message.id || "";
1343
+ if (!params?.pageUrl || !params?.nodeId) {
1344
+ const errorResponse = {
1345
+ id: requestId,
1346
+ type: "error" /* ERROR */,
1347
+ success: false,
1348
+ error: {
1349
+ code: "E011" /* INVALID_PARAMS */,
1350
+ name: "INVALID_PARAMS",
1351
+ message: "\u7F3A\u5C11 pageUrl \u6216 nodeId \u53C2\u6570"
1352
+ }
1353
+ };
1354
+ ws.send(JSON.stringify(errorResponse));
1355
+ return;
1356
+ }
1357
+ const targetIndex = params.extensionIndex || 1;
1358
+ const targetExtension = this.connectionManager.findExtensionByIndex(targetIndex);
1359
+ if (!targetExtension) {
1360
+ const errorResponse = {
1361
+ id: requestId,
1362
+ type: "error" /* ERROR */,
1363
+ success: false,
1364
+ error: {
1365
+ code: "E018" /* NO_EXTENSION_CONNECTED */,
1366
+ name: "NO_EXTENSION_CONNECTED",
1367
+ message: targetIndex === 1 ? "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin" : `\u672A\u627E\u5230\u5E8F\u53F7\u4E3A #${targetIndex} \u7684\u6269\u5C55\u5B9E\u4F8B`
1368
+ }
1369
+ };
1370
+ ws.send(JSON.stringify(errorResponse));
1371
+ this.logger.warn(`\u672A\u627E\u5230\u6269\u5C55\u5B9E\u4F8B #${targetIndex}`);
1372
+ return;
1373
+ }
1374
+ const forwardMessage = {
1375
+ id: requestId,
1376
+ type: "navigate_to_node" /* NAVIGATE_TO_NODE */,
1377
+ params: {
1378
+ pageUrl: params.pageUrl,
1379
+ nodeId: params.nodeId
1380
+ }
1381
+ };
1382
+ this.requestHandler.registerPendingRequest(requestId, ws, () => {
1383
+ try {
1384
+ targetExtension.send(JSON.stringify(forwardMessage));
1385
+ this.logger.info(`\u8F6C\u53D1 NAVIGATE_TO_NODE \u8BF7\u6C42\u5230\u6269\u5C55 #${targetIndex}: ${params.pageUrl} -> ${params.nodeId}`);
1386
+ } catch (error) {
1387
+ this.logger.error(`\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25: ${error}`);
1388
+ const errorResponse = {
1389
+ id: requestId,
1390
+ type: "error" /* ERROR */,
1391
+ success: false,
1392
+ error: {
1393
+ code: "E001" /* CONNECTION_FAILED */,
1394
+ name: "CONNECTION_FAILED",
1395
+ message: "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25"
1396
+ }
1397
+ };
1398
+ ws.send(JSON.stringify(errorResponse));
1399
+ }
1400
+ });
1401
+ }
1125
1402
  /**
1126
1403
  * 停止服务器
1127
1404
  */
@@ -1168,6 +1445,247 @@ var MGServer = class {
1168
1445
  function createServer(options) {
1169
1446
  return new MGServer(options);
1170
1447
  }
1448
+
1449
+ // src/cli/client.ts
1450
+ import WebSocket2 from "ws";
1451
+
1452
+ // src/server/daemon.ts
1453
+ import { spawn } from "child_process";
1454
+ import { fileURLToPath as fileURLToPath2 } from "url";
1455
+ import { dirname as dirname4, join as join3 } from "path";
1456
+ function isServerRunning() {
1457
+ const info = readServerInfo();
1458
+ if (!info) {
1459
+ return { running: false, info: null };
1460
+ }
1461
+ if (!isProcessRunning(info.pid)) {
1462
+ deleteServerInfo();
1463
+ return { running: false, info: null };
1464
+ }
1465
+ return { running: true, info };
1466
+ }
1467
+ async function startServerDaemon(port) {
1468
+ const { running, info } = isServerRunning();
1469
+ if (running && info) {
1470
+ throw new MGError(
1471
+ "E016" /* SERVER_ALREADY_RUNNING */,
1472
+ `Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
1473
+ );
1474
+ }
1475
+ ensureConfigDir();
1476
+ const currentFile = fileURLToPath2(import.meta.url);
1477
+ const currentDir = dirname4(currentFile);
1478
+ const serverScript = join3(currentDir, "daemon-runner.js");
1479
+ const args = ["--foreground"];
1480
+ if (port) {
1481
+ args.push("--port", String(port));
1482
+ }
1483
+ const child = spawn(process.execPath, [serverScript, ...args], {
1484
+ detached: true,
1485
+ stdio: "ignore",
1486
+ env: {
1487
+ ...process.env,
1488
+ MG_DAEMON: "1"
1489
+ }
1490
+ });
1491
+ child.unref();
1492
+ const startTime = Date.now();
1493
+ while (Date.now() - startTime < SERVER_START_TIMEOUT) {
1494
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
1495
+ const { running: running2, info: info2 } = isServerRunning();
1496
+ if (running2 && info2) {
1497
+ return info2;
1498
+ }
1499
+ }
1500
+ throw new MGError("E015" /* SERVER_START_FAILED */, "Server \u542F\u52A8\u8D85\u65F6");
1501
+ }
1502
+
1503
+ // src/cli/client.ts
1504
+ var MGClient = class {
1505
+ ws = null;
1506
+ options;
1507
+ constructor(options = {}) {
1508
+ this.options = options;
1509
+ }
1510
+ /**
1511
+ * 连接到 Server
1512
+ */
1513
+ async connect() {
1514
+ const serverInfo = readServerInfo();
1515
+ if (serverInfo) {
1516
+ if (isProcessRunning(serverInfo.pid)) {
1517
+ const currentVersion = getVersion();
1518
+ if (!isVersionMatch(currentVersion, serverInfo.version)) {
1519
+ console.warn(`\u26A0\uFE0F \u7248\u672C\u4E0D\u5339\u914D: CLI ${currentVersion} vs Server ${serverInfo.version}`);
1520
+ console.warn("\u63D0\u793A: \u5982\u9700\u5BF9\u9F50\u7248\u672C\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C `npx -y @hangox/mg-cli@latest server restart`");
1521
+ }
1522
+ try {
1523
+ await this.tryConnect(serverInfo.port);
1524
+ return;
1525
+ } catch {
1526
+ }
1527
+ }
1528
+ }
1529
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
1530
+ try {
1531
+ await this.tryConnect(port);
1532
+ return;
1533
+ } catch {
1534
+ }
1535
+ }
1536
+ if (!this.options.noAutoStart) {
1537
+ console.log("Server \u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u81EA\u52A8\u542F\u52A8...");
1538
+ try {
1539
+ const info = await startServerDaemon();
1540
+ console.log(`Server \u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3: ${info.port}`);
1541
+ await this.waitForServer(info.port);
1542
+ return;
1543
+ } catch (error) {
1544
+ throw new MGError(
1545
+ "E015" /* SERVER_START_FAILED */,
1546
+ `\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25: ${error instanceof Error ? error.message : error}`
1547
+ );
1548
+ }
1549
+ }
1550
+ throw new MGError("E001" /* CONNECTION_FAILED */, ErrorMessages["E001" /* CONNECTION_FAILED */]);
1551
+ }
1552
+ /**
1553
+ * 尝试连接指定端口
1554
+ */
1555
+ tryConnect(port) {
1556
+ return new Promise((resolve2, reject) => {
1557
+ const ws = new WebSocket2(`ws://localhost:${port}`);
1558
+ const timer = setTimeout(() => {
1559
+ ws.close();
1560
+ reject(new Error("\u8FDE\u63A5\u8D85\u65F6"));
1561
+ }, PORT_SCAN_TIMEOUT);
1562
+ ws.on("open", () => {
1563
+ clearTimeout(timer);
1564
+ this.ws = ws;
1565
+ this.register();
1566
+ resolve2();
1567
+ });
1568
+ ws.on("error", (error) => {
1569
+ clearTimeout(timer);
1570
+ reject(error);
1571
+ });
1572
+ });
1573
+ }
1574
+ /**
1575
+ * 等待 Server 就绪
1576
+ */
1577
+ async waitForServer(port) {
1578
+ const startTime = Date.now();
1579
+ const interval = 500;
1580
+ while (Date.now() - startTime < SERVER_START_TIMEOUT) {
1581
+ try {
1582
+ await this.tryConnect(port);
1583
+ return;
1584
+ } catch {
1585
+ await new Promise((r) => setTimeout(r, interval));
1586
+ }
1587
+ }
1588
+ throw new Error("\u7B49\u5F85 Server \u542F\u52A8\u8D85\u65F6");
1589
+ }
1590
+ /**
1591
+ * 注册为 Consumer
1592
+ */
1593
+ register() {
1594
+ if (!this.ws) return;
1595
+ const message = {
1596
+ type: "register" /* REGISTER */,
1597
+ data: {
1598
+ connectionType: "consumer" /* CONSUMER */
1599
+ },
1600
+ timestamp: Date.now()
1601
+ };
1602
+ this.ws.send(JSON.stringify(message));
1603
+ }
1604
+ /**
1605
+ * 发送请求并等待响应
1606
+ */
1607
+ async request(type, params, pageUrl) {
1608
+ if (!this.ws) {
1609
+ throw new MGError("E001" /* CONNECTION_FAILED */, "\u672A\u8FDE\u63A5\u5230 Server");
1610
+ }
1611
+ const requestId = generateId();
1612
+ const message = {
1613
+ id: requestId,
1614
+ type,
1615
+ params,
1616
+ pageUrl,
1617
+ timestamp: Date.now()
1618
+ };
1619
+ return new Promise((resolve2, reject) => {
1620
+ const timer = setTimeout(() => {
1621
+ reject(new MGError("E012" /* REQUEST_TIMEOUT */, ErrorMessages["E012" /* REQUEST_TIMEOUT */]));
1622
+ }, REQUEST_TIMEOUT);
1623
+ const messageHandler = (data) => {
1624
+ try {
1625
+ const response = JSON.parse(data.toString());
1626
+ if (response.id === requestId) {
1627
+ clearTimeout(timer);
1628
+ this.ws?.off("message", messageHandler);
1629
+ if (response.success) {
1630
+ resolve2(response.data);
1631
+ } else {
1632
+ const error = response.error;
1633
+ reject(
1634
+ new MGError(
1635
+ error?.code || "E099" /* UNKNOWN_ERROR */,
1636
+ error?.message || "\u672A\u77E5\u9519\u8BEF"
1637
+ )
1638
+ );
1639
+ }
1640
+ }
1641
+ } catch {
1642
+ }
1643
+ };
1644
+ this.ws.on("message", messageHandler);
1645
+ this.ws.send(JSON.stringify(message));
1646
+ });
1647
+ }
1648
+ /**
1649
+ * 带重试的请求
1650
+ */
1651
+ async requestWithRetry(type, params, pageUrl) {
1652
+ if (this.options.noRetry) {
1653
+ return this.request(type, params, pageUrl);
1654
+ }
1655
+ let lastError = null;
1656
+ for (let attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++) {
1657
+ try {
1658
+ return await this.request(type, params, pageUrl);
1659
+ } catch (error) {
1660
+ lastError = error instanceof Error ? error : new Error(String(error));
1661
+ if (error instanceof MGError) {
1662
+ const retryable = ["E017" /* CONNECTION_LOST */, "E012" /* REQUEST_TIMEOUT */];
1663
+ if (!retryable.includes(error.code)) {
1664
+ throw error;
1665
+ }
1666
+ }
1667
+ if (attempt < MAX_RETRY_COUNT) {
1668
+ const delay = RETRY_INTERVALS[attempt] || RETRY_INTERVALS[RETRY_INTERVALS.length - 1];
1669
+ await new Promise((r) => setTimeout(r, delay));
1670
+ try {
1671
+ await this.connect();
1672
+ } catch {
1673
+ }
1674
+ }
1675
+ }
1676
+ }
1677
+ throw lastError || new MGError("E099" /* UNKNOWN_ERROR */, "\u8BF7\u6C42\u5931\u8D25");
1678
+ }
1679
+ /**
1680
+ * 关闭连接
1681
+ */
1682
+ close() {
1683
+ if (this.ws) {
1684
+ this.ws.close();
1685
+ this.ws = null;
1686
+ }
1687
+ }
1688
+ };
1171
1689
  export {
1172
1690
  CONFIG_DIR,
1173
1691
  ConnectionManager,
@@ -1187,6 +1705,7 @@ export {
1187
1705
  Logger,
1188
1706
  MAX_PORT_ATTEMPTS,
1189
1707
  MAX_RETRY_COUNT,
1708
+ MGClient,
1190
1709
  MGError,
1191
1710
  MGServer,
1192
1711
  MessageType,