@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.js CHANGED
@@ -161,21 +161,28 @@ ${hint}`);
161
161
  }
162
162
  }
163
163
  function prepareRunArgsJson(args, fileContent, baseDir) {
164
- let raw = "";
165
164
  if (fileContent !== void 0) {
166
- raw = fileContent.trim();
167
- } else if (args.length > 0) {
168
- const joined = joinShellArgs(args);
169
- if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
170
- raw = joined;
171
- } else if (args.every((arg) => arg.includes("="))) {
172
- raw = buildArgsFromKeyValuePairs(args);
173
- } else {
174
- raw = joined;
165
+ const trimmed = fileContent.trim();
166
+ if (!trimmed) {
167
+ throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
175
168
  }
169
+ const expanded2 = expandFileRefs(trimmed, baseDir);
170
+ return parseArgsJson(expanded2);
176
171
  }
177
- if (!raw) {
178
- throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
172
+ if (args.length === 0) {
173
+ return "{}";
174
+ }
175
+ const joined = joinShellArgs(args);
176
+ let raw = "";
177
+ if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
178
+ raw = joined;
179
+ } else if (args.every((arg) => arg.includes("="))) {
180
+ raw = buildArgsFromKeyValuePairs(args);
181
+ } else {
182
+ raw = joined;
183
+ }
184
+ if (!raw.trim()) {
185
+ throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
179
186
  }
180
187
  const expanded = expandFileRefs(raw, baseDir);
181
188
  return parseArgsJson(expanded);
@@ -194,6 +201,30 @@ var __filename = fileURLToPath2(import.meta.url);
194
201
  var __dirname = path.dirname(__filename);
195
202
  var CDP_PORT = 9222;
196
203
  var CDP_URL = `http://localhost:${CDP_PORT}`;
