@opentiny/webmcp-cli 0.0.4 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,23 @@ npm install -g .
17
17
 
18
18
  > **注意:** 在本地联调时,建议使用 `npm install -g .`,这会确保在你的 PATH 中生成有效的可执行文件(`webmcp-cli`)。
19
19
 
20
+ ## 测试
21
+
22
+ 统一使用 [Vitest](https://vitest.dev/)(与 `@opentiny/next-sdk` 一致):
23
+
24
+ ```bash
25
+ # 单元测试(无浏览器依赖,已接入根目录 pnpm test)
26
+ pnpm test
27
+
28
+ # 浏览器 E2E(需本机 Chrome/Edge,或设置 CHROME_PATH;默认 headless)
29
+ pnpm build:webmcp-cli
30
+ pnpm test:browser
31
+ ```
32
+
33
+ CI 中浏览器任务使用 [browser-actions/setup-chrome](https://github.com/browser-actions/setup-chrome) 安装 Chrome,并通过 `CHROME_PATH` + `WEBMCP_HEADLESS=1` 启动。
34
+
35
+ ---
36
+
20
37
  ## 核心架构特性
21
38
 
22
39
  - **后台浏览器驻留**:如果当前没有开启带有调试端口 (`9222`) 的 Chrome,CLI 会自动在后台拉起一个基于你本地 Profile 的独立 Chrome 实例。
@@ -76,7 +93,7 @@ webmcp-cli run <toolName> <argsJson> [-t tabid]
76
93
  ```
77
94
 
78
95
  **示例一:执行页面自带工具**
79
- 假设网页使用 `navigator.modelContext.registerTool` 注册了一个名为 `change-color` 的工具:
96
+ 假设网页使用 `document.modelContext.registerTool` 注册了一个名为 `change-color` 的工具:
80
97
  ```bash
81
98
  webmcp-cli run change-color '{"color": "#ff0000"}'
82
99
  ```
package/dist/bin.cjs CHANGED
@@ -223,11 +223,37 @@ var import_url2 = require("url");
223
223
  var import_meta = {};
224
224
  var __filename = (0, import_url2.fileURLToPath)(import_meta.url);
225
225
  var __dirname = import_path2.default.dirname(__filename);
226
- var CDP_PORT = 9222;
227
- var CDP_URL = `http://localhost:${CDP_PORT}`;
226
+ function getCdpPort() {
227
+ const raw = process.env.WEBMCP_CDP_PORT;
228
+ if (raw) {
229
+ const port = Number.parseInt(raw, 10);
230
+ if (Number.isFinite(port) && port > 0) return port;
231
+ }
232
+ return 9222;
233
+ }
228
234
  function getWorkspaceDir() {
229
235
  return process.env.WEBMCP_WORKSPACE || import_path2.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
230
236
  }
237
+ function shouldLaunchHeadless() {
238
+ if (process.env.WEBMCP_HEADLESS === "0") return false;
239
+ return process.env.WEBMCP_HEADLESS === "1" || process.env.CI === "true" || process.env.CI === "1";
240
+ }
241
+ function readActivePortFromProfile(userDataDir) {
242
+ try {
243
+ const content = import_fs2.default.readFileSync(import_path2.default.join(userDataDir, "DevToolsActivePort"), "utf-8");
244
+ const port = parseInt(content.split("\n")[0].trim(), 10);
245
+ return Number.isFinite(port) && port > 0 ? port : null;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+ function candidateCdpBaseUrls(port) {
251
+ return [
252
+ `http://127.0.0.1:${port}`,
253
+ `http://[::1]:${port}`,
254
+ `http://localhost:${port}`
255
+ ];
256
+ }
231
257
  function getLastTabIdFilePath() {
232
258
  return import_path2.default.join(getWorkspaceDir(), ".last-tab-id");
233
259
  }
@@ -321,6 +347,13 @@ async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
321
347
  }
322
348
  }
323
349
  function getDefaultBrowserPath() {
350
+ const envPath = process.env.CHROME_PATH || process.env.WEBMCP_CHROME_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
351
+ if (envPath && import_fs2.default.existsSync(envPath)) {
352
+ return { path: envPath, name: "Chrome" };
353
+ }
354
+ if (envPath) {
355
+ console.log(import_picocolors.default.yellow(`\u6307\u5B9A\u7684\u6D4F\u89C8\u5668\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u5C06\u56DE\u9000\u5230\u81EA\u52A8\u68C0\u6D4B: ${envPath}`));
356
+ }
324
357
  const platform = import_os.default.platform();
325
358
  if (platform === "darwin") {
326
359
  const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
@@ -364,16 +397,30 @@ async function startBrowserInBackground() {
364
397
  if (!browserInfo || !import_fs2.default.existsSync(browserInfo.path)) {
365
398
  throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6216 Edge \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
366
399
  }
367
- console.log(import_picocolors.default.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
368
- const userDataDir = process.env.WEBMCP_WORKSPACE || import_path2.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
400
+ const cdpPort = getCdpPort();
401
+ console.log(import_picocolors.default.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${cdpPort})...`));
402
+ const userDataDir = getWorkspaceDir();
403
+ if (!import_fs2.default.existsSync(userDataDir)) {
404
+ import_fs2.default.mkdirSync(userDataDir, { recursive: true });
405
+ }
406
+ try {
407
+ import_fs2.default.unlinkSync(import_path2.default.join(userDataDir, "DevToolsActivePort"));
408
+ } catch {
409
+ }
369
410
  const launchArgs = [
370
- `--remote-debugging-port=${CDP_PORT}`,
411
+ `--remote-debugging-port=${cdpPort}`,
371
412
  `--user-data-dir=${userDataDir}`,
372
413
  "--no-first-run",
373
414
  "--no-default-browser-check"
374
415
  ];
375
- if (import_os.default.platform() === "win32") {
376
- launchArgs.push("--remote-debugging-address=127.0.0.1");
416
+ if (shouldLaunchHeadless()) {
417
+ launchArgs.push(
418
+ "--headless=new",
419
+ "--no-sandbox",
420
+ "--disable-setuid-sandbox",
421
+ "--disable-gpu",
422
+ "--disable-dev-shm-usage"
423
+ );
377
424
  }
378
425
  const child = (0, import_child_process.spawn)(
379
426
  browserInfo.path,
@@ -384,15 +431,22 @@ async function startBrowserInBackground() {
384
431
  }
385
432
  );
386
433
  child.unref();
387
- const pollUrls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
388
- for (let i = 0; i < 30; i++) {
389
- for (const url of pollUrls) {
390
- try {
391
- await httpGet(url, 1e3);
392
- console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
393
- await new Promise((resolve3) => setTimeout(resolve3, 500));
394
- return;
395
- } catch {
434
+ for (let i = 0; i < 40; i++) {
435
+ const ports = /* @__PURE__ */ new Set([cdpPort]);
436
+ const activePort = readActivePortFromProfile(userDataDir);
437
+ if (activePort) ports.add(activePort);
438
+ for (const port of ports) {
439
+ for (const baseUrl of candidateCdpBaseUrls(port)) {
440
+ try {
441
+ await httpGet(`${baseUrl}/json/version`, 1e3);
442
+ if (port !== cdpPort) {
443
+ console.log(import_picocolors.default.yellow(`\u68C0\u6D4B\u5230\u7AEF\u53E3 ${cdpPort} \u5DF2\u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\uFF0C${browserInfo.name} \u5DF2\u81EA\u52A8\u5207\u6362\u5230\u7AEF\u53E3 ${port}\u3002`));
444
+ }
445
+ console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\uFF08${baseUrl}\uFF09\u3002`));
446
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
447
+ return baseUrl;
448
+ } catch {
449
+ }
396
450
  }
397
451
  }
398
452
  await new Promise((resolve3) => setTimeout(resolve3, 500));
@@ -400,13 +454,16 @@ async function startBrowserInBackground() {
400
454
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
401
455
  }
402
456
  async function connectBrowser() {
457
+ const userDataDir = getWorkspaceDir();
458
+ const cdpPort = getCdpPort();
459
+ const recordedPort = readActivePortFromProfile(userDataDir);
403
460
  const versionAddresses = [
404
- `http://127.0.0.1:${CDP_PORT}/json/version`,
405
- `http://localhost:${CDP_PORT}/json/version`
461
+ ...recordedPort && recordedPort !== cdpPort ? candidateCdpBaseUrls(recordedPort).map((u) => `${u}/json/version`) : [],
462
+ ...candidateCdpBaseUrls(cdpPort).map((u) => `${u}/json/version`)
406
463
  ];
407
464
  const existingUrl = await findAvailableCdpUrl(versionAddresses, 2);
408
465
  if (existingUrl) {
409
- console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${existingUrl}...`));
466
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${cdpPort} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${existingUrl}...`));
410
467
  try {
411
468
  const browser = await connectPuppeteer(existingUrl);
412
469
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${existingUrl}`));
@@ -418,25 +475,22 @@ async function connectBrowser() {
418
475
  );
419
476
  }
420
477
  }
421
- console.log(import_picocolors.default.yellow(`connectBrowser: \u7AEF\u53E3 ${CDP_PORT} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
478
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u7AEF\u53E3 ${cdpPort} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
479
+ let launchedUrl;
422
480
  try {
423
- await startBrowserInBackground();
481
+ launchedUrl = await startBrowserInBackground();
424
482
  } catch (launchError) {
425
483
  const msg = launchError instanceof Error ? launchError.message : String(launchError);
426
484
  console.error(import_picocolors.default.red(`\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
427
485
  throw new Error("Browser launch failed.");
428
486
  }
429
- const launchedUrl = await findAvailableCdpUrl(versionAddresses, 5);
430
- if (!launchedUrl) {
431
- throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
432
- }
433
487
  try {
434
488
  console.log(import_picocolors.default.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${launchedUrl}...`));
435
489
  const browser = await connectPuppeteer(launchedUrl);
436
490
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${launchedUrl}`));
437
491
  return browser;
438
492
  } catch {
439
- throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
493
+ throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08${launchedUrl}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
440
494
  }
441
495
  }
442
496
  async function getPageTargetId(page) {
@@ -516,10 +570,11 @@ async function getTargetPage(browser, tabid) {
516
570
  }
517
571
  if (!targetPage) {
518
572
  try {
519
- const urls = [
520
- `http://localhost:${CDP_PORT}/json/list`,
521
- `http://127.0.0.1:${CDP_PORT}/json/list`
522
- ];
573
+ const workspaceDir = getWorkspaceDir();
574
+ const cdpPort = getCdpPort();
575
+ const recordedPort = readActivePortFromProfile(workspaceDir);
576
+ const ports = recordedPort && recordedPort !== cdpPort ? [recordedPort, cdpPort] : [cdpPort];
577
+ const urls = ports.flatMap((port) => candidateCdpBaseUrls(port).map((u) => `${u}/json/list`));
523
578
  let activeTargetId = null;
524
579
  for (const url of urls) {
525
580
  try {
@@ -556,10 +611,10 @@ async function getTargetPage(browser, tabid) {
556
611
  return targetPage;
557
612
  }
558
613
  async function injectIntoPage(page) {
559
- await injectWebMCPPolyfillAndTools(page, true);
614
+ await injectWebMCPPolyfillAndTools(page);
560
615
  }
561
- async function injectWebMCPPolyfillAndTools(page, force = false) {
562
- const polyfillReady = !force && await page.evaluate(() => {
616
+ async function injectWebMCPPolyfillAndTools(page) {
617
+ const polyfillReady = await page.evaluate(() => {
563
618
  return !!window.__webmcpcli_init;
564
619
  }).catch(() => false);
565
620
  if (!polyfillReady) {
@@ -649,11 +704,19 @@ async function stateCommand({ tabid }) {
649
704
  const state = await page.evaluate(async () => {
650
705
  const url = document.URL;
651
706
  const title = document.title;
652
- const mcp = navigator.modelContextTesting || navigator.modelContext;
707
+ const mcp = document.modelContext || navigator.modelContext;
653
708
  let webmcpTools = [];
654
- if (mcp && typeof mcp.listTools === "function") {
655
- const toolsResult = await mcp.listTools();
656
- webmcpTools = toolsResult?.tools || toolsResult || [];
709
+ if (mcp && typeof mcp.getTools === "function") {
710
+ try {
711
+ const toolsResult = await mcp.getTools();
712
+ const list = toolsResult?.tools || toolsResult || [];
713
+ webmcpTools = list.filter((t) => t && typeof t.name === "string").map((t) => ({
714
+ name: t.name,
715
+ description: typeof t.description === "string" ? t.description.slice(0, 200) : void 0
716
+ }));
717
+ } catch {
718
+ webmcpTools = [];
719
+ }
657
720
  }
658
721
  return { url, title, webmcpTools };
659
722
  });
@@ -695,15 +758,19 @@ async function runCommand({
695
758
  try {
696
759
  const page = await getTargetPage(browser, tabid);
697
760
  const urlBefore = page.url();
761
+ await injectIntoPage(page);
698
762
  const cleanedArgs = argsJson.trim();
699
763
  let result;
700
764
  try {
701
765
  result = await page.evaluate(async (name, inputString) => {
702
- const mcp = navigator.modelContextTesting || navigator.modelContext;
766
+ const mcp = document.modelContext || document.modelContext || document.modelContext;
703
767
  if (!mcp || typeof mcp.executeTool !== "function") {
704
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
768
+ throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
705
769
  }
706
- let res = await mcp.executeTool(name, inputString);
770
+ const tools = await mcp.getTools();
771
+ const toolObj = tools.find((t) => t.name === name);
772
+ if (!toolObj) throw new Error(`Tool ${name} not found`);
773
+ let res = await mcp.executeTool(toolObj, inputString);
707
774
  if (typeof res === "string") {
708
775
  try {
709
776
  res = JSON.parse(res);
@@ -880,7 +947,7 @@ async function tabsForwardCommand(tabid) {
880
947
  // package.json
881
948
  var package_default = {
882
949
  name: "@opentiny/webmcp-cli",
883
- version: "0.0.4",
950
+ version: "0.0.8",
884
951
  type: "module",
885
952
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
886
953
  main: "dist/index.js",
@@ -898,7 +965,10 @@ var package_default = {
898
965
  build: "tsup && pnpm run build:inject",
899
966
  prepublishOnly: "pnpm run build",
900
967
  "link:global": "npm install -g .",
901
- start: "node dist/bin.js"
968
+ start: "node dist/bin.js",
969
+ test: "vitest run",
970
+ "test:watch": "vitest",
971
+ "test:browser": "vitest run --config vitest.e2e.config.ts"
902
972
  },
903
973
  keywords: [
904
974
  "chrome",
@@ -920,6 +990,7 @@ var package_default = {
920
990
  clipboardy: "^5.3.1",
921
991
  commander: "^11.1.0",
922
992
  json5: "^2.2.3",
993
+ marked: "^15.0.7",
923
994
  picocolors: "^1.0.0",
924
995
  "puppeteer-core": "^22.0.0"
925
996
  },
@@ -928,7 +999,8 @@ var package_default = {
928
999
  esbuild: "^0.25.0",
929
1000
  tsup: "^8.0.2",
930
1001
  tsx: "^4.0.0",
931
- typescript: "^5.3.3"
1002
+ typescript: "^5.3.3",
1003
+ vitest: "^4.1.10"
932
1004
  }
933
1005
  };
934
1006
 
@@ -938,6 +1010,30 @@ function parseTabId(id) {
938
1010
  if (!id) return void 0;
939
1011
  return id;
940
1012
  }
1013
+ function tryParseBrowserStateText(text) {
1014
+ const tryParse = (jsonStr) => {
1015
+ try {
1016
+ const parsed = JSON.parse(jsonStr);
1017
+ if (parsed && typeof parsed.content === "string") {
1018
+ return parsed;
1019
+ }
1020
+ } catch {
1021
+ }
1022
+ return null;
1023
+ };
1024
+ const direct = tryParse(text);
1025
+ if (direct) return direct;
1026
+ if (text.startsWith("\u6D4F\u89C8\u5668\u72B6\u6001: ")) {
1027
+ let jsonStr = text.substring("\u6D4F\u89C8\u5668\u72B6\u6001: ".length);
1028
+ const firstNewline = jsonStr.indexOf("\n");
1029
+ if (firstNewline !== -1) {
1030
+ const maybeJson = jsonStr.substring(0, firstNewline);
1031
+ if (tryParse(maybeJson)) jsonStr = maybeJson;
1032
+ }
1033
+ return tryParse(jsonStr);
1034
+ }
1035
+ return null;
1036
+ }
941
1037
  function cleanOldLogs(baseDir, logDir) {
942
1038
  try {
943
1039
  const oldLogFile = import_path3.default.join(baseDir, "webmcp-cli.log");
@@ -980,6 +1076,51 @@ function cleanOldLogs(baseDir, logDir) {
980
1076
  } catch (e) {
981
1077
  }
982
1078
  }
1079
+ function formatLogResult(result) {
1080
+ if (result === void 0 || result === null) {
1081
+ return "null";
1082
+ }
1083
+ try {
1084
+ if (typeof result !== "object") {
1085
+ return String(result);
1086
+ }
1087
+ const clone = JSON.parse(JSON.stringify(result));
1088
+ let a11yTreeStr = "";
1089
+ let searchResultStr = "";
1090
+ if (clone && Array.isArray(clone.content) && clone.content.length > 0 && clone.content[0] && typeof clone.content[0].text === "string") {
1091
+ const text = clone.content[0].text;
1092
+ const browserState = tryParseBrowserStateText(text);
1093
+ if (browserState) {
1094
+ a11yTreeStr = browserState.content;
1095
+ browserState.content = "[Formatted A11y Tree - See details below]";
1096
+ clone.content[0].text = JSON.stringify(browserState, null, 2);
1097
+ } else if (text.includes("A11y Tree \u641C\u7D22\u7ED3\u679C") || text.includes("\u65E0\u969C\u788D\u6811\u641C\u7D22\u7ED3\u679C") || text.includes("\u65E0\u969C\u788D\u6811\u641C\u7D22") || text.includes("\u547D\u4E2D\u884C") || text.includes("context:")) {
1098
+ searchResultStr = text;
1099
+ clone.content[0].text = "[Search A11y Tree Result - See details below]";
1100
+ }
1101
+ }
1102
+ let out = JSON.stringify(clone, null, 2);
1103
+ if (a11yTreeStr) {
1104
+ out += `
1105
+
1106
+ [FORMATTED A11Y TREE]:
1107
+ ${a11yTreeStr}`;
1108
+ }
1109
+ if (searchResultStr) {
1110
+ out += `
1111
+
1112
+ [FORMATTED SEARCH RESULT]:
1113
+ ${searchResultStr}`;
1114
+ }
1115
+ return out;
1116
+ } catch (e) {
1117
+ try {
1118
+ return JSON.stringify(result, null, 2);
1119
+ } catch {
1120
+ return String(result);
1121
+ }
1122
+ }
1123
+ }
983
1124
  function writeLog(commandName, args, result, error) {
984
1125
  try {
985
1126
  const baseDir = process.env.WEBMCP_WORKSPACE || import_path3.default.join(import_os2.default.homedir(), ".webmcp_chrome_profile");
@@ -1005,15 +1146,8 @@ ${error instanceof Error ? error.stack : String(error)}
1005
1146
  } else if (result) {
1006
1147
  logText += `RESULT:
1007
1148
  `;
1008
- try {
1009
- if (result && typeof result === "object") {
1010
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1011
- } else {
1012
- logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1013
- }
1014
- } catch {
1015
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1016
- }
1149
+ const formattedResult = formatLogResult(result);
1150
+ logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1017
1151
  } else {
1018
1152
  logText += `RESULT: null
1019
1153
  `;
@@ -1043,7 +1177,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
1043
1177
  try {
1044
1178
  const result = await stateCommand(args);
1045
1179
  writeLog("state", args, result);
1046
- console.log(JSON.stringify(result, null, 2));
1180
+ console.log(formatLogResult(result));
1047
1181
  } catch (error) {
1048
1182
  handleCommandError(error, "state", args);
1049
1183
  }
@@ -1077,7 +1211,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
1077
1211
  tabid: parseTabId(options.tabid)
1078
1212
  });
1079
1213
  writeLog("run", { ...rawArgs, finalArgsJson }, result);
1080
- console.log(JSON.stringify(result, null, 2));
1214
+ console.log(formatLogResult(result));
1081
1215
  } catch (error) {
1082
1216
  handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
1083
1217
  }