@opentiny/webmcp-cli 0.0.1-alpha.5 → 0.0.2

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
@@ -24,27 +24,202 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/bin.ts
27
+ var import_fs3 = __toESM(require("fs"), 1);
28
+ var import_path3 = __toESM(require("path"), 1);
29
+ var import_os2 = __toESM(require("os"), 1);
27
30
  var import_commander = require("commander");
28
31
  var import_picocolors2 = __toESM(require("picocolors"), 1);
29
- var import_fs2 = require("fs");
30
- var import_path2 = require("path");
32
+ var import_fs4 = require("fs");
33
+ var import_path4 = require("path");
34
+
35
+ // src/expand-file-refs.ts
36
+ var import_fs = require("fs");
37
+ var import_path = require("path");
38
+ var import_url = require("url");
39
+ var JSON_STRING_LITERAL = /"(?:[^"\\]|\\.)*"/g;
40
+ var UNQUOTED_FILE_REF = /(?<![\\"])@(base64)?file:([^\s"',}\]]+)/g;
41
+ var FILE_REF_PREFIX = /^@(base64)?file:(.+)$/s;
42
+ function resolveFilePath(filePath, baseDir = process.cwd()) {
43
+ let normalizedPath = filePath.trim();
44
+ if (normalizedPath.startsWith('"') && normalizedPath.endsWith('"') || normalizedPath.startsWith("'") && normalizedPath.endsWith("'")) {
45
+ normalizedPath = normalizedPath.slice(1, -1).trim();
46
+ }
47
+ if (normalizedPath.startsWith("file://")) {
48
+ normalizedPath = (0, import_url.fileURLToPath)(normalizedPath);
49
+ }
50
+ if ((0, import_path.isAbsolute)(normalizedPath)) {
51
+ return (0, import_path.normalize)(normalizedPath);
52
+ }
53
+ return (0, import_path.resolve)(baseDir, normalizedPath);
54
+ }
55
+ function decodeFilePath(rawPath) {
56
+ return rawPath.replace(/\\\\/g, "\\");
57
+ }
58
+ function readFileRefContent(filePath, baseDir, base64) {
59
+ const absPath = resolveFilePath(filePath, baseDir);
60
+ let content;
61
+ try {
62
+ content = (0, import_fs.readFileSync)(absPath, "utf-8");
63
+ } catch (e) {
64
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}" (${absPath}): ${e.message}`);
65
+ }
66
+ return base64 ? Buffer.from(content, "utf-8").toString("base64") : content;
67
+ }
68
+ function expandFileRefValue(rawInner, baseDir) {
69
+ const match = rawInner.match(FILE_REF_PREFIX);
70
+ if (!match) {
71
+ return null;
72
+ }
73
+ const [, base64Flag, rawPath] = match;
74
+ const filePath = decodeFilePath(rawPath);
75
+ const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
76
+ return JSON.stringify(value);
77
+ }
78
+ function expandQuotedFileRefs(argsJson, baseDir) {
79
+ return argsJson.replace(JSON_STRING_LITERAL, (jsonStringLiteral) => {
80
+ const rawInner = jsonStringLiteral.slice(1, -1);
81
+ const replacement = expandFileRefValue(rawInner, baseDir);
82
+ return replacement ?? jsonStringLiteral;
83
+ });
84
+ }
85
+ function expandUnquotedFileRefs(argsJson, baseDir) {
86
+ return argsJson.replace(UNQUOTED_FILE_REF, (match, base64Flag, filePath) => {
87
+ const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
88
+ return JSON.stringify(value);
89
+ });
90
+ }
91
+ function expandFileRefs(argsJson, baseDir = process.cwd()) {
92
+ const withQuotedExpanded = expandQuotedFileRefs(argsJson, baseDir);
93
+ return expandUnquotedFileRefs(withQuotedExpanded, baseDir);
94
+ }
95
+ function getFileBaseDir(fileOption) {
96
+ if (!fileOption) {
97
+ return process.cwd();
98
+ }
99
+ return (0, import_path.dirname)((0, import_path.resolve)(process.cwd(), fileOption));
100
+ }
101
+
102
+ // src/parse-run-args.ts
103
+ var import_json5 = __toESM(require("json5"), 1);
104
+ function cleanJsonString(str) {
105
+ let cleaned = str.trim();
106
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
107
+ cleaned = cleaned.slice(1, -1).trim();
108
+ }
109
+ return cleaned;
110
+ }
111
+ function looksLikeObjectBody(raw) {
112
+ return /^("?[\w-]+"?\s*:)/.test(raw);
113
+ }
114
+ function joinShellArgs(args) {
115
+ const cleaned = args.map(cleanJsonString);
116
+ const first = cleaned[0] ?? "";
117
+ if (first.startsWith("{")) {
118
+ return cleaned.join(" ");
119
+ }
120
+ if (looksLikeObjectBody(first)) {
121
+ let joined = cleaned.join(" ");
122
+ if (!joined.startsWith("{")) {
123
+ joined = `{${joined}}`;
124
+ }
125
+ if (!joined.endsWith("}")) {
126
+ joined = `${joined}}`;
127
+ }
128
+ return joined;
129
+ }
130
+ return cleaned.join(" ");
131
+ }
132
+ function insertMissingCommas(raw) {
133
+ return raw.replace(/"(\s+)("?[\w-]+"?\s*:)/g, '",$2');
134
+ }
135
+ function quoteUnquotedStringValues(raw) {
136
+ return raw.replace(
137
+ /([{,]\s*(?:"([^"]+)"|([\w-]+))\s*:\s*)(?!"@(?:base64)?file:|"|true|false|null|-?\d|\{|\[)([^",}\]]+?)(?=\s*[,}])/g,
138
+ (_match, prefix, _quotedKey, _unquotedKey, value) => {
139
+ const trimmed = value.trim();
140
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
141
+ return prefix + trimmed;
142
+ }
143
+ return prefix + JSON.stringify(trimmed);
144
+ }
145
+ );
146
+ }
147
+ function normalizeShellDamagedJson(raw) {
148
+ let normalized = raw.trim();
149
+ normalized = insertMissingCommas(normalized);
150
+ normalized = quoteUnquotedStringValues(normalized);
151
+ return normalized;
152
+ }
153
+ function buildArgsFromKeyValuePairs(args) {
154
+ const obj = {};
155
+ for (const pair of args) {
156
+ const index = pair.indexOf("=");
157
+ if (index === -1) {
158
+ throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u8BF7\u4F7F\u7528 key=value\uFF0C\u6216 JSON / -f \u6587\u4EF6\u3002`);
159
+ }
160
+ const key = pair.substring(0, index).trim();
161
+ let val = cleanJsonString(pair.substring(index + 1).trim());
162
+ try {
163
+ obj[key] = JSON.parse(val);
164
+ } catch {
165
+ obj[key] = val;
166
+ }
167
+ }
168
+ return JSON.stringify(obj);
169
+ }
170
+ function parseArgsJson(raw) {
171
+ const trimmed = cleanJsonString(raw);
172
+ try {
173
+ return JSON.stringify(import_json5.default.parse(trimmed));
174
+ } catch (firstError) {
175
+ const repaired = normalizeShellDamagedJson(trimmed);
176
+ try {
177
+ return JSON.stringify(import_json5.default.parse(repaired));
178
+ } catch {
179
+ const hint = `\u63D0\u793A\uFF1Abash \u4E2D JSON \u5FC5\u987B\u7528\u5355\u5F15\u53F7\u5305\u88F9\uFF0C\u4F8B\u5982 '{"title":"\u6807\u9898","content":"@base64file:./article.md"}'\uFF1B\u6216\u4F7F\u7528 key=value\uFF1Atitle=\u6807\u9898 content=@base64file:./article.md\uFF1B\u6216\u4F7F\u7528 -f args.json\u3002`;
180
+ const message = firstError instanceof Error ? firstError.message : String(firstError);
181
+ throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${message}
182
+ ${hint}`);
183
+ }
184
+ }
185
+ }
186
+ function prepareRunArgsJson(args, fileContent, baseDir) {
187
+ let raw = "";
188
+ 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;
198
+ }
199
+ }
200
+ if (!raw) {
201
+ throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
202
+ }
203
+ const expanded = expandFileRefs(raw, baseDir);
204
+ return parseArgsJson(expanded);
205
+ }
31
206
 
