@opentiny/webmcp-cli 0.0.1-alpha.3 → 0.0.1-alpha.5

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/bin.js CHANGED
@@ -1,14 +1,10 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
2
 
9
3
  // src/bin.ts
10
4
  import { Command } from "commander";
11
5
  import pc2 from "picocolors";
6
+ import { readFileSync } from "fs";
7
+ import { resolve } from "path";
12
8
 
13
9
  // src/browser.ts
14
10
  import puppeteer from "puppeteer-core";
@@ -16,20 +12,34 @@ import pc from "picocolors";
16
12
  import fs from "fs";
17
13
  import os from "os";
18
14
  import path from "path";
15
+ import http from "http";
19
16
  import { spawn } from "child_process";
20
17
  import { fileURLToPath } from "url";
21
18
  var __filename = fileURLToPath(import.meta.url);
22
19
  var __dirname = path.dirname(__filename);
23
20
  var CDP_PORT = 9222;
24
21
  var CDP_URL = `http://localhost:${CDP_PORT}`;
25
- async function fetchWithTimeout(url, timeoutMs = 1500) {
26
- const controller = new AbortController();
27
- const id = setTimeout(() => controller.abort(), timeoutMs);
28
- try {
29
- return await fetch(url, { signal: controller.signal });
30
- } finally {
31
- clearTimeout(id);
32
- }
22
+ function httpGet(url, timeoutMs = 1500) {
23
+ return new Promise((resolve2, reject) => {
24
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
25
+ let data = "";
26
+ res.on("data", (chunk) => {
27
+ data += chunk.toString();
28
+ });
29
+ res.on("end", () => {
30
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
31
+ resolve2(data);
32
+ } else {
33
+ reject(new Error(`HTTP ${res.statusCode}`));
34
+ }
35
+ });
36
+ });
37
+ req.on("error", reject);
38
+ req.on("timeout", () => {
39
+ req.destroy();
40
+ reject(new Error("request timeout"));
41
+ });
42
+ });
33
43
  }
34
44
  function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation timed out") {
35
45
  let timeoutId;
