@opentiny/webmcp-cli 0.0.3 → 0.0.4

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
@@ -184,21 +184,28 @@ ${hint}`);
184
184
  }
185
185
  }
186
186
  function prepareRunArgsJson(args, fileContent, baseDir) {
187
- let raw = "";
188
187
  if (fileContent !== void 0) {
189
- raw = fileContent.trim();
190
- } else if (args.length > 0) {
191
- const joined = joinShellArgs(args);
192
- if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
193
- raw = joined;
194
- } else if (args.every((arg) => arg.includes("="))) {
195
- raw = buildArgsFromKeyValuePairs(args);
196
- } else {
197
- raw = joined;
188
+ const trimmed = fileContent.trim();
189
+ if (!trimmed) {
190
+ throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
198
191
  }
192
+ const expanded2 = expandFileRefs(trimmed, baseDir);
193
+ return parseArgsJson(expanded2);
199
194
  }
200
- if (!raw) {
201
- throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
195
+ if (args.length === 0) {
196
+ return "{}";
197
+ }
198
+ const joined = joinShellArgs(args);
199
+ let raw = "";
200
+ if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
201
+ raw = joined;
202
+ } else if (args.every((arg) => arg.includes("="))) {
203
+ raw = buildArgsFromKeyValuePairs(args);
204
+ } else {
205
+ raw = joined;
206
+ }
207
+ if (!raw.trim()) {
208
+ throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
202
209
  }
203
210
  const expanded = expandFileRefs(raw, baseDir);
204
211
  return parseArgsJson(expanded);
@@ -218,6 +225,30 @@ var __filename = (0, import_url2.fileURLToPath)(import_meta.url);
218
225
  var __dirname = import_path2.default.dirname(__filename);
219
226
  var CDP_PORT = 9222;
220
227
  var CDP_URL = `http://localhost:${CDP_PORT}`;
