@opentiny/webmcp-cli 0.0.1-alpha.6 → 0.0.3

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,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin.ts
4
+ import fs2 from "fs";
5
+ import path2 from "path";
6
+ import os2 from "os";
4
7
  import { Command } from "commander";
5
8
  import pc2 from "picocolors";
6
9
  import { readFileSync as readFileSync2 } from "fs";
@@ -237,6 +240,31 @@ async function checkCdpReady(url, retries = 3) {
237
240
  }
238
241
  return false;
239
242
  }
243
+ async function findAvailableCdpUrl(addresses, retries = 2) {
244
+ for (const addr of addresses) {
245
+ if (await checkCdpReady(addr, retries)) {
246
+ return addr.replace("/json/version", "");
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+ async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
252
+ try {
253
+ return await promiseWithTimeout(
254
+ puppeteer.connect({ browserURL: browserBaseUrl, defaultViewport: null }),
255
+ 1e4,
256
+ `puppeteer.connect to ${browserBaseUrl} timed out`
257
+ );
258
+ } catch (err) {
259
+ if (attempt >= maxAttempts) {
260
+ throw err;
261
+ }
262
+ const delayMs = 500 * attempt;
263
+ console.log(pc.yellow(`connectBrowser: \u7B2C ${attempt} \u6B21\u8FDE\u63A5\u5931\u8D25\uFF0C${delayMs}ms \u540E\u91CD\u8BD5...`));
264
+ await new Promise((resolve3) => setTimeout(resolve3, delayMs));
265
+ return connectPuppeteer(browserBaseUrl, attempt + 1, maxAttempts);
266
+ }
267
+ }
240
268
  function getDefaultBrowserPath() {
241
269
  const platform = os.platform();
242
270
  if (platform === "darwin") {
@@ -283,72 +311,56 @@ async function startBrowserInBackground() {
283
311
  }
284
312
  console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
285
313
  const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
314
+ const launchArgs = [
315
+ `--remote-debugging-port=${CDP_PORT}`,
316
+ `--user-data-dir=${userDataDir}`,
317
+ "--no-first-run",
318
+ "--no-default-browser-check"
319
+ ];
320
+ if (os.platform() === "win32") {
321
+ launchArgs.push("--remote-debugging-address=127.0.0.1");
322
+ }
286
323
  const child = spawn(
287
324
  browserInfo.path,
288
- [
289
- `--remote-debugging-port=${CDP_PORT}`,
290
- `--user-data-dir=${userDataDir}`,
291
- "--no-first-run",
292
- "--no-default-browser-check"
293
- ],
325
+ launchArgs,
294
326
  {
295
327
  detached: true,
296
328
  stdio: "ignore"
297
329
  }
298
330
  );
299
331
  child.unref();
300
- for (let i = 0; i < 20; i++) {
301
- try {
302
- const urls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
303
- for (const url of urls) {
304
- try {
305
- await httpGet(url, 1e3);
306
- console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
307
- return;
308
- } catch (err) {
309
- }
332
+ const pollUrls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
333
+ for (let i = 0; i < 30; i++) {
334
+ for (const url of pollUrls) {
335
+ try {
336
+ await httpGet(url, 1e3);
337
+ console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
338
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
339
+ return;
340
+ } catch {
310
341
  }
311
- } catch (e) {
312
342
  }
313
343
  await new Promise((resolve3) => setTimeout(resolve3, 500));
314
344
  }
315
345
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
316
346
  }
317
347
  async function connectBrowser() {
318
- const targetFilter = (target) => {
319
- try {
320
- const info = typeof target._getTargetInfo === "function" ? target._getTargetInfo() : target;
321
- const type = info.type || "";
322
- const url = info.url || "";
323
- if (type === "service_worker" || type === "shared_worker" || type === "iframe" || type === "other" || type === "webview" || type === "background_page") {
324
- return false;
325
- }
326
- if (url.startsWith("devtools://") || url.startsWith("chrome-extension://")) {
327
- return false;
328
- }
329
- return true;
330
- } catch (e) {
331
- return false;
332
- }
333
- };
334
- const portAddresses = [
348
+ const versionAddresses = [
335
349
  `http://127.0.0.1:${CDP_PORT}/json/version`,
336
350
  `http://localhost:${CDP_PORT}/json/version`
337
351
  ];
338
- for (const addr of portAddresses) {
339
- const isReady = await checkCdpReady(addr, 2);
340
- if (!isReady) continue;
341
- const browserURL = addr.replace("/json/version", "");
352
+ const existingUrl = await findAvailableCdpUrl(versionAddresses, 2);
353
+ if (existingUrl) {
354
+ console.log(pc.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${existingUrl}...`));
342
355
  try {
343
- console.log(pc.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
344
- const browser2 = await promiseWithTimeout(
345
- puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
346
- 1e4,
347
- `puppeteer.connect to ${browserURL} timed out`
356
+ const browser = await connectPuppeteer(existingUrl);
357
+ console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${existingUrl}`));
358
+ return browser;
359
+ } catch (err) {
360
+ const msg = err instanceof Error ? err.message : String(err);
361
+ throw new Error(
362
+ `\u65E0\u6CD5\u8FDE\u63A5\u5230\u5DF2\u5728\u8FD0\u884C\u7684\u6D4F\u89C8\u5668\uFF08${existingUrl}\uFF09\uFF1A${msg}\u3002CDP \u7AEF\u53E3\u53EF\u7528\uFF0C\u8BF7\u52FF\u91CD\u590D\u542F\u52A8\u6D4F\u89C8\u5668\uFF1B\u53EF\u5173\u95ED\u591A\u4F59\u7A97\u53E3\u540E\u91CD\u8BD5\u3002`
348
363
  );
349
- console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
350
- return browser2;
351
- } catch {
352
364
  }
353
365
  }
354
366
  console.log(pc.yellow(`connectBrowser: \u7AEF\u53E3 ${CDP_PORT} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
@@ -359,26 +371,21 @@ async function connectBrowser() {
359
371
  console.error(pc.red(`\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
360
372
  throw new Error("Browser launch failed.");
361
373
  }
362
- for (const addr of portAddresses) {
363
- const isReady = await checkCdpReady(addr, 3);
364
- if (!isReady) continue;
365
- const browserURL = addr.replace("/json/version", "");
366
- try {
367
- console.log(pc.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
368
- const browser2 = await promiseWithTimeout(
369
- puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
370
- 1e4,
371
- `puppeteer.connect to ${browserURL} after launch timed out`
372
- );
373
- console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
374
- return browser2;
375
- } catch {
376
- }
374
+ const launchedUrl = await findAvailableCdpUrl(versionAddresses, 5);
375
+ if (!launchedUrl) {
376
+ 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`);
377
+ }
378
+ try {
379
+ console.log(pc.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${launchedUrl}...`));
380
+ const browser = await connectPuppeteer(launchedUrl);
381
+ console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${launchedUrl}`));
382
+ return browser;
383
+ } catch {
384
+ 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`);
377
385
  }
378
- 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`);
379
386
  }
380
- async function getPageTargetId(page2) {
381
- const session = await page2.target().createCDPSession();
387
+ async function getPageTargetId(page) {
388
+ const session = await page.target().createCDPSession();
382
389
  try {
383
390
  const { targetInfo } = await session.send("Target.getTargetInfo");
384
391
  return targetInfo.targetId;
@@ -387,8 +394,8 @@ async function getPageTargetId(page2) {
387
394
  });
388
395
  }
389
396
  }
390
- function getPageTargets(browser2) {
391
- return browser2.targets().filter((t) => {
397
+ function getPageTargets(browser) {
398
+ return browser.targets().filter((t) => {
392
399
  try {
393
400
  const type = (typeof t.type === "function" ? t.type() : t.type) || "";
394
401
  const url = (typeof t.url === "function" ? t.url() : t.url) || "";
@@ -401,26 +408,26 @@ function getPageTargets(browser2) {
401
408
  function getTargetIdFromTarget(target) {
402
409
  return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
403
410
  }
404
- function findPageTargetByTabId(browser2, tabid2) {
405
- const pageTargets = getPageTargets(browser2);
411
+ function findPageTargetByTabId(browser, tabid) {
412
+ const pageTargets = getPageTargets(browser);
406
413
  return pageTargets.find((t) => {
407
414
  const tid = getTargetIdFromTarget(t);
408
- return tid === tabid2 || tid.includes(tabid2);
415
+ return tid === tabid || tid.includes(tabid);
409
416
  }) ?? null;
410
417
  }
411
- async function activateTabById(browser2, tabid2) {
412
- const target = findPageTargetByTabId(browser2, tabid2);
418
+ async function activateTabById(browser, tabid) {
419
+ const target = findPageTargetByTabId(browser, tabid);
413
420
  if (!target) {
414
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
421
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
415
422
  }
416
423
  const realTabId = getTargetIdFromTarget(target);
417
- const pages = await browser2.pages();
424
+ const pages = await browser.pages();
418
425
  let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
419
426
  if (!sessionPage && pages.length > 0) {
420
427
  sessionPage = pages[0];
421
428
  }
422
429
  if (!sessionPage) {
423
- sessionPage = await browser2.newPage();
430
+ sessionPage = await browser.newPage();
424
431
  }
425
432
  const session = await sessionPage.createCDPSession();
426
433
  try {
@@ -430,18 +437,18 @@ async function activateTabById(browser2, tabid2) {
430
437
  });
431
438
  }
432
439
  }
433
- async function getTargetPage(browser2, tabid2) {
434
- const pageTargets = getPageTargets(browser2);
440
+ async function getTargetPage(browser, tabid) {
441
+ const pageTargets = getPageTargets(browser);
435
442
  if (pageTargets.length === 0) {
436
- const newPage = await browser2.newPage();
443
+ const newPage = await browser.newPage();
437
444
  await injectWebMCPPolyfillAndTools(newPage);
438
445
  return newPage;
439
446
  }
440
447
  let targetPage = null;
441
- if (tabid2 !== void 0) {
442
- const target = findPageTargetByTabId(browser2, tabid2);
448
+ if (tabid !== void 0) {
449
+ const target = findPageTargetByTabId(browser, tabid);
443
450
  if (!target) {
444
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
451
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
445
452
  }
446
453
  targetPage = await target.page();
447
454
  } else {
@@ -484,11 +491,11 @@ async function getTargetPage(browser2, tabid2) {
484
491
  await injectWebMCPPolyfillAndTools(targetPage);
485
492
  return targetPage;
486
493
  }
487
- async function injectIntoPage(page2) {
488
- await injectWebMCPPolyfillAndTools(page2, true);
494
+ async function injectIntoPage(page) {
495
+ await injectWebMCPPolyfillAndTools(page, true);
489
496
  }
490
- async function injectWebMCPPolyfillAndTools(page2, force = false) {
491
- const polyfillReady = !force && await page2.evaluate(() => {
497
+ async function injectWebMCPPolyfillAndTools(page, force = false) {
498
+ const polyfillReady = !force && await page.evaluate(() => {
492
499
  return !!window.__webmcpcli_init;
493
500
  }).catch(() => false);
494
501
  if (!polyfillReady) {
@@ -499,31 +506,63 @@ async function injectWebMCPPolyfillAndTools(page2, force = false) {
499
506
  }
500
507
  const scriptContent = fs.readFileSync(injectScriptPath, "utf-8");
501
508
  try {
502
- await page2.evaluate(scriptContent);
509
+ await page.evaluate(scriptContent);
503
510
  } catch (err) {
504
511
  const msg = err instanceof Error ? err.message : String(err);
505
512
  throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
506
513
  }
507
- await new Promise((resolve3) => setTimeout(resolve3, 300));
514
+ await page.waitForFunction(() => {
515
+ const mcp = navigator.modelContext;
516
+ return mcp && typeof mcp.registerTool === "function";
517
+ }, { timeout: 1e4 }).catch(() => {
518
+ console.warn("\u7B49\u5F85 WebMCP registerTool \u63A5\u53E3\u5C31\u7EEA\u8D85\u65F6\uFF0C\u6B63\u5728\u7EE7\u7EED\u6CE8\u5165...");
519
+ });
508
520
  }
509
- await injectDomainTools(page2);
521
+ await injectDomainTools(page);
510
522
  }
511
- async function injectDomainTools(page2) {
523
+ function getToolsBundleName(hostname) {
524
+ const normalized = hostname.split(":")[0].toLowerCase();
525
+ const toolsDir = path.resolve(__dirname, "webmcp-tools");
526
+ if (!fs.existsSync(toolsDir)) {
527
+ return null;
528
+ }
529
+ let files = [];
530
+ try {
531
+ files = fs.readdirSync(toolsDir);
532
+ } catch {
533
+ return null;
534
+ }
535
+ const supportedDomains = files.filter((f) => f.endsWith(".js")).map((f) => f.slice(0, -3));
536
+ if (supportedDomains.includes(normalized)) {
537
+ return `webmcp-tools/${normalized}.js`;
538
+ }
539
+ for (const domain of supportedDomains) {
540
+ if (normalized.endsWith("." + domain)) {
541
+ return `webmcp-tools/${domain}.js`;
542
+ }
543
+ }
544
+ return null;
545
+ }
546
+ async function injectDomainTools(page) {
512
547
  let hostname;
513
548
  try {
514
- const url = new URL(page2.url());
549
+ const url = new URL(page.url());
515
550
  hostname = url.hostname;
516
551
  } catch {
517
552
  return;
518
553
  }
519
- const toolBundlePath = path.resolve(__dirname, "webmcp-tools", `${hostname}.js`);
554
+ const bundleName = getToolsBundleName(hostname);
555
+ if (!bundleName) {
556
+ return;
557
+ }
558
+ const toolBundlePath = path.resolve(__dirname, bundleName);
520
559
  if (!fs.existsSync(toolBundlePath)) {
521
560
  return;
522
561
  }
523
562
  console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
524
563
  const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
525
564
  try {
526
- await page2.evaluate(toolScript);
565
+ await page.evaluate(toolScript);
527
566
  console.log(pc.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
528
567
  } catch (err) {
529
568
  const msg = err instanceof Error ? err.message : String(err);
@@ -532,60 +571,23 @@ async function injectDomainTools(page2) {
532
571
  }
533
572
 
534
573
  // src/commands/state.ts
535
- async function stateCommand({ tabid: tabid2 }) {
536
- const browser2 = await connectBrowser();
574
+ async function stateCommand({ tabid }) {
575
+ const browser = await connectBrowser();
537
576
  try {
538
- const page2 = await getTargetPage(browser2, tabid2);
539
- await injectIntoPage(page2);
540
- const state = await page2.evaluate(async () => {
577
+ const page = await getTargetPage(browser, tabid);
578
+ await injectIntoPage(page);
579
+ const state = await page.evaluate(async () => {
541
580
  const url = document.URL;
542
581
  const title = document.title;
543
582
  const mcp = navigator.modelContextTesting || navigator.modelContext;
544
583
  let webmcpTools = [];
545
- let contentData = `\u9875\u9762\u5DF2\u51C6\u5907\u597D: ${title}`;
546
- if (mcp) {
547
- if (typeof mcp.listTools === "function") {
548
- const toolsResult = await mcp.listTools();
549
- webmcpTools = toolsResult?.tools || toolsResult || [];
550
- }
551
- if (typeof mcp.executeTool === "function") {
552
- try {
553
- const argsString = JSON.stringify({ action: "browserState" });
554
- let stateRes = await mcp.executeTool("page-agent-tool", argsString);
555
- if (typeof stateRes === "string") {
556
- try {
557
- stateRes = JSON.parse(stateRes);
558
- } catch (e) {
559
- }
560
- }
561
- if (stateRes && stateRes.content && stateRes.content.length > 0) {
562
- const textContent = stateRes.content.map((c) => c.text).join("\\n");
563
- const prefix = "\u6D4F\u89C8\u5668\u72B6\u6001: ";
564
- if (textContent.startsWith(prefix)) {
565
- try {
566
- const jsonStr = textContent.substring(prefix.length);
567
- const parsedState = JSON.parse(jsonStr);
568
- contentData = parsedState.content;
569
- } catch (e) {
570
- contentData = textContent;
571
- }
572
- } else {
573
- contentData = textContent;
574
- }
575
- }
576
- } catch (e) {
577
- console.error("Snapshot error:", e.message);
578
- }
579
- }
584
+ if (mcp && typeof mcp.listTools === "function") {
585
+ const toolsResult = await mcp.listTools();
586
+ webmcpTools = toolsResult?.tools || toolsResult || [];
580
587
  }
581
- return {
582
- content: contentData,
583
- url,
584
- title,
585
- webmcpTools
586
- };
588
+ return { url, title, webmcpTools };
587
589
  });
588
- const pages = await browser2.pages();
590
+ const pages = await browser.pages();
589
591
  const tabs2 = await Promise.all(
590
592
  pages.map(async (p) => {
591
593
  const pUrl = p.url();
@@ -607,7 +609,7 @@ async function stateCommand({ tabid: tabid2 }) {
607
609
  tabs: tabs2.filter(Boolean)
608
610
  };
609
611
  } finally {
610
- await browser2.disconnect();
612
+ await browser.disconnect();
611
613
  }
612
614
  }
613
615
 
@@ -615,13 +617,13 @@ async function stateCommand({ tabid: tabid2 }) {
615
617
  async function runCommand({
616
618
  toolName,
617
619
  argsJson,
618
- tabid: tabid2
620
+ tabid
619
621
  }) {
620
- const browser2 = await connectBrowser();
622
+ const browser = await connectBrowser();
621
623
  try {
622
- const page2 = await getTargetPage(browser2, tabid2);
624
+ const page = await getTargetPage(browser, tabid);
623
625
  const cleanedArgs = argsJson.trim();
624
- const result2 = await page2.evaluate(async (name, inputString) => {
626
+ const result = await page.evaluate(async (name, inputString) => {
625
627
  const mcp = navigator.modelContextTesting || navigator.modelContext;
626
628
  if (!mcp || typeof mcp.executeTool !== "function") {
627
629
  throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
@@ -642,35 +644,6 @@ async function runCommand({
642
644
  }
643
645
  return res;
644
646
  }, toolName, cleanedArgs);
645
- return result2;
646
- } finally {
647
- await browser2.disconnect();
648
- }
649
- }
650
-
651
- // src/commands/evaluate.ts
652
- async function evaluateCommand({
653
- jsScript,
654
- tabid
655
- }) {
656
- const browser = await connectBrowser();
657
- try {
658
- const page = await getTargetPage(browser, tabid);
659
- const result = await page.evaluate(async (script) => {
660
- try {
661
- const result = eval(script);
662
- if (result instanceof Promise) {
663
- return await result;
664
- }
665
- return result;
666
- } catch (error) {
667
- return {
668
- error: true,
669
- message: error.message,
670
- stack: error.stack
671
- };
672
- }
673
- }, jsScript);
674
647
  return result;
675
648
  } finally {
676
649
  await browser.disconnect();
@@ -695,114 +668,114 @@ function normalizeUrl(url) {
695
668
  return url;
696
669
  }
697
670
  async function tabsOpenCommand(url) {
698
- const browser2 = await connectBrowser();
671
+ const browser = await connectBrowser();
699
672
  try {
700
- const page2 = await browser2.newPage();
673
+ const page = await browser.newPage();
701
674
  url = normalizeUrl(url);
702
- await page2.goto(url, { waitUntil: "domcontentloaded" });
703
- await injectIntoPage(page2);
675
+ await page.goto(url, { waitUntil: "domcontentloaded" });
676
+ await injectIntoPage(page);
704
677
  await new Promise((resolve3) => setTimeout(resolve3, 500));
705
678
  return {
706
679
  success: true,
707
- tabid: await getPageTargetId(page2),
708
- url: page2.url(),
709
- title: await page2.title()
680
+ tabid: await getPageTargetId(page),
681
+ url: page.url(),
682
+ title: await page.title()
710
683
  };
711
684
  } finally {
712
- await browser2.disconnect();
685
+ await browser.disconnect();
713
686
  }
714
687
  }
715
- async function tabsCloseCommand(tabid2) {
716
- const browser2 = await connectBrowser();
688
+ async function tabsCloseCommand(tabid) {
689
+ const browser = await connectBrowser();
717
690
  try {
718
- const target = findPageTargetByTabId(browser2, tabid2);
691
+ const target = findPageTargetByTabId(browser, tabid);
719
692
  if (!target) {
720
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
693
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
721
694
  }
722
- const page2 = await target.page();
723
- if (!page2) {
724
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
695
+ const page = await target.page();
696
+ if (!page) {
697
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
725
698
  }
726
- const closedTabid = await getPageTargetId(page2).catch(() => tabid2);
727
- await page2.close();
699
+ const closedTabid = await getPageTargetId(page).catch(() => tabid);
700
+ await page.close();
728
701
  return {
729
702
  success: true,
730
703
  tabid: closedTabid
731
704
  };
732
705
  } finally {
733
- await browser2.disconnect();
706
+ await browser.disconnect();
734
707
  }
735
708
  }
736
- async function tabsSwitchCommand(tabid2) {
737
- const browser2 = await connectBrowser();
709
+ async function tabsSwitchCommand(tabid) {
710
+ const browser = await connectBrowser();
738
711
  try {
739
- await activateTabById(browser2, tabid2);
740
- const page2 = await getTargetPage(browser2, tabid2);
712
+ await activateTabById(browser, tabid);
713
+ const page = await getTargetPage(browser, tabid);
741
714
  return {
742
715
  success: true,
743
- tabid: await getPageTargetId(page2),
744
- url: page2.url(),
745
- title: await page2.title()
716
+ tabid: await getPageTargetId(page),
717
+ url: page.url(),
718
+ title: await page.title()
746
719
  };
747
720
  } finally {
748
- await browser2.disconnect();
721
+ await browser.disconnect();
749
722
  }
750
723
  }
751
- async function tabsBackCommand(tabid2) {
752
- const browser2 = await connectBrowser();
724
+ async function tabsBackCommand(tabid) {
725
+ const browser = await connectBrowser();
753
726
  try {
754
- const page2 = await getTargetPage(browser2, tabid2);
755
- const response = await page2.goBack();
727
+ const page = await getTargetPage(browser, tabid);
728
+ const response = await page.goBack();
756
729
  if (!response) {
757
730
  return {
758
731
  success: false,
759
732
  error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
760
- tabid: await getPageTargetId(page2).catch(() => tabid2),
761
- url: page2.url(),
762
- title: await page2.title()
733
+ tabid: await getPageTargetId(page).catch(() => tabid),
734
+ url: page.url(),
735
+ title: await page.title()
763
736
  };
764
737
  }
765
- await injectIntoPage(page2);
738
+ await injectIntoPage(page);
766
739
  return {
767
740
  success: true,
768
- tabid: await getPageTargetId(page2),
769
- url: page2.url(),
770
- title: await page2.title()
741
+ tabid: await getPageTargetId(page),
742
+ url: page.url(),
743
+ title: await page.title()
771
744
  };
772
745
  } finally {
773
- await browser2.disconnect();
746
+ await browser.disconnect();
774
747
  }
775
748
  }
776
- async function tabsForwardCommand(tabid2) {
777
- const browser2 = await connectBrowser();
749
+ async function tabsForwardCommand(tabid) {
750
+ const browser = await connectBrowser();
778
751
  try {
779
- const page2 = await getTargetPage(browser2, tabid2);
780
- const response = await page2.goForward();
752
+ const page = await getTargetPage(browser, tabid);
753
+ const response = await page.goForward();
781
754
  if (!response) {
782
755
  return {
783
756
  success: false,
784
757
  error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
785
- tabid: await getPageTargetId(page2).catch(() => tabid2),
786
- url: page2.url(),
787
- title: await page2.title()
758
+ tabid: await getPageTargetId(page).catch(() => tabid),
759
+ url: page.url(),
760
+ title: await page.title()
788
761
  };
789
762
  }
790
- await injectIntoPage(page2);
763
+ await injectIntoPage(page);
791
764
  return {
792
765
  success: true,
793
- tabid: await getPageTargetId(page2),
794
- url: page2.url(),
795
- title: await page2.title()
766
+ tabid: await getPageTargetId(page),
767
+ url: page.url(),
768
+ title: await page.title()
796
769
  };
797
770
  } finally {
798
- await browser2.disconnect();
771
+ await browser.disconnect();
799
772
  }
800
773
  }
801
774
 
802
775
  // package.json
803
776
  var package_default = {
804
777
  name: "@opentiny/webmcp-cli",
805
- version: "0.0.1-alpha.6",
778
+ version: "0.0.3",
806
779
  type: "module",
807
780
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
808
781
  main: "dist/index.js",
@@ -860,8 +833,97 @@ function parseTabId(id) {
860
833
  if (!id) return void 0;
861
834
  return id;
862
835
  }
863
- function handleCommandError(error, commandName) {
836
+ function cleanOldLogs(baseDir, logDir) {
837
+ try {
838
+ const oldLogFile = path2.join(baseDir, "webmcp-cli.log");
839
+ if (fs2.existsSync(oldLogFile)) {
840
+ try {
841
+ fs2.unlinkSync(oldLogFile);
842
+ } catch {
843
+ }
844
+ }
845
+ if (fs2.existsSync(baseDir)) {
846
+ const baseFiles = fs2.readdirSync(baseDir);
847
+ for (const file of baseFiles) {
848
+ if (/^webmcp-cli-\d{4}-\d{2}-\d{2}\.log$/.test(file)) {
849
+ try {
850
+ fs2.unlinkSync(path2.join(baseDir, file));
851
+ } catch {
852
+ }
853
+ }
854
+ }
855
+ }
856
+ if (!fs2.existsSync(logDir)) return;
857
+ const files = fs2.readdirSync(logDir);
858
+ const logFilePattern = /^webmcp-cli-(\d{4}-\d{2}-\d{2})\.log$/;
859
+ const now = Date.now();
860
+ const maxAgeMs = 7 * 24 * 60 * 60 * 1e3;
861
+ for (const file of files) {
862
+ const match = file.match(logFilePattern);
863
+ if (match) {
864
+ const fileDateStr = match[1];
865
+ const fileDate = new Date(fileDateStr).getTime();
866
+ if (isNaN(fileDate)) continue;
867
+ if (now - fileDate > maxAgeMs) {
868
+ try {
869
+ fs2.unlinkSync(path2.join(logDir, file));
870
+ } catch {
871
+ }
872
+ }
873
+ }
874
+ }
875
+ } catch (e) {
876
+ }
877
+ }
878
+ function writeLog(commandName, args, result, error) {
879
+ try {
880
+ const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os2.homedir(), ".webmcp_chrome_profile");
881
+ const logDir = path2.join(baseDir, "logs");
882
+ if (!fs2.existsSync(logDir)) {
883
+ fs2.mkdirSync(logDir, { recursive: true });
884
+ }
885
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
886
+ const logFile = path2.join(logDir, `webmcp-cli-${todayStr}.log`);
887
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
888
+ let logText = `========================================
889
+ `;
890
+ logText += `[${timestamp}] COMMAND: ${commandName}
891
+ `;
892
+ logText += `ARGS:
893
+ ${JSON.stringify(args, null, 2)}
894
+
895
+ `;
896
+ if (error) {
897
+ logText += `ERROR:
898
+ ${error instanceof Error ? error.stack : String(error)}
899
+ `;
900
+ } else if (result) {
901
+ logText += `RESULT:
902
+ `;
903
+ try {
904
+ if (result && typeof result === "object") {
905
+ logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
906
+ } else {
907
+ logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
908
+ }
909
+ } catch {
910
+ logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
911
+ }
912
+ } else {
913
+ logText += `RESULT: null
914
+ `;
915
+ }
916
+ logText += `========================================
917
+
918
+ `;
919
+ fs2.appendFileSync(logFile, logText, "utf-8");
920
+ cleanOldLogs(baseDir, logDir);
921
+ } catch (e) {
922
+ }
923
+ }
924
+ function handleCommandError(error, commandName, args) {
864
925
  const msg = error instanceof Error ? error.message : String(error);
926
+ writeLog(commandName, args, null, error);
865
927
  console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
866
928
  process.exit(1);
867
929
  }
@@ -871,12 +933,14 @@ program.name("webmcp-cli").description("WebMCP CLI for interacting with browser
871
933
  process.env.WEBMCP_WORKSPACE = opts.workspace;
872
934
  }
873
935
  });
874
- program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\u72B6\u6001\uFF08\u5185\u5BB9\u3001\u6240\u6709\u9875\u7B7E\u5217\u8868\u3001\u53EF\u7528 WebMCP \u5DE5\u5177\u5217\u8868\uFF09").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (options) => {
936
+ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\u5BFC\u822A\u5143\u6570\u636E\uFF08url\u3001title\u3001webmcpTools\u3001\u6240\u6709\u9875\u7B7E\u5217\u8868\uFF09").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (options) => {
937
+ const args = { tabid: parseTabId(options.tabid) };
875
938
  try {
876
- const result2 = await stateCommand({ tabid: parseTabId(options.tabid) });
877
- console.log(JSON.stringify(result2, null, 2));
939
+ const result = await stateCommand(args);
940
+ writeLog("state", args, result);
941
+ console.log(JSON.stringify(result, null, 2));
878
942
  } catch (error) {
879
- handleCommandError(error, "state");
943
+ handleCommandError(error, "state", args);
880
944
  }
881
945
  });
882
946
  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(
@@ -887,6 +951,8 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
887
951
  @base64file:<path> \u8BFB\u53D6\u6587\u4EF6\u5E76 Base64 \u7F16\u7801
888
952
  \u793A\u4F8B\uFF1Awebmcp-cli run mytool '{"content":"@base64file:./doc.md"}'`
889
953
  ).action(async (toolName, args, options) => {
954
+ let finalArgsJson = "";
955
+ const rawArgs = { toolName, args, file: options.file, tabid: options.tabid };
890
956
  try {
891
957
  let fileContent;
892
958
  let fileBaseDir = process.cwd();
@@ -899,75 +965,78 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
899
965
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 "${filePath}": ${e.message}`);
900
966
  }
901
967
  }
902
- const finalArgsJson = prepareRunArgsJson(args ?? [], fileContent, fileBaseDir);
903
- const result2 = await runCommand({
968
+ finalArgsJson = prepareRunArgsJson(args ?? [], fileContent, fileBaseDir);
969
+ const result = await runCommand({
904
970
  toolName,
905
971
  argsJson: finalArgsJson,
906
972
  tabid: parseTabId(options.tabid)
907
973
  });
908
- console.log(JSON.stringify(result2, null, 2));
909
- } catch (error) {
910
- handleCommandError(error, "run");
911
- }
912
- });
913
- program.command("evaluate <jsScript>").description("\u5411\u5F53\u524D\u7F51\u9875\u6216\u6307\u5B9A\u9875\u7B7E\u6CE8\u5165\u5E76\u6267\u884C JavaScript \u4EE3\u7801").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (jsScript2, options) => {
914
- try {
915
- const result2 = await evaluateCommand({
916
- jsScript: jsScript2,
917
- tabid: parseTabId(options.tabid)
918
- });
919
- console.log(JSON.stringify(result2, null, 2));
974
+ writeLog("run", { ...rawArgs, finalArgsJson }, result);
975
+ console.log(JSON.stringify(result, null, 2));
920
976
  } catch (error) {
921
- handleCommandError(error, "evaluate");
977
+ handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
922
978
  }
923
979
  });
924
980
  program.command("clipboard <content>").description("\u5C06\u5185\u5BB9\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F").action(async (content) => {
981
+ const args = { content };
925
982
  try {
926
983
  await setClipboard(content);
927
- console.log(JSON.stringify({ success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" }, null, 2));
984
+ const result = { success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" };
985
+ writeLog("clipboard", args, result);
986
+ console.log(JSON.stringify(result, null, 2));
928
987
  } catch (error) {
929
- handleCommandError(error, "clipboard");
988
+ handleCommandError(error, "clipboard", args);
930
989
  }
931
990
  });
932
991
  var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
933
992
  tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
993
+ const args = { url };
934
994
  try {
935
- const result2 = await tabsOpenCommand(url);
936
- console.log(JSON.stringify(result2, null, 2));
995
+ const result = await tabsOpenCommand(url);
996
+ writeLog("tabs open", args, result);
997
+ console.log(JSON.stringify(result, null, 2));
937
998
  } catch (error) {
938
- handleCommandError(error, "tabs open");
999
+ handleCommandError(error, "tabs open", args);
939
1000
  }
940
1001
  });
941
- tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
1002
+ tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
1003
+ const args = { tabid };
942
1004
  try {
943
- const result2 = await tabsCloseCommand(tabid2);
944
- console.log(JSON.stringify(result2, null, 2));
1005
+ const result = await tabsCloseCommand(tabid);
1006
+ writeLog("tabs close", args, result);
1007
+ console.log(JSON.stringify(result, null, 2));
945
1008
  } catch (error) {
946
- handleCommandError(error, "tabs close");
1009
+ handleCommandError(error, "tabs close", args);
947
1010
  }
948
1011
  });
949
- tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
1012
+ tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
1013
+ const args = { tabid };
950
1014
  try {
951
- const result2 = await tabsSwitchCommand(tabid2);
952
- console.log(JSON.stringify(result2, null, 2));
1015
+ const result = await tabsSwitchCommand(tabid);
1016
+ writeLog("tabs switch", args, result);
1017
+ console.log(JSON.stringify(result, null, 2));
953
1018
  } catch (error) {
954
- handleCommandError(error, "tabs switch");
1019
+ handleCommandError(error, "tabs switch", args);
955
1020
  }
956
1021
  });
957
- tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid2) => {
1022
+ tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
1023
+ const args = { tabid: parseTabId(tabid) };
958
1024
  try {
959
- const result2 = await tabsBackCommand(parseTabId(tabid2));
960
- console.log(JSON.stringify(result2, null, 2));
1025
+ const result = await tabsBackCommand(args.tabid);
1026
+ writeLog("tabs back", args, result);
1027
+ console.log(JSON.stringify(result, null, 2));
961
1028
  } catch (error) {
962
- handleCommandError(error, "tabs back");
1029
+ handleCommandError(error, "tabs back", args);
963
1030
  }
964
1031
  });
965
- tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid2) => {
1032
+ tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
1033
+ const args = { tabid: parseTabId(tabid) };
966
1034
  try {
967
- const result2 = await tabsForwardCommand(parseTabId(tabid2));
968
- console.log(JSON.stringify(result2, null, 2));
1035
+ const result = await tabsForwardCommand(args.tabid);
1036
+ writeLog("tabs forward", args, result);
1037
+ console.log(JSON.stringify(result, null, 2));
969
1038
  } catch (error) {
970
- handleCommandError(error, "tabs forward");
1039
+ handleCommandError(error, "tabs forward", args);
971
1040
  }
972
1041
  });
973
1042
  program.parse(process.argv);