32
207
  // src/browser.ts
33
208
  var import_puppeteer_core = __toESM(require("puppeteer-core"), 1);
34
209
  var import_picocolors = __toESM(require("picocolors"), 1);
35
- var import_fs = __toESM(require("fs"), 1);
210
+ var import_fs2 = __toESM(require("fs"), 1);
36
211
  var import_os = __toESM(require("os"), 1);
37
- var import_path = __toESM(require("path"), 1);
212
+ var import_path2 = __toESM(require("path"), 1);
38
213
  var import_http = __toESM(require("http"), 1);
39
214
  var import_child_process = require("child_process");
40
- var import_url = require("url");
215
+ var import_url2 = require("url");
41
216
  var import_meta = {};
42
- var __filename = (0, import_url.fileURLToPath)(import_meta.url);
43
- var __dirname = import_path.default.dirname(__filename);
217
+ var __filename = (0, import_url2.fileURLToPath)(import_meta.url);
218
+ var __dirname = import_path2.default.dirname(__filename);
44
219
  var CDP_PORT = 9222;
45
220
  var CDP_URL = `http://localhost:${CDP_PORT}`;
46
221
  function httpGet(url, timeoutMs = 1500) {
47
- return new Promise((resolve2, reject) => {
222
+ return new Promise((resolve3, reject) => {
48
223
  const req = import_http.default.get(url, { timeout: timeoutMs }, (res) => {
49
224
  let data = "";
50
225
  res.on("data", (chunk) => {
@@ -52,7 +227,7 @@ function httpGet(url, timeoutMs = 1500) {
52
227
  });
53
228
  res.on("end", () => {
54
229
  if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
55
- resolve2(data);
230
+ resolve3(data);
56
231
  } else {
57
232
  reject(new Error(`HTTP ${res.statusCode}`));
58
233
  }
@@ -93,9 +268,9 @@ function getDefaultBrowserPath() {
93
268
  const platform = import_os.default.platform();
94
269
  if (platform === "darwin") {
95
270
  const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
96
- if (import_fs.default.existsSync(chromePath)) return { path: chromePath, name: "Chrome" };
271
+ if (import_fs2.default.existsSync(chromePath)) return { path: chromePath, name: "Chrome" };
97
272
  const edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge";
98
- if (import_fs.default.existsSync(edgePath)) return { path: edgePath, name: "Edge" };
273
+ if (import_fs2.default.existsSync(edgePath)) return { path: edgePath, name: "Edge" };
99
274
  return null;
100
275
  } else if (platform === "win32") {
101
276
  const chromePaths = [
@@ -103,7 +278,7 @@ function getDefaultBrowserPath() {
103
278
  process.env.PROGRAMFILES + "\\Google\\Chrome\\Application\\chrome.exe",
104
279
  process.env["PROGRAMFILES(X86)"] + "\\Google\\Chrome\\Application\\chrome.exe"
105
280
  ];
106
- const foundChrome = chromePaths.find((p) => import_fs.default.existsSync(p));
281
+ const foundChrome = chromePaths.find((p) => import_fs2.default.existsSync(p));
107
282
  if (foundChrome) return { path: foundChrome, name: "Chrome" };
108
283
  const edgePaths = [
109
284
  process.env.LOCALAPPDATA + "\\Microsoft\\Edge\\Application\\msedge.exe",
@@ -112,7 +287,7 @@ function getDefaultBrowserPath() {
112
287
  "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
113
288
  "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"
114
289
  ];
115
- const foundEdge = edgePaths.find((p) => import_fs.default.existsSync(p));
290
+ const foundEdge = edgePaths.find((p) => import_fs2.default.existsSync(p));
116
291
  if (foundEdge) return { path: foundEdge, name: "Edge" };
117
292
  return null;
118
293
  } else {
@@ -124,17 +299,17 @@ function getDefaultBrowserPath() {
124
299
  { p: "/usr/bin/microsoft-edge", name: "Edge" },
125
300
  { p: "/usr/bin/microsoft-edge-stable", name: "Edge" }
126
301
  ];
127
- const found = paths.find(({ p }) => import_fs.default.existsSync(p));
302
+ const found = paths.find(({ p }) => import_fs2.default.existsSync(p));
128
303
  return found ? { path: found.p, name: found.name } : null;
129
304
  }
130
305
  }
131
306
  async function startBrowserInBackground() {
132
307
  const browserInfo = getDefaultBrowserPath();
133
- if (!browserInfo || !import_fs.default.existsSync(browserInfo.path)) {
308
+ if (!browserInfo || !import_fs2.default.existsSync(browserInfo.path)) {
134
309
  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
310
  }
136
311
  console.log(import_picocolors.default.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
137
- const userDataDir = process.env.WEBMCP_WORKSPACE || import_path.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
312
+ const userDataDir = process.env.WEBMCP_WORKSPACE || import_path2.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
138
313
  const child = (0, import_child_process.spawn)(
139
314
  browserInfo.path,
140
315
  [
@@ -162,7 +337,7 @@ async function startBrowserInBackground() {
162
337
  }
163
338
  } catch (e) {
164
339
  }
165
- await new Promise((resolve2) => setTimeout(resolve2, 500));
340
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
166
341
  }
167
342
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
168
343
  }
@@ -193,13 +368,13 @@ async function connectBrowser() {
193
368
  const browserURL = addr.replace("/json/version", "");
194
369
  try {
195
370
  console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
196
- const browser2 = await promiseWithTimeout(
371
+ const browser = await promiseWithTimeout(
197
372
  import_puppeteer_core.default.connect({ browserURL, defaultViewport: null, targetFilter }),
198
373
  1e4,
199
374
  `puppeteer.connect to ${browserURL} timed out`
200
375
  );
201
376
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
202
- return browser2;
377
+ return browser;
203
378
  } catch {
204
379
  }
205
380
  }
@@ -217,20 +392,20 @@ async function connectBrowser() {
217
392
  const browserURL = addr.replace("/json/version", "");
218
393
  try {
219
394
  console.log(import_picocolors.default.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
220
- const browser2 = await promiseWithTimeout(
395
+ const browser = await promiseWithTimeout(
221
396
  import_puppeteer_core.default.connect({ browserURL, defaultViewport: null, targetFilter }),
222
397
  1e4,
223
398
  `puppeteer.connect to ${browserURL} after launch timed out`
224
399
  );
225
400
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
226
- return browser2;
401
+ return browser;
227
402
  } catch {
228
403
  }
229
404
  }
230
405
  throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
231
406
  }
232
- async function getPageTargetId(page2) {
233
- const session = await page2.target().createCDPSession();
407
+ async function getPageTargetId(page) {
408
+ const session = await page.target().createCDPSession();
234
409
  try {
235
410
  const { targetInfo } = await session.send("Target.getTargetInfo");
236
411
  return targetInfo.targetId;
@@ -239,8 +414,8 @@ async function getPageTargetId(page2) {
239
414
  });
240
415
  }
241
416
  }
242
- function getPageTargets(browser2) {
243
- return browser2.targets().filter((t) => {
417
+ function getPageTargets(browser) {
418
+ return browser.targets().filter((t) => {
244
419
  try {
245
420
  const type = (typeof t.type === "function" ? t.type() : t.type) || "";
246
421
  const url = (typeof t.url === "function" ? t.url() : t.url) || "";
@@ -253,26 +428,26 @@ function getPageTargets(browser2) {
253
428
  function getTargetIdFromTarget(target) {
254
429
  return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
255
430
  }
256
- function findPageTargetByTabId(browser2, tabid2) {
257
- const pageTargets = getPageTargets(browser2);
431
+ function findPageTargetByTabId(browser, tabid) {
432
+ const pageTargets = getPageTargets(browser);
258
433
  return pageTargets.find((t) => {
259
434
  const tid = getTargetIdFromTarget(t);
260
- return tid === tabid2 || tid.includes(tabid2);
435
+ return tid === tabid || tid.includes(tabid);
261
436
  }) ?? null;
262
437
  }
263
- async function activateTabById(browser2, tabid2) {
264
- const target = findPageTargetByTabId(browser2, tabid2);
438
+ async function activateTabById(browser, tabid) {
439
+ const target = findPageTargetByTabId(browser, tabid);
265
440
  if (!target) {
266
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
441
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
267
442
  }
268
443
  const realTabId = getTargetIdFromTarget(target);
269
- const pages = await browser2.pages();
444
+ const pages = await browser.pages();
270
445
  let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
271
446
  if (!sessionPage && pages.length > 0) {
272
447
  sessionPage = pages[0];
273
448
  }
274
449
  if (!sessionPage) {
275
- sessionPage = await browser2.newPage();
450
+ sessionPage = await browser.newPage();
276
451
  }
277
452
  const session = await sessionPage.createCDPSession();
278
453
  try {
@@ -282,18 +457,18 @@ async function activateTabById(browser2, tabid2) {
282
457
  });
283
458
  }
284
459
  }
285
- async function getTargetPage(browser2, tabid2) {
286
- const pageTargets = getPageTargets(browser2);
460
+ async function getTargetPage(browser, tabid) {
461
+ const pageTargets = getPageTargets(browser);
287
462
  if (pageTargets.length === 0) {
288
- const newPage = await browser2.newPage();
463
+ const newPage = await browser.newPage();
289
464
  await injectWebMCPPolyfillAndTools(newPage);
290
465
  return newPage;
291
466
  }
292
467
  let targetPage = null;
293
- if (tabid2 !== void 0) {
294
- const target = findPageTargetByTabId(browser2, tabid2);
468
+ if (tabid !== void 0) {
469
+ const target = findPageTargetByTabId(browser, tabid);
295
470
  if (!target) {
296
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
471
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
297
472
  }
298
473
  targetPage = await target.page();
299
474
  } else {
@@ -336,46 +511,78 @@ async function getTargetPage(browser2, tabid2) {
336
511
  await injectWebMCPPolyfillAndTools(targetPage);
337
512
  return targetPage;
338
513
  }
339
- async function injectIntoPage(page2) {
340
- await injectWebMCPPolyfillAndTools(page2, true);
514
+ async function injectIntoPage(page) {
515
+ await injectWebMCPPolyfillAndTools(page, true);
341
516
  }
342
- async function injectWebMCPPolyfillAndTools(page2, force = false) {
343
- const polyfillReady = !force && await page2.evaluate(() => {
517
+ async function injectWebMCPPolyfillAndTools(page, force = false) {
518
+ const polyfillReady = !force && await page.evaluate(() => {
344
519
  return !!window.__webmcpcli_init;
345
520
  }).catch(() => false);
346
521
  if (!polyfillReady) {
347
522
  console.log(import_picocolors.default.cyan("\u5F53\u524D\u9875\u9762\u5C1A\u672A\u6CE8\u5165 WebMCP \u73AF\u5883\uFF0C\u6B63\u5728\u6267\u884C\u81EA\u52A8\u6CE8\u5165..."));
348
- const injectScriptPath = import_path.default.resolve(__dirname, "inject-bundle.js");
349
- if (!import_fs.default.existsSync(injectScriptPath)) {
523
+ const injectScriptPath = import_path2.default.resolve(__dirname, "inject-bundle.js");
524
+ if (!import_fs2.default.existsSync(injectScriptPath)) {
350
525
  throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`);
351
526
  }
352
- const scriptContent = import_fs.default.readFileSync(injectScriptPath, "utf-8");
527
+ const scriptContent = import_fs2.default.readFileSync(injectScriptPath, "utf-8");
353
528
  try {
354
- await page2.evaluate(scriptContent);
529
+ await page.evaluate(scriptContent);
355
530
  } catch (err) {
356
531
  const msg = err instanceof Error ? err.message : String(err);
357
532
  throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
358
533
  }
359
- await new Promise((resolve2) => setTimeout(resolve2, 300));
534
+ await page.waitForFunction(() => {
535
+ const mcp = navigator.modelContext;
536
+ return mcp && typeof mcp.registerTool === "function";
537
+ }, { timeout: 1e4 }).catch(() => {
538
+ console.warn("\u7B49\u5F85 WebMCP registerTool \u63A5\u53E3\u5C31\u7EEA\u8D85\u65F6\uFF0C\u6B63\u5728\u7EE7\u7EED\u6CE8\u5165...");
539
+ });
360
540
  }
361
- await injectDomainTools(page2);
541
+ await injectDomainTools(page);
362
542
  }
363
- async function injectDomainTools(page2) {
543
+ function getToolsBundleName(hostname) {
544
+ const normalized = hostname.split(":")[0].toLowerCase();
545
+ const toolsDir = import_path2.default.resolve(__dirname, "webmcp-tools");
546
+ if (!import_fs2.default.existsSync(toolsDir)) {
547
+ return null;
548
+ }
549
+ let files = [];
550
+ try {
551
+ files = import_fs2.default.readdirSync(toolsDir);
552
+ } catch {
553
+ return null;
554
+ }
555
+ const supportedDomains = files.filter((f) => f.endsWith(".js")).map((f) => f.slice(0, -3));
556
+ if (supportedDomains.includes(normalized)) {
557
+ return `webmcp-tools/${normalized}.js`;
558
+ }
559
+ for (const domain of supportedDomains) {
560
+ if (normalized.endsWith("." + domain)) {
561
+ return `webmcp-tools/${domain}.js`;
562
+ }
563
+ }
564
+ return null;
565
+ }
566
+ async function injectDomainTools(page) {
364
567
  let hostname;
365
568
  try {
366
- const url = new URL(page2.url());
569
+ const url = new URL(page.url());
367
570
  hostname = url.hostname;
368
571
  } catch {
369
572
  return;
370
573
  }
371
- const toolBundlePath = import_path.default.resolve(__dirname, "webmcp-tools", `${hostname}.js`);
372
- if (!import_fs.default.existsSync(toolBundlePath)) {
574
+ const bundleName = getToolsBundleName(hostname);
575
+ if (!bundleName) {
576
+ return;
577
+ }
578
+ const toolBundlePath = import_path2.default.resolve(__dirname, bundleName);
579
+ if (!import_fs2.default.existsSync(toolBundlePath)) {
373
580
  return;
374
581
  }
375
582
  console.log(import_picocolors.default.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
376
- const toolScript = import_fs.default.readFileSync(toolBundlePath, "utf-8");
583
+ const toolScript = import_fs2.default.readFileSync(toolBundlePath, "utf-8");
377
584
  try {
378
- await page2.evaluate(toolScript);
585
+ await page.evaluate(toolScript);
379
586
  console.log(import_picocolors.default.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
380
587
  } catch (err) {
381
588
  const msg = err instanceof Error ? err.message : String(err);
@@ -384,67 +591,30 @@ async function injectDomainTools(page2) {
384
591
  }
385
592
 
386
593
  // src/commands/state.ts
387
- async function stateCommand({ tabid: tabid2 }) {
388
- const browser2 = await connectBrowser();
594
+ async function stateCommand({ tabid }) {
595
+ const browser = await connectBrowser();
389
596
  try {
390
- const page2 = await getTargetPage(browser2, tabid2);
391
- await injectIntoPage(page2);
392
- const state = await page2.evaluate(async () => {
597
+ const page = await getTargetPage(browser, tabid);
598
+ await injectIntoPage(page);
599
+ const state = await page.evaluate(async () => {
393
600
  const url = document.URL;
394
601
  const title = document.title;
395
602
  const mcp = navigator.modelContextTesting || navigator.modelContext;
396
603
  let webmcpTools = [];
397
- let contentData = `\u9875\u9762\u5DF2\u51C6\u5907\u597D: ${title}`;
398
- if (mcp) {
399
- if (typeof mcp.listTools === "function") {
400
- const toolsResult = await mcp.listTools();
401
- webmcpTools = toolsResult?.tools || toolsResult || [];
402
- }
403
- if (typeof mcp.executeTool === "function") {
404
- try {
405
- const argsString = JSON.stringify({ action: "browserState" });
406
- let stateRes = await mcp.executeTool("page-agent-tool", argsString);
407
- if (typeof stateRes === "string") {
408
- try {
409
- stateRes = JSON.parse(stateRes);
410
- } catch (e) {
411
- }
412
- }
413
- if (stateRes && stateRes.content && stateRes.content.length > 0) {
414
- const textContent = stateRes.content.map((c) => c.text).join("\\n");
415
- const prefix = "\u6D4F\u89C8\u5668\u72B6\u6001: ";
416
- if (textContent.startsWith(prefix)) {
417
- try {
418
- const jsonStr = textContent.substring(prefix.length);
419
- const parsedState = JSON.parse(jsonStr);
420
- contentData = parsedState.content;
421
- } catch (e) {
422
- contentData = textContent;
423
- }
424
- } else {
425
- contentData = textContent;
426
- }
427
- }
428
- } catch (e) {
429
- console.error("Snapshot error:", e.message);
430
- }
431
- }
604
+ if (mcp && typeof mcp.listTools === "function") {
605
+ const toolsResult = await mcp.listTools();
606
+ webmcpTools = toolsResult?.tools || toolsResult || [];
432
607
  }
433
- return {
434
- content: contentData,
435
- url,
436
- title,
437
- webmcpTools
438
- };
608
+ return { url, title, webmcpTools };
439
609
  });
440
- const pages = await browser2.pages();
610
+ const pages = await browser.pages();
441
611
  const tabs2 = await Promise.all(
442
612
  pages.map(async (p) => {
443
613
  const pUrl = p.url();
444
614
  if (pUrl.startsWith("devtools://")) return null;
445
615
  const pTitle = await Promise.race([
446
616
  p.title().catch(() => "Unknown"),
447
- new Promise((resolve2) => setTimeout(() => resolve2("Unknown"), 500))
617
+ new Promise((resolve3) => setTimeout(() => resolve3("Unknown"), 500))
448
618
  ]);
449
619
  return {
450
620
  // 使用真实的 Chrome target ID,而非数组下标
@@ -459,31 +629,21 @@ async function stateCommand({ tabid: tabid2 }) {
459
629
  tabs: tabs2.filter(Boolean)
460
630
  };
461
631
  } finally {
462
- await browser2.disconnect();
632
+ await browser.disconnect();
463
633
  }
464
634
  }
465
635
 
466
636
  // src/commands/run.ts
467
- var import_json5 = __toESM(require("json5"), 1);
468
637
  async function runCommand({
469
638
  toolName,
470
639
  argsJson,
471
- tabid: tabid2
640
+ tabid
472
641
  }) {
473
- const browser2 = await connectBrowser();
642
+ const browser = await connectBrowser();
474
643
  try {
475
- const page2 = await getTargetPage(browser2, tabid2);
476
- let cleanedArgs = argsJson.trim();
477
- if (cleanedArgs.startsWith("'") && cleanedArgs.endsWith("'")) {
478
- cleanedArgs = cleanedArgs.slice(1, -1).trim();
479
- }
480
- try {
481
- const obj = import_json5.default.parse(cleanedArgs);
482
- cleanedArgs = JSON.stringify(obj);
483
- } catch (e) {
484
- throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${e.message}`);
485
- }
486
- const result2 = await page2.evaluate(async (name, inputString) => {
644
+ const page = await getTargetPage(browser, tabid);
645
+ const cleanedArgs = argsJson.trim();
646
+ const result = await page.evaluate(async (name, inputString) => {
487
647
  const mcp = navigator.modelContextTesting || navigator.modelContext;
488
648
  if (!mcp || typeof mcp.executeTool !== "function") {
489
649
  throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
@@ -492,39 +652,18 @@ async function runCommand({
492
652
  if (typeof res === "string") {
493
653
  try {
494
654
  res = JSON.parse(res);
495
- } catch (e) {
655
+ } catch {
496
656
  }
497
657
  }
498
- }, toolName, cleanedArgs);
499
- return result2;
500
- } finally {
501
- await browser2.disconnect();
502
- }
503
- }
504
-
505
- // src/commands/evaluate.ts
506
- async function evaluateCommand({
507
- jsScript,
508
- tabid
509
- }) {
510
- const browser = await connectBrowser();
511
- try {
512
- const page = await getTargetPage(browser, tabid);
513
- const result = await page.evaluate(async (script) => {
514
- try {
515
- const result = eval(script);
516
- if (result instanceof Promise) {
517
- return await result;
518
- }
519
- return result;
520
- } catch (error) {
521
- return {
522
- error: true,
523
- message: error.message,
524
- stack: error.stack
525
- };
658
+ if (res === void 0 || res === null) {
659
+ throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
526
660
  }
527
- }, jsScript);
661
+ if (typeof res === "object" && res.success === false) {
662
+ const failed = res;
663
+ throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
664
+ }
665
+ return res;
666
+ }, toolName, cleanedArgs);
528
667
  return result;
529
668
  } finally {
530
669
  await browser.disconnect();
@@ -549,114 +688,114 @@ function normalizeUrl(url) {
549
688
  return url;
550
689
  }
551
690
  async function tabsOpenCommand(url) {
552
- const browser2 = await connectBrowser();
691
+ const browser = await connectBrowser();
553
692
  try {
554
- const page2 = await browser2.newPage();
693
+ const page = await browser.newPage();
555
694
  url = normalizeUrl(url);
556
- await page2.goto(url, { waitUntil: "domcontentloaded" });
557
- await injectIntoPage(page2);
558
- await new Promise((resolve2) => setTimeout(resolve2, 500));
695
+ await page.goto(url, { waitUntil: "domcontentloaded" });
696
+ await injectIntoPage(page);
697
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
559
698
  return {
560
699
  success: true,
561
- tabid: await getPageTargetId(page2),
562
- url: page2.url(),
563
- title: await page2.title()
700
+ tabid: await getPageTargetId(page),
701
+ url: page.url(),
702
+ title: await page.title()
564
703
  };
565
704
  } finally {
566
- await browser2.disconnect();
705
+ await browser.disconnect();
567
706
  }
568
707
  }
569
- async function tabsCloseCommand(tabid2) {
570
- const browser2 = await connectBrowser();
708
+ async function tabsCloseCommand(tabid) {
709
+ const browser = await connectBrowser();
571
710
  try {
572
- const target = findPageTargetByTabId(browser2, tabid2);
711
+ const target = findPageTargetByTabId(browser, tabid);
573
712
  if (!target) {
574
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
713
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
575
714
  }
576
- const page2 = await target.page();
577
- if (!page2) {
578
- throw new Error(`Tab with targetId "${tabid2}" not found.`);
715
+ const page = await target.page();
716
+ if (!page) {
717
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
579
718
  }
580
- const closedTabid = await getPageTargetId(page2).catch(() => tabid2);
581
- await page2.close();
719
+ const closedTabid = await getPageTargetId(page).catch(() => tabid);
720
+ await page.close();
582
721
  return {
583
722
  success: true,
584
723
  tabid: closedTabid
585
724
  };
586
725
  } finally {
587
- await browser2.disconnect();
726
+ await browser.disconnect();
588
727
  }
589
728
  }
590
- async function tabsSwitchCommand(tabid2) {
591
- const browser2 = await connectBrowser();
729
+ async function tabsSwitchCommand(tabid) {
730
+ const browser = await connectBrowser();
592
731
  try {
593
- await activateTabById(browser2, tabid2);
594
- const page2 = await getTargetPage(browser2, tabid2);
732
+ await activateTabById(browser, tabid);
733
+ const page = await getTargetPage(browser, tabid);
595
734
  return {
596
735
  success: true,
597
- tabid: await getPageTargetId(page2),
598
- url: page2.url(),
599
- title: await page2.title()
736
+ tabid: await getPageTargetId(page),
737
+ url: page.url(),
738
+ title: await page.title()
600
739
  };
601
740
  } finally {
602
- await browser2.disconnect();
741
+ await browser.disconnect();
603
742
  }
604
743
  }
605
- async function tabsBackCommand(tabid2) {
606
- const browser2 = await connectBrowser();
744
+ async function tabsBackCommand(tabid) {
745
+ const browser = await connectBrowser();
607
746
  try {
608
- const page2 = await getTargetPage(browser2, tabid2);
609
- const response = await page2.goBack();
747
+ const page = await getTargetPage(browser, tabid);
748
+ const response = await page.goBack();
610
749
  if (!response) {
611
750
  return {
612
751
  success: false,
613
752
  error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
614
- tabid: await getPageTargetId(page2).catch(() => tabid2),
615
- url: page2.url(),
616
- title: await page2.title()
753
+ tabid: await getPageTargetId(page).catch(() => tabid),
754
+ url: page.url(),
755
+ title: await page.title()
617
756
  };
618
757
  }
619
- await injectIntoPage(page2);
758
+ await injectIntoPage(page);
620
759
  return {
621
760
  success: true,
622
- tabid: await getPageTargetId(page2),
623
- url: page2.url(),
624
- title: await page2.title()
761
+ tabid: await getPageTargetId(page),
762
+ url: page.url(),
763
+ title: await page.title()
625
764
  };
626
765
  } finally {
627
- await browser2.disconnect();
766
+ await browser.disconnect();
628
767
  }
629
768
  }
630
- async function tabsForwardCommand(tabid2) {
631
- const browser2 = await connectBrowser();
769
+ async function tabsForwardCommand(tabid) {
770
+ const browser = await connectBrowser();
632
771
  try {
633
- const page2 = await getTargetPage(browser2, tabid2);
634
- const response = await page2.goForward();
772
+ const page = await getTargetPage(browser, tabid);
773
+ const response = await page.goForward();
635
774
  if (!response) {
636
775
  return {
637
776
  success: false,
638
777
  error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
639
- tabid: await getPageTargetId(page2).catch(() => tabid2),
640
- url: page2.url(),
641
- title: await page2.title()
778
+ tabid: await getPageTargetId(page).catch(() => tabid),
779
+ url: page.url(),
780
+ title: await page.title()
642
781
  };
643
782
  }
644
- await injectIntoPage(page2);
783
+ await injectIntoPage(page);
645
784
  return {
646
785
  success: true,
647
- tabid: await getPageTargetId(page2),
648
- url: page2.url(),
649
- title: await page2.title()
786
+ tabid: await getPageTargetId(page),
787
+ url: page.url(),
788
+ title: await page.title()
650
789
  };
651
790
  } finally {
652
- await browser2.disconnect();
791
+ await browser.disconnect();
653
792
  }
654
793
  }
655
794
 
656
795
  // package.json
657
796
  var package_default = {
658
797
  name: "@opentiny/webmcp-cli",
659
- version: "0.0.1-alpha.5",
798
+ version: "0.0.2",
660
799
  type: "module",
661
800
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
662
801
  main: "dist/index.js",
@@ -710,47 +849,118 @@ var package_default = {
710
849
 
711
850
  // src/bin.ts
712
851
  var program = new import_commander.Command();
713
- function cleanJsonString(str) {
714
- let cleaned = str.trim();
715
- if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
716
- cleaned = cleaned.slice(1, -1).trim();
717
- }
718
- return cleaned;
719
- }
720
852
  function parseTabId(id) {
721
853
  if (!id) return void 0;
722
854
  return id;
723
855
  }
724
- function handleCommandError(error, commandName) {
856
+ function cleanOldLogs(baseDir, logDir) {
857
+ try {
858
+ const oldLogFile = import_path3.default.join(baseDir, "webmcp-cli.log");
859
+ if (import_fs3.default.existsSync(oldLogFile)) {
860
+ try {
861
+ import_fs3.default.unlinkSync(oldLogFile);
862
+ } catch {
863
+ }
864
+ }
865
+ if (import_fs3.default.existsSync(baseDir)) {
866
+ const baseFiles = import_fs3.default.readdirSync(baseDir);
867
+ for (const file of baseFiles) {
868
+ if (/^webmcp-cli-\d{4}-\d{2}-\d{2}\.log$/.test(file)) {
869
+ try {
870
+ import_fs3.default.unlinkSync(import_path3.default.join(baseDir, file));
871
+ } catch {
872
+ }
873
+ }
874
+ }
875
+ }
876
+ if (!import_fs3.default.existsSync(logDir)) return;
877
+ const files = import_fs3.default.readdirSync(logDir);
878
+ const logFilePattern = /^webmcp-cli-(\d{4}-\d{2}-\d{2})\.log$/;
879
+ const now = Date.now();
880
+ const maxAgeMs = 7 * 24 * 60 * 60 * 1e3;
881
+ for (const file of files) {
882
+ const match = file.match(logFilePattern);
883
+ if (match) {
884
+ const fileDateStr = match[1];
885
+ const fileDate = new Date(fileDateStr).getTime();
886
+ if (isNaN(fileDate)) continue;
887
+ if (now - fileDate > maxAgeMs) {
888
+ try {
889
+ import_fs3.default.unlinkSync(import_path3.default.join(logDir, file));
890
+ } catch {
891
+ }
892
+ }
893
+ }
894
+ }
895
+ } catch (e) {
896
+ }
897
+ }
898
+ function writeLog(commandName, args, result, error) {
899
+ try {
900
+ const baseDir = process.env.WEBMCP_WORKSPACE || import_path3.default.join(import_os2.default.homedir(), ".webmcp_chrome_profile");
901
+ const logDir = import_path3.default.join(baseDir, "logs");
902
+ if (!import_fs3.default.existsSync(logDir)) {
903
+ import_fs3.default.mkdirSync(logDir, { recursive: true });
904
+ }
905
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
906
+ const logFile = import_path3.default.join(logDir, `webmcp-cli-${todayStr}.log`);
907
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
908
+ let logText = `========================================
909
+ `;
910
+ logText += `[${timestamp}] COMMAND: ${commandName}
911
+ `;
912
+ logText += `ARGS:
913
+ ${JSON.stringify(args, null, 2)}
914
+
915
+ `;
916
+ if (error) {
917
+ logText += `ERROR:
918
+ ${error instanceof Error ? error.stack : String(error)}
919
+ `;
920
+ } else if (result) {
921
+ logText += `RESULT:
922
+ `;
923
+ try {
924
+ if (result && typeof result === "object") {
925
+ logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
926
+ } else {
927
+ logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
928
+ }
929
+ } catch {
930
+ logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
931
+ }
932
+ } else {
933
+ logText += `RESULT: null
934
+ `;
935
+ }
936
+ logText += `========================================
937
+
938
+ `;
939
+ import_fs3.default.appendFileSync(logFile, logText, "utf-8");
940
+ cleanOldLogs(baseDir, logDir);
941
+ } catch (e) {
942
+ }
943
+ }
944
+ function handleCommandError(error, commandName, args) {
725
945
  const msg = error instanceof Error ? error.message : String(error);
946
+ writeLog(commandName, args, null, error);
726
947
  console.error(import_picocolors2.default.red(`Error executing ${commandName} command: ${msg}`));
727
948
  process.exit(1);
728
949
  }
729
- function expandFileRefs(argsJson) {
730
- return argsJson.replace(/"@(base64)?file:([^"]+)"/g, (_match, base64Flag, filePath) => {
731
- const absPath = (0, import_path2.resolve)(process.cwd(), filePath);
732
- let content;
733
- try {
734
- content = (0, import_fs2.readFileSync)(absPath, "utf-8");
735
- } catch (e) {
736
- throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}": ${e.message}`);
737
- }
738
- const value = base64Flag ? Buffer.from(content, "utf-8").toString("base64") : content;
739
- return JSON.stringify(value);
740
- });
741
- }
742
950
  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) => {
743
951
  const opts = thisCommand.opts();
744
952
  if (opts.workspace) {
745
953
  process.env.WEBMCP_WORKSPACE = opts.workspace;
746
954
  }
747
955
  });
748
- 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) => {
956
+ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\u5BFC\u822A\u5143\u6570\u636E\uFF08url\u3001title\u3001webmcpTools\u3001\u6240\u6709\u9875\u7B7E\u5217\u8868\uFF09").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (options) => {
957
+ const args = { tabid: parseTabId(options.tabid) };
749
958
  try {
750
- const result2 = await stateCommand({ tabid: parseTabId(options.tabid) });
751
- console.log(JSON.stringify(result2, null, 2));
959
+ const result = await stateCommand(args);
960
+ writeLog("state", args, result);
961
+ console.log(JSON.stringify(result, null, 2));
752
962
  } catch (error) {
753
- handleCommandError(error, "state");
963
+ handleCommandError(error, "state", args);
754
964
  }
755
965
  });
756
966
  program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u9875\u7B7E\u8C03\u7528\u6307\u5B9A\u7684 WebMCP \u5DE5\u5177\u6267\u884C\u64CD\u4F5C").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").option(
@@ -761,111 +971,92 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
761
971
  @base64file:<path> \u8BFB\u53D6\u6587\u4EF6\u5E76 Base64 \u7F16\u7801
762
972
  \u793A\u4F8B\uFF1Awebmcp-cli run mytool '{"content":"@base64file:./doc.md"}'`
763
973
  ).action(async (toolName, args, options) => {
974
+ let finalArgsJson = "";
975
+ const rawArgs = { toolName, args, file: options.file, tabid: options.tabid };
764
976
  try {
765
- let finalArgsJson = "";
977
+ let fileContent;
978
+ let fileBaseDir = process.cwd();
766
979
  if (options.file) {
767
- const filePath = (0, import_path2.resolve)(process.cwd(), options.file);
980
+ const filePath = (0, import_path4.resolve)(process.cwd(), options.file);
981
+ fileBaseDir = getFileBaseDir(options.file);
768
982
  try {
769
- finalArgsJson = (0, import_fs2.readFileSync)(filePath, "utf-8").trim();
983
+ fileContent = (0, import_fs4.readFileSync)(filePath, "utf-8");
770
984
  } catch (e) {
771
985
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 "${filePath}": ${e.message}`);
772
986
  }
773
- } else if (args && args.length > 0) {
774
- const firstArg = args[0].trim();
775
- const cleanedFirstArg = cleanJsonString(firstArg);
776
- if (cleanedFirstArg.startsWith("{") && cleanedFirstArg.endsWith("}")) {
777
- finalArgsJson = args.map(cleanJsonString).join(" ");
778
- } else {
779
- const obj = {};
780
- for (const pair of args) {
781
- const index = pair.indexOf("=");
782
- if (index === -1) {
783
- throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u5FC5\u987B\u662F JSON \u5B57\u7B26\u4E32\u6216 key=value \u683C\u5F0F\u3002`);
784
- }
785
- const key = pair.substring(0, index).trim();
786
- let val = pair.substring(index + 1).trim();
787
- val = cleanJsonString(val);
788
- try {
789
- obj[key] = JSON.parse(val);
790
- } catch {
791
- obj[key] = val;
792
- }
793
- }
794
- finalArgsJson = JSON.stringify(obj);
795
- }
796
987
  }
797
- if (!finalArgsJson) {
798
- throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
799
- }
800
- finalArgsJson = expandFileRefs(finalArgsJson);
801
- const result2 = await runCommand({
988
+ finalArgsJson = prepareRunArgsJson(args ?? [], fileContent, fileBaseDir);
989
+ const result = await runCommand({
802
990
  toolName,
803
991
  argsJson: finalArgsJson,
804
992
  tabid: parseTabId(options.tabid)
805
993
  });
806
- console.log(JSON.stringify(result2, null, 2));
807
- } catch (error) {
808
- handleCommandError(error, "run");
809
- }
810
- });
811
- 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) => {
812
- try {
813
- const result2 = await evaluateCommand({
814
- jsScript: jsScript2,
815
- tabid: parseTabId(options.tabid)
816
- });
817
- console.log(JSON.stringify(result2, null, 2));
994
+ writeLog("run", { ...rawArgs, finalArgsJson }, result);
995
+ console.log(JSON.stringify(result, null, 2));
818
996
  } catch (error) {
819
- handleCommandError(error, "evaluate");
997
+ handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
820
998
  }
821
999
  });
822
1000
  program.command("clipboard <content>").description("\u5C06\u5185\u5BB9\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F").action(async (content) => {
1001
+ const args = { content };
823
1002
  try {
824
1003
  await setClipboard(content);
825
- console.log(JSON.stringify({ success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" }, null, 2));
1004
+ const result = { success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" };
1005
+ writeLog("clipboard", args, result);
1006
+ console.log(JSON.stringify(result, null, 2));
826
1007
  } catch (error) {
827
- handleCommandError(error, "clipboard");
1008
+ handleCommandError(error, "clipboard", args);
828
1009
  }
829
1010
  });
830
1011
  var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
831
1012
  tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
1013
+ const args = { url };
832
1014
  try {
833
- const result2 = await tabsOpenCommand(url);
834
- console.log(JSON.stringify(result2, null, 2));
1015
+ const result = await tabsOpenCommand(url);
1016
+ writeLog("tabs open", args, result);
1017
+ console.log(JSON.stringify(result, null, 2));
835
1018
  } catch (error) {
836
- handleCommandError(error, "tabs open");
1019
+ handleCommandError(error, "tabs open", args);
837
1020
  }
838
1021
  });
839
- tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
1022
+ tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
1023
+ const args = { tabid };
840
1024
  try {
841
- const result2 = await tabsCloseCommand(tabid2);
842
- console.log(JSON.stringify(result2, null, 2));
1025
+ const result = await tabsCloseCommand(tabid);
1026
+ writeLog("tabs close", args, result);
1027
+ console.log(JSON.stringify(result, null, 2));
843
1028
  } catch (error) {
844
- handleCommandError(error, "tabs close");
1029
+ handleCommandError(error, "tabs close", args);
845
1030
  }
846
1031
  });
847
- tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid2) => {
1032
+ tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
1033
+ const args = { tabid };
848
1034
  try {
849
- const result2 = await tabsSwitchCommand(tabid2);
850
- console.log(JSON.stringify(result2, null, 2));
1035
+ const result = await tabsSwitchCommand(tabid);
1036
+ writeLog("tabs switch", args, result);
1037
+ console.log(JSON.stringify(result, null, 2));
851
1038
  } catch (error) {
852
- handleCommandError(error, "tabs switch");
1039
+ handleCommandError(error, "tabs switch", args);
853
1040
  }
854
1041
  });
855
- tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid2) => {
1042
+ tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
1043
+ const args = { tabid: parseTabId(tabid) };
856
1044
  try {
857
- const result2 = await tabsBackCommand(parseTabId(tabid2));
858
- console.log(JSON.stringify(result2, null, 2));
1045
+ const result = await tabsBackCommand(args.tabid);
1046
+ writeLog("tabs back", args, result);
1047
+ console.log(JSON.stringify(result, null, 2));
859
1048
  } catch (error) {
860
- handleCommandError(error, "tabs back");
1049
+ handleCommandError(error, "tabs back", args);
861
1050
  }
862
1051
  });
863
- tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid2) => {
1052
+ tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
1053
+ const args = { tabid: parseTabId(tabid) };
864
1054
  try {
865
- const result2 = await tabsForwardCommand(parseTabId(tabid2));
866
- console.log(JSON.stringify(result2, null, 2));
1055
+ const result = await tabsForwardCommand(args.tabid);
1056
+ writeLog("tabs forward", args, result);
1057
+ console.log(JSON.stringify(result, null, 2));
867
1058
  } catch (error) {
868
- handleCommandError(error, "tabs forward");
1059
+ handleCommandError(error, "tabs forward", args);
869
1060
  }
870
1061
  });
871
1062
  program.parse(process.argv);