yiyan-browser-agent 1.4.5 → 1.4.7

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/cli.js DELETED
@@ -1,857 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
-
31
- // src/cli.ts
32
- var cli_exports = {};
33
- __export(cli_exports, {
34
- formatCliOutput: () => formatCliOutput,
35
- parseCliArgs: () => parseCliArgs,
36
- printHelp: () => printHelp,
37
- runCli: () => runCli
38
- });
39
- module.exports = __toCommonJS(cli_exports);
40
-
41
- // src/agent.ts
42
- var import_path = __toESM(require("path"));
43
- var import_os = __toESM(require("os"));
44
-
45
- // src/types.ts
46
- var YiyanAgentError = class extends Error {
47
- constructor(type, message) {
48
- super(message);
49
- this.type = type;
50
- this.name = "YiyanAgentError";
51
- }
52
- type;
53
- };
54
- var DEFAULT_OPTIONS = {
55
- timeout: 12e4,
56
- retryCount: 3,
57
- profileDir: "",
58
- chromePath: ""
59
- };
60
- var YIYAN_CHAT_URL = "https://yiyan.baidu.com/";
61
-
62
- // src/browser.ts
63
- var import_playwright = require("playwright");
64
- var import_fs = __toESM(require("fs"));
65
- var SELECTORS = {
66
- // 输入框选择器(按优先级排序)
67
- chatInput: [
68
- "#chat-input",
69
- 'textarea[placeholder*="\u8F93\u5165"]',
70
- "textarea[placeholder]",
71
- "textarea",
72
- '[contenteditable="true"][role="textbox"]',
73
- '[contenteditable="true"]'
74
- ],
75
- // 发送按钮选择器
76
- sendButton: [
77
- 'button[aria-label*="\u53D1\u9001"]',
78
- 'button[aria-label*="Send"]',
79
- '[data-testid="send-button"]',
80
- 'button[type="submit"]',
81
- '[class*="send-btn"]',
82
- '[class*="sendBtn"]',
83
- '[class*="send-button"]'
84
- ],
85
- // 响应容器选择器
86
- responseContainer: [
87
- '[class*="answerBox"]',
88
- '[class*="response"]',
89
- '[class*="ai-message"]',
90
- '[class*="assistant"]',
91
- '[class*="markdown"]',
92
- '[class*="message-content"]',
93
- '[class*="chat-message"]'
94
- ],
95
- // 停止生成按钮
96
- stopButton: [
97
- 'button[aria-label*="\u505C\u6B62"]',
98
- 'button[aria-label*="Stop"]',
99
- '[class*="stop-btn"]',
100
- '[class*="stopBtn"]',
101
- '[class*="stop-gen"]'
102
- ]
103
- };
104
- function log(verbose, msg) {
105
- if (verbose) {
106
- process.stderr.write(`[yiyan-browser-agent] ${msg}
107
- `);
108
- }
109
- }
110
- async function launchBrowser(options) {
111
- const { sessionDir, headless, timeout = 3e4, verbose = false } = options;
112
- process.stderr.write("[yiyan-browser-agent] \u542F\u52A8 Chromium \u6D4F\u89C8\u5668...\n");
113
- try {
114
- if (!import_fs.default.existsSync(sessionDir)) {
115
- import_fs.default.mkdirSync(sessionDir, { recursive: true });
116
- }
117
- const context = await import_playwright.chromium.launchPersistentContext(sessionDir, {
118
- headless,
119
- viewport: { width: 1280, height: 900 },
120
- userAgent: [
121
- "Mozilla/5.0 (X11; Linux x86_64)",
122
- "AppleWebKit/537.36 (KHTML, like Gecko)",
123
- "Chrome/124.0.0.0 Safari/537.36"
124
- ].join(" "),
125
- args: [
126
- "--disable-blink-features=AutomationControlled",
127
- "--no-first-run",
128
- "--no-default-browser-check",
129
- "--no-sandbox",
130
- "--disable-setuid-sandbox",
131
- "--disable-dev-shm-usage"
132
- ],
133
- ignoreDefaultArgs: ["--enable-automation"]
134
- });
135
- context.setDefaultTimeout(timeout);
136
- const pages = context.pages();
137
- const page = pages.length > 0 ? pages[0] : await context.newPage();
138
- await page.addInitScript(() => {
139
- Object.defineProperty(navigator, "webdriver", { get: () => false });
140
- });
141
- process.stderr.write("[yiyan-browser-agent] \u6D4F\u89C8\u5668\u542F\u52A8\u5B8C\u6210\n");
142
- return { context, page };
143
- } catch (error) {
144
- const errorMsg = error instanceof Error ? error.message : String(error);
145
- process.stderr.write(`[yiyan-browser-agent] \u2717 \u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25: ${errorMsg}
146
- `);
147
- process.stderr.write("[yiyan-browser-agent] \u63D0\u793A: Ubuntu \u9700\u8981\u5148\u8FD0\u884C: npx playwright install-deps chromium\n");
148
- throw new YiyanAgentError(
149
- "BROWSER_LAUNCH",
150
- `Failed to launch browser: ${errorMsg}`
151
- );
152
- }
153
- }
154
- async function closeBrowser(context, verbose) {
155
- process.stderr.write("[yiyan-browser-agent] \u5173\u95ED\u6D4F\u89C8\u5668...\n");
156
- try {
157
- await context.close();
158
- process.stderr.write("[yiyan-browser-agent] \u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\n");
159
- } catch {
160
- }
161
- }
162
- async function navigateToYiyan(page, verbose = false) {
163
- process.stderr.write("[yiyan-browser-agent] \u5BFC\u822A\u5230\u6587\u5FC3\u4E00\u8A00...\n");
164
- try {
165
- await page.goto(YIYAN_CHAT_URL, { waitUntil: "domcontentloaded", timeout: 3e4 });
166
- await page.waitForTimeout(1500);
167
- process.stderr.write("[yiyan-browser-agent] \u9875\u9762\u52A0\u8F7D\u5B8C\u6210\n");
168
- } catch (err) {
169
- process.stderr.write(`[yiyan-browser-agent] \u5BFC\u822A\u8B66\u544A: ${err instanceof Error ? err.message : String(err)}
170
- `);
171
- }
172
- }
173
- async function checkLoggedIn(page, verbose = false) {
174
- await page.waitForTimeout(2e3);
175
- const isLoggedIn = await page.evaluate(() => {
176
- const url = window.location.href;
177
- const bodyText = document.body?.innerText || "";
178
- if (url.includes("/auth") || url.includes("/login") || url.includes("/sign")) return false;
179
- if (bodyText.includes("\u672A\u767B\u5F55") || bodyText.includes("\u8BF7\u767B\u5F55")) return false;
180
- if (document.querySelector('input[type="password"]')) return false;
181
- const loginBtn = document.querySelector("button");
182
- if (loginBtn && loginBtn.textContent?.trim() === "\u767B\u5F55") return false;
183
- return true;
184
- });
185
- process.stderr.write(isLoggedIn ? "[yiyan-browser-agent] \u2713 \u5DF2\u68C0\u6D4B\u5230\u767B\u5F55\u72B6\u6001\n" : "[yiyan-browser-agent] \u26A0 \u672A\u767B\u5F55\n");
186
- return isLoggedIn;
187
- }
188
- async function checkCaptcha(page, verbose = false) {
189
- const hasCaptcha = await page.evaluate(() => {
190
- const bodyText = document.body.innerText || "";
191
- const indicators = ["\u9A8C\u8BC1\u7801", "\u8BF7\u5B8C\u6210\u9A8C\u8BC1", "\u5B89\u5168\u9A8C\u8BC1", "\u6ED1\u52A8\u9A8C\u8BC1", "captcha"];
192
- for (const kw of indicators) {
193
- if (bodyText.includes(kw)) return true;
194
- }
195
- const dialogs = document.querySelectorAll('[role="dialog"], [class*="captcha"], [class*="verify"]');
196
- return dialogs.length > 0;
197
- });
198
- if (hasCaptcha) log(verbose, "\u26A0 \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\uFF01");
199
- return hasCaptcha;
200
- }
201
- async function waitForUserAction(page, reason, verbose = false) {
202
- const messages = {
203
- captcha: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u5B8C\u6210\u9A8C\u8BC1",
204
- login: "\u68C0\u6D4B\u5230\u672A\u767B\u5F55\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u767B\u5F55",
205
- "no-reply": "AI \u672A\u56DE\u590D\uFF0C\u8BF7\u68C0\u67E5\u6D4F\u89C8\u5668\u662F\u5426\u9700\u8981\u624B\u52A8\u64CD\u4F5C"
206
- };
207
- process.stderr.write(`
208
- [yiyan-browser-agent] \u26A0 ${messages[reason]}
209
- `);
210
- process.stderr.write("[yiyan-browser-agent] \u7B49\u5F85\u60A8\u64CD\u4F5C\u5B8C\u6210...\uFF08\u64CD\u4F5C\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u7EE7\u7EED\uFF09\n\n");
211
- const maxWait = 18e4;
212
- const start = Date.now();
213
- while (Date.now() - start < maxWait) {
214
- await page.waitForTimeout(2e3);
215
- if (reason === "captcha" && !await checkCaptcha(page, false)) {
216
- log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF01");
217
- return;
218
- }
219
- if (reason === "login" && await checkLoggedIn(page, false)) {
220
- log(verbose, "\u2713 \u767B\u5F55\u6210\u529F\uFF01");
221
- return;
222
- }
223
- if (reason === "no-reply" && await getMessageCount(page) > 0) {
224
- log(verbose, "\u2713 AI \u5DF2\u5F00\u59CB\u56DE\u590D\uFF01");
225
- return;
226
- }
227
- }
228
- log(verbose, "\u26A0 \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\u8D85\u65F6\uFF083\u5206\u949F\uFF09");
229
- }
230
- async function sendMessage(page, message, verbose = false, headful = false) {
231
- const input = await findInput(page, verbose);
232
- if (!input) {
233
- throw new YiyanAgentError("NETWORK", "Cannot find the Yiyan chat input box");
234
- }
235
- await input.click({ force: true });
236
- await page.waitForTimeout(200);
237
- await page.keyboard.press("Control+a");
238
- await page.waitForTimeout(100);
239
- const isTextarea = await input.evaluate((el) => el.tagName.toLowerCase() === "textarea");
240
- if (isTextarea) {
241
- await input.fill(message);
242
- } else {
243
- await input.evaluate((el, content) => {
244
- el.focus();
245
- document.execCommand("selectAll", false, null);
246
- document.execCommand("delete", false, null);
247
- document.execCommand("insertText", false, content);
248
- el.dispatchEvent(new InputEvent("input", { bubbles: true, data: content }));
249
- }, message);
250
- }
251
- await page.waitForTimeout(400);
252
- log(verbose, `\u5DF2\u8F93\u5165\u95EE\u9898: "${message.substring(0, 50)}..."`);
253
- const sent = await clickSendButton(page);
254
- if (!sent) {
255
- await page.keyboard.press("Enter");
256
- }
257
- await page.waitForTimeout(500);
258
- if (await checkCaptcha(page, verbose)) {
259
- if (headful) {
260
- await waitForUserAction(page, "captcha", verbose);
261
- } else {
262
- throw new YiyanAgentError("CAPTCHA", "Captcha detected. Use --headful to solve it.");
263
- }
264
- }
265
- }
266
- async function findInput(page, verbose = false) {
267
- for (const sel of SELECTORS.chatInput) {
268
- try {
269
- const el = await page.waitForSelector(sel, { timeout: 4e3, state: "visible" });
270
- if (el) {
271
- log(verbose, `\u2713 \u8F93\u5165\u6846: ${sel}`);
272
- return el;
273
- }
274
- } catch {
275
- }
276
- }
277
- return null;
278
- }
279
- async function clickSendButton(page) {
280
- for (const sel of SELECTORS.sendButton) {
281
- try {
282
- const el = await page.$(sel);
283
- if (el && await el.isVisible() && await el.isEnabled()) {
284
- await el.click();
285
- return true;
286
- }
287
- } catch {
288
- }
289
- }
290
- return false;
291
- }
292
- async function waitForReply(page, timeout, verbose = false, headful = false) {
293
- const maxWait = Math.min(timeout, 12e4);
294
- const stableDelay = 2500;
295
- const start = Date.now();
296
- const initialCount = await getMessageCount(page);
297
- let appeared = false;
298
- while (Date.now() - start < 12e3) {
299
- const count = await getMessageCount(page);
300
- if (count > initialCount) {
301
- appeared = true;
302
- log(verbose, "\u2713 AI \u56DE\u590D\u5DF2\u5F00\u59CB\u751F\u6210");
303
- break;
304
- }
305
- await page.waitForTimeout(400);
306
- }
307
- if (!appeared) {
308
- log(verbose, "\u56DE\u590D\u53EF\u80FD\u5EF6\u8FDF\uFF0C\u7EE7\u7EED\u7B49\u5F85...");
309
- }
310
- let lastText = "";
311
- let stableStart = null;
312
- while (Date.now() - start < maxWait) {
313
- const text = await extractLastMessage(page);
314
- if (text !== lastText) {
315
- lastText = text;
316
- stableStart = null;
317
- } else if (text.length > 0) {
318
- if (!stableStart) stableStart = Date.now();
319
- else if (Date.now() - stableStart >= stableDelay) {
320
- if (!await isGenerating(page)) {
321
- log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${text.length}\u5B57\uFF09`);
322
- break;
323
- }
324
- stableStart = null;
325
- }
326
- }
327
- await page.waitForTimeout(500);
328
- }
329
- if (lastText.length === 0) {
330
- if (await checkCaptcha(page, verbose)) {
331
- if (headful) {
332
- await waitForUserAction(page, "captcha", verbose);
333
- } else {
334
- throw new YiyanAgentError("CAPTCHA", "Captcha detected.");
335
- }
336
- } else if (headful) {
337
- await waitForUserAction(page, "no-reply", verbose);
338
- } else {
339
- throw new YiyanAgentError("TIMEOUT", "AI reply timeout.");
340
- }
341
- }
342
- await page.waitForTimeout(1e3);
343
- }
344
- async function getMessageCount(page) {
345
- return await page.evaluate(() => {
346
- for (const sel of [
347
- '[class*="answerBox"]',
348
- '[class*="assistant"]',
349
- '[class*="ai-message"]',
350
- '[class*="response"]'
351
- ]) {
352
- const els = document.querySelectorAll(sel);
353
- if (els.length > 0) return els.length;
354
- }
355
- return document.querySelectorAll('[class*="message"]').length;
356
- });
357
- }
358
- async function isGenerating(page) {
359
- return await page.evaluate(() => {
360
- for (const sel of ['button[aria-label*="\u505C\u6B62"]', '[class*="stop"]', '[class*="generating"]']) {
361
- const el = document.querySelector(sel);
362
- if (el) {
363
- const s = window.getComputedStyle(el);
364
- if (s.display !== "none" && s.visibility !== "hidden") return true;
365
- }
366
- }
367
- for (const sel of ['[class*="loading"]', '[class*="typing"]', '[class*="spinner"]']) {
368
- const el = document.querySelector(sel);
369
- if (el) {
370
- const s = window.getComputedStyle(el);
371
- if (s.display !== "none" && s.visibility !== "hidden") return true;
372
- }
373
- }
374
- return false;
375
- });
376
- }
377
- async function extractLastMessage(page) {
378
- return await page.evaluate(() => {
379
- function getFullText(el) {
380
- if (!el) return "";
381
- let result = "";
382
- function walk(node) {
383
- if (node.nodeType === Node.TEXT_NODE) {
384
- result += node.textContent || "";
385
- return;
386
- }
387
- if (node.nodeType !== Node.ELEMENT_NODE) return;
388
- const tag = node.tagName.toLowerCase();
389
- if (tag === "pre") {
390
- const codeEl = node.querySelector("code");
391
- if (codeEl) {
392
- const cls = codeEl.className || "";
393
- const lang = (cls.match(/language-(\S+)/) || [])[1] || "";
394
- result += "\n```" + lang + "\n" + (codeEl.textContent || "") + "\n```\n";
395
- } else {
396
- result += "\n```\n" + (node.textContent || "") + "\n```\n";
397
- }
398
- return;
399
- }
400
- for (const child of node.childNodes) walk(child);
401
- if (["p", "div", "li", "br", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
402
- result += "\n";
403
- }
404
- }
405
- walk(el);
406
- return result.trim();
407
- }
408
- for (const sel of SELECTORS.responseContainer) {
409
- const els = document.querySelectorAll(sel);
410
- if (els.length > 0) {
411
- const text = getFullText(els[els.length - 1]);
412
- if (text.length > 10) return text;
413
- }
414
- }
415
- const mdEls = document.querySelectorAll('[class*="markdown"], [class*="prose"]');
416
- if (mdEls.length > 0) {
417
- const text = getFullText(mdEls[mdEls.length - 1]);
418
- if (text.length > 10) return text;
419
- }
420
- return "";
421
- });
422
- }
423
- async function extractReply(page, question, verbose = false) {
424
- log(verbose, "\u63D0\u53D6 AI \u56DE\u590D...");
425
- const text = await extractLastMessage(page);
426
- if (text.length > 0) {
427
- const cleaned = text.replace(/^参考\d+个网页\s*/, "").replace(/^深度思考已完成\s*/, "").replace(/^思考完成[::]\s*/, "").replace(/\n{3,}/g, "\n\n").trim();
428
- log(verbose, `\u63D0\u53D6\u6210\u529F: ${cleaned.length} \u5B57\u7B26`);
429
- return cleaned;
430
- }
431
- throw new YiyanAgentError("TIMEOUT", "Failed to extract reply");
432
- }
433
- async function dumpDebugInfo(page) {
434
- const info = await page.evaluate(() => {
435
- const classFreq = {};
436
- document.querySelectorAll("*").forEach((el) => {
437
- el.classList.forEach((c) => {
438
- if (c.match(/message|chat|input|send|stop|markdown|content|assistant|user|bot|answer/i)) {
439
- classFreq[c] = (classFreq[c] || 0) + 1;
440
- }
441
- });
442
- });
443
- const inputs = Array.from(document.querySelectorAll("textarea, [contenteditable]")).map((e) => ({
444
- tag: e.tagName,
445
- id: e.id || null,
446
- class: (e.className?.toString() || "").substring(0, 80),
447
- placeholder: e.placeholder || null,
448
- editable: e.isContentEditable,
449
- visible: e.offsetParent !== null
450
- }));
451
- return {
452
- url: window.location.href,
453
- title: document.title,
454
- classes: Object.entries(classFreq).sort((a, b) => b[1] - a[1]).slice(0, 40),
455
- inputs
456
- };
457
- });
458
- console.log("\n" + "\u2550".repeat(60));
459
- console.log(" DOM DEBUG INFO");
460
- console.log("\u2550".repeat(60));
461
- console.log("URL :", info.url);
462
- console.log("Title :", info.title);
463
- console.log("\nInput elements:");
464
- info.inputs.forEach((i) => console.log(" ", JSON.stringify(i)));
465
- console.log("\nMatching CSS classes (by frequency):");
466
- info.classes.forEach(([cls, count]) => console.log(` ${String(count).padStart(3)}x .${cls}`));
467
- console.log("\u2550".repeat(60) + "\n");
468
- }
469
-
470
- // src/agent.ts
471
- var DEFAULT_SESSION_DIR = import_path.default.join(import_os.default.homedir(), ".yiyan-browser-agent", "session");
472
- var YiyanAgent = class {
473
- options;
474
- sessionDir;
475
- verbose;
476
- _context = null;
477
- // 保存当前 context 以便关闭
478
- constructor(options) {
479
- this.options = {
480
- ...DEFAULT_OPTIONS,
481
- ...options
482
- };
483
- this.sessionDir = this.options.profileDir || DEFAULT_SESSION_DIR;
484
- this.verbose = options?.verbose ?? false;
485
- const fs2 = require("fs");
486
- fs2.mkdirSync(this.sessionDir, { recursive: true });
487
- }
488
- /**
489
- * 关闭浏览器(如果正在运行)
490
- */
491
- async close() {
492
- if (this._context) {
493
- try {
494
- await this._context.close();
495
- } catch {
496
- }
497
- this._context = null;
498
- }
499
- }
500
- /**
501
- * 发送问题并获取答案
502
- * @param question 问题文本
503
- * @param headful 是否使用有头浏览器(可见窗口),默认为 false(headless)
504
- */
505
- async ask(question, headful = false) {
506
- return this.executeAsk(question, headful);
507
- }
508
- /**
509
- * 执行单次问答
510
- */
511
- async executeAsk(question, headful) {
512
- const { context, page } = await launchBrowser({
513
- sessionDir: this.sessionDir,
514
- headless: !headful,
515
- timeout: this.options.timeout,
516
- verbose: this.verbose
517
- });
518
- try {
519
- await navigateToYiyan(page, this.verbose);
520
- const isLoggedIn = await checkLoggedIn(page, this.verbose);
521
- if (!isLoggedIn) {
522
- if (headful) {
523
- await waitForUserAction(page, "login", this.verbose);
524
- } else {
525
- throw new YiyanAgentError(
526
- "CAPTCHA",
527
- 'Not logged in. Please run "yiyan-browser-agent login" first, or use headed mode.'
528
- );
529
- }
530
- }
531
- await sendMessage(page, question, this.verbose, headful);
532
- await waitForReply(page, this.options.timeout, this.verbose, headful);
533
- const reply = await extractReply(page, question, this.verbose);
534
- return reply;
535
- } finally {
536
- await closeBrowser(context, this.verbose);
537
- }
538
- }
539
- /**
540
- * 登录文心一言(打开有头浏览器让用户手动登录)
541
- * 登录成功后,后续的 ask 调用将自动使用登录状态
542
- */
543
- async login() {
544
- const { context, page } = await launchBrowser({
545
- sessionDir: this.sessionDir,
546
- headless: false,
547
- timeout: 6e4,
548
- verbose: this.verbose
549
- });
550
- try {
551
- await navigateToYiyan(page, this.verbose);
552
- process.stderr.write("\n[yiyan-browser-agent] \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n");
553
- process.stderr.write("[yiyan-browser-agent] \u2551 \u{1F510} LOGIN REQUIRED \u2551\n");
554
- process.stderr.write("[yiyan-browser-agent] \u2551 \u2551\n");
555
- process.stderr.write("[yiyan-browser-agent] \u2551 1. Log in to Yiyan in the browser window \u2551\n");
556
- process.stderr.write("[yiyan-browser-agent] \u2551 2. Return here and press ENTER to continue \u2551\n");
557
- process.stderr.write("[yiyan-browser-agent] \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n\n");
558
- await this.waitForEnter();
559
- process.stderr.write("[yiyan-browser-agent] \u2713 Login complete, saving session...\n");
560
- await new Promise((resolve) => setTimeout(resolve, 3e3));
561
- } finally {
562
- await closeBrowser(context, this.verbose);
563
- }
564
- }
565
- /**
566
- * 等待用户按 Enter
567
- */
568
- async waitForEnter() {
569
- const readline = await import("readline");
570
- return new Promise((resolve) => {
571
- const rl = readline.createInterface({
572
- input: process.stdin,
573
- output: process.stderr
574
- });
575
- rl.question("Press ENTER to continue...", () => {
576
- rl.close();
577
- resolve();
578
- });
579
- });
580
- }
581
- /**
582
- * 清除保存的 session
583
- */
584
- async reset() {
585
- const fs2 = await import("fs/promises");
586
- try {
587
- await fs2.rm(this.sessionDir, { recursive: true, force: true });
588
- } catch {
589
- }
590
- }
591
- /**
592
- * 检查状态
593
- */
594
- status() {
595
- const fs2 = require("fs");
596
- return {
597
- loggedIn: fs2.existsSync(this.sessionDir),
598
- sessionPath: this.sessionDir
599
- };
600
- }
601
- /**
602
- * Debug 模式:启动浏览器并输出 DOM 信息
603
- */
604
- async debug() {
605
- const { context, page } = await launchBrowser({
606
- sessionDir: this.sessionDir,
607
- headless: false,
608
- timeout: this.options.timeout,
609
- verbose: true
610
- });
611
- try {
612
- await navigateToYiyan(page, true);
613
- await page.waitForTimeout(3e3);
614
- await dumpDebugInfo(page);
615
- process.stderr.write("\n[yiyan-browser-agent] \u6D4F\u89C8\u5668\u4FDD\u6301\u6253\u5F00\uFF0C\u53EF\u624B\u52A8\u6D4B\u8BD5\u3002\n");
616
- process.stderr.write("[yiyan-browser-agent] \u5173\u95ED\u6D4F\u89C8\u5668\u7A97\u53E3\u7ED3\u675F debug \u6A21\u5F0F...\n");
617
- await new Promise((resolve) => {
618
- context.on("close", () => resolve());
619
- });
620
- } finally {
621
- try {
622
- await context.close();
623
- } catch {
624
- }
625
- }
626
- }
627
- };
628
-
629
- // src/cli.ts
630
- console.log("");
631
- console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
632
- console.log("\u2551 \u{1F916} Yiyan Browser Agent \u2014 \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 \u2551");
633
- console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
634
- console.log("");
635
- function printHelp() {
636
- console.log(`
637
- yiyan-browser-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
638
-
639
- \u7528\u6CD5:
640
- yiyan-browser-agent login \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
641
- yiyan-browser-agent ask "\u95EE\u9898" [--timeout ms] [--headful] [--verbose]
642
- yiyan-browser-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
643
- yiyan-browser-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 session
644
- yiyan-browser-agent debug \u8C03\u8BD5\u6A21\u5F0F\uFF08\u8F93\u51FA DOM \u4FE1\u606F\uFF09
645
-
646
- \u547D\u4EE4:
647
- login \u6253\u5F00\u6D4F\u89C8\u5668\u624B\u52A8\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u9996\u6B21\u4F7F\u7528\u5FC5\u987B\u5148\u767B\u5F55\uFF09
648
- ask \u53D1\u9001\u95EE\u9898\u5E76\u83B7\u53D6\u7B54\u6848\uFF08\u9ED8\u8BA4\u65E0\u5934\u6A21\u5F0F\uFF0C\u4E0D\u5F39\u7A97\uFF1B\u767B\u5F55\u540E\u76F4\u63A5\u4F7F\u7528\uFF09
649
- status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
650
- reset \u6E05\u9664\u4FDD\u5B58\u7684 session
651
- debug \u8C03\u8BD5\u6A21\u5F0F\uFF1A\u542F\u52A8\u6D4F\u89C8\u5668\u8F93\u51FA DOM \u4FE1\u606F\uFF0C\u7528\u4E8E\u6392\u67E5\u9009\u62E9\u5668\u95EE\u9898
652
-
653
- \u9009\u9879:
654
- --timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
655
- --headful \u4F7F\u7528\u6709\u5934\u6D4F\u89C8\u5668\uFF08\u53EF\u89C1\u7A97\u53E3\uFF0C\u7528\u4E8E\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
656
- --verbose \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\uFF08\u8C03\u8BD5\u7528\uFF09
657
- --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
658
-
659
- \u793A\u4F8B:
660
- yiyan-browser-agent login # \u9996\u6B21\u4F7F\u7528\uFF1A\u767B\u5F55\u6587\u5FC3\u4E00\u8A00
661
- yiyan-browser-agent ask "\u4EC0\u4E48\u662F TypeScript\uFF1F" # \u63D0\u95EE\uFF08\u9759\u9ED8\u6A21\u5F0F\uFF09
662
- yiyan-browser-agent ask "\u89E3\u91CA Promise" --verbose # \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7
663
- yiyan-browser-agent ask "30+30=" --headful # \u6709\u5934\u6A21\u5F0F\uFF08\u53EF\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
664
- yiyan-browser-agent debug # \u8C03\u8BD5\u6A21\u5F0F\uFF0C\u8F93\u51FA DOM \u4FE1\u606F
665
- yiyan-browser-agent status
666
- yiyan-browser-agent reset
667
-
668
- \u6D41\u7A0B:
669
- 1. \u5148\u8FD0\u884C yiyan-browser-agent login \u767B\u5F55\uFF08\u53EA\u9700\u4E00\u6B21\uFF0C\u767B\u5F55\u72B6\u6001\u4F1A\u4FDD\u5B58\uFF09
670
- 2. \u4E4B\u540E\u76F4\u63A5 yiyan-browser-agent ask "\u95EE\u9898" \u5373\u53EF\uFF0C\u65E0\u9700\u518D\u767B\u5F55
671
- 3. \u5982\u679C\u9047\u5230\u9A8C\u8BC1\u7801\uFF0C\u52A0 --headful \u5207\u6362\u6709\u5934\u6A21\u5F0F\u624B\u52A8\u5904\u7406
672
-
673
- \u63D0\u793A:
674
- Chromium \u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF08\u7EA6150MB\uFF09\uFF0C\u65E0\u9700\u624B\u52A8\u5B89\u88C5 Chrome
675
- Session \u4FDD\u5B58\u5728 ~/.yiyan-browser-agent/session \u76EE\u5F55
676
- `);
677
- }
678
- function parseCliArgs(args) {
679
- const result = {
680
- command: null
681
- };
682
- for (let i = 0; i < args.length; i++) {
683
- const arg = args[i];
684
- if (arg === "--help") {
685
- result.command = "help";
686
- return result;
687
- }
688
- if (arg === "ask") {
689
- result.command = "ask";
690
- if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
691
- result.question = args[i + 1];
692
- i++;
693
- }
694
- }
695
- if (arg === "status") {
696
- result.command = "status";
697
- }
698
- if (arg === "reset") {
699
- result.command = "reset";
700
- }
701
- if (arg === "login") {
702
- result.command = "login";
703
- }
704
- if (arg === "debug") {
705
- result.command = "debug";
706
- }
707
- if (arg === "--timeout") {
708
- if (i + 1 < args.length) {
709
- result.timeout = parseInt(args[i + 1], 10);
710
- i++;
711
- }
712
- }
713
- if (arg === "--headful") {
714
- result.headful = true;
715
- }
716
- if (arg === "--verbose") {
717
- result.verbose = true;
718
- }
719
- }
720
- return result;
721
- }
722
- function formatCliOutput(output) {
723
- return JSON.stringify(output, null, 2);
724
- }
725
- async function runCli(args) {
726
- const parsed = parseCliArgs(args);
727
- if (parsed.command === "help" || parsed.command === null) {
728
- printHelp();
729
- return;
730
- }
731
- const options = {};
732
- if (parsed.timeout) {
733
- options.timeout = parsed.timeout;
734
- }
735
- if (parsed.verbose) {
736
- options.verbose = true;
737
- }
738
- const agent = new YiyanAgent(options);
739
- const shutdown = async () => {
740
- console.log("\n[yiyan-browser-agent] \u6B63\u5728\u5173\u95ED...");
741
- try {
742
- await agent.close();
743
- } catch {
744
- }
745
- process.exit(0);
746
- };
747
- process.on("SIGINT", shutdown);
748
- process.on("SIGTERM", shutdown);
749
- if (parsed.command === "login") {
750
- try {
751
- console.log("[yiyan-browser-agent] \u542F\u52A8\u767B\u5F55\u6D41\u7A0B...");
752
- await agent.login();
753
- console.log(formatCliOutput({
754
- success: true,
755
- question: "login",
756
- answer: 'Login successful! You can now use "ask" command.',
757
- duration: 0
758
- }));
759
- } catch (error) {
760
- const errorMessage = error instanceof Error ? error.message : String(error);
761
- console.error("[yiyan-browser-agent] \u2717 \u767B\u5F55\u5931\u8D25:", errorMessage);
762
- console.log(formatCliOutput({
763
- success: false,
764
- question: "login",
765
- error: errorMessage,
766
- duration: 0
767
- }));
768
- }
769
- return;
770
- }
771
- if (parsed.command === "status") {
772
- const status = agent.status();
773
- console.log(formatCliOutput({
774
- success: true,
775
- question: "status",
776
- answer: JSON.stringify(status),
777
- duration: 0
778
- }));
779
- return;
780
- }
781
- if (parsed.command === "reset") {
782
- await agent.reset();
783
- console.log(formatCliOutput({
784
- success: true,
785
- question: "reset",
786
- answer: "Session cleared successfully",
787
- duration: 0
788
- }));
789
- return;
790
- }
791
- if (parsed.command === "debug") {
792
- try {
793
- console.log("[yiyan-browser-agent] \u542F\u52A8\u8C03\u8BD5\u6A21\u5F0F...");
794
- await agent.debug();
795
- console.log(formatCliOutput({
796
- success: true,
797
- question: "debug",
798
- answer: "Debug session completed",
799
- duration: 0
800
- }));
801
- } catch (error) {
802
- const errorMessage = error instanceof Error ? error.message : String(error);
803
- console.error("[yiyan-browser-agent] \u2717 \u8C03\u8BD5\u5931\u8D25:", errorMessage);
804
- console.log(formatCliOutput({
805
- success: false,
806
- question: "debug",
807
- error: errorMessage,
808
- duration: 0
809
- }));
810
- }
811
- return;
812
- }
813
- if (parsed.command === "ask") {
814
- if (!parsed.question) {
815
- console.log(formatCliOutput({
816
- success: false,
817
- question: "",
818
- error: 'No question provided. Usage: ask "your question"',
819
- duration: 0
820
- }));
821
- return;
822
- }
823
- const startTime = Date.now();
824
- try {
825
- const answer = await agent.ask(parsed.question, !!parsed.headful);
826
- const duration = Date.now() - startTime;
827
- console.log(formatCliOutput({
828
- success: true,
829
- question: parsed.question,
830
- answer,
831
- duration
832
- }));
833
- } catch (error) {
834
- const duration = Date.now() - startTime;
835
- const errorMessage = error instanceof Error ? error.message : String(error);
836
- console.log(formatCliOutput({
837
- success: false,
838
- question: parsed.question,
839
- error: errorMessage,
840
- duration
841
- }));
842
- }
843
- return;
844
- }
845
- }
846
- runCli(process.argv.slice(2)).catch((err) => {
847
- console.error("[yiyan-browser-agent] Uncaught error:", err);
848
- process.exit(1);
849
- });
850
- // Annotate the CommonJS export names for ESM import in node:
851
- 0 && (module.exports = {
852
- formatCliOutput,
853
- parseCliArgs,
854
- printHelp,
855
- runCli
856
- });
857
- //# sourceMappingURL=cli.js.map