@hangox/mg-cli 1.0.5 → 1.0.7

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/cli.js CHANGED
@@ -18,9 +18,16 @@ import { dirname, resolve, isAbsolute } from "path";
18
18
  // src/shared/constants.ts
19
19
  import { homedir } from "os";
20
20
  import { join } from "path";
21
- var DEFAULT_PORT = 9527;
22
- var PORT_RANGE_START = 9527;
23
- var PORT_RANGE_END = 9536;
21
+ var IS_DEV_MODE = process.env.MG_DEV_MODE === "true";
22
+ var PROD_DEFAULT_PORT = 9527;
23
+ var PROD_PORT_RANGE_START = 9527;
24
+ var PROD_PORT_RANGE_END = 9536;
25
+ var DEV_DEFAULT_PORT = 19527;
26
+ var DEV_PORT_RANGE_START = 19527;
27
+ var DEV_PORT_RANGE_END = 19536;
28
+ var DEFAULT_PORT = IS_DEV_MODE ? DEV_DEFAULT_PORT : PROD_DEFAULT_PORT;
29
+ var PORT_RANGE_START = IS_DEV_MODE ? DEV_PORT_RANGE_START : PROD_PORT_RANGE_START;
30
+ var PORT_RANGE_END = IS_DEV_MODE ? DEV_PORT_RANGE_END : PROD_PORT_RANGE_END;
24
31
  var PORT_SCAN_TIMEOUT = 500;
25
32
  var CONFIG_DIR = join(homedir(), ".mg-plugin");
26
33
  var SERVER_INFO_FILE = join(CONFIG_DIR, "server.json");
@@ -96,27 +103,44 @@ function parseMgpLink(link) {
96
103
  const queryString = urlPart.slice(questionMarkIndex + 1);
97
104
  const params = new URLSearchParams(queryString);
98
105
  const encodedNodeId = params.get("nodeId");
99
- if (!encodedNodeId) {
106
+ const encodedPageId = params.get("pageId");
107
+ if (!encodedNodeId && !encodedPageId) {
100
108
  return null;
101
109
  }
102
- const nodeId = decodeURIComponent(encodedNodeId);
103
- if (!/^(\d+:\d+)(\/\d+:\d+)*$/.test(nodeId)) {
110
+ if (encodedNodeId && encodedPageId) {
104
111
  return null;
105
112
  }
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))) {
113
+ if (encodedNodeId) {
114
+ const nodeId = decodeURIComponent(encodedNodeId);
115
+ if (!/^(\d+:\d+)(\/\d+:\d+)*$/.test(nodeId)) {
112
116
  return null;
113
117
  }
118
+ const encodedNodePath = params.get("nodePath");
119
+ let nodePath;
120
+ if (encodedNodePath) {
121
+ const decodedNodePath = decodeURIComponent(encodedNodePath);
122
+ nodePath = decodedNodePath.split("/").filter(Boolean);
123
+ if (!nodePath.every((segment) => /^\d+:\d+$/.test(segment))) {
124
+ return null;
125
+ }
126
+ }
127
+ return {
128
+ pageUrl,
129
+ nodeId,
130
+ nodePath
131
+ };
114
132
  }
115
- return {
116
- pageUrl,
117
- nodeId,
118
- nodePath
119
- };
133
+ if (encodedPageId) {
134
+ const pageId = decodeURIComponent(encodedPageId);
135
+ if (!/^\d+:\d+$/.test(pageId)) {
136
+ return null;
137
+ }
138
+ return {
139
+ pageUrl,
140
+ pageId
141
+ };
142
+ }
143
+ return null;
120
144
  } catch {
121
145
  return null;
122
146
  }