204
+ function getWorkspaceDir() {
205
+ return process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
206
+ }
207
+ function getLastTabIdFilePath() {
208
+ return path.join(getWorkspaceDir(), ".last-tab-id");
209
+ }
210
+ function setLastActiveTabId(tabid) {
211
+ try {
212
+ const dir = getWorkspaceDir();
213
+ if (!fs.existsSync(dir)) {
214
+ fs.mkdirSync(dir, { recursive: true });
215
+ }
216
+ fs.writeFileSync(getLastTabIdFilePath(), tabid, "utf-8");
217
+ } catch {
218
+ }
219
+ }
220
+ function getLastActiveTabId() {
221
+ try {
222
+ const tabid = fs.readFileSync(getLastTabIdFilePath(), "utf-8").trim();
223
+ return tabid || null;
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
197
228
  function httpGet(url, timeoutMs = 1500) {
198
229
  return new Promise((resolve3, reject) => {
199
230
  const req = http.get(url, { timeout: timeoutMs }, (res) => {
@@ -452,33 +483,42 @@ async function getTargetPage(browser, tabid) {
452
483
  }
453
484
  targetPage = await target.page();
454
485
  } else {
455
- try {
456
- const urls = [
457
- `http://localhost:${CDP_PORT}/json/list`,
458
- `http://127.0.0.1:${CDP_PORT}/json/list`
459
- ];
460
- let activeTargetId = null;
461
- for (const url of urls) {
462
- try {
463
- const body = await httpGet(url, 1e3);
464
- const targetsData = JSON.parse(body);
465
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
466
- if (active) {
467
- activeTargetId = active.id;
468
- break;
486
+ const lastTabId = getLastActiveTabId();
487
+ if (lastTabId) {
488
+ const lastTarget = findPageTargetByTabId(browser, lastTabId);
489
+ if (lastTarget) {
490
+ targetPage = await lastTarget.page();
491
+ }
492
+ }
493
+ if (!targetPage) {
494
+ try {
495
+ const urls = [
496
+ `http://localhost:${CDP_PORT}/json/list`,
497
+ `http://127.0.0.1:${CDP_PORT}/json/list`
498
+ ];
499
+ let activeTargetId = null;
500
+ for (const url of urls) {
501
+ try {
502
+ const body = await httpGet(url, 1e3);
503
+ const targetsData = JSON.parse(body);
504
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
505
+ if (active) {
506
+ activeTargetId = active.id;
507
+ break;
508
+ }
509
+ } catch {
469
510
  }
470
- } catch {
471
511
  }
472
- }
473
- if (activeTargetId) {
474
- for (const target of pageTargets) {
475
- if (getTargetIdFromTarget(target) === activeTargetId) {
476
- targetPage = await target.page();
477
- break;
512
+ if (activeTargetId) {
513
+ for (const target of pageTargets) {
514
+ if (getTargetIdFromTarget(target) === activeTargetId) {
515
+ targetPage = await target.page();
516
+ break;
517
+ }
478
518
  }
479
519
  }
520
+ } catch {
480
521
  }
481
- } catch {
482
522
  }
483
523
  if (!targetPage) {
484
524
  const lastTarget = pageTargets[pageTargets.length - 1];
@@ -559,6 +599,12 @@ async function injectDomainTools(page) {
559
599
  if (!fs.existsSync(toolBundlePath)) {
560
600
  return;
561
601
  }
602
+ const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
603
+ const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
604
+ const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
605
+ if (alreadyInjected) {
606
+ return;
607
+ }
562
608
  console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
563
609
  const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
564
610
  try {
@@ -587,6 +633,7 @@ async function stateCommand({ tabid }) {
587
633
  }
588
634
  return { url, title, webmcpTools };
589
635
  });
636
+ const activeTabid = await getPageTargetId(page).catch(() => null);
590
637
  const pages = await browser.pages();
591
638
  const tabs2 = await Promise.all(
592
639
  pages.map(async (p) => {
@@ -606,6 +653,7 @@ async function stateCommand({ tabid }) {
606
653
  );
607
654
  return {
608
655
  ...state,
656
+ activeTabid,
609
657
  tabs: tabs2.filter(Boolean)
610
658
  };
611
659
  } finally {
@@ -622,28 +670,51 @@ async function runCommand({
622
670
  const browser = await connectBrowser();
623
671
  try {
624
672
  const page = await getTargetPage(browser, tabid);
673
+ const urlBefore = page.url();
625
674
  const cleanedArgs = argsJson.trim();
626
- const result = await page.evaluate(async (name, inputString) => {
627
- const mcp = navigator.modelContextTesting || navigator.modelContext;
628
- if (!mcp || typeof mcp.executeTool !== "function") {
629
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
630
- }
631
- let res = await mcp.executeTool(name, inputString);
632
- if (typeof res === "string") {
675
+ let result;
676
+ try {
677
+ result = await page.evaluate(async (name, inputString) => {
678
+ const mcp = navigator.modelContextTesting || navigator.modelContext;
679
+ if (!mcp || typeof mcp.executeTool !== "function") {
680
+ throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
681
+ }
682
+ let res = await mcp.executeTool(name, inputString);
683
+ if (typeof res === "string") {
684
+ try {
685
+ res = JSON.parse(res);
686
+ } catch {
687
+ }
688
+ }
689
+ if (res === void 0 || res === null) {
690
+ throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
691
+ }
692
+ if (typeof res === "object" && res.success === false) {
693
+ const failed = res;
694
+ throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
695
+ }
696
+ return res;
697
+ }, toolName, cleanedArgs);
698
+ } catch (evalError) {
699
+ const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
700
+ const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
701
+ if (isContextDestroyed) {
702
+ await new Promise((resolve3) => setTimeout(resolve3, 800));
703
+ let newUrl = "";
633
704
  try {
634
- res = JSON.parse(res);
705
+ newUrl = page.url();
635
706
  } catch {
636
707
  }
708
+ if (newUrl && newUrl !== urlBefore) {
709
+ return {
710
+ success: true,
711
+ message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`,
712
+ navigatedTo: newUrl
713
+ };
714
+ }
637
715
  }
638
- if (res === void 0 || res === null) {
639
- throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
640
- }
641
- if (typeof res === "object" && res.success === false) {
642
- const failed = res;
643
- throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
644
- }
645
- return res;
646
- }, toolName, cleanedArgs);
716
+ throw evalError;
717
+ }
647
718
  return result;
648
719
  } finally {
649
720
  await browser.disconnect();
@@ -675,11 +746,19 @@ async function tabsOpenCommand(url) {
675
746
  await page.goto(url, { waitUntil: "domcontentloaded" });
676
747
  await injectIntoPage(page);
677
748
  await new Promise((resolve3) => setTimeout(resolve3, 500));
749
+ let tabid;
750
+ try {
751
+ tabid = await getPageTargetId(page);
752
+ await activateTabById(browser, tabid);
753
+ setLastActiveTabId(tabid);
754
+ } catch (e) {
755
+ console.warn("Best-effort tab activation failed:", e);
756
+ }
678
757
  return {
679
758
  success: true,
680
- tabid: await getPageTargetId(page),
759
+ tabid,
681
760
  url: page.url(),
682
- title: await page.title()
761
+ title: await page.title().catch(() => "")
683
762
  };
684
763
  } finally {
685
764
  await browser.disconnect();
@@ -711,9 +790,11 @@ async function tabsSwitchCommand(tabid) {
711
790
  try {
712
791
  await activateTabById(browser, tabid);
713
792
  const page = await getTargetPage(browser, tabid);
793
+ const realTabid = await getPageTargetId(page);
794
+ setLastActiveTabId(realTabid);
714
795
  return {
715
796
  success: true,
716
- tabid: await getPageTargetId(page),
797
+ tabid: realTabid,
717
798
  url: page.url(),
718
799
  title: await page.title()
719
800
  };
@@ -775,7 +856,7 @@ async function tabsForwardCommand(tabid) {
775
856
  // package.json
776
857
  var package_default = {
777
858
  name: "@opentiny/webmcp-cli",
778
- version: "0.0.3",
859
+ version: "0.0.4",
779
860
  type: "module",
780
861
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
781
862
  main: "dist/index.js",