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