@opentiny/webmcp-cli 0.0.1-alpha.1 → 0.0.1-alpha.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
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/bin.ts
10
10
  import { Command } from "commander";
11
- import pc3 from "picocolors";
11
+ import pc2 from "picocolors";
12
12
 
13
13
  // src/browser.ts
14
14
  import puppeteer from "puppeteer-core";
@@ -94,31 +94,54 @@ async function killProcessOnPortIfZombie(port) {
94
94
  } catch (e) {
95
95
  }
96
96
  }
97
- function getDefaultChromePath() {
97
+ function getDefaultBrowserPath() {
98
98
  const platform = os.platform();
99
99
  if (platform === "darwin") {
100
- return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
100
+ const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
101
+ if (fs.existsSync(chromePath)) return { path: chromePath, name: "Chrome" };
102
+ const edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge";
103
+ if (fs.existsSync(edgePath)) return { path: edgePath, name: "Edge" };
104
+ return null;
101
105
  } else if (platform === "win32") {
102
- const paths = [
106
+ const chromePaths = [
103
107
  process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
104
108
  process.env.PROGRAMFILES + "\\Google\\Chrome\\Application\\chrome.exe",
105
109
  process.env["PROGRAMFILES(X86)"] + "\\Google\\Chrome\\Application\\chrome.exe"
106
110
  ];
107
- return paths.find((p) => fs.existsSync(p)) || null;
111
+ const foundChrome = chromePaths.find((p) => fs.existsSync(p));
112
+ if (foundChrome) return { path: foundChrome, name: "Chrome" };
113
+ const edgePaths = [
114
+ process.env.LOCALAPPDATA + "\\Microsoft\\Edge\\Application\\msedge.exe",
115
+ process.env.PROGRAMFILES + "\\Microsoft\\Edge\\Application\\msedge.exe",
116
+ process.env["PROGRAMFILES(X86)"] + "\\Microsoft\\Edge\\Application\\msedge.exe",
117
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
118
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"
119
+ ];
120
+ const foundEdge = edgePaths.find((p) => fs.existsSync(p));
121
+ if (foundEdge) return { path: foundEdge, name: "Edge" };
122
+ return null;
108
123
  } else {
109
- const paths = ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"];
110
- return paths.find((p) => fs.existsSync(p)) || null;
124
+ const paths = [
125
+ { p: "/usr/bin/google-chrome", name: "Chrome" },
126
+ { p: "/usr/bin/google-chrome-stable", name: "Chrome" },
127
+ { p: "/usr/bin/chromium", name: "Chromium" },
128
+ { p: "/usr/bin/chromium-browser", name: "Chromium" },
129
+ { p: "/usr/bin/microsoft-edge", name: "Edge" },
130
+ { p: "/usr/bin/microsoft-edge-stable", name: "Edge" }
131
+ ];
132
+ const found = paths.find(({ p }) => fs.existsSync(p));
133
+ return found ? { path: found.p, name: found.name } : null;
111
134
  }
112
135
  }
113
- async function startChromeInBackground() {
114
- const chromePath = getDefaultChromePath();
115
- if (!chromePath || !fs.existsSync(chromePath)) {
116
- throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
136
+ async function startBrowserInBackground() {
137
+ const browserInfo = getDefaultBrowserPath();
138
+ if (!browserInfo || !fs.existsSync(browserInfo.path)) {
139
+ throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6216 Edge \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
117
140
  }
118
- console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 Chrome \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
141
+ console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
119
142
  const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
120
143
  const child = spawn(
121
- chromePath,
144
+ browserInfo.path,
122
145
  [
123
146
  `--remote-debugging-port=${CDP_PORT}`,
124
147
  `--user-data-dir=${userDataDir}`,
@@ -138,7 +161,7 @@ async function startChromeInBackground() {
138
161
  try {
139
162
  const response = await fetchWithTimeout(url, 1e3);
140
163
  if (response.ok) {
141
- console.log(pc.green("Chrome \u542F\u52A8\u5E76\u5C31\u7EEA\u3002"));
164
+ console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
142
165
  return;
143
166
  }
144
167
  } catch (err) {
@@ -148,7 +171,7 @@ async function startChromeInBackground() {
148
171
  }
149
172
  await new Promise((resolve) => setTimeout(resolve, 500));
150
173
  }
151
- throw new Error("Chrome \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002");
174
+ throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
152
175
  }
153
176
  async function connectBrowser() {
154
177
  const targetFilter = (target) => {
@@ -173,7 +196,7 @@ async function connectBrowser() {
173
196
  throw new Error("127.0.0.1 CDP port not responding");
174
197
  }
175
198
  console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
176
- const browser = await promiseWithTimeout(
199
+ const browser2 = await promiseWithTimeout(
177
200
  puppeteer.connect({
178
201
  browserURL: `http://127.0.0.1:${CDP_PORT}`,
179
202
  defaultViewport: null,
@@ -183,7 +206,7 @@ async function connectBrowser() {
183
206
  "puppeteer.connect to 127.0.0.1 timed out"
184
207
  );
185
208
  console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
186
- return browser;
209
+ return browser2;
187
210
  } catch (error) {
188
211
  try {
189
212
  const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
@@ -191,7 +214,7 @@ async function connectBrowser() {
191
214
  throw new Error("localhost CDP port not responding");
192
215
  }
193
216
  console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
194
- const browser = await promiseWithTimeout(
217
+ const browser2 = await promiseWithTimeout(
195
218
  puppeteer.connect({
196
219
  browserURL: `http://localhost:${CDP_PORT}`,
197
220
  defaultViewport: null,
@@ -201,19 +224,19 @@ async function connectBrowser() {
201
224
  "puppeteer.connect to localhost timed out"
202
225
  );
203
226
  console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
204
- return browser;
227
+ return browser2;
205
228
  } catch (error2) {
206
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)}`));
207
230
  try {
208
231
  await killProcessOnPortIfZombie(CDP_PORT);
209
- await startChromeInBackground();
232
+ await startBrowserInBackground();
210
233
  try {
211
234
  const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
212
235
  if (!is127Ready) {
213
236
  throw new Error("127.0.0.1 CDP port not responding after launch");
214
237
  }
215
238
  console.log(pc.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
216
- const browser = await promiseWithTimeout(
239
+ const browser2 = await promiseWithTimeout(
217
240
  puppeteer.connect({
218
241
  browserURL: `http://127.0.0.1:${CDP_PORT}`,
219
242
  defaultViewport: null,
@@ -223,14 +246,14 @@ async function connectBrowser() {
223
246
  "puppeteer.connect to 127.0.0.1 after launch timed out"
224
247
  );
225
248
  console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
226
- return browser;
249
+ return browser2;
227
250
  } catch (e) {
228
251
  const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
229
252
  if (!isLocalhostReady) {
230
253
  throw new Error("localhost CDP port not responding after launch");
231
254
  }
232
255
  console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
233
- const browser = await promiseWithTimeout(
256
+ const browser2 = await promiseWithTimeout(
234
257
  puppeteer.connect({
235
258
  browserURL: `http://localhost:${CDP_PORT}`,
236
259
  defaultViewport: null,
@@ -240,20 +263,20 @@ async function connectBrowser() {
240
263
  "puppeteer.connect to localhost after launch timed out"
241
264
  );
242
265
  console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
243
- return browser;
266
+ return browser2;
244
267
  }
245
268
  } catch (launchError) {
246
269
  const msg = launchError instanceof Error ? launchError.message : String(launchError);
247
270
  console.error(pc.red(`\u65E0\u6CD5\u8FDE\u63A5\u6216\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
248
- 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 and \u767B\u5F55\u6001\uFF09\uFF0C\u5982\u679C\u4F60\u7684 Chrome \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`));
249
- console.error(pc.yellow(`\u{1F449} \u89E3\u51B3\u529E\u6CD5\uFF1A\u8BF7\u5148\u5B8C\u5168\u9000\u51FA\u5F53\u524D\u7684 Chrome \u6D4F\u89C8\u5668\uFF08\u5728 Mac \u4E0A\u6309 Cmd+Q\uFF09\uFF0C\u7136\u540E\u518D\u91CD\u65B0\u8FD0\u884C\u547D\u4EE4\u3002`));
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`));
250
273
  throw new Error("Browser connection failed.");
251
274
  }
252
275
  }
253
276
  }
254
277
  }
255
- async function getPageTargetId(page) {
256
- const session = await page.target().createCDPSession();
278
+ async function getPageTargetId(page2) {
279
+ const session = await page2.target().createCDPSession();
257
280
  try {
258
281
  const { targetInfo } = await session.send("Target.getTargetInfo");
259
282
  return targetInfo.targetId;
@@ -262,9 +285,8 @@ async function getPageTargetId(page) {
262
285
  });
263
286
  }
264
287
  }
265
- async function getTargetPage(browser, tabid) {
266
- const targets = browser.targets();
267
- const pageTargets = targets.filter((t) => {
288
+ function getPageTargets(browser2) {
289
+ return browser2.targets().filter((t) => {
268
290
  try {
269
291
  const type = (typeof t.type === "function" ? t.type() : t.type) || "";
270
292
  const url = (typeof t.url === "function" ? t.url() : t.url) || "";
@@ -273,23 +295,53 @@ async function getTargetPage(browser, tabid) {
273
295
  return false;
274
296
  }
275
297
  });
298
+ }
299
+ function getTargetIdFromTarget(target) {
300
+ return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
301
+ }
302
+ function findPageTargetByTabId(browser2, tabid2) {
303
+ const pageTargets = getPageTargets(browser2);
304
+ return pageTargets.find((t) => {
305
+ const tid = getTargetIdFromTarget(t);
306
+ return tid === tabid2 || tid.includes(tabid2);
307
+ }) ?? null;
308
+ }
309
+ async function activateTabById(browser2, tabid2) {
310
+ const target = findPageTargetByTabId(browser2, tabid2);
311
+ if (!target) {
312
+ throw new Error(`Tab with targetId "${tabid2}" not found.`);
313
+ }
314
+ const realTabId = getTargetIdFromTarget(target);
315
+ const pages = await browser2.pages();
316
+ let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
317
+ if (!sessionPage && pages.length > 0) {
318
+ sessionPage = pages[0];
319
+ }
320
+ if (!sessionPage) {
321
+ sessionPage = await browser2.newPage();
322
+ }
323
+ const session = await sessionPage.createCDPSession();
324
+ try {
325
+ await session.send("Target.activateTarget", { targetId: realTabId });
326
+ } finally {
327
+ await session.detach().catch(() => {
328
+ });
329
+ }
330
+ }
331
+ async function getTargetPage(browser2, tabid2) {
332
+ const pageTargets = getPageTargets(browser2);
276
333
  if (pageTargets.length === 0) {
277
- const newPage = await browser.newPage();
334
+ const newPage = await browser2.newPage();
278
335
  await injectWebMCPPolyfillAndTools(newPage);
279
336
  return newPage;
280
337
  }
281
338
  let targetPage = null;
282
- if (tabid !== void 0) {
283
- for (const target of pageTargets) {
284
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
285
- if (tid === tabid || tid.includes(tabid)) {
286
- targetPage = await target.page();
287
- break;
288
- }
289
- }
290
- if (!targetPage) {
291
- throw new Error(`Tab with targetId "${tabid}" not found.`);
339
+ if (tabid2 !== void 0) {
340
+ const target = findPageTargetByTabId(browser2, tabid2);
341
+ if (!target) {
342
+ throw new Error(`Tab with targetId "${tabid2}" not found.`);
292
343
  }
344
+ targetPage = await target.page();
293
345
  } else {
294
346
  try {
295
347
  const urls = [
@@ -313,8 +365,7 @@ async function getTargetPage(browser, tabid) {
313
365
  }
314
366
  if (activeTargetId) {
315
367
  for (const target of pageTargets) {
316
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
317
- if (tid === activeTargetId) {
368
+ if (getTargetIdFromTarget(target) === activeTargetId) {
318
369
  targetPage = await target.page();
319
370
  break;
320
371
  }
@@ -333,11 +384,11 @@ async function getTargetPage(browser, tabid) {
333
384
  await injectWebMCPPolyfillAndTools(targetPage);
334
385
  return targetPage;
335
386
  }
336
- async function injectIntoPage(page) {
337
- await injectWebMCPPolyfillAndTools(page, true);
387
+ async function injectIntoPage(page2) {
388
+ await injectWebMCPPolyfillAndTools(page2, true);
338
389
  }
339
- async function injectWebMCPPolyfillAndTools(page, force = false) {
340
- const polyfillReady = !force && await page.evaluate(() => {
390
+ async function injectWebMCPPolyfillAndTools(page2, force = false) {
391
+ const polyfillReady = !force && await page2.evaluate(() => {
341
392
  return !!window.__webmcpcli_init;
342
393
  }).catch(() => false);
343
394
  if (!polyfillReady) {
@@ -348,19 +399,19 @@ async function injectWebMCPPolyfillAndTools(page, force = false) {
348
399
  }
349
400
  const scriptContent = fs.readFileSync(injectScriptPath, "utf-8");
350
401
  try {
351
- await page.evaluate(scriptContent);
402
+ await page2.evaluate(scriptContent);
352
403
  } catch (err) {
353
404
  const msg = err instanceof Error ? err.message : String(err);
354
405
  throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
355
406
  }
356
407
  await new Promise((resolve) => setTimeout(resolve, 300));
357
408
  }
358
- await injectDomainTools(page);
409
+ await injectDomainTools(page2);
359
410
  }
360
- async function injectDomainTools(page) {
411
+ async function injectDomainTools(page2) {
361
412
  let hostname;
362
413
  try {
363
- const url = new URL(page.url());
414
+ const url = new URL(page2.url());
364
415
  hostname = url.hostname;
365
416
  } catch {
366
417
  return;
@@ -372,7 +423,7 @@ async function injectDomainTools(page) {
372
423
  console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
373
424
  const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
374
425
  try {
375
- await page.evaluate(toolScript);
426
+ await page2.evaluate(toolScript);
376
427
  console.log(pc.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
377
428
  } catch (err) {
378
429
  const msg = err instanceof Error ? err.message : String(err);
@@ -381,11 +432,12 @@ async function injectDomainTools(page) {
381
432
  }
382
433
 
383
434
  // src/commands/state.ts
384
- async function stateCommand({ tabid }) {
385
- const browser = await connectBrowser();
435
+ async function stateCommand({ tabid: tabid2 }) {
436
+ const browser2 = await connectBrowser();
386
437
  try {
387
- const page = await getTargetPage(browser, tabid);
388
- const state = await page.evaluate(async () => {
438
+ const page2 = await getTargetPage(browser2, tabid2);
439
+ await injectIntoPage(page2);
440
+ const state = await page2.evaluate(async () => {
389
441
  const url = document.URL;
390
442
  const title = document.title;
391
443
  const mcp = navigator.modelContextTesting || navigator.modelContext;
@@ -433,27 +485,29 @@ async function stateCommand({ tabid }) {
433
485
  webmcpTools
434
486
  };
435
487
  });
436
- const pages = await browser.pages();
437
- const tabs = await Promise.all(pages.map(async (p) => {
438
- const pUrl = p.url();
439
- if (pUrl.startsWith("devtools://")) return null;
440
- const pTitle = await Promise.race([
441
- p.title().catch(() => "Unknown"),
442
- new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
443
- ]);
444
- return {
445
- // 使用真实的 Chrome target ID,而非数组下标
446
- tabid: await getPageTargetId(p).catch(() => pUrl),
447
- title: pTitle,
448
- url: pUrl
449
- };
450
- }));
488
+ const pages = await browser2.pages();
489
+ const tabs2 = await Promise.all(
490
+ pages.map(async (p) => {
491
+ const pUrl = p.url();
492
+ if (pUrl.startsWith("devtools://")) return null;
493
+ const pTitle = await Promise.race([
494
+ p.title().catch(() => "Unknown"),
495
+ new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
496
+ ]);
497
+ return {
498
+ // 使用真实的 Chrome target ID,而非数组下标
499
+ tabid: await getPageTargetId(p).catch(() => pUrl),
500
+ title: pTitle,
501
+ url: pUrl
502
+ };
503
+ })
504
+ );
451
505
  return {
452
506
  ...state,
453
- tabs: tabs.filter(Boolean)
507
+ tabs: tabs2.filter(Boolean)
454
508
  };
455
509
  } finally {
456
- await browser.disconnect();
510
+ await browser2.disconnect();
457
511
  }
458
512
  }
459
513
 
@@ -461,17 +515,17 @@ async function stateCommand({ tabid }) {
461
515
  async function runCommand({
462
516
  toolName,
463
517
  argsJson,
464
- tabid
518
+ tabid: tabid2
465
519
  }) {
466
- const browser = await connectBrowser();
520
+ const browser2 = await connectBrowser();
467
521
  try {
468
- const page = await getTargetPage(browser, tabid);
522
+ const page2 = await getTargetPage(browser2, tabid2);
469
523
  try {
470
524
  JSON.parse(argsJson);
471
525
  } catch (e) {
472
526
  throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON: ${e.message}`);
473
527
  }
474
- const result = await page.evaluate(async (name, inputString) => {
528
+ const result2 = await page2.evaluate(async (name, inputString) => {
475
529
  const mcp = navigator.modelContextTesting || navigator.modelContext;
476
530
  if (!mcp || typeof mcp.executeTool !== "function") {
477
531
  throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
@@ -485,69 +539,216 @@ async function runCommand({
485
539
  }
486
540
  return res;
487
541
  }, toolName, argsJson);
488
- return result;
542
+ return result2;
489
543
  } finally {
490
- await browser.disconnect();
544
+ await browser2.disconnect();
491
545
  }
492
546
  }
493
547
 
494
- // src/commands/open.ts
495
- import pc2 from "picocolors";
496
- async function openCommand(url, { tabid, newTab }) {
548
+ // src/commands/evaluate.ts
549
+ async function evaluateCommand({
550
+ jsScript,
551
+ tabid
552
+ }) {
497
553
  const browser = await connectBrowser();
498
554
  try {
499
- let page;
500
- if (newTab) {
501
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
502
- page = await browser.newPage();
503
- } else {
504
- console.log(pc2.yellow("openCommand: \u83B7\u53D6\u6D4F\u89C8\u5668 targets..."));
505
- const targets = browser.targets();
506
- const pageTargets = targets.filter((t) => {
507
- try {
508
- const type = (typeof t.type === "function" ? t.type() : t.type) || "";
509
- const urlStr = (typeof t.url === "function" ? t.url() : t.url) || "";
510
- return type === "page" && !urlStr.startsWith("devtools://");
511
- } catch {
512
- return false;
555
+ const page = await getTargetPage(browser, tabid);
556
+ const result = await page.evaluate(async (script) => {
557
+ try {
558
+ const result = eval(script);
559
+ if (result instanceof Promise) {
560
+ return await result;
513
561
  }
514
- });
515
- console.log(pc2.yellow(`openCommand: \u666E\u901A\u9875\u9762 targets \u6570\u91CF: ${pageTargets.length}`));
516
- let selectedTarget;
517
- if (tabid !== void 0) {
518
- console.log(pc2.yellow(`openCommand: \u6B63\u5728\u5339\u914D\u6307\u5B9A\u7684 tabid: ${tabid}...`));
519
- selectedTarget = pageTargets.find((t) => {
520
- const tid = typeof t._getTargetInfo === "function" ? t._getTargetInfo().targetId : t._targetId || t.targetId || "";
521
- return tid === tabid || tid.includes(tabid);
522
- });
523
- }
524
- if (selectedTarget) {
525
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u5339\u914D\u7684 target \u8F6C\u6362\u4E3A page..."));
526
- page = await selectedTarget.page();
527
- } else if (pageTargets.length > 0) {
528
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u6700\u540E\u4E00\u4E2A target \u8F6C\u6362\u4E3A page..."));
529
- page = await pageTargets[pageTargets.length - 1].page();
530
- }
531
- if (!page) {
532
- console.log(pc2.yellow("openCommand: \u6CA1\u6709\u627E\u5230\u53EF\u7528 page\uFF0C\u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
533
- page = await browser.newPage();
562
+ return result;
563
+ } catch (error) {
564
+ return {
565
+ error: true,
566
+ message: error.message,
567
+ stack: error.stack
568
+ };
534
569
  }
570
+ }, jsScript);
571
+ return result;
572
+ } finally {
573
+ await browser.disconnect();
574
+ }
575
+ }
576
+
577
+ // src/commands/clipboard.ts
578
+ import clipboard from "clipboardy";
579
+ async function setClipboard(content) {
580
+ try {
581
+ await clipboard.write(content);
582
+ } catch (error) {
583
+ throw new Error(`\u8BBE\u7F6E\u526A\u8D34\u677F\u5931\u8D25: ${error.message}`);
584
+ }
585
+ }
586
+
587
+ // src/commands/tabs.ts
588
+ function normalizeUrl(url) {
589
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
590
+ return "https://" + url;
591
+ }
592
+ return url;
593
+ }
594
+ async function tabsOpenCommand(url) {
595
+ const browser2 = await connectBrowser();
596
+ try {
597
+ const page2 = await browser2.newPage();
598
+ url = normalizeUrl(url);
599
+ await page2.goto(url, { waitUntil: "domcontentloaded" });
600
+ await injectIntoPage(page2);
601
+ await new Promise((resolve) => setTimeout(resolve, 500));
602
+ return {
603
+ success: true,
604
+ tabid: await getPageTargetId(page2),
605
+ url: page2.url(),
606
+ title: await page2.title()
607
+ };
608
+ } finally {
609
+ await browser2.disconnect();
610
+ }
611
+ }
612
+ async function tabsCloseCommand(tabid2) {
613
+ const browser2 = await connectBrowser();
614
+ try {
615
+ const target = findPageTargetByTabId(browser2, tabid2);
616
+ if (!target) {
617
+ throw new Error(`Tab with targetId "${tabid2}" not found.`);
535
618
  }
536
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
537
- url = "https://" + url;
619
+ const page2 = await target.page();
620
+ if (!page2) {
621
+ throw new Error(`Tab with targetId "${tabid2}" not found.`);
538
622
  }
539
- console.log(pc2.cyan(`\u6B63\u5728\u6253\u5F00: ${url}`));
540
- await page.goto(url, { waitUntil: "domcontentloaded" });
541
- await injectIntoPage(page);
623
+ const closedTabid = await getPageTargetId(page2).catch(() => tabid2);
624
+ await page2.close();
542
625
  return {
543
626
  success: true,
544
- url: page.url(),
545
- title: await page.title()
627
+ tabid: closedTabid
546
628
  };
547
629
  } finally {
548
- await browser.disconnect();
630
+ await browser2.disconnect();
631
+ }
632
+ }
633
+ async function tabsSwitchCommand(tabid2) {
634
+ const browser2 = await connectBrowser();
635
+ try {
636
+ await activateTabById(browser2, tabid2);
637
+ const page2 = await getTargetPage(browser2, tabid2);
638
+ return {
639
+ success: true,
640
+ tabid: await getPageTargetId(page2),
641
+ url: page2.url(),
642
+ title: await page2.title()
643
+ };
644
+ } finally {
645
+ await browser2.disconnect();
646
+ }
647
+ }
648
+ async function tabsBackCommand(tabid2) {
649
+ const browser2 = await connectBrowser();
650
+ try {
651
+ const page2 = await getTargetPage(browser2, tabid2);
652
+ const response = await page2.goBack();
653
+ if (!response) {
654
+ return {
655
+ success: false,
656
+ error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
657
+ tabid: await getPageTargetId(page2).catch(() => tabid2),
658
+ url: page2.url(),
659
+ title: await page2.title()
660
+ };
661
+ }
662
+ await injectIntoPage(page2);
663
+ return {
664
+ success: true,
665
+ tabid: await getPageTargetId(page2),
666
+ url: page2.url(),
667
+ title: await page2.title()
668
+ };
669
+ } finally {
670
+ await browser2.disconnect();
549
671
  }
550
672
  }
673
+ async function tabsForwardCommand(tabid2) {
674
+ const browser2 = await connectBrowser();
675
+ try {
676
+ const page2 = await getTargetPage(browser2, tabid2);
677
+ const response = await page2.goForward();
678
+ if (!response) {
679
+ return {
680
+ success: false,
681
+ error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
682
+ tabid: await getPageTargetId(page2).catch(() => tabid2),
683
+ url: page2.url(),
684
+ title: await page2.title()
685
+ };
686
+ }
687
+ await injectIntoPage(page2);
688
+ return {
689
+ success: true,
690
+ tabid: await getPageTargetId(page2),
691
+ url: page2.url(),
692
+ title: await page2.title()
693
+ };
694
+ } finally {
695
+ await browser2.disconnect();
696
+ }
697
+ }
698
+
699
+ // package.json
700
+ var package_default = {
701
+ name: "@opentiny/webmcp-cli",
702
+ version: "0.0.1-alpha.3",
703
+ type: "module",
704
+ description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
705
+ main: "dist/index.js",
706
+ types: "dist/index.d.ts",
707
+ bin: {
708
+ "webmcp-cli": "./dist/bin.js"
709
+ },
710
+ files: [
711
+ "dist",
712
+ "README.md"
713
+ ],
714
+ scripts: {
715
+ dev: "tsup --watch",
716
+ "build:inject": "node scripts/build-inject.mjs",
717
+ build: "tsup && pnpm run build:inject",
718
+ prepublishOnly: "pnpm run build",
719
+ "link:global": "npm install -g .",
720
+ start: "node dist/bin.js"
721
+ },
722
+ keywords: [
723
+ "chrome",
724
+ "puppeteer",
725
+ "cli"
726
+ ],
727
+ license: "MIT",
728
+ author: "OpenTiny Team",
729
+ engines: {
730
+ node: ">=16"
731
+ },
732
+ publishConfig: {
733
+ access: "public"
734
+ },
735
+ dependencies: {
736
+ "@mcp-b/webmcp-polyfill": "catalog:",
737
+ "@opentiny/next-sdk": "workspace:^",
738
+ "@page-agent/page-controller": "catalog:",
739
+ clipboardy: "^5.3.1",
740
+ commander: "^11.1.0",
741
+ picocolors: "^1.0.0",
742
+ "puppeteer-core": "^22.0.0"
743
+ },
744
+ devDependencies: {
745
+ "@types/node": "^20.0.0",
746
+ esbuild: "^0.25.0",
747
+ tsup: "^8.0.2",
748
+ tsx: "^4.0.0",
749
+ typescript: "^5.3.3"
750
+ }
751
+ };
551
752
 
552
753
  // src/bin.ts
553
754
  var program = new Command();
@@ -555,7 +756,12 @@ function parseTabId(id) {
555
756
  if (!id) return void 0;
556
757
  return id;
557
758
  }
558
- program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version("1.0.0").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) => {
759
+ function handleCommandError(error, commandName) {
760
+ const msg = error instanceof Error ? error.message : String(error);
761
+ console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
762
+ process.exit(1);
763
+ }
764
+ 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) => {
559
765
  const opts = thisCommand.opts();
560
766
  if (opts.workspace) {
561
767
  process.env.WEBMCP_WORKSPACE = opts.workspace;
@@ -563,39 +769,82 @@ program.name("webmcp-cli").description("WebMCP CLI for interacting with browser
563
769
  });
564
770
  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) => {
565
771
  try {
566
- const result = await stateCommand({ tabid: parseTabId(options.tabid) });
567
- console.log(JSON.stringify(result, null, 2));
772
+ const result2 = await stateCommand({ tabid: parseTabId(options.tabid) });
773
+ console.log(JSON.stringify(result2, null, 2));
568
774
  } catch (error) {
569
- const msg = error instanceof Error ? error.message : String(error);
570
- console.error(pc3.red(`Error executing state command: ${msg}`));
571
- process.exit(1);
775
+ handleCommandError(error, "state");
572
776
  }
573
777
  });
574
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) => {
575
779
  try {
576
- const result = await runCommand({
780
+ const result2 = await runCommand({
577
781
  toolName,
578
782
  argsJson,
579
783
  tabid: parseTabId(options.tabid)
580
784
  });
581
- console.log(JSON.stringify(result, null, 2));
785
+ console.log(JSON.stringify(result2, null, 2));
582
786
  } catch (error) {
583
- const msg = error instanceof Error ? error.message : String(error);
584
- console.error(pc3.red(`Error executing run command: ${msg}`));
585
- process.exit(1);
787
+ handleCommandError(error, "run");
586
788
  }
587
789
  });
588
- program.command("open <url>").description("\u5728\u5F53\u524D\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6307\u5B9A\u7F51\u9875").option("-t, --tabid <id>", "\u5728\u6307\u5B9A\u9875\u7B7E\u4E2D\u6253\u5F00").option("-n, --new-tab", "\u5728\u4E00\u4E2A\u5168\u65B0\u7684\u9875\u7B7E\u4E2D\u6253\u5F00").action(async (url, options) => {
790
+ 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) => {
589
791
  try {
590
- const result = await openCommand(url, {
591
- tabid: parseTabId(options.tabid),
592
- newTab: options.newTab
792
+ const result2 = await evaluateCommand({
793
+ jsScript: jsScript2,
794
+ tabid: parseTabId(options.tabid)
593
795
  });
594
- console.log(JSON.stringify(result, null, 2));
796
+ console.log(JSON.stringify(result2, null, 2));
797
+ } catch (error) {
798
+ handleCommandError(error, "evaluate");
799
+ }
800
+ });
801
+ program.command("clipboard <content>").description("\u5C06\u5185\u5BB9\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F").action(async (content) => {
802
+ try {
803
+ await setClipboard(content);
804
+ console.log(JSON.stringify({ success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" }, null, 2));
805
+ } catch (error) {
806
+ handleCommandError(error, "clipboard");
807
+ }
808
+ });
809
+ var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
810
+ tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
811
+ try {
812
+ const result2 = await tabsOpenCommand(url);
813
+ console.log(JSON.stringify(result2, null, 2));
814
+ } catch (error) {
815
+ handleCommandError(error, "tabs open");
816
+ }
817
+ });
818
+ tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
819
+ try {
820
+ const result2 = await tabsCloseCommand(tabid2);
821
+ console.log(JSON.stringify(result2, null, 2));
822
+ } catch (error) {
823
+ handleCommandError(error, "tabs close");
824
+ }
825
+ });
826
+ tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
827
+ try {
828
+ const result2 = await tabsSwitchCommand(tabid2);
829
+ console.log(JSON.stringify(result2, null, 2));
830
+ } catch (error) {
831
+ handleCommandError(error, "tabs switch");
832
+ }
833
+ });
834
+ tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid2) => {
835
+ try {
836
+ const result2 = await tabsBackCommand(parseTabId(tabid2));
837
+ console.log(JSON.stringify(result2, null, 2));
838
+ } catch (error) {
839
+ handleCommandError(error, "tabs back");
840
+ }
841
+ });
842
+ tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid2) => {
843
+ try {
844
+ const result2 = await tabsForwardCommand(parseTabId(tabid2));
845
+ console.log(JSON.stringify(result2, null, 2));
595
846
  } catch (error) {
596
- const msg = error instanceof Error ? error.message : String(error);
597
- console.error(pc3.red(`Error executing open command: ${msg}`));
598
- process.exit(1);
847
+ handleCommandError(error, "tabs forward");
599
848
  }
600
849
  });
601
850
  program.parse(process.argv);