@@ -346,7 +370,7 @@ var ErrorNames = {
346
370
  var ErrorMessages = {
347
371
  ["E001" /* CONNECTION_FAILED */]: "\u65E0\u6CD5\u8FDE\u63A5\u5230 MG Server",
348
372
  ["E002" /* CONNECTION_TIMEOUT */]: "\u8FDE\u63A5\u8D85\u65F6",
349
- ["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server",
373
+ ["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u5B89\u88C5 MG Plugin \u6D4F\u89C8\u5668\u6269\u5C55: https://chromewebstore.google.com/detail/mg-plugin/ddhihanlpcdneicohnglnaliefnkaeja",
350
374
  ["E004" /* PAGE_NOT_FOUND */]: "\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762",
351
375
  ["E005" /* NODE_NOT_FOUND */]: "\u8282\u70B9\u4E0D\u5B58\u5728",
352
376
  ["E006" /* NO_SELECTION */]: "\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9",
@@ -470,7 +494,7 @@ function createLogger(options) {
470
494
  // src/server/connection-manager.ts
471
495
  var ConnectionManager = class {
472
496
  logger;
473
- /** Provider 连接(按页面 URL 索引) */
497
+ /** Provider 连接(按页面 URL 索引,支持同 URL 多个连接) */
474
498
  providers = /* @__PURE__ */ new Map();
475
499
  /** Consumer 连接 */
476
500
  consumers = /* @__PURE__ */ new Map();
@@ -506,7 +530,8 @@ var ConnectionManager = class {
506
530
  */
507
531
  checkHeartbeats() {
508
532
  const now = Date.now();
509
- for (const [id, ws] of this.allConnections) {
533
+ const entries = Array.from(this.allConnections.entries());
534
+ for (const [id, ws] of entries) {
510
535
  const lastActive = ws.connectionInfo.lastActiveAt.getTime();
511
536
  const elapsed = now - lastActive;
512
537
  if (elapsed > HEARTBEAT_TIMEOUT) {
@@ -536,14 +561,10 @@ var ConnectionManager = class {
536
561
  managedWs.isAlive = true;
537
562
  this.allConnections.set(connectionId, managedWs);
538
563
  if (type === "provider" /* PROVIDER */ && pageUrl) {
539
- const existing = this.providers.get(pageUrl);
540
- if (existing) {
541
- this.logger.info(`\u9875\u9762 ${pageUrl} \u5DF2\u6709\u8FDE\u63A5\uFF0C\u66FF\u6362\u4E3A\u65B0\u8FDE\u63A5`);
542
- this.removeConnection(existing);
543
- existing.close();
544
- }
545
- this.providers.set(pageUrl, managedWs);
546
- this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl}`);
564
+ const existing = this.providers.get(pageUrl) || [];
565
+ existing.push(managedWs);
566
+ this.providers.set(pageUrl, existing);
567
+ this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl} (\u5F53\u524D\u8BE5\u9875\u9762\u8FDE\u63A5\u6570: ${existing.length})`);
547
568
  } else if (type === "consumer" /* CONSUMER */) {
548
569
  this.consumers.set(connectionId, managedWs);
549
570
  this.logger.info(`Consumer \u8FDE\u63A5: ${connectionId}`);
@@ -557,8 +578,17 @@ var ConnectionManager = class {
557
578
  const { connectionId, connectionInfo } = ws;
558
579
  this.allConnections.delete(connectionId);
559
580
  if (connectionInfo.type === "provider" /* PROVIDER */ && connectionInfo.pageUrl) {
560
- this.providers.delete(connectionInfo.pageUrl);
561
- this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl}`);
581
+ const connections = this.providers.get(connectionInfo.pageUrl);
582
+ if (connections) {
583
+ const index = connections.findIndex((c) => c.connectionId === connectionId);
584
+ if (index !== -1) {
585
+ connections.splice(index, 1);
586
+ if (connections.length === 0) {
587
+ this.providers.delete(connectionInfo.pageUrl);
588
+ }
589
+ }
590
+ }
591
+ this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl} (\u8FDE\u63A5ID: ${connectionId})`);
562
592
  } else if (connectionInfo.type === "consumer" /* CONSUMER */) {
563
593
  this.consumers.delete(connectionId);
564
594
  this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
@@ -572,31 +602,48 @@ var ConnectionManager = class {
572
602
  ws.isAlive = true;
573
603
  }
574
604
  /**
575
- * 根据页面 URL 查找 Provider
605
+ * 根据页面 URL 查找 Provider(返回第一个可用的连接)
576
606
  */
577
607
  findProviderByPageUrl(pageUrl) {
578
- return this.providers.get(pageUrl);
608
+ const connections = this.providers.get(pageUrl);
609
+ return connections?.[0];
579
610
  }
580
611
  /**
581
612
  * 获取第一个可用的 Provider
582
613
  */
583
614
  getFirstProvider() {
584
- const iterator = this.providers.values();
585
- const first = iterator.next();
586
- return first.value;
615
+ const allConnections = Array.from(this.providers.values());
616
+ for (const connections of allConnections) {
617
+ if (connections.length > 0) {
618
+ return connections[0];
619
+ }
620
+ }
621
+ return void 0;
587
622
  }
588
623
  /**
589
624
  * 获取所有 Provider 信息
590
625
  */
591
626
  getAllProviders() {
592
- return Array.from(this.providers.values()).map((ws) => ws.connectionInfo);
627
+ const result = [];
628
+ const allConnections = Array.from(this.providers.values());
629
+ for (const connections of allConnections) {
630
+ for (const ws of connections) {
631
+ result.push(ws.connectionInfo);
632
+ }
633
+ }
634
+ return result;
593
635
  }
594
636
  /**
595
637
  * 获取连接统计
596
638
  */
597
639
  getStats() {
640
+ let providerCount = 0;
641
+ const allConnections = Array.from(this.providers.values());
642
+ for (const connections of allConnections) {
643
+ providerCount += connections.length;
644
+ }
598
645
  return {
599
- providers: this.providers.size,
646
+ providers: providerCount,
600
647
  consumers: this.consumers.size,
601
648
  total: this.allConnections.size
602
649
  };
@@ -612,7 +659,8 @@ var ConnectionManager = class {
612
659
  */
613
660
  closeAll() {
614
661
  this.stopHeartbeatCheck();
615
- for (const ws of this.allConnections.values()) {
662
+ const allWs = Array.from(this.allConnections.values());
663
+ for (const ws of allWs) {
616
664
  ws.close();
617
665
  }
618
666
  this.providers.clear();
@@ -648,7 +696,7 @@ var RequestHandler = class {
648
696
  } else {
649
697
  provider = this.connectionManager.getFirstProvider();
650
698
  if (!provider) {
651
- this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, "\u6CA1\u6709\u9875\u9762\u8FDE\u63A5\u5230 Server");
699
+ this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, ErrorMessages["E003" /* NO_PAGE_CONNECTED */]);
652
700
  return;
653
701
  }
654
702
  }
@@ -766,24 +814,11 @@ function getVersion() {
766
814
  try {
767
815
  const currentFile = fileURLToPath(import.meta.url);
768
816
  const currentDir = dirname3(currentFile);
769
- const versionFilePaths = [
770
- join2(currentDir, "..", "VERSION"),
771
- // dist/xxx.js -> ../VERSION
772
- join2(currentDir, "..", "..", "VERSION")
773
- // src/shared/version.ts -> ../../VERSION
774
- ];
775
- for (const versionFilePath of versionFilePaths) {
776
- if (existsSync3(versionFilePath)) {
777
- const version = readFileSync2(versionFilePath, "utf-8").trim();
778
- if (version) {
779
- cachedVersion = version;
780
- return cachedVersion;
781
- }
782
- }
783
- }
784
817
  const packageJsonPaths = [
785
818
  join2(currentDir, "..", "package.json"),
819
+ // dist/xxx.js -> ../package.json
786
820
  join2(currentDir, "..", "..", "package.json")
821
+ // src/shared/version.ts -> ../../package.json
787
822
  ];
788
823
  for (const packageJsonPath of packageJsonPaths) {
789
824
  if (existsSync3(packageJsonPath)) {
@@ -801,7 +836,11 @@ function getVersion() {
801
836
  return cachedVersion;
802
837
  }
803
838
  }
839
+ var DEV_VERSION = "9.9.9";
804
840
  function isVersionMatch(version1, version2) {
841
+ if (version1 === DEV_VERSION || version2 === DEV_VERSION) {
842
+ return true;
843
+ }
805
844
  return version1 === version2;
806
845
  }
807
846
 
@@ -1472,7 +1511,8 @@ function createServerCommand() {
1472
1511
  } else {
1473
1512
  console.log(``);
1474
1513
  console.log(`\u5DF2\u8FDE\u63A5\u9875\u9762: \u65E0`);
1475
- console.log(`\u63D0\u793A: \u8BF7\u5728 Chrome \u4E2D\u6253\u5F00 MasterGo \u9875\u9762\u5E76\u786E\u4FDD\u63D2\u4EF6\u5DF2\u542F\u7528`);
1514
+ console.log(`\u63D0\u793A: \u8BF7\u5B89\u88C5 MG Plugin \u6D4F\u89C8\u5668\u6269\u5C55\u5E76\u5728 Chrome \u4E2D\u6253\u5F00 MasterGo \u9875\u9762`);
1515
+ console.log(` \u63D2\u4EF6\u5730\u5740: https://chromewebstore.google.com/detail/mg-plugin/ddhihanlpcdneicohnglnaliefnkaeja`);
1476
1516
  }
1477
1517
  } catch {
1478
1518
  console.log("MG Server \u72B6\u6001: \u8FD0\u884C\u4E2D \u2713");
@@ -1551,7 +1591,7 @@ import { writeFileSync as writeFileSync3 } from "fs";
1551
1591
  import { resolve as resolve3, dirname as dirname6 } from "path";
1552
1592
  import { mkdirSync as mkdirSync4 } from "fs";
1553
1593
  function createGetNodeByLinkCommand() {
1554
- 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("--raw", "\u4FDD\u7559\u539F\u59CB\u6570\u636E\uFF0C\u4E0D\u7CBE\u7B80\u9ED8\u8BA4\u503C\u5B57\u6BB5", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1594
+ return new Command3("get_node_by_link").description("\u89E3\u6790 mgp:// \u534F\u8BAE\u94FE\u63A5\u5E76\u83B7\u53D6\u8282\u70B9/\u9875\u9762\u4FE1\u606F").requiredOption("--link <url>", "mgp:// \u534F\u8BAE\u94FE\u63A5\uFF08\u652F\u6301 nodeId \u548C pageId\uFF09").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("--raw", "\u4FDD\u7559\u539F\u59CB\u6570\u636E\uFF0C\u4E0D\u7CBE\u7B80\u9ED8\u8BA4\u503C\u5B57\u6BB5", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
1555
1595
  await handleGetNodeByLink(options);
1556
1596
  });
1557
1597
  }
@@ -1560,26 +1600,43 @@ async function handleGetNodeByLink(options) {
1560
1600
  if (!parsed) {
1561
1601
  console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
1562
1602
  console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
1563
- console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]/nodeId`);
1603
+ console.error(`\u671F\u671B\u683C\u5F0F:`);
1604
+ console.error(` \u8282\u70B9\u94FE\u63A5: mgp://[mastergo_page_url]?nodeId=[\u8282\u70B9ID]`);
1605
+ console.error(` \u9875\u9762\u94FE\u63A5: mgp://[mastergo_page_url]?pageId=[\u9875\u9762ID]`);
1564
1606
  process.exit(1);
1565
1607
  }
1566
- const { pageUrl, nodeId } = parsed;
1608
+ const { pageUrl, nodeId, pageId } = parsed;
1609
+ const isPageLink = !!pageId;
1567
1610
  const client = new MGClient({
1568
1611
  noAutoStart: options.noAutoStart,
1569
1612
  noRetry: options.noRetry
1570
1613
  });
1571
1614
  try {
1572
1615
  await client.connect();
1573
- const params = {
1574
- nodeId,
1575
- maxDepth: parseInt(options.maxDepth || "1", 10),
1576
- includeInvisible: options.includeInvisible || false
1577
- };
1578
- const data = await client.requestWithRetry(
1579
- "get_node_by_id" /* GET_NODE_BY_ID */,
1580
- params,
1581
- pageUrl
1582
- );
1616
+ let data;
1617
+ if (isPageLink) {
1618
+ const params = {
1619
+ pageId,
1620
+ maxDepth: parseInt(options.maxDepth || "1", 10),
1621
+ includeInvisible: options.includeInvisible || false
1622
+ };
1623
+ data = await client.requestWithRetry(
1624
+ "get_page_by_id" /* GET_PAGE_BY_ID */,
1625
+ params,
1626
+ pageUrl
1627
+ );
1628
+ } else {
1629
+ const params = {
1630
+ nodeId,
1631
+ maxDepth: parseInt(options.maxDepth || "1", 10),
1632
+ includeInvisible: options.includeInvisible || false
1633
+ };
1634
+ data = await client.requestWithRetry(
1635
+ "get_node_by_id" /* GET_NODE_BY_ID */,
1636
+ params,
1637
+ pageUrl
1638
+ );
1639
+ }
1583
1640
  const outputData = options.raw ? data : trimNodeDefaults(data);
1584
1641
  const outputPath = resolve3(options.output);
1585
1642
  const outputDir = dirname6(outputPath);
@@ -1591,9 +1648,15 @@ async function handleGetNodeByLink(options) {
1591
1648
  console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1592
1649
  console.log(`Link: ${options.link}`);
1593
1650
  console.log(`\u9875\u9762 URL: ${pageUrl}`);
1594
- console.log(`\u8282\u70B9 ID: ${nodeId}`);
1651
+ if (isPageLink) {
1652
+ console.log(`\u9875\u9762 ID: ${pageId}`);
1653
+ console.log(`\u7C7B\u578B: \u9875\u9762`);
1654
+ } else {
1655
+ console.log(`\u8282\u70B9 ID: ${nodeId}`);
1656
+ console.log(`\u7C7B\u578B: \u8282\u70B9`);
1657
+ }
1595
1658
  console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1596
- console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
1659
+ console.log(`\u904D\u5386\u6DF1\u5EA6: ${options.maxDepth || "1"}`);
1597
1660
  if (!options.raw) {
1598
1661
  console.log(`\u6570\u636E\u6A21\u5F0F: \u7CBE\u7B80\u6A21\u5F0F (\u4F7F\u7528 --raw \u83B7\u53D6\u5B8C\u6574\u6570\u636E)`);
1599
1662
  }
@@ -1657,18 +1720,19 @@ async function handleGetAllNodes(options) {
1657
1720
 
1658
1721
  // src/cli/commands/export-image.ts
1659
1722
  import { Command as Command5 } from "commander";
1660
- import { writeFileSync as writeFileSync5 } from "fs";
1661
- import { resolve as resolve5, dirname as dirname8, extname } from "path";
1723
+ import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
1724
+ import { resolve as resolve5, dirname as dirname8, extname, basename } from "path";
1662
1725
  import { mkdirSync as mkdirSync6 } from "fs";
1663
1726
  import { tmpdir } from "os";
1727
+ import { vdConvert } from "vd-tool";
1664
1728
  function createExportImageCommand() {
1665
- 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\u4E0E --nodeId/--domain/--fileId \u4E8C\u9009\u4E00").option("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u4E0E --domain/--fileId \u914D\u5408\u4F7F\u7528").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com", "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("--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) => {
1729
+ 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\u4E0E --nodeId/--domain/--fileId \u4E8C\u9009\u4E00").option("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u4E0E --domain/--fileId \u914D\u5408\u4F7F\u7528").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com", "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("--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\u3001VECTOR\uFF08Android VectorDrawable\uFF09", "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) => {
1666
1730
  await handleExportImage(options);
1667
1731
  });
1668
1732
  }
1669
1733
  async function handleExportImage(options) {
1670
1734
  const format = options.format?.toUpperCase() || "PNG";
1671
- const validFormats = ["PNG", "JPG", "SVG", "PDF", "WEBP"];
1735
+ const validFormats = ["PNG", "JPG", "SVG", "PDF", "WEBP", "VECTOR"];
1672
1736
  if (!validFormats.includes(format)) {
1673
1737
  console.error(`\u9519\u8BEF: \u4E0D\u652F\u6301\u7684\u683C\u5F0F "${options.format}"`);
1674
1738
  console.error(`\u652F\u6301\u7684\u683C\u5F0F: ${validFormats.join(", ")}`);
@@ -1687,10 +1751,11 @@ async function handleExportImage(options) {
1687
1751
  let nodeId;
1688
1752
  if (options.link) {
1689
1753
  const linkInfo = parseMgpLink(options.link);
1690
- if (!linkInfo) {
1754
+ if (!linkInfo || !linkInfo.nodeId) {
1691
1755
  console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
1692
1756
  console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
1693
1757
  console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]?nodeId=xxx`);
1758
+ console.error(`\u6CE8\u610F: \u6B64\u547D\u4EE4\u4E0D\u652F\u6301\u9875\u9762\u94FE\u63A5 (pageId)\uFF0C\u8BF7\u4F7F\u7528\u8282\u70B9\u94FE\u63A5 (nodeId)`);
1694
1759
  process.exit(1);
1695
1760
  }
1696
1761
  pageUrl = linkInfo.pageUrl;
@@ -1710,8 +1775,10 @@ async function handleExportImage(options) {
1710
1775
  });
1711
1776
  try {
1712
1777
  await client.connect();
1778
+ const isVectorFormat = format === "VECTOR";
1779
+ const requestFormat = isVectorFormat ? "SVG" : format;
1713
1780
  const params = {
1714
- format,
1781
+ format: requestFormat,
1715
1782
  useAbsoluteBounds: options.useAbsoluteBounds || false,
1716
1783
  useRenderBounds: options.useRenderBounds !== false
1717
1784
  };
@@ -1747,9 +1814,38 @@ async function handleExportImage(options) {
1747
1814
  const outputDir = dirname8(outputPath);
1748
1815
  mkdirSync6(outputDir, { recursive: true });
1749
1816
  const buffer = Buffer.from(response.data, "base64");
1750
- writeFileSync5(outputPath, buffer);
1751
- const sizeKB = (buffer.length / 1024).toFixed(2);
1752
- console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
1817
+ let finalPath = outputPath;
1818
+ let finalSize = buffer.length;
1819
+ if (isVectorFormat) {
1820
+ const tempSvgPath = resolve5(tmpdir(), `temp_${Date.now()}.svg`);
1821
+ writeFileSync5(tempSvgPath, buffer);
1822
+ try {
1823
+ const vectorOutputDir = dirname8(outputPath);
1824
+ const convertedPath = await convertSvgToVector(tempSvgPath, vectorOutputDir);
1825
+ const expectedOutputName = basename(tempSvgPath, ".svg") + ".xml";
1826
+ const expectedOutputPath = resolve5(vectorOutputDir, expectedOutputName);
1827
+ if (expectedOutputPath !== outputPath) {
1828
+ const { renameSync, existsSync: existsSync4 } = await import("fs");
1829
+ if (existsSync4(expectedOutputPath)) {
1830
+ renameSync(expectedOutputPath, outputPath);
1831
+ } else if (existsSync4(convertedPath)) {
1832
+ renameSync(convertedPath, outputPath);
1833
+ }
1834
+ }
1835
+ const { statSync } = await import("fs");
1836
+ finalSize = statSync(outputPath).size;
1837
+ finalPath = outputPath;
1838
+ } finally {
1839
+ try {
1840
+ unlinkSync2(tempSvgPath);
1841
+ } catch {
1842
+ }
1843
+ }
1844
+ } else {
1845
+ writeFileSync5(outputPath, buffer);
1846
+ }
1847
+ const sizeKB = (finalSize / 1024).toFixed(2);
1848
+ console.log(`\u6587\u4EF6\u8DEF\u5F84: ${finalPath}`);
1753
1849
  if (options.link) {
1754
1850
  console.log(`Link: ${options.link}`);
1755
1851
  }
@@ -1758,8 +1854,8 @@ async function handleExportImage(options) {
1758
1854
  } else {
1759
1855
  console.log("\u8282\u70B9 ID: (\u9009\u4E2D\u7684\u8282\u70B9)");
1760
1856
  }
1761
- console.log(`\u5BFC\u51FA\u683C\u5F0F: ${format}`);
1762
- console.log(`\u6587\u4EF6\u5927\u5C0F: ${buffer.length.toLocaleString()} \u5B57\u8282 (\u7EA6 ${sizeKB} KB)`);
1857
+ console.log(`\u5BFC\u51FA\u683C\u5F0F: ${format}${isVectorFormat ? " (Android VectorDrawable)" : ""}`);
1858
+ console.log(`\u6587\u4EF6\u5927\u5C0F: ${finalSize.toLocaleString()} \u5B57\u8282 (\u7EA6 ${sizeKB} KB)`);
1763
1859
  } catch (error) {
1764
1860
  if (error instanceof MGError) {
1765
1861
  console.error(`\u9519\u8BEF [${error.code}]: ${error.message}`);
@@ -1777,10 +1873,35 @@ function getExtension(format) {
1777
1873
  JPG: ".jpg",
1778
1874
  SVG: ".svg",
1779
1875
  PDF: ".pdf",
1780
- WEBP: ".webp"
1876
+ WEBP: ".webp",
1877
+ VECTOR: ".xml"
1781
1878
  };
1782
1879
  return extensions[format];
1783
1880
  }
1881
+ async function convertSvgToVector(svgPath, outputDir) {
1882
+ try {
1883
+ const result = await vdConvert(svgPath, {
1884
+ outDir: outputDir
1885
+ });
1886
+ if (result.errors && result.errors.length > 0) {
1887
+ throw new Error(`VectorDrawable \u8F6C\u6362\u5931\u8D25: ${result.errors.join(", ")}`);
1888
+ }
1889
+ if (result.warnings && result.warnings.length > 0) {
1890
+ console.log(`\u8B66\u544A: ${result.warnings.join(", ")}`);
1891
+ }
1892
+ return result.output;
1893
+ } catch (error) {
1894
+ if (error instanceof Error) {
1895
+ if (error.message.includes("ENOENT") || error.message.includes("java")) {
1896
+ throw new Error(
1897
+ "VectorDrawable \u8F6C\u6362\u9700\u8981 Java 8 \u6216\u66F4\u9AD8\u7248\u672C\u3002\n\u8BF7\u5B89\u88C5 Java: https://adoptium.net/ \u6216 brew install openjdk"
1898
+ );
1899
+ }
1900
+ throw error;
1901
+ }
1902
+ throw new Error(`VectorDrawable \u8F6C\u6362\u5931\u8D25: ${error}`);
1903
+ }
1904
+ }
1784
1905
 
1785
1906
  // src/cli/commands/execute-code.ts
1786
1907
  import { Command as Command6 } from "commander";
@@ -1800,7 +1921,7 @@ async function handleExecuteCode(code, options) {
1800
1921
  if (!parsed) {
1801
1922
  console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
1802
1923
  console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
1803
- console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]?nodeId=xxx`);
1924
+ console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]?nodeId=xxx \u6216 mgp://[mastergo_page_url]?pageId=xxx`);
1804
1925
  process.exit(1);
1805
1926
  }
1806
1927
  pageUrl = parsed.pageUrl;
@@ -1905,7 +2026,7 @@ import { writeFileSync as writeFileSync7 } from "fs";
1905
2026
  import { resolve as resolve7, dirname as dirname10 } from "path";
1906
2027
  import { mkdirSync as mkdirSync8 } from "fs";
1907
2028
  function createGetNodeForSpaceCommand() {
1908
- return new Command8("get_node_for_space").description("\u83B7\u53D6\u8282\u70B9\u7684\u7A7A\u95F4\u4F4D\u7F6E\u4FE1\u606F\uFF08id\u3001name\u3001x\u3001y\u3001width\u3001height\uFF09\uFF0C\u7528\u4E8E AI \u7406\u89E3\u5143\u7D20\u5E03\u5C40\u3002\u9ED8\u8BA4\u83B7\u53D6\u6700\u6DF1\u5C42\u7EA7").option("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u4E0E --link \u4E8C\u9009\u4E00").option("--link <url>", "mgp:// \u534F\u8BAE\u94FE\u63A5\u3002\u4E0E --nodeId \u4E8C\u9009\u4E00").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com\u3002\u4E0E --nodeId \u914D\u5408\u4F7F\u7528", "mastergo.netease.com").option("--fileId <id>", "\u6587\u4EF6 ID\uFF08\u7EAF\u6570\u5B57\uFF09\uFF0C\u4E0E --domain \u548C --nodeId \u914D\u5408\u4F7F\u7528").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 99\uFF08\u83B7\u53D6\u6700\u6DF1\u5C42\u7EA7\uFF09", "99").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) => {
2029
+ return new Command8("get_node_for_space").description("\u83B7\u53D6\u8282\u70B9\u6216\u9875\u9762\u7684\u7A7A\u95F4\u4F4D\u7F6E\u4FE1\u606F\uFF08id\u3001name\u3001x\u3001y\u3001width\u3001height\uFF09\uFF0C\u7528\u4E8E AI \u7406\u89E3\u5143\u7D20\u5E03\u5C40\u3002\u9ED8\u8BA4\u83B7\u53D6\u6700\u6DF1\u5C42\u7EA7\u3002\u652F\u6301 nodeId \u548C pageId \u94FE\u63A5").option("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u4E0E --link \u4E8C\u9009\u4E00").option("--link <url>", "mgp:// \u534F\u8BAE\u94FE\u63A5\uFF08\u652F\u6301 nodeId \u548C pageId\uFF09\u3002\u4E0E --nodeId \u4E8C\u9009\u4E00").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84").option("--domain <domain>", "MasterGo \u57DF\u540D\uFF0C\u9ED8\u8BA4 mastergo.netease.com\u3002\u4E0E --nodeId \u914D\u5408\u4F7F\u7528", "mastergo.netease.com").option("--fileId <id>", "\u6587\u4EF6 ID\uFF08\u7EAF\u6570\u5B57\uFF09\uFF0C\u4E0E --domain \u548C --nodeId \u914D\u5408\u4F7F\u7528").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 99\uFF08\u83B7\u53D6\u6700\u6DF1\u5C42\u7EA7\uFF09", "99").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) => {
1909
2030
  await handleGetNodeForSpace(options);
1910
2031
  });
1911
2032
  }
@@ -1920,16 +2041,27 @@ async function handleGetNodeForSpace(options) {
1920
2041
  }
1921
2042
  let pageUrl;
1922
2043
  let nodeId;
2044
+ let pageId;
2045
+ let isPageMode = false;
1923
2046
  if (options.link) {
1924
2047
  const parsed = parseMgpLink(options.link);
1925
2048
  if (!parsed) {
1926
2049
  console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
1927
2050
  console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
1928
- console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]?nodeId=xxx`);
2051
+ console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]?nodeId=xxx \u6216 mgp://[mastergo_page_url]?pageId=xxx`);
1929
2052
  process.exit(1);
1930
2053
  }
1931
2054
  pageUrl = parsed.pageUrl;
1932
- nodeId = parsed.nodeId;
2055
+ if (parsed.pageId) {
2056
+ isPageMode = true;
2057
+ pageId = parsed.pageId;
2058
+ } else if (parsed.nodeId) {
2059
+ nodeId = parsed.nodeId;
2060
+ } else {
2061
+ console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u94FE\u63A5\u4E2D\u5FC5\u987B\u5305\u542B nodeId \u6216 pageId \u53C2\u6570`);
2062
+ console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
2063
+ process.exit(1);
2064
+ }
1933
2065
  } else {
1934
2066
  nodeId = options.nodeId;
1935
2067
  if (options.fileId) {
@@ -1943,16 +2075,32 @@ async function handleGetNodeForSpace(options) {
1943
2075
  });
1944
2076
  try {
1945
2077
  await client.connect();
1946
- const params = {
1947
- nodeId,
1948
- maxDepth: parseInt(options.maxDepth || "99", 10),
1949
- includeInvisible: options.includeInvisible || false
1950
- };
1951
- const data = await client.requestWithRetry(
1952
- "get_node_by_id" /* GET_NODE_BY_ID */,
1953
- params,
1954
- pageUrl
1955
- );
2078
+ const maxDepth = parseInt(options.maxDepth || "99", 10);
2079
+ const includeInvisible = options.includeInvisible || false;
2080
+ let data;
2081
+ if (isPageMode && pageId) {
2082
+ const params = {
2083
+ pageId,
2084
+ maxDepth,
2085
+ includeInvisible
2086
+ };
2087
+ data = await client.requestWithRetry(
2088
+ "get_page_by_id" /* GET_PAGE_BY_ID */,
2089
+ params,
2090
+ pageUrl
2091
+ );
2092
+ } else {
2093
+ const params = {
2094
+ nodeId,
2095
+ maxDepth,
2096
+ includeInvisible
2097
+ };
2098
+ data = await client.requestWithRetry(
2099
+ "get_node_by_id" /* GET_NODE_BY_ID */,
2100
+ params,
2101
+ pageUrl
2102
+ );
2103
+ }
1956
2104
  const spaceData = extractSpaceInfo(data);
1957
2105
  const outputPath = resolve7(options.output);
1958
2106
  const outputDir = dirname10(outputPath);
@@ -1966,9 +2114,15 @@ async function handleGetNodeForSpace(options) {
1966
2114
  console.log(`Link: ${options.link}`);
1967
2115
  console.log(`\u9875\u9762 URL: ${pageUrl}`);
1968
2116
  }
1969
- console.log(`\u8282\u70B9 ID: ${nodeId}`);
2117
+ if (isPageMode) {
2118
+ console.log(`\u9875\u9762 ID: ${pageId}`);
2119
+ console.log(`\u6A21\u5F0F: \u9875\u9762\u7A7A\u95F4\u4FE1\u606F`);
2120
+ } else {
2121
+ console.log(`\u8282\u70B9 ID: ${nodeId}`);
2122
+ console.log(`\u6A21\u5F0F: \u8282\u70B9\u7A7A\u95F4\u4FE1\u606F`);
2123
+ }
1970
2124
  console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
1971
- console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
2125
+ console.log(`\u8282\u70B9\u6DF1\u5EA6: ${maxDepth}`);
1972
2126
  console.log(`\u6570\u636E\u6A21\u5F0F: \u7A7A\u95F4\u4FE1\u606F (\u4EC5 id, name, x, y, width, height)`);
1973
2127
  } catch (error) {
1974
2128
  if (error instanceof MGError) {