@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.cjs CHANGED
@@ -26,6 +26,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/bin.ts
27
27
  var import_commander = require("commander");
28
28
  var import_picocolors2 = __toESM(require("picocolors"), 1);
29
+ var import_fs2 = require("fs");
30
+ var import_path2 = require("path");
29
31
 
30
32
  // src/browser.ts
31
33
  var import_puppeteer_core = __toESM(require("puppeteer-core"), 1);
@@ -33,6 +35,7 @@ var import_picocolors = __toESM(require("picocolors"), 1);
33
35
  var import_fs = __toESM(require("fs"), 1);
34
36
  var import_os = __toESM(require("os"), 1);
35
37
  var import_path = __toESM(require("path"), 1);
38
+ var import_http = __toESM(require("http"), 1);
36
39
  var import_child_process = require("child_process");
37
40
  var import_url = require("url");
38
41
  var import_meta = {};
@@ -40,14 +43,27 @@ var __filename = (0, import_url.fileURLToPath)(import_meta.url);
40
43
  var __dirname = import_path.default.dirname(__filename);
41
44
  var CDP_PORT = 9222;
42
45
  var CDP_URL = `http://localhost:${CDP_PORT}`;
43
- async function fetchWithTimeout(url, timeoutMs = 1500) {
44
- const controller = new AbortController();
45
- const id = setTimeout(() => controller.abort(), timeoutMs);
46
- try {
47
- return await fetch(url, { signal: controller.signal });
48
- } finally {
49
- clearTimeout(id);
50
- }
46
+ function httpGet(url, timeoutMs = 1500) {
47
+ return new Promise((resolve2, reject) => {
48
+ const req = import_http.default.get(url, { timeout: timeoutMs }, (res) => {
49
+ let data = "";
50
+ res.on("data", (chunk) => {
51
+ data += chunk.toString();
52
+ });
53
+ res.on("end", () => {
54
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
55
+ resolve2(data);
56
+ } else {
57
+ reject(new Error(`HTTP ${res.statusCode}`));
58
+ }
59
+ });
60
+ });
61
+ req.on("error", reject);
62
+ req.on("timeout", () => {
63
+ req.destroy();
64
+ reject(new Error("request timeout"));
65
+ });
66
+ });
51
67
  }
52
68
  function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation timed out") {
53
69
  let timeoutId;