228
+ function getWorkspaceDir() {
229
+ return process.env.WEBMCP_WORKSPACE || import_path2.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
230
+ }
231
+ function getLastTabIdFilePath() {
232
+ return import_path2.default.join(getWorkspaceDir(), ".last-tab-id");
233
+ }
234
+ function setLastActiveTabId(tabid) {
235
+ try {
236
+ const dir = getWorkspaceDir();
237
+ if (!import_fs2.default.existsSync(dir)) {
238
+ import_fs2.default.mkdirSync(dir, { recursive: true });
239
+ }
240
+ import_fs2.default.writeFileSync(getLastTabIdFilePath(), tabid, "utf-8");
241
+ } catch {
242
+ }
243
+ }
244
+ function getLastActiveTabId() {
245
+ try {
246
+ const tabid = import_fs2.default.readFileSync(getLastTabIdFilePath(), "utf-8").trim();
247
+ return tabid || null;
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
221
252
  function httpGet(url, timeoutMs = 1500) {
222
253
  return new Promise((resolve3, reject) => {
223
254
  const req = import_http.default.get(url, { timeout: timeoutMs }, (res) => {
@@ -476,33 +507,42 @@ async function getTargetPage(browser, tabid) {
476
507
  }
477
508
  targetPage = await target.page();
478
509
  } else {
479
- try {
480
- const urls = [
481
- `http://localhost:${CDP_PORT}/json/list`,
482
- `http://127.0.0.1:${CDP_PORT}/json/list`
483
- ];
484
- let activeTargetId = null;
485
- for (const url of urls) {
486
- try {
487
- const body = await httpGet(url, 1e3);
488
- const targetsData = JSON.parse(body);
489
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
490
- if (active) {
491
- activeTargetId = active.id;
492
- break;
510
+ const lastTabId = getLastActiveTabId();
511
+ if (lastTabId) {
512
+ const lastTarget = findPageTargetByTabId(browser, lastTabId);
513
+ if (lastTarget) {
514
+ targetPage = await lastTarget.page();
515
+ }
516
+ }
517
+ if (!targetPage) {
518
+ try {
519
+ const urls = [
520
+ `http://localhost:${CDP_PORT}/json/list`,
521
+ `http://127.0.0.1:${CDP_PORT}/json/list`
522
+ ];
523
+ let activeTargetId = null;
524
+ for (const url of urls) {
525
+ try {
526
+ const body = await httpGet(url, 1e3);
527
+ const targetsData = JSON.parse(body);
528
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
529
+ if (active) {
530
+ activeTargetId = active.id;
531
+ break;
532
+ }
533
+ } catch {
493
534
  }
494
- } catch {
495
535
  }
496
- }
497
- if (activeTargetId) {
498
- for (const target of pageTargets) {
499
- if (getTargetIdFromTarget(target) === activeTargetId) {
500
- targetPage = await target.page();
501
- break;
536
+ if (activeTargetId) {
537
+ for (const target of pageTargets) {
538
+ if (getTargetIdFromTarget(target) === activeTargetId) {
539
+ targetPage = await target.page();
540
+ break;
541
+ }
502
542
  }
503
543
  }
544
+ } catch {
504
545
  }
505
- } catch {
506
546
  }
507
547
  if (!targetPage) {
508
548
  const lastTarget = pageTargets[pageTargets.length - 1];
@@ -583,6 +623,12 @@ async function injectDomainTools(page) {
583
623
  if (!import_fs2.default.existsSync(toolBundlePath)) {
584
624
  return;
585
625
  }
626
+ const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
627
+ const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
628
+ const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
629
+ if (alreadyInjected) {
630
+ return;
631
+ }
586
632
  console.log(import_picocolors.default.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
587
633
  const toolScript = import_fs2.default.readFileSync(toolBundlePath, "utf-8");
588
634
  try {
@@ -611,6 +657,7 @@ async function stateCommand({ tabid }) {
611
657
  }
612
658
  return { url, title, webmcpTools };
613
659
  });
660
+ const activeTabid = await getPageTargetId(page).catch(() => null);
614
661
  const pages = await browser.pages();
615
662
  const tabs2 = await Promise.all(
616
663
  pages.map(async (p) => {
@@ -630,6 +677,7 @@ async function stateCommand({ tabid }) {
630
677
  );
631
678
  return {
632
679
  ...state,
680
+ activeTabid,
633
681
  tabs: tabs2.filter(Boolean)
634
682
  };
635
683
  } finally {
@@ -646,28 +694,51 @@ async function runCommand({
646
694
  const browser = await connectBrowser();
647
695
  try {
648
696
  const page = await getTargetPage(browser, tabid);
697
+ const urlBefore = page.url();
649
698
  const cleanedArgs = argsJson.trim();
650
- const result = await page.evaluate(async (name, inputString) => {
651
- const mcp = navigator.modelContextTesting || navigator.modelContext;
652
- if (!mcp || typeof mcp.executeTool !== "function") {
653
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
654
- }
655
- let res = await mcp.executeTool(name, inputString);
656
- if (typeof res === "string") {
699
+ let result;
700
+ try {
701
+ result = await page.evaluate(async (name, inputString) => {
702
+ const mcp = navigator.modelContextTesting || navigator.modelContext;
703
+ 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)");
705
+ }
706
+ let res = await mcp.executeTool(name, inputString);
707
+ if (typeof res === "string") {
708
+ try {
709
+ res = JSON.parse(res);
710
+ } catch {
711
+ }
712
+ }
713
+ if (res === void 0 || res === null) {
714
+ throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
715
+ }
716
+ if (typeof res === "object" && res.success === false) {
717
+ const failed = res;
718
+ throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
719
+ }
720
+ return res;
721
+ }, toolName, cleanedArgs);
722
+ } catch (evalError) {
723
+ const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
724
+ const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
725
+ if (isContextDestroyed) {
726
+ await new Promise((resolve3) => setTimeout(resolve3, 800));
727
+ let newUrl = "";
657
728
  try {
658
- res = JSON.parse(res);
729
+ newUrl = page.url();
659
730
  } catch {
660
731
  }
732
+ if (newUrl && newUrl !== urlBefore) {
733
+ return {
734
+ success: true,
735
+ message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`,
736
+ navigatedTo: newUrl
737
+ };
738
+ }
661
739
  }
662
- if (res === void 0 || res === null) {
663
- throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
664
- }
665
- if (typeof res === "object" && res.success === false) {
666
- const failed = res;
667
- throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
668
- }
669
- return res;
670
- }, toolName, cleanedArgs);
740
+ throw evalError;
741
+ }
671
742
  return result;
672
743
  } finally {
673
744
  await browser.disconnect();
@@ -699,11 +770,19 @@ async function tabsOpenCommand(url) {
699
770
  await page.goto(url, { waitUntil: "domcontentloaded" });
700
771
  await injectIntoPage(page);
701
772
  await new Promise((resolve3) => setTimeout(resolve3, 500));
773
+ let tabid;
774
+ try {
775
+ tabid = await getPageTargetId(page);
776
+ await activateTabById(browser, tabid);
777
+ setLastActiveTabId(tabid);
778
+ } catch (e) {
779
+ console.warn("Best-effort tab activation failed:", e);
780
+ }
702
781
  return {
703
782
  success: true,
704
- tabid: await getPageTargetId(page),
783
+ tabid,
705
784
  url: page.url(),
706
- title: await page.title()
785
+ title: await page.title().catch(() => "")
707
786
  };
708
787
  } finally {
709
788
  await browser.disconnect();
@@ -735,9 +814,11 @@ async function tabsSwitchCommand(tabid) {
735
814
  try {
736
815
  await activateTabById(browser, tabid);
737
816
  const page = await getTargetPage(browser, tabid);
817
+ const realTabid = await getPageTargetId(page);
818
+ setLastActiveTabId(realTabid);
738
819
  return {
739
820
  success: true,
740
- tabid: await getPageTargetId(page),
821
+ tabid: realTabid,
741
822
  url: page.url(),
742
823
  title: await page.title()
743
824
  };
@@ -799,7 +880,7 @@ async function tabsForwardCommand(tabid) {
799
880
  // package.json
800
881
  var package_default = {
801
882
  name: "@opentiny/webmcp-cli",
802
- version: "0.0.3",
883
+ version: "0.0.4",
803
884
  type: "module",
804
885
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
805
886
  main: "dist/index.js",