@@ -45,8 +55,8 @@ function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation time
45
55
  async function checkCdpReady(url, retries = 3) {
46
56
  for (let i = 0; i < retries; i++) {
47
57
  try {
48
- const res = await fetchWithTimeout(url, 1500);
49
- if (res.ok) return true;
58
+ await httpGet(url, 1500);
59
+ return true;
50
60
  } catch {
51
61
  }
52
62
  if (i < retries - 1) {
@@ -55,45 +65,6 @@ async function checkCdpReady(url, retries = 3) {
55
65
  }
56
66
  return false;
57
67
  }
58
- async function killProcessOnPortIfZombie(port) {
59
- const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1);
60
- if (isResponding) {
61
- console.log(pc.green(`connectBrowser: \u7AEF\u53E3 ${port} \u4E0A\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\u4ECD\u5728\u6B63\u5E38\u54CD\u5E94\uFF0C\u8DF3\u8FC7\u5F3A\u6740\uFF0C\u5C1D\u8BD5\u76F4\u63A5\u63A5\u7BA1\u3002`));
62
- return;
63
- }
64
- try {
65
- const platform = os.platform();
66
- const { execSync } = __require("child_process");
67
- if (platform === "darwin" || platform === "linux") {
68
- console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
69
- const pids = execSync(`lsof -t -i :${port}`).toString().trim();
70
- if (pids) {
71
- console.log(pc.yellow(`\u53D1\u73B0\u50F5\u5C38 PID: ${pids.split("\n").join(", ")}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
72
- execSync(`kill -9 ${pids.split("\n").join(" ")}`);
73
- console.log(pc.green(`\u6210\u529F\u6E05\u7406\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B`));
74
- }
75
- } else if (platform === "win32") {
76
- console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406 Windows \u4E0A\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
77
- const output = execSync(`netstat -ano | findstr :${port}`).toString().trim();
78
- if (output) {
79
- const lines = output.split("\n");
80
- const pids = /* @__PURE__ */ new Set();
81
- lines.forEach((line) => {
82
- const parts = line.trim().split(/\s+/);
83
- const pid = parts[parts.length - 1];
84
- if (pid && /^\d+$/.test(pid) && pid !== "0") {
85
- pids.add(pid);
86
- }
87
- });
88
- pids.forEach((pid) => {
89
- console.log(pc.yellow(`\u53D1\u73B0 Windows \u6B8B\u7559\u50F5\u5C38 PID: ${pid}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
90
- execSync(`taskkill /F /PID ${pid}`);
91
- });
92
- }
93
- }
94
- } catch (e) {
95
- }
96
- }
97
68
  function getDefaultBrowserPath() {
98
69
  const platform = os.platform();
99
70
  if (platform === "darwin") {
@@ -156,20 +127,18 @@ async function startBrowserInBackground() {
156
127
  child.unref();
157
128
  for (let i = 0; i < 20; i++) {
158
129
  try {
159
- const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
130
+ const urls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
160
131
  for (const url of urls) {
161
132
  try {
162
- const response = await fetchWithTimeout(url, 1e3);
163
- if (response.ok) {
164
- console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
165
- return;
166
- }
133
+ await httpGet(url, 1e3);
134
+ console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
135
+ return;
167
136
  } catch (err) {
168
137
  }
169
138
  }
170
139
  } catch (e) {
171
140
  }
172
- await new Promise((resolve) => setTimeout(resolve, 500));
141
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
173
142
  }
174
143
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
175
144
  }
@@ -190,90 +159,51 @@ async function connectBrowser() {
190
159
  return false;
191
160
  }
192
161
  };
193
- try {
194
- const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
195
- if (!is127Ready) {
196
- throw new Error("127.0.0.1 CDP port not responding");
162
+ const portAddresses = [
163
+ `http://127.0.0.1:${CDP_PORT}/json/version`,
164
+ `http://localhost:${CDP_PORT}/json/version`
165
+ ];
166
+ for (const addr of portAddresses) {
167
+ const isReady = await checkCdpReady(addr, 2);
168
+ if (!isReady) continue;
169
+ const browserURL = addr.replace("/json/version", "");
170
+ try {
171
+ console.log(pc.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
172
+ const browser2 = await promiseWithTimeout(
173
+ puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
174
+ 1e4,
175
+ `puppeteer.connect to ${browserURL} timed out`
176
+ );
177
+ console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
178
+ return browser2;
179
+ } catch {
197
180
  }
198
- console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
199
- const browser2 = await promiseWithTimeout(
200
- puppeteer.connect({
201
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
202
- defaultViewport: null,
203
- targetFilter
204
- }),
205
- 1e4,
206
- "puppeteer.connect to 127.0.0.1 timed out"
207
- );
208
- console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
209
- return browser2;
210
- } catch (error) {
181
+ }
182
+ console.log(pc.yellow(`connectBrowser: \u7AEF\u53E3 ${CDP_PORT} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
183
+ try {
184
+ await startBrowserInBackground();
185
+ } catch (launchError) {
186
+ const msg = launchError instanceof Error ? launchError.message : String(launchError);
187
+ console.error(pc.red(`\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
188
+ throw new Error("Browser launch failed.");
189
+ }
190
+ for (const addr of portAddresses) {
191
+ const isReady = await checkCdpReady(addr, 3);
192
+ if (!isReady) continue;
193
+ const browserURL = addr.replace("/json/version", "");
211
194
  try {
212
- const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
213
- if (!isLocalhostReady) {
214
- throw new Error("localhost CDP port not responding");
215
- }
216
- console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
195
+ console.log(pc.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
217
196
  const browser2 = await promiseWithTimeout(
218
- puppeteer.connect({
219
- browserURL: `http://localhost:${CDP_PORT}`,
220
- defaultViewport: null,
221
- targetFilter
222
- }),
197
+ puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
223
198
  1e4,
224
- "puppeteer.connect to localhost timed out"
199
+ `puppeteer.connect to ${browserURL} after launch timed out`
225
200
  );
226
- console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
201
+ console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
227
202
  return browser2;
228
- } catch (error2) {
229
- console.log(pc.yellow(`connectBrowser: \u8FDE\u63A5\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u5524\u8D77\u6D4F\u89C8\u5668\u3002\u9519\u8BEF\u539F\u56E0: ${error2 instanceof Error ? error2.message : String(error2)}`));
230
- try {
231
- await killProcessOnPortIfZombie(CDP_PORT);
232
- await startBrowserInBackground();
233
- try {
234
- const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
235
- if (!is127Ready) {
236
- throw new Error("127.0.0.1 CDP port not responding after launch");
237
- }
238
- console.log(pc.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
239
- const browser2 = await promiseWithTimeout(
240
- puppeteer.connect({
241
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
242
- defaultViewport: null,
243
- targetFilter
244
- }),
245
- 1e4,
246
- "puppeteer.connect to 127.0.0.1 after launch timed out"
247
- );
248
- console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
249
- return browser2;
250
- } catch (e) {
251
- const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
252
- if (!isLocalhostReady) {
253
- throw new Error("localhost CDP port not responding after launch");
254
- }
255
- console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
256
- const browser2 = await promiseWithTimeout(
257
- puppeteer.connect({
258
- browserURL: `http://localhost:${CDP_PORT}`,
259
- defaultViewport: null,
260
- targetFilter
261
- }),
262
- 1e4,
263
- "puppeteer.connect to localhost after launch timed out"
264
- );
265
- console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
266
- return browser2;
267
- }
268
- } catch (launchError) {
269
- const msg = launchError instanceof Error ? launchError.message : String(launchError);
270
- console.error(pc.red(`\u65E0\u6CD5\u8FDE\u63A5\u6216\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
271
- console.error(pc.yellow(`\u{1F4A1} \u63D0\u793A\uFF1A\u7531\u4E8E\u6211\u4EEC\u8981\u4F7F\u7528\u4F60\u65E5\u5E38\u7684\u9ED8\u8BA4\u6D4F\u89C8\u5668\uFF08\u5305\u542B\u4F60\u7684\u4E66\u7B7E\u548C\u767B\u5F55\u6001\uFF09\uFF0C\u5982\u679C\u4F60\u7684 Chrome/Edge \u76EE\u524D\u6B63\u5904\u4E8E\u6253\u5F00\u72B6\u6001\uFF0C\u5B83\u4F1A\u62D2\u7EDD\u4F7F\u7528\u5E26\u6709\u8C03\u8BD5\u7AEF\u53E3\u7684\u65B0\u53C2\u6570\u542F\u52A8\u3002`));
272
- console.error(pc.yellow(`\u{1F449} \u89E3\u51B3\u529E\u6CD5\uFF1A\u8BF7\u5148\u5B8C\u5168\u9000\u51FA\u5F53\u524D\u7684 Chrome \u6216 Edge \u6D4F\u89C8\u5668\uFF08\u5728 Mac \u4E0A\u6309 Cmd+Q\uFF0CWindows \u4E0A\u53F3\u952E\u4EFB\u52A1\u680F\u56FE\u6807\u9000\u51FA\uFF09\uFF0C\u7136\u540E\u518D\u91CD\u65B0\u8FD0\u884C\u547D\u4EE4\u3002`));
273
- throw new Error("Browser connection failed.");
274
- }
203
+ } catch {
275
204
  }
276
205
  }
206
+ 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`);
277
207
  }
278
208
  async function getPageTargetId(page2) {
279
209
  const session = await page2.target().createCDPSession();
@@ -351,14 +281,12 @@ async function getTargetPage(browser2, tabid2) {
351
281
  let activeTargetId = null;
352
282
  for (const url of urls) {
353
283
  try {
354
- const res = await fetchWithTimeout(url, 1e3);
355
- if (res.ok) {
356
- const targetsData = await res.json();
357
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
358
- if (active) {
359
- activeTargetId = active.id;
360
- break;
361
- }
284
+ const body = await httpGet(url, 1e3);
285
+ const targetsData = JSON.parse(body);
286
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
287
+ if (active) {
288
+ activeTargetId = active.id;
289
+ break;
362
290
  }
363
291
  } catch {
364
292
  }
@@ -404,7 +332,7 @@ async function injectWebMCPPolyfillAndTools(page2, force = false) {
404
332
  const msg = err instanceof Error ? err.message : String(err);
405
333
  throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
406
334
  }
407
- await new Promise((resolve) => setTimeout(resolve, 300));
335
+ await new Promise((resolve2) => setTimeout(resolve2, 300));
408
336
  }
409
337
  await injectDomainTools(page2);
410
338
  }
@@ -492,7 +420,7 @@ async function stateCommand({ tabid: tabid2 }) {
492
420
  if (pUrl.startsWith("devtools://")) return null;
493
421
  const pTitle = await Promise.race([
494
422
  p.title().catch(() => "Unknown"),
495
- new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
423
+ new Promise((resolve2) => setTimeout(() => resolve2("Unknown"), 500))
496
424
  ]);
497
425
  return {
498
426
  // 使用真实的 Chrome target ID,而非数组下标
@@ -512,6 +440,7 @@ async function stateCommand({ tabid: tabid2 }) {
512
440
  }
513
441
 
514
442
  // src/commands/run.ts
443
+ import JSON5 from "json5";
515
444
  async function runCommand({
516
445
  toolName,
517
446
  argsJson,
@@ -520,10 +449,15 @@ async function runCommand({
520
449
  const browser2 = await connectBrowser();
521
450
  try {
522
451
  const page2 = await getTargetPage(browser2, tabid2);
452
+ let cleanedArgs = argsJson.trim();
453
+ if (cleanedArgs.startsWith("'") && cleanedArgs.endsWith("'")) {
454
+ cleanedArgs = cleanedArgs.slice(1, -1).trim();
455
+ }
523
456
  try {
524
- JSON.parse(argsJson);
457
+ const obj = JSON5.parse(cleanedArgs);
458
+ cleanedArgs = JSON.stringify(obj);
525
459
  } catch (e) {
526
- throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON: ${e.message}`);
460
+ throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${e.message}`);
527
461
  }
528
462
  const result2 = await page2.evaluate(async (name, inputString) => {
529
463
  const mcp = navigator.modelContextTesting || navigator.modelContext;
@@ -537,8 +471,7 @@ async function runCommand({
537
471
  } catch (e) {
538
472
  }
539
473
  }
540
- return res;
541
- }, toolName, argsJson);
474
+ }, toolName, cleanedArgs);
542
475
  return result2;
543
476
  } finally {
544
477
  await browser2.disconnect();
@@ -598,7 +531,7 @@ async function tabsOpenCommand(url) {
598
531
  url = normalizeUrl(url);
599
532
  await page2.goto(url, { waitUntil: "domcontentloaded" });
600
533
  await injectIntoPage(page2);
601
- await new Promise((resolve) => setTimeout(resolve, 500));
534
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
602
535
  return {
603
536
  success: true,
604
537
  tabid: await getPageTargetId(page2),
@@ -699,7 +632,7 @@ async function tabsForwardCommand(tabid2) {
699
632
  // package.json
700
633
  var package_default = {
701
634
  name: "@opentiny/webmcp-cli",
702
- version: "0.0.1-alpha.3",
635
+ version: "0.0.1-alpha.5",
703
636
  type: "module",
704
637
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
705
638
  main: "dist/index.js",
@@ -738,6 +671,7 @@ var package_default = {
738
671
  "@page-agent/page-controller": "catalog:",
739
672
  clipboardy: "^5.3.1",
740
673
  commander: "^11.1.0",
674
+ json5: "^2.2.3",
741
675
  picocolors: "^1.0.0",
742
676
  "puppeteer-core": "^22.0.0"
743
677
  },
@@ -752,6 +686,13 @@ var package_default = {
752
686
 
753
687
  // src/bin.ts
754
688
  var program = new Command();
689
+ function cleanJsonString(str) {
690
+ let cleaned = str.trim();
691
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
692
+ cleaned = cleaned.slice(1, -1).trim();
693
+ }
694
+ return cleaned;
695
+ }
755
696
  function parseTabId(id) {
756
697
  if (!id) return void 0;
757
698
  return id;
@@ -761,6 +702,19 @@ function handleCommandError(error, commandName) {
761
702
  console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
762
703
  process.exit(1);
763
704
  }
705
+ function expandFileRefs(argsJson) {
706
+ return argsJson.replace(/"@(base64)?file:([^"]+)"/g, (_match, base64Flag, filePath) => {
707
+ const absPath = resolve(process.cwd(), filePath);
708
+ let content;
709
+ try {
710
+ content = readFileSync(absPath, "utf-8");
711
+ } catch (e) {
712
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}": ${e.message}`);
713
+ }
714
+ const value = base64Flag ? Buffer.from(content, "utf-8").toString("base64") : content;
715
+ return JSON.stringify(value);
716
+ });
717
+ }
764
718
  program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version(package_default.version).option("-w, --workspace <path>", "\u6307\u5B9A\u81EA\u5B9A\u4E49\u7684\u6D4F\u89C8\u5668\u5DE5\u4F5C\u7A7A\u95F4\uFF08\u7528\u6237\u914D\u7F6E\u76EE\u5F55\uFF09\u8DEF\u5F84").hook("preAction", (thisCommand) => {
765
719
  const opts = thisCommand.opts();
766
720
  if (opts.workspace) {
@@ -775,11 +729,54 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
775
729
  handleCommandError(error, "state");
776
730
  }
777
731
  });
778
- program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u9875\u7B7E\u8C03\u7528\u6307\u5B9A\u7684 WebMCP \u5DE5\u5177\u6267\u884C\u64CD\u4F5C").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (toolName, argsJson, options) => {
732
+ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u9875\u7B7E\u8C03\u7528\u6307\u5B9A\u7684 WebMCP \u5DE5\u5177\u6267\u884C\u64CD\u4F5C").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").option(
733
+ "-f, --file <path>",
734
+ `\u4ECE\u6307\u5B9A .json \u6587\u4EF6\u8BFB\u53D6\u6574\u4E2A\u53C2\u6570\uFF08\u6587\u4EF6\u5185\u5BB9\u5373\u4E3A\u53C2\u6570 JSON\uFF09\u3002
735
+ \u5982\u9700\u5728\u53C2\u6570\u4E2D\u5185\u8054\u5F15\u7528\u6587\u4EF6\uFF0C\u4F7F\u7528\u5360\u4F4D\u7B26\u8BED\u6CD5\uFF1A
736
+ @file:<path> \u8BFB\u53D6\u6587\u4EF6\u539F\u59CB\u6587\u672C
737
+ @base64file:<path> \u8BFB\u53D6\u6587\u4EF6\u5E76 Base64 \u7F16\u7801
738
+ \u793A\u4F8B\uFF1Awebmcp-cli run mytool '{"content":"@base64file:./doc.md"}'`
739
+ ).action(async (toolName, args, options) => {
779
740
  try {
741
+ let finalArgsJson = "";
742
+ if (options.file) {
743
+ const filePath = resolve(process.cwd(), options.file);
744
+ try {
745
+ finalArgsJson = readFileSync(filePath, "utf-8").trim();
746
+ } catch (e) {
747
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 "${filePath}": ${e.message}`);
748
+ }
749
+ } else if (args && args.length > 0) {
750
+ const firstArg = args[0].trim();
751
+ const cleanedFirstArg = cleanJsonString(firstArg);
752
+ if (cleanedFirstArg.startsWith("{") && cleanedFirstArg.endsWith("}")) {
753
+ finalArgsJson = args.map(cleanJsonString).join(" ");
754
+ } else {
755
+ const obj = {};
756
+ for (const pair of args) {
757
+ const index = pair.indexOf("=");
758
+ if (index === -1) {
759
+ throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u5FC5\u987B\u662F JSON \u5B57\u7B26\u4E32\u6216 key=value \u683C\u5F0F\u3002`);
760
+ }
761
+ const key = pair.substring(0, index).trim();
762
+ let val = pair.substring(index + 1).trim();
763
+ val = cleanJsonString(val);
764
+ try {
765
+ obj[key] = JSON.parse(val);
766
+ } catch {
767
+ obj[key] = val;
768
+ }
769
+ }
770
+ finalArgsJson = JSON.stringify(obj);
771
+ }
772
+ }
773
+ if (!finalArgsJson) {
774
+ throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
775
+ }
776
+ finalArgsJson = expandFileRefs(finalArgsJson);
780
777
  const result2 = await runCommand({
781
778
  toolName,
782
- argsJson,
779
+ argsJson: finalArgsJson,
783
780
  tabid: parseTabId(options.tabid)
784
781
  });
785
782
  console.log(JSON.stringify(result2, null, 2));