@@ -63,8 +79,8 @@ function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation time
63
79
  async function checkCdpReady(url, retries = 3) {
64
80
  for (let i = 0; i < retries; i++) {
65
81
  try {
66
- const res = await fetchWithTimeout(url, 1500);
67
- if (res.ok) return true;
82
+ await httpGet(url, 1500);
83
+ return true;
68
84
  } catch {
69
85
  }
70
86
  if (i < retries - 1) {
@@ -73,45 +89,6 @@ async function checkCdpReady(url, retries = 3) {
73
89
  }
74
90
  return false;
75
91
  }
76
- async function killProcessOnPortIfZombie(port) {
77
- const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1);
78
- if (isResponding) {
79
- console.log(import_picocolors.default.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`));
80
- return;
81
- }
82
- try {
83
- const platform = import_os.default.platform();
84
- const { execSync } = require("child_process");
85
- if (platform === "darwin" || platform === "linux") {
86
- console.log(import_picocolors.default.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
87
- const pids = execSync(`lsof -t -i :${port}`).toString().trim();
88
- if (pids) {
89
- console.log(import_picocolors.default.yellow(`\u53D1\u73B0\u50F5\u5C38 PID: ${pids.split("\n").join(", ")}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
90
- execSync(`kill -9 ${pids.split("\n").join(" ")}`);
91
- console.log(import_picocolors.default.green(`\u6210\u529F\u6E05\u7406\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B`));
92
- }
93
- } else if (platform === "win32") {
94
- console.log(import_picocolors.default.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406 Windows \u4E0A\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
95
- const output = execSync(`netstat -ano | findstr :${port}`).toString().trim();
96
- if (output) {
97
- const lines = output.split("\n");
98
- const pids = /* @__PURE__ */ new Set();
99
- lines.forEach((line) => {
100
- const parts = line.trim().split(/\s+/);
101
- const pid = parts[parts.length - 1];
102
- if (pid && /^\d+$/.test(pid) && pid !== "0") {
103
- pids.add(pid);
104
- }
105
- });
106
- pids.forEach((pid) => {
107
- console.log(import_picocolors.default.yellow(`\u53D1\u73B0 Windows \u6B8B\u7559\u50F5\u5C38 PID: ${pid}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
108
- execSync(`taskkill /F /PID ${pid}`);
109
- });
110
- }
111
- }
112
- } catch (e) {
113
- }
114
- }
115
92
  function getDefaultBrowserPath() {
116
93
  const platform = import_os.default.platform();
117
94
  if (platform === "darwin") {
@@ -174,20 +151,18 @@ async function startBrowserInBackground() {
174
151
  child.unref();
175
152
  for (let i = 0; i < 20; i++) {
176
153
  try {
177
- const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
154
+ const urls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
178
155
  for (const url of urls) {
179
156
  try {
180
- const response = await fetchWithTimeout(url, 1e3);
181
- if (response.ok) {
182
- console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
183
- return;
184
- }
157
+ await httpGet(url, 1e3);
158
+ console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
159
+ return;
185
160
  } catch (err) {
186
161
  }
187
162
  }
188
163
  } catch (e) {
189
164
  }
190
- await new Promise((resolve) => setTimeout(resolve, 500));
165
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
191
166
  }
192
167
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
193
168
  }
@@ -208,90 +183,51 @@ async function connectBrowser() {
208
183
  return false;
209
184
  }
210
185
  };
211
- try {
212
- const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
213
- if (!is127Ready) {
214
- throw new Error("127.0.0.1 CDP port not responding");
186
+ const portAddresses = [
187
+ `http://127.0.0.1:${CDP_PORT}/json/version`,
188
+ `http://localhost:${CDP_PORT}/json/version`
189
+ ];
190
+ for (const addr of portAddresses) {
191
+ const isReady = await checkCdpReady(addr, 2);
192
+ if (!isReady) continue;
193
+ const browserURL = addr.replace("/json/version", "");
194
+ try {
195
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
196
+ const browser2 = await promiseWithTimeout(
197
+ import_puppeteer_core.default.connect({ browserURL, defaultViewport: null, targetFilter }),
198
+ 1e4,
199
+ `puppeteer.connect to ${browserURL} timed out`
200
+ );
201
+ console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
202
+ return browser2;
203
+ } catch {
215
204
  }
216
- console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
217
- const browser2 = await promiseWithTimeout(
218
- import_puppeteer_core.default.connect({
219
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
220
- defaultViewport: null,
221
- targetFilter
222
- }),
223
- 1e4,
224
- "puppeteer.connect to 127.0.0.1 timed out"
225
- );
226
- console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
227
- return browser2;
228
- } catch (error) {
205
+ }
206
+ 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...`));
207
+ try {
208
+ await startBrowserInBackground();
209
+ } catch (launchError) {
210
+ const msg = launchError instanceof Error ? launchError.message : String(launchError);
211
+ console.error(import_picocolors.default.red(`\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
212
+ throw new Error("Browser launch failed.");
213
+ }
214
+ for (const addr of portAddresses) {
215
+ const isReady = await checkCdpReady(addr, 3);
216
+ if (!isReady) continue;
217
+ const browserURL = addr.replace("/json/version", "");
229
218
  try {
230
- const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
231
- if (!isLocalhostReady) {
232
- throw new Error("localhost CDP port not responding");
233
- }
234
- console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
219
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
235
220
  const browser2 = await promiseWithTimeout(
236
- import_puppeteer_core.default.connect({
237
- browserURL: `http://localhost:${CDP_PORT}`,
238
- defaultViewport: null,
239
- targetFilter
240
- }),
221
+ import_puppeteer_core.default.connect({ browserURL, defaultViewport: null, targetFilter }),
241
222
  1e4,
242
- "puppeteer.connect to localhost timed out"
223
+ `puppeteer.connect to ${browserURL} after launch timed out`
243
224
  );
244
- console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
225
+ console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
245
226
  return browser2;
246
- } catch (error2) {
247
- console.log(import_picocolors.default.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)}`));
248
- try {
249
- await killProcessOnPortIfZombie(CDP_PORT);
250
- await startBrowserInBackground();
251
- try {
252
- const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
253
- if (!is127Ready) {
254
- throw new Error("127.0.0.1 CDP port not responding after launch");
255
- }
256
- console.log(import_picocolors.default.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
257
- const browser2 = await promiseWithTimeout(
258
- import_puppeteer_core.default.connect({
259
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
260
- defaultViewport: null,
261
- targetFilter
262
- }),
263
- 1e4,
264
- "puppeteer.connect to 127.0.0.1 after launch timed out"
265
- );
266
- console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
267
- return browser2;
268
- } catch (e) {
269
- const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
270
- if (!isLocalhostReady) {
271
- throw new Error("localhost CDP port not responding after launch");
272
- }
273
- console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
274
- const browser2 = await promiseWithTimeout(
275
- import_puppeteer_core.default.connect({
276
- browserURL: `http://localhost:${CDP_PORT}`,
277
- defaultViewport: null,
278
- targetFilter
279
- }),
280
- 1e4,
281
- "puppeteer.connect to localhost after launch timed out"
282
- );
283
- console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
284
- return browser2;
285
- }
286
- } catch (launchError) {
287
- const msg = launchError instanceof Error ? launchError.message : String(launchError);
288
- console.error(import_picocolors.default.red(`\u65E0\u6CD5\u8FDE\u63A5\u6216\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
289
- console.error(import_picocolors.default.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`));
290
- console.error(import_picocolors.default.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`));
291
- throw new Error("Browser connection failed.");
292
- }
227
+ } catch {
293
228
  }
294
229
  }
230
+ 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`);
295
231
  }
296
232
  async function getPageTargetId(page2) {
297
233
  const session = await page2.target().createCDPSession();
@@ -369,14 +305,12 @@ async function getTargetPage(browser2, tabid2) {
369
305
  let activeTargetId = null;
370
306
  for (const url of urls) {
371
307
  try {
372
- const res = await fetchWithTimeout(url, 1e3);
373
- if (res.ok) {
374
- const targetsData = await res.json();
375
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
376
- if (active) {
377
- activeTargetId = active.id;
378
- break;
379
- }
308
+ const body = await httpGet(url, 1e3);
309
+ const targetsData = JSON.parse(body);
310
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
311
+ if (active) {
312
+ activeTargetId = active.id;
313
+ break;
380
314
  }
381
315
  } catch {
382
316
  }
@@ -422,7 +356,7 @@ async function injectWebMCPPolyfillAndTools(page2, force = false) {
422
356
  const msg = err instanceof Error ? err.message : String(err);
423
357
  throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
424
358
  }
425
- await new Promise((resolve) => setTimeout(resolve, 300));
359
+ await new Promise((resolve2) => setTimeout(resolve2, 300));
426
360
  }
427
361
  await injectDomainTools(page2);
428
362
  }
@@ -510,7 +444,7 @@ async function stateCommand({ tabid: tabid2 }) {
510
444
  if (pUrl.startsWith("devtools://")) return null;
511
445
  const pTitle = await Promise.race([
512
446
  p.title().catch(() => "Unknown"),
513
- new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
447
+ new Promise((resolve2) => setTimeout(() => resolve2("Unknown"), 500))
514
448
  ]);
515
449
  return {
516
450
  // 使用真实的 Chrome target ID,而非数组下标
@@ -530,6 +464,7 @@ async function stateCommand({ tabid: tabid2 }) {
530
464
  }
531
465
 
532
466
  // src/commands/run.ts
467
+ var import_json5 = __toESM(require("json5"), 1);
533
468
  async function runCommand({
534
469
  toolName,
535
470
  argsJson,
@@ -538,10 +473,15 @@ async function runCommand({
538
473
  const browser2 = await connectBrowser();
539
474
  try {
540
475
  const page2 = await getTargetPage(browser2, tabid2);
476
+ let cleanedArgs = argsJson.trim();
477
+ if (cleanedArgs.startsWith("'") && cleanedArgs.endsWith("'")) {
478
+ cleanedArgs = cleanedArgs.slice(1, -1).trim();
479
+ }
541
480
  try {
542
- JSON.parse(argsJson);
481
+ const obj = import_json5.default.parse(cleanedArgs);
482
+ cleanedArgs = JSON.stringify(obj);
543
483
  } catch (e) {
544
- throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON: ${e.message}`);
484
+ throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${e.message}`);
545
485
  }
546
486
  const result2 = await page2.evaluate(async (name, inputString) => {
547
487
  const mcp = navigator.modelContextTesting || navigator.modelContext;
@@ -555,8 +495,7 @@ async function runCommand({
555
495
  } catch (e) {
556
496
  }
557
497
  }
558
- return res;
559
- }, toolName, argsJson);
498
+ }, toolName, cleanedArgs);
560
499
  return result2;
561
500
  } finally {
562
501
  await browser2.disconnect();
@@ -616,7 +555,7 @@ async function tabsOpenCommand(url) {
616
555
  url = normalizeUrl(url);
617
556
  await page2.goto(url, { waitUntil: "domcontentloaded" });
618
557
  await injectIntoPage(page2);
619
- await new Promise((resolve) => setTimeout(resolve, 500));
558
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
620
559
  return {
621
560
  success: true,
622
561
  tabid: await getPageTargetId(page2),
@@ -717,7 +656,7 @@ async function tabsForwardCommand(tabid2) {
717
656
  // package.json
718
657
  var package_default = {
719
658
  name: "@opentiny/webmcp-cli",
720
- version: "0.0.1-alpha.3",
659
+ version: "0.0.1-alpha.5",
721
660
  type: "module",
722
661
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
723
662
  main: "dist/index.js",
@@ -756,6 +695,7 @@ var package_default = {
756
695
  "@page-agent/page-controller": "catalog:",
757
696
  clipboardy: "^5.3.1",
758
697
  commander: "^11.1.0",
698
+ json5: "^2.2.3",
759
699
  picocolors: "^1.0.0",
760
700
  "puppeteer-core": "^22.0.0"
761
701
  },
@@ -770,6 +710,13 @@ var package_default = {
770
710
 
771
711
  // src/bin.ts
772
712
  var program = new import_commander.Command();
713
+ function cleanJsonString(str) {
714
+ let cleaned = str.trim();
715
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
716
+ cleaned = cleaned.slice(1, -1).trim();
717
+ }
718
+ return cleaned;
719
+ }
773
720
  function parseTabId(id) {
774
721
  if (!id) return void 0;
775
722
  return id;
@@ -779,6 +726,19 @@ function handleCommandError(error, commandName) {
779
726
  console.error(import_picocolors2.default.red(`Error executing ${commandName} command: ${msg}`));
780
727
  process.exit(1);
781
728
  }
729
+ function expandFileRefs(argsJson) {
730
+ return argsJson.replace(/"@(base64)?file:([^"]+)"/g, (_match, base64Flag, filePath) => {
731
+ const absPath = (0, import_path2.resolve)(process.cwd(), filePath);
732
+ let content;
733
+ try {
734
+ content = (0, import_fs2.readFileSync)(absPath, "utf-8");
735
+ } catch (e) {
736
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}": ${e.message}`);
737
+ }
738
+ const value = base64Flag ? Buffer.from(content, "utf-8").toString("base64") : content;
739
+ return JSON.stringify(value);
740
+ });
741
+ }
782
742
  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) => {
783
743
  const opts = thisCommand.opts();
784
744
  if (opts.workspace) {
@@ -793,11 +753,54 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
793
753
  handleCommandError(error, "state");
794
754
  }
795
755
  });
796
- 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) => {
756
+ 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(
757
+ "-f, --file <path>",
758
+ `\u4ECE\u6307\u5B9A .json \u6587\u4EF6\u8BFB\u53D6\u6574\u4E2A\u53C2\u6570\uFF08\u6587\u4EF6\u5185\u5BB9\u5373\u4E3A\u53C2\u6570 JSON\uFF09\u3002
759
+ \u5982\u9700\u5728\u53C2\u6570\u4E2D\u5185\u8054\u5F15\u7528\u6587\u4EF6\uFF0C\u4F7F\u7528\u5360\u4F4D\u7B26\u8BED\u6CD5\uFF1A
760
+ @file:<path> \u8BFB\u53D6\u6587\u4EF6\u539F\u59CB\u6587\u672C
761
+ @base64file:<path> \u8BFB\u53D6\u6587\u4EF6\u5E76 Base64 \u7F16\u7801
762
+ \u793A\u4F8B\uFF1Awebmcp-cli run mytool '{"content":"@base64file:./doc.md"}'`
763
+ ).action(async (toolName, args, options) => {
797
764
  try {
765
+ let finalArgsJson = "";
766
+ if (options.file) {
767
+ const filePath = (0, import_path2.resolve)(process.cwd(), options.file);
768
+ try {
769
+ finalArgsJson = (0, import_fs2.readFileSync)(filePath, "utf-8").trim();
770
+ } catch (e) {
771
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 "${filePath}": ${e.message}`);
772
+ }
773
+ } else if (args && args.length > 0) {
774
+ const firstArg = args[0].trim();
775
+ const cleanedFirstArg = cleanJsonString(firstArg);
776
+ if (cleanedFirstArg.startsWith("{") && cleanedFirstArg.endsWith("}")) {
777
+ finalArgsJson = args.map(cleanJsonString).join(" ");
778
+ } else {
779
+ const obj = {};
780
+ for (const pair of args) {
781
+ const index = pair.indexOf("=");
782
+ if (index === -1) {
783
+ 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`);
784
+ }
785
+ const key = pair.substring(0, index).trim();
786
+ let val = pair.substring(index + 1).trim();
787
+ val = cleanJsonString(val);
788
+ try {
789
+ obj[key] = JSON.parse(val);
790
+ } catch {
791
+ obj[key] = val;
792
+ }
793
+ }
794
+ finalArgsJson = JSON.stringify(obj);
795
+ }
796
+ }
797
+ if (!finalArgsJson) {
798
+ throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
799
+ }
800
+ finalArgsJson = expandFileRefs(finalArgsJson);
798
801
  const result2 = await runCommand({
799
802
  toolName,
800
- argsJson,
803
+ argsJson: finalArgsJson,
801
804
  tabid: parseTabId(options.tabid)
802
805
  });
803
806
  console.log(JSON.stringify(result2, null, 2));