yiyan-browser-agent 1.4.6 → 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,858 +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
- const responseSelectors = SELECTORS.responseContainer;
379
- return await page.evaluate((selectors) => {
380
- function getFullText(el) {
381
- if (!el) return "";
382
- let result = "";
383
- function walk(node) {
384
- if (node.nodeType === Node.TEXT_NODE) {
385
- result += node.textContent || "";
386
- return;
387
- }
388
- if (node.nodeType !== Node.ELEMENT_NODE) return;
389
- const tag = node.tagName.toLowerCase();
390
- if (tag === "pre") {
391
- const codeEl = node.querySelector("code");
392
- if (codeEl) {
393
- const cls = codeEl.className || "";
394
- const lang = (cls.match(/language-(\S+)/) || [])[1] || "";
395
- result += "\n```" + lang + "\n" + (codeEl.textContent || "") + "\n```\n";
396
- } else {
397
- result += "\n```\n" + (node.textContent || "") + "\n```\n";
398
- }
399
- return;
400
- }
401
- for (const child of node.childNodes) walk(child);
402
- if (["p", "div", "li", "br", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
403
- result += "\n";
404
- }
405
- }
406
- walk(el);
407
- return result.trim();
408
- }
409
- for (const sel of selectors) {
410
- const els = document.querySelectorAll(sel);
411
- if (els.length > 0) {
412
- const text = getFullText(els[els.length - 1]);
413
- if (text.length > 10) return text;
414
- }
415
- }
416
- const mdEls = document.querySelectorAll('[class*="markdown"], [class*="prose"]');
417
- if (mdEls.length > 0) {
418
- const text = getFullText(mdEls[mdEls.length - 1]);
419
- if (text.length > 10) return text;
420
- }
421
- return "";
422
- }, responseSelectors);
423
- }
424
- async function extractReply(page, question, verbose = false) {
425
- log(verbose, "\u63D0\u53D6 AI \u56DE\u590D...");
426
- const text = await extractLastMessage(page);
427
- if (text.length > 0) {
428
- const cleaned = text.replace(/^参考\d+个网页\s*/, "").replace(/^深度思考已完成\s*/, "").replace(/^思考完成[::]\s*/, "").replace(/\n{3,}/g, "\n\n").trim();
429
- log(verbose, `\u63D0\u53D6\u6210\u529F: ${cleaned.length} \u5B57\u7B26`);
430
- return cleaned;
431
- }
432
- throw new YiyanAgentError("TIMEOUT", "Failed to extract reply");
433
- }
434
- async function dumpDebugInfo(page) {
435
- const info = await page.evaluate(() => {
436
- const classFreq = {};
437
- document.querySelectorAll("*").forEach((el) => {
438
- el.classList.forEach((c) => {
439
- if (c.match(/message|chat|input|send|stop|markdown|content|assistant|user|bot|answer/i)) {
440
- classFreq[c] = (classFreq[c] || 0) + 1;
441
- }
442
- });
443
- });
444
- const inputs = Array.from(document.querySelectorAll("textarea, [contenteditable]")).map((e) => ({
445
- tag: e.tagName,
446
- id: e.id || null,
447
- class: (e.className?.toString() || "").substring(0, 80),
448
- placeholder: e.placeholder || null,
449
- editable: e.isContentEditable,
450
- visible: e.offsetParent !== null
451
- }));
452
- return {
453
- url: window.location.href,
454
- title: document.title,
455
- classes: Object.entries(classFreq).sort((a, b) => b[1] - a[1]).slice(0, 40),
456
- inputs
457
- };
458
- });
459
- console.log("\n" + "\u2550".repeat(60));
460
- console.log(" DOM DEBUG INFO");
461
- console.log("\u2550".repeat(60));
462
- console.log("URL :", info.url);
463
- console.log("Title :", info.title);
464
- console.log("\nInput elements:");
465
- info.inputs.forEach((i) => console.log(" ", JSON.stringify(i)));
466
- console.log("\nMatching CSS classes (by frequency):");
467
- info.classes.forEach(([cls, count]) => console.log(` ${String(count).padStart(3)}x .${cls}`));
468
- console.log("\u2550".repeat(60) + "\n");
469
- }
470
-
471
- // src/agent.ts
472
- var DEFAULT_SESSION_DIR = import_path.default.join(import_os.default.homedir(), ".yiyan-browser-agent", "session");
473
- var YiyanAgent = class {
474
- options;
475
- sessionDir;
476
- verbose;
477
- _context = null;
478
- // 保存当前 context 以便关闭
479
- constructor(options) {
480
- this.options = {
481
- ...DEFAULT_OPTIONS,
482
- ...options
483
- };
484
- this.sessionDir = this.options.profileDir || DEFAULT_SESSION_DIR;
485
- this.verbose = options?.verbose ?? false;
486
- const fs2 = require("fs");
487
- fs2.mkdirSync(this.sessionDir, { recursive: true });
488
- }
489
- /**
490
- * 关闭浏览器(如果正在运行)
491
- */
492
- async close() {
493
- if (this._context) {
494
- try {
495
- await this._context.close();
496
- } catch {
497
- }
498
- this._context = null;
499
- }
500
- }
501
- /**
502
- * 发送问题并获取答案
503
- * @param question 问题文本
504
- * @param headful 是否使用有头浏览器(可见窗口),默认为 false(headless)
505
- */
506
- async ask(question, headful = false) {
507
- return this.executeAsk(question, headful);
508
- }
509
- /**
510
- * 执行单次问答
511
- */
512
- async executeAsk(question, headful) {
513
- const { context, page } = await launchBrowser({
514
- sessionDir: this.sessionDir,
515
- headless: !headful,
516
- timeout: this.options.timeout,
517
- verbose: this.verbose
518
- });
519
- try {
520
- await navigateToYiyan(page, this.verbose);
521
- const isLoggedIn = await checkLoggedIn(page, this.verbose);
522
- if (!isLoggedIn) {
523
- if (headful) {
524
- await waitForUserAction(page, "login", this.verbose);
525
- } else {
526
- throw new YiyanAgentError(
527
- "CAPTCHA",
528
- 'Not logged in. Please run "yiyan-browser-agent login" first, or use headed mode.'
529
- );
530
- }
531
- }
532
- await sendMessage(page, question, this.verbose, headful);
533
- await waitForReply(page, this.options.timeout, this.verbose, headful);
534
- const reply = await extractReply(page, question, this.verbose);
535
- return reply;
536
- } finally {
537
- await closeBrowser(context, this.verbose);
538
- }
539
- }
540
- /**
541
- * 登录文心一言(打开有头浏览器让用户手动登录)
542
- * 登录成功后,后续的 ask 调用将自动使用登录状态
543
- */
544
- async login() {
545
- const { context, page } = await launchBrowser({
546
- sessionDir: this.sessionDir,
547
- headless: false,
548
- timeout: 6e4,
549
- verbose: this.verbose
550
- });
551
- try {
552
- await navigateToYiyan(page, this.verbose);
553
- 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");
554
- process.stderr.write("[yiyan-browser-agent] \u2551 \u{1F510} LOGIN REQUIRED \u2551\n");
555
- process.stderr.write("[yiyan-browser-agent] \u2551 \u2551\n");
556
- process.stderr.write("[yiyan-browser-agent] \u2551 1. Log in to Yiyan in the browser window \u2551\n");
557
- process.stderr.write("[yiyan-browser-agent] \u2551 2. Return here and press ENTER to continue \u2551\n");
558
- 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");
559
- await this.waitForEnter();
560
- process.stderr.write("[yiyan-browser-agent] \u2713 Login complete, saving session...\n");
561
- await new Promise((resolve) => setTimeout(resolve, 3e3));
562
- } finally {
563
- await closeBrowser(context, this.verbose);
564
- }
565
- }
566
- /**
567
- * 等待用户按 Enter
568
- */
569
- async waitForEnter() {
570
- const readline = await import("readline");
571
- return new Promise((resolve) => {
572
- const rl = readline.createInterface({
573
- input: process.stdin,
574
- output: process.stderr
575
- });
576
- rl.question("Press ENTER to continue...", () => {
577
- rl.close();
578
- resolve();
579
- });
580
- });
581
- }
582
- /**
583
- * 清除保存的 session
584
- */
585
- async reset() {
586
- const fs2 = await import("fs/promises");
587
- try {
588
- await fs2.rm(this.sessionDir, { recursive: true, force: true });
589
- } catch {
590
- }
591
- }
592
- /**
593
- * 检查状态
594
- */
595
- status() {
596
- const fs2 = require("fs");
597
- return {
598
- loggedIn: fs2.existsSync(this.sessionDir),
599
- sessionPath: this.sessionDir
600
- };
601
- }
602
- /**
603
- * Debug 模式:启动浏览器并输出 DOM 信息
604
- */
605
- async debug() {
606
- const { context, page } = await launchBrowser({
607
- sessionDir: this.sessionDir,
608
- headless: false,
609
- timeout: this.options.timeout,
610
- verbose: true
611
- });
612
- try {
613
- await navigateToYiyan(page, true);
614
- await page.waitForTimeout(3e3);
615
- await dumpDebugInfo(page);
616
- process.stderr.write("\n[yiyan-browser-agent] \u6D4F\u89C8\u5668\u4FDD\u6301\u6253\u5F00\uFF0C\u53EF\u624B\u52A8\u6D4B\u8BD5\u3002\n");
617
- process.stderr.write("[yiyan-browser-agent] \u5173\u95ED\u6D4F\u89C8\u5668\u7A97\u53E3\u7ED3\u675F debug \u6A21\u5F0F...\n");
618
- await new Promise((resolve) => {
619
- context.on("close", () => resolve());
620
- });
621
- } finally {
622
- try {
623
- await context.close();
624
- } catch {
625
- }
626
- }
627
- }
628
- };
629
-
630
- // src/cli.ts
631
- console.log("");
632
- 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");
633
- console.log("\u2551 \u{1F916} Yiyan Browser Agent \u2014 \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 \u2551");
634
- 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");
635
- console.log("");
636
- function printHelp() {
637
- console.log(`
638
- yiyan-browser-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
639
-
640
- \u7528\u6CD5:
641
- yiyan-browser-agent login \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
642
- yiyan-browser-agent ask "\u95EE\u9898" [--timeout ms] [--headful] [--verbose]
643
- yiyan-browser-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
644
- yiyan-browser-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 session
645
- yiyan-browser-agent debug \u8C03\u8BD5\u6A21\u5F0F\uFF08\u8F93\u51FA DOM \u4FE1\u606F\uFF09
646
-
647
- \u547D\u4EE4:
648
- 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
649
- 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
650
- status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
651
- reset \u6E05\u9664\u4FDD\u5B58\u7684 session
652
- 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
653
-
654
- \u9009\u9879:
655
- --timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
656
- --headful \u4F7F\u7528\u6709\u5934\u6D4F\u89C8\u5668\uFF08\u53EF\u89C1\u7A97\u53E3\uFF0C\u7528\u4E8E\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
657
- --verbose \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\uFF08\u8C03\u8BD5\u7528\uFF09
658
- --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
659
-
660
- \u793A\u4F8B:
661
- yiyan-browser-agent login # \u9996\u6B21\u4F7F\u7528\uFF1A\u767B\u5F55\u6587\u5FC3\u4E00\u8A00
662
- yiyan-browser-agent ask "\u4EC0\u4E48\u662F TypeScript\uFF1F" # \u63D0\u95EE\uFF08\u9759\u9ED8\u6A21\u5F0F\uFF09
663
- yiyan-browser-agent ask "\u89E3\u91CA Promise" --verbose # \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7
664
- yiyan-browser-agent ask "30+30=" --headful # \u6709\u5934\u6A21\u5F0F\uFF08\u53EF\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
665
- yiyan-browser-agent debug # \u8C03\u8BD5\u6A21\u5F0F\uFF0C\u8F93\u51FA DOM \u4FE1\u606F
666
- yiyan-browser-agent status
667
- yiyan-browser-agent reset
668
-
669
- \u6D41\u7A0B:
670
- 1. \u5148\u8FD0\u884C yiyan-browser-agent login \u767B\u5F55\uFF08\u53EA\u9700\u4E00\u6B21\uFF0C\u767B\u5F55\u72B6\u6001\u4F1A\u4FDD\u5B58\uFF09
671
- 2. \u4E4B\u540E\u76F4\u63A5 yiyan-browser-agent ask "\u95EE\u9898" \u5373\u53EF\uFF0C\u65E0\u9700\u518D\u767B\u5F55
672
- 3. \u5982\u679C\u9047\u5230\u9A8C\u8BC1\u7801\uFF0C\u52A0 --headful \u5207\u6362\u6709\u5934\u6A21\u5F0F\u624B\u52A8\u5904\u7406
673
-
674
- \u63D0\u793A:
675
- Chromium \u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF08\u7EA6150MB\uFF09\uFF0C\u65E0\u9700\u624B\u52A8\u5B89\u88C5 Chrome
676
- Session \u4FDD\u5B58\u5728 ~/.yiyan-browser-agent/session \u76EE\u5F55
677
- `);
678
- }
679
- function parseCliArgs(args) {
680
- const result = {
681
- command: null
682
- };
683
- for (let i = 0; i < args.length; i++) {
684
- const arg = args[i];
685
- if (arg === "--help") {
686
- result.command = "help";
687
- return result;
688
- }
689
- if (arg === "ask") {
690
- result.command = "ask";
691
- if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
692
- result.question = args[i + 1];
693
- i++;
694
- }
695
- }
696
- if (arg === "status") {
697
- result.command = "status";
698
- }
699
- if (arg === "reset") {
700
- result.command = "reset";
701
- }
702
- if (arg === "login") {
703
- result.command = "login";
704
- }
705
- if (arg === "debug") {
706
- result.command = "debug";
707
- }
708
- if (arg === "--timeout") {
709
- if (i + 1 < args.length) {
710
- result.timeout = parseInt(args[i + 1], 10);
711
- i++;
712
- }
713
- }
714
- if (arg === "--headful") {
715
- result.headful = true;
716
- }
717
- if (arg === "--verbose") {
718
- result.verbose = true;
719
- }
720
- }
721
- return result;
722
- }
723
- function formatCliOutput(output) {
724
- return JSON.stringify(output, null, 2);
725
- }
726
- async function runCli(args) {
727
- const parsed = parseCliArgs(args);
728
- if (parsed.command === "help" || parsed.command === null) {
729
- printHelp();
730
- return;
731
- }
732
- const options = {};
733
- if (parsed.timeout) {
734
- options.timeout = parsed.timeout;
735
- }
736
- if (parsed.verbose) {
737
- options.verbose = true;
738
- }
739
- const agent = new YiyanAgent(options);
740
- const shutdown = async () => {
741
- console.log("\n[yiyan-browser-agent] \u6B63\u5728\u5173\u95ED...");
742
- try {
743
- await agent.close();
744
- } catch {
745
- }
746
- process.exit(0);
747
- };
748
- process.on("SIGINT", shutdown);
749
- process.on("SIGTERM", shutdown);
750
- if (parsed.command === "login") {
751
- try {
752
- console.log("[yiyan-browser-agent] \u542F\u52A8\u767B\u5F55\u6D41\u7A0B...");
753
- await agent.login();
754
- console.log(formatCliOutput({
755
- success: true,
756
- question: "login",
757
- answer: 'Login successful! You can now use "ask" command.',
758
- duration: 0
759
- }));
760
- } catch (error) {
761
- const errorMessage = error instanceof Error ? error.message : String(error);
762
- console.error("[yiyan-browser-agent] \u2717 \u767B\u5F55\u5931\u8D25:", errorMessage);
763
- console.log(formatCliOutput({
764
- success: false,
765
- question: "login",
766
- error: errorMessage,
767
- duration: 0
768
- }));
769
- }
770
- return;
771
- }
772
- if (parsed.command === "status") {
773
- const status = agent.status();
774
- console.log(formatCliOutput({
775
- success: true,
776
- question: "status",
777
- answer: JSON.stringify(status),
778
- duration: 0
779
- }));
780
- return;
781
- }
782
- if (parsed.command === "reset") {
783
- await agent.reset();
784
- console.log(formatCliOutput({
785
- success: true,
786
- question: "reset",
787
- answer: "Session cleared successfully",
788
- duration: 0
789
- }));
790
- return;
791
- }
792
- if (parsed.command === "debug") {
793
- try {
794
- console.log("[yiyan-browser-agent] \u542F\u52A8\u8C03\u8BD5\u6A21\u5F0F...");
795
- await agent.debug();
796
- console.log(formatCliOutput({
797
- success: true,
798
- question: "debug",
799
- answer: "Debug session completed",
800
- duration: 0
801
- }));
802
- } catch (error) {
803
- const errorMessage = error instanceof Error ? error.message : String(error);
804
- console.error("[yiyan-browser-agent] \u2717 \u8C03\u8BD5\u5931\u8D25:", errorMessage);
805
- console.log(formatCliOutput({
806
- success: false,
807
- question: "debug",
808
- error: errorMessage,
809
- duration: 0
810
- }));
811
- }
812
- return;
813
- }
814
- if (parsed.command === "ask") {
815
- if (!parsed.question) {
816
- console.log(formatCliOutput({
817
- success: false,
818
- question: "",
819
- error: 'No question provided. Usage: ask "your question"',
820
- duration: 0
821
- }));
822
- return;
823
- }
824
- const startTime = Date.now();
825
- try {
826
- const answer = await agent.ask(parsed.question, !!parsed.headful);
827
- const duration = Date.now() - startTime;
828
- console.log(formatCliOutput({
829
- success: true,
830
- question: parsed.question,
831
- answer,
832
- duration
833
- }));
834
- } catch (error) {
835
- const duration = Date.now() - startTime;
836
- const errorMessage = error instanceof Error ? error.message : String(error);
837
- console.log(formatCliOutput({
838
- success: false,
839
- question: parsed.question,
840
- error: errorMessage,
841
- duration
842
- }));
843
- }
844
- return;
845
- }
846
- }
847
- runCli(process.argv.slice(2)).catch((err) => {
848
- console.error("[yiyan-browser-agent] Uncaught error:", err);
849
- process.exit(1);
850
- });
851
- // Annotate the CommonJS export names for ESM import in node:
852
- 0 && (module.exports = {
853
- formatCliOutput,
854
- parseCliArgs,
855
- printHelp,
856
- runCli
857
- });
858
- //# sourceMappingURL=cli.js.map