yiyan-browser-agent 1.2.0 → 1.3.0

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 CHANGED
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/agent.ts
4
- import path3 from "path";
5
- import os3 from "os";
10
+ import path from "path";
11
+ import os from "os";
6
12
 
7
13
  // src/types.ts
8
14
  var YiyanAgentError = class extends Error {
@@ -21,279 +27,74 @@ var DEFAULT_OPTIONS = {
21
27
  };
22
28
  var YIYAN_CHAT_URL = "https://yiyan.baidu.com/";
23
29
 
24
- // src/profile.ts
25
- import path from "path";
26
- import os from "os";
27
- import fs from "fs";
28
- import fsp from "fs/promises";
29
- var CONFIG_FILE_PATH = path.join(os.homedir(), ".yiyan-browser-agent", "config.json");
30
- function readConfig() {
31
- try {
32
- if (fs.existsSync(CONFIG_FILE_PATH)) {
33
- const content = fs.readFileSync(CONFIG_FILE_PATH, "utf-8");
34
- return JSON.parse(content);
35
- }
36
- } catch {
37
- }
38
- return {};
39
- }
40
- function saveConfig(config) {
41
- const configDir = path.dirname(CONFIG_FILE_PATH);
42
- if (!fs.existsSync(configDir)) {
43
- fs.mkdirSync(configDir, { recursive: true });
44
- }
45
- fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2));
46
- }
47
- var LINUX_CHROME_PATHS = [
48
- "/usr/bin/google-chrome-stable",
49
- "/usr/bin/google-chrome",
50
- "/usr/bin/chromium-browser",
51
- "/usr/bin/chromium",
52
- "/snap/bin/google-chrome"
53
- // Ubuntu snap 安装
54
- ];
55
- function detectLinuxChromePath() {
56
- for (const chromePath of LINUX_CHROME_PATHS) {
57
- if (fs.existsSync(chromePath)) {
58
- return chromePath;
59
- }
60
- }
61
- return null;
62
- }
63
- function getChromeExecutablePath(platform, userPath) {
64
- if (userPath && fs.existsSync(userPath)) {
65
- return userPath;
66
- }
67
- switch (platform) {
68
- case "win32":
69
- const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
70
- const winPath = path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe");
71
- if (fs.existsSync(winPath)) {
72
- return winPath;
73
- }
74
- const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
75
- const winPathX86 = path.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe");
76
- if (fs.existsSync(winPathX86)) {
77
- return winPathX86;
78
- }
79
- return null;
80
- case "darwin":
81
- const macPath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
82
- if (fs.existsSync(macPath)) {
83
- return macPath;
84
- }
85
- return null;
86
- case "linux":
87
- return detectLinuxChromePath();
88
- default:
89
- return null;
90
- }
91
- }
92
- var PROFILE_TARGET_NAME = "chrome-profile";
93
- async function clearCopiedProfile(profileDir) {
94
- const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
95
- if (fs.existsSync(targetPath)) {
96
- await fsp.rm(targetPath, { recursive: true, force: true });
97
- }
98
- }
99
- function profileExists(profileDir) {
100
- const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
101
- return fs.existsSync(targetPath);
102
- }
103
-
104
30
  // src/browser.ts
105
- import { chromium } from "playwright-core";
106
- import http from "http";
107
- import { spawn, execSync } from "child_process";
108
- import path2 from "path";
109
- import os2 from "os";
110
- import fs2 from "fs";
111
- var CDP_PORT = 19222;
112
- var DEBUG_DIR = path2.join(os2.homedir(), ".yiyan-browser-agent", "debug");
31
+ import { chromium } from "playwright";
32
+ import fs from "fs";
113
33
  function log(verbose, msg) {
114
34
  if (verbose) {
115
35
  process.stderr.write(`[yiyan-agent] ${msg}
116
36
  `);
117
37
  }
118
38
  }
119
- function ensureDebugDir() {
120
- if (!fs2.existsSync(DEBUG_DIR)) {
121
- fs2.mkdirSync(DEBUG_DIR, { recursive: true });
122
- }
123
- }
124
- function killProcessOnPort(port) {
125
- try {
126
- if (process.platform === "win32") {
127
- const output = execSync(
128
- `netstat -ano | findstr :${port} | findstr LISTENING`,
129
- { encoding: "utf-8", timeout: 5e3 }
130
- ).trim();
131
- if (output) {
132
- const lines = output.split("\n").filter((l) => l.trim());
133
- const pids = /* @__PURE__ */ new Set();
134
- for (const line of lines) {
135
- const parts = line.trim().split(/\s+/);
136
- const pid = parts[parts.length - 1];
137
- if (pid && /^\d+$/.test(pid)) pids.add(pid);
138
- }
139
- for (const pid of pids) {
140
- try {
141
- execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
142
- } catch {
143
- }
144
- }
145
- }
146
- } else {
147
- const output = execSync(
148
- `lsof -ti :${port}`,
149
- { encoding: "utf-8", timeout: 5e3 }
150
- ).trim();
151
- if (output) {
152
- const pids = output.split("\n").filter((l) => l.trim());
153
- for (const pid of pids) {
154
- try {
155
- process.kill(parseInt(pid, 10), "SIGKILL");
156
- } catch {
157
- }
158
- }
159
- }
160
- }
161
- } catch {
162
- }
163
- }
164
- function killProcessTree(pid) {
165
- try {
166
- if (process.platform === "win32") {
167
- execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
168
- } else {
169
- try {
170
- process.kill(-pid, "SIGKILL");
171
- } catch {
172
- process.kill(pid, "SIGKILL");
173
- }
174
- }
175
- } catch {
176
- }
177
- }
178
- function waitForCDP(port, timeout = 15e3) {
179
- return new Promise((resolve, reject) => {
180
- const start = Date.now();
181
- const check = () => {
182
- http.get(`http://localhost:${port}/json/version`, (res) => {
183
- let data = "";
184
- res.on("data", (chunk) => data += chunk);
185
- res.on("end", () => {
186
- try {
187
- resolve(JSON.parse(data));
188
- } catch (e) {
189
- reject(e);
190
- }
191
- });
192
- }).on("error", () => {
193
- if (Date.now() - start > timeout) reject(new Error("CDP connection timeout"));
194
- else setTimeout(check, 500);
195
- });
196
- };
197
- check();
198
- });
199
- }
200
39
  async function launchBrowser(options) {
201
- const { chromePath, profilePath, headless, timeout = 3e4, verbose = false } = options;
202
- if (!chromePath || !fs2.existsSync(chromePath)) {
203
- throw new YiyanAgentError(
204
- "BROWSER_LAUNCH",
205
- `Chrome not found at: ${chromePath || "unknown path"}. Please install Chrome or use --chrome-path to specify a custom path.`
206
- );
207
- }
40
+ const { sessionDir, headless, timeout = 3e4, verbose = false } = options;
208
41
  try {
209
- log(verbose, "\u6E05\u7406\u6B8B\u7559 Chrome \u8FDB\u7A0B...");
210
- killProcessOnPort(CDP_PORT);
211
- await new Promise((resolve) => setTimeout(resolve, 500));
212
- const chromeArgs = [
213
- `--remote-debugging-port=${CDP_PORT}`,
214
- `--user-data-dir=${profilePath}`,
215
- "--no-first-run",
216
- "--no-default-browser-check",
217
- "--disable-blink-features=AutomationControlled"
218
- ];
219
- if (os2.platform() === "linux") {
220
- chromeArgs.push(
42
+ log(verbose, "\u542F\u52A8 Chromium \u6D4F\u89C8\u5668...");
43
+ if (!fs.existsSync(sessionDir)) {
44
+ fs.mkdirSync(sessionDir, { recursive: true });
45
+ }
46
+ const context = await chromium.launchPersistentContext(sessionDir, {
47
+ headless,
48
+ viewport: { width: 1280, height: 900 },
49
+ userAgent: [
50
+ "Mozilla/5.0 (X11; Linux x86_64)",
51
+ "AppleWebKit/537.36 (KHTML, like Gecko)",
52
+ "Chrome/124.0.0.0 Safari/537.36"
53
+ ].join(" "),
54
+ args: [
55
+ "--disable-blink-features=AutomationControlled",
56
+ "--no-first-run",
57
+ "--no-default-browser-check",
221
58
  "--no-sandbox",
222
59
  "--disable-setuid-sandbox",
223
60
  "--disable-dev-shm-usage"
224
- );
225
- }
226
- if (headless) {
227
- chromeArgs.push("--headless=new");
228
- }
229
- log(verbose, `\u542F\u52A8 Chrome: ${chromePath} ${headless ? "(headless)" : "(headed)"}`);
230
- log(verbose, `Chrome \u53C2\u6570: ${chromeArgs.join(" ")}`);
231
- const chromeProcess = spawn(chromePath, chromeArgs, {
232
- detached: true,
233
- stdio: "ignore"
234
- });
235
- chromeProcess.unref();
236
- const spawnError = new Promise((_, reject) => {
237
- chromeProcess.on("error", (err) => {
238
- reject(new YiyanAgentError("BROWSER_LAUNCH", `Failed to spawn Chrome: ${err.message}`));
239
- });
61
+ ],
62
+ ignoreDefaultArgs: ["--enable-automation"]
240
63
  });
241
- log(verbose, "\u7B49\u5F85 CDP \u7AEF\u53E3\u5C31\u7EEA...");
242
- await Promise.race([
243
- waitForCDP(CDP_PORT, timeout),
244
- spawnError
245
- ]);
246
- log(verbose, "\u901A\u8FC7 CDP \u8FDE\u63A5\u6D4F\u89C8\u5668...");
247
- const cdpBrowser = await chromium.connectOverCDP(`http://localhost:${CDP_PORT}`);
248
- const contexts = cdpBrowser.contexts();
249
- const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
250
64
  context.setDefaultTimeout(timeout);
251
65
  const pages = context.pages();
252
66
  const page = pages.length > 0 ? pages[0] : await context.newPage();
67
+ await page.addInitScript(() => {
68
+ Object.defineProperty(navigator, "webdriver", { get: () => false });
69
+ });
253
70
  log(verbose, "\u6D4F\u89C8\u5668\u542F\u52A8\u5B8C\u6210");
254
- return { browser: context, cdpBrowser, page, chromeProcess };
71
+ return { context, page };
255
72
  } catch (error) {
256
- killProcessOnPort(CDP_PORT);
257
73
  throw new YiyanAgentError(
258
74
  "BROWSER_LAUNCH",
259
75
  `Failed to launch browser: ${error instanceof Error ? error.message : String(error)}`
260
76
  );
261
77
  }
262
78
  }
263
- async function closeBrowser(browser, chromeProcess, cdpBrowser, verbose) {
79
+ async function closeBrowser(context, verbose) {
264
80
  log(!!verbose, "\u5173\u95ED\u6D4F\u89C8\u5668...");
265
- if (cdpBrowser) {
266
- try {
267
- await cdpBrowser.close();
268
- } catch {
269
- }
270
- }
271
81
  try {
272
- const pages = browser.pages();
273
- for (const page of pages) {
274
- await page.close().catch(() => {
275
- });
276
- }
82
+ await context.close();
83
+ log(!!verbose, "\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED");
277
84
  } catch {
278
85
  }
279
- if (chromeProcess && chromeProcess.pid) {
280
- killProcessTree(chromeProcess.pid);
281
- }
282
- killProcessOnPort(CDP_PORT);
283
- log(!!verbose, "\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED");
284
86
  }
285
87
  async function navigateToYiyan(page, verbose = false) {
286
88
  log(verbose, "\u5BFC\u822A\u5230\u6587\u5FC3\u4E00\u8A00\u804A\u5929\u9875\u9762...");
287
- await page.goto(YIYAN_CHAT_URL, {
288
- waitUntil: "networkidle",
289
- timeout: 6e4
290
- });
291
- await page.waitForTimeout(5e3);
292
89
  try {
293
- await page.waitForSelector('[contenteditable="true"]', { timeout: 15e3 });
294
- log(verbose, "\u9875\u9762\u52A0\u8F7D\u5B8C\u6210\uFF0C\u8F93\u5165\u6846\u5DF2\u5C31\u7EEA");
295
- } catch {
296
- log(verbose, "\u8B66\u544A\uFF1A\u672A\u68C0\u6D4B\u5230\u8F93\u5165\u6846\uFF0C\u7EE7\u7EED\u6267\u884C");
90
+ await page.goto(YIYAN_CHAT_URL, {
91
+ waitUntil: "domcontentloaded",
92
+ timeout: 3e4
93
+ });
94
+ await page.waitForTimeout(1500);
95
+ log(verbose, "\u9875\u9762\u52A0\u8F7D\u5B8C\u6210");
96
+ } catch (err) {
97
+ log(verbose, `\u5BFC\u822A\u8B66\u544A: ${err instanceof Error ? err.message : String(err)}`);
297
98
  }
298
99
  }
299
100
  async function checkCaptcha(page, verbose = false) {
@@ -349,7 +150,7 @@ async function waitForUserAction(page, reason, verbose = false) {
349
150
  const maxWait = 18e4;
350
151
  const startTime = Date.now();
351
152
  while (Date.now() - startTime < maxWait) {
352
- await new Promise((resolve) => setTimeout(resolve, 2e3));
153
+ await page.waitForTimeout(2e3);
353
154
  if (reason === "captcha") {
354
155
  const stillHasCaptcha = await checkCaptcha(page, false);
355
156
  if (!stillHasCaptcha) {
@@ -378,24 +179,38 @@ async function waitForUserAction(page, reason, verbose = false) {
378
179
  log(verbose, "\u26A0 \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\u8D85\u65F6\uFF083\u5206\u949F\uFF09");
379
180
  }
380
181
  async function sendMessage(page, message, verbose = false, headful = false) {
381
- const inputSelector = '[contenteditable="true"]';
182
+ const inputSelectors = [
183
+ '[contenteditable="true"][role="textbox"]',
184
+ '[contenteditable="true"]',
185
+ "textarea[placeholder]",
186
+ "textarea"
187
+ ];
382
188
  log(verbose, "\u7B49\u5F85\u8F93\u5165\u6846\u51FA\u73B0...");
383
- const inputElement = await page.waitForSelector(inputSelector, { timeout: 15e3 });
189
+ let inputElement = null;
190
+ for (const sel of inputSelectors) {
191
+ try {
192
+ inputElement = await page.waitForSelector(sel, { timeout: 4e3, state: "visible" });
193
+ if (inputElement) break;
194
+ } catch {
195
+ }
196
+ }
384
197
  if (!inputElement) {
385
- log(verbose, "\u26A0 \u8F93\u5165\u6846\u672A\u627E\u5230\uFF01");
386
- throw new YiyanAgentError("NETWORK", "Input element not found on page");
198
+ throw new YiyanAgentError("NETWORK", "Cannot find the Yiyan chat input box");
387
199
  }
388
200
  log(verbose, "\u2713 \u8F93\u5165\u6846\u5DF2\u627E\u5230");
389
- await inputElement.click();
390
- await page.waitForTimeout(500);
201
+ await inputElement.click({ force: true });
202
+ await page.waitForTimeout(200);
203
+ await page.keyboard.press("Control+a");
204
+ await page.waitForTimeout(100);
205
+ await page.evaluate((element, content) => {
206
+ element.focus();
207
+ document.execCommand("selectAll", false, null);
208
+ document.execCommand("delete", false, null);
209
+ document.execCommand("insertText", false, content);
210
+ element.dispatchEvent(new InputEvent("input", { bubbles: true, data: content }));
211
+ }, inputElement, message);
391
212
  log(verbose, `\u8F93\u5165\u95EE\u9898: "${message}"`);
392
- await page.keyboard.type(message, { delay: 30 });
393
- const filledValue = await inputElement.innerText();
394
- if (filledValue.includes(message)) {
395
- log(verbose, "\u2713 \u95EE\u9898\u5DF2\u6210\u529F\u586B\u5165\u8F93\u5165\u6846");
396
- } else {
397
- log(verbose, `\u26A0 \u8F93\u5165\u6846\u5185\u5BB9: "${filledValue.substring(0, 50)}"`);
398
- }
213
+ await page.waitForTimeout(400);
399
214
  log(verbose, "\u6309 Enter \u53D1\u9001\u6D88\u606F...");
400
215
  await page.keyboard.press("Enter");
401
216
  await page.waitForTimeout(3e3);
@@ -403,47 +218,32 @@ async function sendMessage(page, message, verbose = false, headful = false) {
403
218
  if (headful) {
404
219
  await waitForUserAction(page, "captcha", verbose);
405
220
  log(verbose, "\u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF0C\u91CD\u65B0\u53D1\u9001\u6D88\u606F...");
406
- const inputEl = await page.waitForSelector(inputSelector, { timeout: 1e4 });
407
- if (inputEl) {
408
- await inputEl.click();
409
- await page.waitForTimeout(300);
410
- await page.keyboard.type(message, { delay: 30 });
411
- await page.keyboard.press("Enter");
412
- await page.waitForTimeout(3e3);
221
+ for (const sel of inputSelectors) {
222
+ try {
223
+ const inputEl = await page.waitForSelector(sel, { timeout: 1e4 });
224
+ if (inputEl) {
225
+ await inputEl.click({ force: true });
226
+ await page.waitForTimeout(300);
227
+ await page.evaluate((element, content) => {
228
+ element.focus();
229
+ document.execCommand("selectAll", false, null);
230
+ document.execCommand("delete", false, null);
231
+ document.execCommand("insertText", false, content);
232
+ }, inputEl, message);
233
+ await page.keyboard.press("Enter");
234
+ await page.waitForTimeout(3e3);
235
+ break;
236
+ }
237
+ } catch {
238
+ }
413
239
  }
414
240
  } else {
415
241
  throw new YiyanAgentError(
416
242
  "CAPTCHA",
417
- 'Yiyan detected automation and triggered a captcha. Use headed mode (default) to manually solve it, or run "login" first.'
243
+ "Yiyan detected automation and triggered a captcha. Use headed mode to manually solve it."
418
244
  );
419
245
  }
420
246
  }
421
- const url = page.url();
422
- log(verbose, `\u5F53\u524D URL: ${url}`);
423
- if (url.includes("/chat/")) {
424
- log(verbose, "\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165\u5BF9\u8BDD\u9875\u9762");
425
- } else if (url === "https://yiyan.baidu.com/" || url === "https://yiyan.baidu.com") {
426
- log(verbose, "\u4ECD\u5728\u6B22\u8FCE\u9875\uFF0C\u5C1D\u8BD5\u70B9\u51FB\u53D1\u9001\u6309\u94AE...");
427
- try {
428
- const sendBtn = page.locator('[class*="send__"]').first();
429
- await sendBtn.click({ timeout: 5e3 });
430
- await page.waitForTimeout(3e3);
431
- log(verbose, "\u2713 \u5DF2\u70B9\u51FB\u53D1\u9001\u6309\u94AE");
432
- } catch {
433
- log(verbose, "\u26A0 \u70B9\u51FB\u53D1\u9001\u6309\u94AE\u5931\u8D25\uFF0C\u7EE7\u7EED\u7B49\u5F85\u56DE\u590D...");
434
- }
435
- } else {
436
- log(verbose, `\u5F53\u524D\u9875\u9762: ${url}`);
437
- }
438
- }
439
- async function saveDebugScreenshot(page, name, verbose) {
440
- try {
441
- ensureDebugDir();
442
- const screenshotPath = path2.join(DEBUG_DIR, `${name}.png`);
443
- await page.screenshot({ path: screenshotPath, fullPage: true });
444
- log(verbose, `\u8C03\u8BD5\u622A\u56FE\u5DF2\u4FDD\u5B58: ${screenshotPath}`);
445
- } catch {
446
- }
447
247
  }
448
248
  async function waitForReply(page, timeout, verbose = false, headful = false) {
449
249
  const maxWait = Math.min(timeout, 6e4);
@@ -479,13 +279,13 @@ async function waitForReply(page, timeout, verbose = false, headful = false) {
479
279
  });
480
280
  if (state.hasAiReply && state.aiTextLen > 0) {
481
281
  if (!replyStarted) {
482
- log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5F00\u59CB\u751F\u6210\uFF08${state.aiTextLen}\u5B57\uFF09: ${state.aiPreview.substring(0, 60)}...`);
282
+ log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5F00\u59CB\u751F\u6210\uFF08${state.aiTextLen}\u5B57\uFF09`);
483
283
  replyStarted = true;
484
284
  }
485
285
  if (state.aiTextLen === lastLen) {
486
286
  stableCount++;
487
287
  if (stableCount >= 3) {
488
- log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${state.aiTextLen}\u5B57\uFF0C\u8FDE\u7EED ${stableCount} \u6B21\u957F\u5EA6\u4E0D\u53D8\uFF09`);
288
+ log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${state.aiTextLen}\u5B57\uFF09`);
489
289
  break;
490
290
  }
491
291
  } else {
@@ -493,134 +293,31 @@ async function waitForReply(page, timeout, verbose = false, headful = false) {
493
293
  lastLen = state.aiTextLen;
494
294
  }
495
295
  }
496
- await page.waitForTimeout(1500);
296
+ await page.waitForTimeout(500);
497
297
  }
498
298
  if (!replyStarted) {
499
299
  if (await checkCaptcha(page, verbose)) {
500
300
  if (headful) {
501
301
  await waitForUserAction(page, "captcha", verbose);
502
- const retryStart = Date.now();
503
- const retryMaxWait = 3e4;
504
- while (Date.now() - retryStart < retryMaxWait) {
505
- const retryState = await page.evaluate(() => {
506
- const answerBox = document.querySelector('[class*="answerBox"]');
507
- if (answerBox) {
508
- const text = answerBox.innerText?.trim() || "";
509
- return { hasAiReply: text.length > 0, aiTextLen: text.length };
510
- }
511
- return { hasAiReply: false, aiTextLen: 0 };
512
- });
513
- if (retryState.hasAiReply) {
514
- log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u901A\u8FC7\u540E AI \u5DF2\u5F00\u59CB\u56DE\u590D");
515
- replyStarted = true;
516
- break;
517
- }
518
- await page.waitForTimeout(1500);
519
- }
520
302
  } else {
521
303
  throw new YiyanAgentError(
522
304
  "CAPTCHA",
523
- 'Yiyan detected automation and triggered a captcha. Use --headful flag to manually solve it, or run "login" first.'
305
+ "Yiyan detected automation and triggered a captcha."
524
306
  );
525
307
  }
526
308
  } else if (headful) {
527
309
  await waitForUserAction(page, "no-reply", verbose);
528
- const retryStart = Date.now();
529
- const retryMaxWait = 6e4;
530
- while (Date.now() - retryStart < retryMaxWait) {
531
- const retryState = await page.evaluate(() => {
532
- const answerBox = document.querySelector('[class*="answerBox"]');
533
- if (answerBox) {
534
- const text = answerBox.innerText?.trim() || "";
535
- return { hasAiReply: text.length > 0, aiTextLen: text.length };
536
- }
537
- return { hasAiReply: false, aiTextLen: 0 };
538
- });
539
- if (retryState.hasAiReply) {
540
- if (retryState.aiTextLen === lastLen) {
541
- stableCount++;
542
- if (stableCount >= 3) {
543
- log(verbose, `\u2713 \u7528\u6237\u64CD\u4F5C\u540E AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${retryState.aiTextLen}\u5B57\uFF09`);
544
- replyStarted = true;
545
- break;
546
- }
547
- } else {
548
- stableCount = 0;
549
- lastLen = retryState.aiTextLen;
550
- }
551
- }
552
- await page.waitForTimeout(1500);
553
- }
554
310
  } else {
555
311
  throw new YiyanAgentError(
556
312
  "TIMEOUT",
557
- 'AI reply timeout in headless mode. Try running "yiyan-agent login" first, or use --headful flag.'
313
+ "AI reply timeout in headless mode. Try running login first, or use headed mode."
558
314
  );
559
315
  }
560
316
  }
561
- await page.waitForTimeout(2e3);
562
- const pageDump = await page.evaluate(() => {
563
- const result = {};
564
- const selectors = [
565
- '[class*="answerBox"]',
566
- '[class*="dialogueCardList"]',
567
- '[class*="dialogue_card_item"]',
568
- '[class*="chatViewer"]',
569
- '[class*="flowBox"]',
570
- '[class*="roleSystem"]',
571
- '[class*="mdRenderContainer"]',
572
- '[class*="agent-markdown"]',
573
- '[class*="markdown"]',
574
- '[class*="content"]',
575
- '[class*="chat"]',
576
- '[class*="message"]'
577
- ];
578
- for (const sel of selectors) {
579
- result[`selector:${sel}`] = document.querySelectorAll(sel).length;
580
- }
581
- const answerBox = document.querySelector('[class*="answerBox"]');
582
- if (answerBox) {
583
- result["answerBox"] = {
584
- textLen: answerBox.innerText?.trim().length || 0,
585
- preview: (answerBox.innerText?.trim() || "").substring(0, 200),
586
- classes: answerBox.className?.toString().substring(0, 100) || ""
587
- };
588
- }
589
- result["bodyText"] = document.body.innerText?.substring(0, 1e3) || "";
590
- const longTextEls = [];
591
- const mainContent = document.querySelector('[class*="chatViewer"]') || document.querySelector('[class*="chat"]') || document.body;
592
- for (const el of Array.from(mainContent.querySelectorAll("div, section, article"))) {
593
- const text = el.innerText?.trim() || "";
594
- if (text.length > 50 && text.length < 1e4) {
595
- longTextEls.push({
596
- tag: el.tagName,
597
- classes: el.className?.toString().split(" ").slice(0, 3).join(" ") || "",
598
- textLen: text.length,
599
- preview: text.substring(0, 80)
600
- });
601
- }
602
- }
603
- result["longTextElements"] = longTextEls.sort((a, b) => b.textLen - a.textLen).slice(0, 10);
604
- return result;
605
- });
606
- log(verbose, "=== \u9875\u9762\u8BCA\u65AD ===");
607
- for (const [key, value] of Object.entries(pageDump)) {
608
- if (key === "bodyText" || key === "longTextElements") continue;
609
- log(verbose, ` ${key}: ${JSON.stringify(value)}`);
610
- }
611
- if (pageDump["longTextElements"] && pageDump["longTextElements"].length > 0) {
612
- log(verbose, " \u957F\u6587\u672C\u5143\u7D20 TOP 10:");
613
- for (const el of pageDump["longTextElements"]) {
614
- log(verbose, ` <${el.tag}> .${el.classes} (${el.textLen}\u5B57): ${el.preview}...`);
615
- }
616
- }
617
- if (pageDump["bodyText"]) {
618
- log(verbose, ` \u9875\u9762\u6587\u672C(\u524D1000\u5B57): ${pageDump["bodyText"].substring(0, 1e3)}`);
619
- }
317
+ await page.waitForTimeout(1e3);
620
318
  }
621
319
  async function extractReply(page, question, verbose = false) {
622
320
  log(verbose, "\u63D0\u53D6 AI \u56DE\u590D\u5185\u5BB9...");
623
- await saveDebugScreenshot(page, "before-extract", verbose);
624
321
  const reply = await page.evaluate((userQuestion) => {
625
322
  const debugInfo = [];
626
323
  const answerBox = document.querySelector('[class*="answerBox"]');
@@ -628,19 +325,12 @@ async function extractReply(page, question, verbose = false) {
628
325
  if (answerBox) {
629
326
  const clone = answerBox.cloneNode(true);
630
327
  const processItems = clone.querySelectorAll('[class*="processItem"], [class*="processContent"]');
631
- for (const item of processItems) {
632
- item.remove();
633
- }
328
+ for (const item of processItems) item.remove();
634
329
  const toolMessages = clone.querySelectorAll('[class*="toolMessage"]');
635
- for (const msg of toolMessages) {
636
- msg.remove();
637
- }
330
+ for (const msg of toolMessages) msg.remove();
638
331
  const thinkHeaders = clone.querySelectorAll('[class*="headerMask"], [class*="topHeader"]');
639
- for (const h of thinkHeaders) {
640
- h.remove();
641
- }
332
+ for (const h of thinkHeaders) h.remove();
642
333
  const text = clone.innerText?.trim() || "";
643
- debugInfo.push(`answerBox after removing thinking: ${text.length} chars`);
644
334
  const cleanedText = text.replace(/^参考\d+个网页\s*/, "").replace(/^深度思考已完成\s*/, "").replace(/^思考完成[::]\s*/, "").replace(/^准备输出结果\s*/, "").trim();
645
335
  if (cleanedText.length > 0) {
646
336
  debugInfo.push(`method1 success: ${cleanedText.length} chars`);
@@ -652,12 +342,10 @@ async function extractReply(page, question, verbose = false) {
652
342
  for (const container of allMdContainers) {
653
343
  const parent = container.parentElement;
654
344
  const parentClass = parent?.className?.toString() || "";
655
- if (parentClass.includes("toolMessage") || parentClass.includes("process")) {
656
- continue;
657
- }
345
+ if (parentClass.includes("toolMessage") || parentClass.includes("process")) continue;
658
346
  const text = container.innerText?.trim() || "";
659
347
  if (text.length > 10) {
660
- debugInfo.push(`method2 success: ${text.length} chars (parent: ${parentClass.substring(0, 50)})`);
348
+ debugInfo.push(`method2 success: ${text.length} chars`);
661
349
  return JSON.stringify({ text, debug: debugInfo });
662
350
  }
663
351
  }
@@ -676,21 +364,6 @@ async function extractReply(page, question, verbose = false) {
676
364
  }
677
365
  }
678
366
  }
679
- const flowBoxes = document.querySelectorAll('[class*="flowBox"]');
680
- debugInfo.push(`flowBoxes count: ${flowBoxes.length}`);
681
- for (let i = flowBoxes.length - 1; i >= 0; i--) {
682
- const box = flowBoxes[i];
683
- const boxClass = box.className?.toString() || "";
684
- if (boxClass.includes("questionBox")) continue;
685
- const clone = box.cloneNode(true);
686
- const thinkEls = clone.querySelectorAll('[class*="processItem"], [class*="processContent"], [class*="toolMessage"]');
687
- for (const el of thinkEls) el.remove();
688
- const text = clone.innerText?.trim() || "";
689
- if (text.length > 10) {
690
- debugInfo.push(`method4 success: flowBox[${i}] ${text.length} chars`);
691
- return JSON.stringify({ text, debug: debugInfo });
692
- }
693
- }
694
367
  const fullText = document.body.innerText;
695
368
  const lines = fullText.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
696
369
  const uiWords = /* @__PURE__ */ new Set([
@@ -707,27 +380,15 @@ async function extractReply(page, question, verbose = false) {
707
380
  "\u672A\u767B\u5F55",
708
381
  "\u767B\u5F55",
709
382
  "\u5185\u5BB9\u7531AI\u751F\u6210\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u8BF7\u4ED4\u7EC6\u7504\u522B",
710
- "\u53C2\u8003",
711
- "\u6DF1\u5EA6\u5206\u6790\u9700\u6C42\u5E76\u89E3\u7B54",
712
- "\u901A\u7528\u5199\u4F5C",
713
- "\u9605\u8BFB\u5206\u6790",
714
- "\u7F51\u9875\u5DE5\u574A",
715
- "\u667A\u80FD\u7FFB\u8BD1",
716
- "\u4EE3\u7801\u7F16\u7A0B"
383
+ "\u53C2\u8003"
717
384
  ]);
718
385
  const thinkingKeywords = [
719
386
  "\u6DF1\u5EA6\u601D\u8003\u5DF2\u5B8C\u6210",
720
387
  "\u601D\u8003\u5B8C\u6210",
721
- "\u51C6\u5907\u8F93\u51FA\u7ED3\u679C",
722
- "picaole\u5728\u8BE2\u95EE",
723
- "picaole\u95EE\u4E86\u4E00\u4E2A",
724
- "picaole\u5728\u641C\u7D22",
725
- "\u76F4\u63A5\u56DE\u7B54\u5373\u53EF",
726
- "\u4E0D\u9700\u8981\u590D\u6742\u5206\u6790",
727
- "\u6309\u7167\u56DE\u7B54\u65B9\u5F0F"
388
+ "\u51C6\u5907\u8F93\u51FA\u7ED3\u679C"
728
389
  ];
729
390
  const userQIdx = lines.findIndex((l) => l === userQuestion || l.includes(userQuestion));
730
- debugInfo.push(`userQ idx in body text: ${userQIdx}, total lines: ${lines.length}`);
391
+ debugInfo.push(`userQ idx: ${userQIdx}, lines: ${lines.length}`);
731
392
  if (userQIdx >= 0) {
732
393
  const replyLines = [];
733
394
  for (let i = userQIdx + 1; i < lines.length; i++) {
@@ -736,34 +397,15 @@ async function extractReply(page, question, verbose = false) {
736
397
  if (thinkingKeywords.some((kw) => line.includes(kw))) continue;
737
398
  if (line.startsWith("\u641C\u7D22") || line.includes("\u7BC7\u8D44\u6599")) continue;
738
399
  if (line.match(/^参考\d+个网页/)) continue;
739
- if (line.length > 0) {
740
- replyLines.push(line);
741
- }
400
+ if (line.length > 0) replyLines.push(line);
742
401
  if (line === "\u5FEB\u901F" || line === "\u66F4\u591A" || line.includes("\u5185\u5BB9\u7531AI\u751F\u6210")) break;
743
402
  }
744
403
  if (replyLines.length > 0) {
745
- debugInfo.push(`method5 success: ${replyLines.length} lines`);
404
+ debugInfo.push(`method4 success: ${replyLines.length} lines`);
746
405
  return JSON.stringify({ text: replyLines.join("\n"), debug: debugInfo });
747
406
  }
748
407
  }
749
- const allTextBlocks = [];
750
- const blockElements = document.querySelectorAll("div, section, article, p");
751
- for (const el of blockElements) {
752
- const elText = el.innerText?.trim() || "";
753
- if (elText.length > 50 && elText.length < 1e4) {
754
- if (uiWords.has(elText)) continue;
755
- if (thinkingKeywords.some((kw) => elText.includes(kw))) continue;
756
- allTextBlocks.push({ text: elText, length: elText.length });
757
- }
758
- }
759
- allTextBlocks.sort((a, b) => b.length - a.length);
760
- if (allTextBlocks.length > 0) {
761
- debugInfo.push(`method6 fallback: longest text block ${allTextBlocks[0].length} chars`);
762
- return JSON.stringify({ text: allTextBlocks[0].text, debug: debugInfo });
763
- }
764
- debugInfo.push(`ALL METHODS FAILED`);
765
- debugInfo.push(`body text length: ${fullText.length}`);
766
- debugInfo.push(`body text first 500: ${fullText.substring(0, 500)}`);
408
+ debugInfo.push("ALL METHODS FAILED");
767
409
  return JSON.stringify({ text: "", debug: debugInfo });
768
410
  }, question);
769
411
  let parsed;
@@ -779,92 +421,39 @@ async function extractReply(page, question, verbose = false) {
779
421
  log(verbose, `\u63D0\u53D6\u6210\u529F\uFF0C\u56DE\u590D\u957F\u5EA6: ${parsed.text.length} \u5B57\u7B26`);
780
422
  return parsed.text;
781
423
  }
782
- log(verbose, "\u63D0\u53D6\u5931\u8D25\uFF0C\u6240\u6709\u65B9\u6CD5\u5747\u672A\u627E\u5230\u56DE\u590D\u5185\u5BB9");
783
424
  throw new YiyanAgentError("TIMEOUT", "Failed to extract reply content");
784
425
  }
785
426
 
786
427
  // src/agent.ts
787
- var DEFAULT_PROFILE_BASE_DIR = path3.join(
788
- os3.homedir(),
789
- ".yiyan-browser-agent"
790
- );
428
+ var DEFAULT_SESSION_DIR = path.join(os.homedir(), ".yiyan-browser-agent", "session");
791
429
  var YiyanAgent = class {
792
430
  options;
793
- profileDir;
431
+ sessionDir;
794
432
  verbose;
795
- savedChromePath;
796
433
  constructor(options) {
797
434
  this.options = {
798
435
  ...DEFAULT_OPTIONS,
799
436
  ...options
800
437
  };
801
- this.profileDir = this.options.profileDir || DEFAULT_PROFILE_BASE_DIR;
438
+ this.sessionDir = this.options.profileDir || DEFAULT_SESSION_DIR;
802
439
  this.verbose = options?.verbose ?? false;
803
- const config = readConfig();
804
- this.savedChromePath = config.chromePath || null;
805
- }
806
- /**
807
- * 保存 Chrome 路径到配置文件
808
- */
809
- saveChromePath(chromePath) {
810
- saveConfig({ chromePath });
811
- this.savedChromePath = chromePath;
812
- }
813
- /**
814
- * 获取有效的 Chrome 路径
815
- * 优先级:用户指定 > 配置文件保存 > 自动探测
816
- */
817
- getEffectiveChromePath(userPath) {
818
- const platform = os3.platform();
819
- if (userPath) {
820
- return userPath;
821
- }
822
- if (this.savedChromePath) {
823
- return this.savedChromePath;
824
- }
825
- return getChromeExecutablePath(platform);
826
440
  }
827
441
  /**
828
442
  * 发送问题并获取答案
829
443
  * @param question 问题文本
830
- * @param headful 是否使用有头浏览器(可见窗口),默认为 false(优先 headless,登录后无需弹窗)
831
- *
832
- * 策略:先尝试 headless(不弹窗),如果失败(验证码/未登录)自动回退到有头模式
444
+ * @param headful 是否使用有头浏览器(可见窗口),默认为 falseheadless
833
445
  */
834
446
  async ask(question, headful = false) {
835
- if (headful) {
836
- return this.executeAsk(question, true);
837
- }
838
- try {
839
- return await this.executeAsk(question, false);
840
- } catch (error) {
841
- if (error instanceof YiyanAgentError && (error.type === "CAPTCHA" || error.type === "TIMEOUT")) {
842
- if (this.verbose) {
843
- process.stderr.write("[yiyan-agent] headless \u6A21\u5F0F\u5931\u8D25\uFF0C\u81EA\u52A8\u5207\u6362\u5230\u6709\u5934\u6A21\u5F0F...\n");
844
- }
845
- return this.executeAsk(question, true);
846
- }
847
- throw error;
848
- }
447
+ return this.executeAsk(question, headful);
849
448
  }
850
449
  /**
851
450
  * 执行单次问答
852
451
  */
853
452
  async executeAsk(question, headful) {
854
- const platform = os3.platform();
855
- const chromePath = this.getEffectiveChromePath(this.options.chromePath);
856
- if (!chromePath) {
857
- throw new YiyanAgentError(
858
- "BROWSER_LAUNCH",
859
- `Chrome not found on ${platform}. Please install Chrome or use --chrome-path to specify a custom path.`
860
- );
861
- }
862
- const profilePath = path3.join(this.profileDir, "chrome-profile");
863
- const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
864
- chromePath,
865
- profilePath,
453
+ const { context, page } = await launchBrowser({
454
+ sessionDir: this.sessionDir,
866
455
  headless: !headful,
867
- timeout: 3e4,
456
+ timeout: this.options.timeout,
868
457
  verbose: this.verbose
869
458
  });
870
459
  try {
@@ -885,7 +474,7 @@ var YiyanAgent = class {
885
474
  const reply = await extractReply(page, question, this.verbose);
886
475
  return reply;
887
476
  } finally {
888
- await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
477
+ await closeBrowser(context, this.verbose);
889
478
  }
890
479
  }
891
480
  /**
@@ -893,59 +482,61 @@ var YiyanAgent = class {
893
482
  * 登录成功后,后续的 ask 调用将自动使用登录状态
894
483
  */
895
484
  async login() {
896
- const platform = os3.platform();
897
- const chromePath = this.getEffectiveChromePath(this.options.chromePath);
898
- if (!chromePath) {
899
- throw new YiyanAgentError(
900
- "BROWSER_LAUNCH",
901
- `Chrome not found on ${platform}. Please install Chrome or use --chrome-path to specify a custom path.`
902
- );
903
- }
904
- const profilePath = path3.join(this.profileDir, "chrome-profile");
905
- const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
906
- chromePath,
907
- profilePath,
485
+ const { context, page } = await launchBrowser({
486
+ sessionDir: this.sessionDir,
908
487
  headless: false,
909
488
  timeout: 6e4,
910
489
  verbose: this.verbose
911
490
  });
912
491
  try {
913
- await page.goto("https://yiyan.baidu.com/", {
914
- waitUntil: "networkidle",
915
- timeout: 6e4
916
- });
917
- await page.waitForTimeout(5e3);
918
- process.stderr.write("\n[yiyan-agent] ============================================\n");
919
- process.stderr.write("[yiyan-agent] \u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\n");
920
- process.stderr.write("[yiyan-agent] \u767B\u5F55\u6210\u529F\u540E\uFF0C\u56DE\u5230\u6B64\u5904\u6309 Enter \u952E\u7EE7\u7EED\n");
921
- process.stderr.write("[yiyan-agent] ============================================\n\n");
922
- const readline = await import("readline");
923
- await new Promise((resolve) => {
924
- const rl = readline.createInterface({
925
- input: process.stdin,
926
- output: process.stderr
927
- });
928
- rl.question("\u6309 Enter \u7EE7\u7EED...", () => {
929
- rl.close();
930
- resolve();
931
- });
932
- });
933
- process.stderr.write("[yiyan-agent] \u2713 \u767B\u5F55\u5B8C\u6210\uFF0C\u6B63\u5728\u4FDD\u5B58\u767B\u5F55\u72B6\u6001...\n");
492
+ await navigateToYiyan(page, this.verbose);
493
+ process.stderr.write("\n[yiyan-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");
494
+ process.stderr.write("[yiyan-agent] \u2551 \u{1F510} LOGIN REQUIRED \u2551\n");
495
+ process.stderr.write("[yiyan-agent] \u2551 \u2551\n");
496
+ process.stderr.write("[yiyan-agent] \u2551 1. Log in to Yiyan in the browser window \u2551\n");
497
+ process.stderr.write("[yiyan-agent] \u2551 2. Return here and press ENTER to continue \u2551\n");
498
+ process.stderr.write("[yiyan-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");
499
+ await this.waitForEnter();
500
+ process.stderr.write("[yiyan-agent] \u2713 Login complete, saving session...\n");
934
501
  await new Promise((resolve) => setTimeout(resolve, 3e3));
935
502
  } finally {
936
- await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
503
+ await closeBrowser(context, this.verbose);
937
504
  }
938
505
  }
939
- sleep(ms) {
940
- return new Promise((resolve) => setTimeout(resolve, ms));
506
+ /**
507
+ * 等待用户按 Enter
508
+ */
509
+ async waitForEnter() {
510
+ const readline = await import("readline");
511
+ return new Promise((resolve) => {
512
+ const rl = readline.createInterface({
513
+ input: process.stdin,
514
+ output: process.stderr
515
+ });
516
+ rl.question("Press ENTER to continue...", () => {
517
+ rl.close();
518
+ resolve();
519
+ });
520
+ });
941
521
  }
522
+ /**
523
+ * 清除保存的 session
524
+ */
942
525
  async reset() {
943
- await clearCopiedProfile(this.profileDir);
526
+ const fs2 = await import("fs/promises");
527
+ try {
528
+ await fs2.rm(this.sessionDir, { recursive: true, force: true });
529
+ } catch {
530
+ }
944
531
  }
532
+ /**
533
+ * 检查状态
534
+ */
945
535
  status() {
536
+ const fs2 = __require("fs");
946
537
  return {
947
- loggedIn: profileExists(this.profileDir),
948
- profilePath: path3.join(this.profileDir, "chrome-profile")
538
+ loggedIn: fs2.existsSync(this.sessionDir),
539
+ sessionPath: this.sessionDir
949
540
  };
950
541
  }
951
542
  };
@@ -956,49 +547,39 @@ function printHelp() {
956
547
  yiyan-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
957
548
 
958
549
  \u7528\u6CD5:
959
- yiyan-agent login [--chrome-path <path>] \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
960
- yiyan-agent ask "\u95EE\u9898" [--timeout ms] [--retry n] [--headful] [--verbose] [--chrome-path <path>]
961
- yiyan-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
962
- yiyan-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
963
- yiyan-agent config \u663E\u793A\u5F53\u524D\u914D\u7F6E
550
+ yiyan-agent login \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
551
+ yiyan-agent ask "\u95EE\u9898" [--timeout ms] [--headful] [--verbose]
552
+ yiyan-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
553
+ yiyan-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 session
964
554
 
965
555
  \u547D\u4EE4:
966
556
  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
967
557
  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
968
558
  status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
969
- reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
970
- config \u663E\u793A\u5F53\u524D\u914D\u7F6E
559
+ reset \u6E05\u9664\u4FDD\u5B58\u7684 session
971
560
 
972
561
  \u9009\u9879:
973
- --timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
974
- --retry <n> \u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3
975
- --headful \u4F7F\u7528\u6709\u5934\u6D4F\u89C8\u5668\uFF08\u53EF\u89C1\u7A97\u53E3\uFF0C\u7528\u4E8E\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
976
- --verbose \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\uFF08\u8C03\u8BD5\u7528\uFF09
977
- --chrome-path <path> \u6307\u5B9A Chrome \u53EF\u6267\u884C\u6587\u4EF6\u8DEF\u5F84\uFF08\u4F1A\u4FDD\u5B58\u914D\u7F6E\uFF0C\u540E\u7EED\u81EA\u52A8\u4F7F\u7528\uFF09
978
- --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
562
+ --timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
563
+ --headful \u4F7F\u7528\u6709\u5934\u6D4F\u89C8\u5668\uFF08\u53EF\u89C1\u7A97\u53E3\uFF0C\u7528\u4E8E\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
564
+ --verbose \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\uFF08\u8C03\u8BD5\u7528\uFF09
565
+ --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
979
566
 
980
567
  \u793A\u4F8B:
981
568
  yiyan-agent login # \u9996\u6B21\u4F7F\u7528\uFF1A\u767B\u5F55\u6587\u5FC3\u4E00\u8A00
982
- yiyan-agent login --chrome-path "/usr/bin/google-chrome-stable" # Linux \u6307\u5B9A Chrome \u8DEF\u5F84
983
569
  yiyan-agent ask "\u4EC0\u4E48\u662F TypeScript\uFF1F" # \u63D0\u95EE\uFF08\u9759\u9ED8\u6A21\u5F0F\uFF09
984
570
  yiyan-agent ask "\u89E3\u91CA Promise" --verbose # \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7
985
571
  yiyan-agent ask "30+30=" --headful # \u6709\u5934\u6A21\u5F0F\uFF08\u53EF\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
986
572
  yiyan-agent status
987
573
  yiyan-agent reset
988
- yiyan-agent config # \u663E\u793A\u4FDD\u5B58\u7684 Chrome \u8DEF\u5F84\u7B49\u914D\u7F6E
989
574
 
990
575
  \u6D41\u7A0B:
991
576
  1. \u5148\u8FD0\u884C yiyan-agent login \u767B\u5F55\uFF08\u53EA\u9700\u4E00\u6B21\uFF0C\u767B\u5F55\u72B6\u6001\u4F1A\u4FDD\u5B58\uFF09
992
577
  2. \u4E4B\u540E\u76F4\u63A5 yiyan-agent ask "\u95EE\u9898" \u5373\u53EF\uFF0C\u65E0\u9700\u518D\u767B\u5F55
993
578
  3. \u5982\u679C\u9047\u5230\u9A8C\u8BC1\u7801\uFF0C\u52A0 --headful \u5207\u6362\u6709\u5934\u6A21\u5F0F\u624B\u52A8\u5904\u7406
994
579
 
995
- Linux \u63D0\u793A:
996
- \u5982\u679C login \u6CA1\u6709\u53CD\u5E94\uFF0C\u8BF7\u5C1D\u8BD5\u6307\u5B9A Chrome \u8DEF\u5F84\uFF1A
997
- yiyan-agent login --chrome-path "/usr/bin/google-chrome-stable"
998
- \u5E38\u89C1\u8DEF\u5F84\uFF1A
999
- /usr/bin/google-chrome-stable (Debian/Ubuntu apt \u5B89\u88C5)
1000
- /usr/bin/chromium-browser (\u67D0\u4E9B Ubuntu \u7248\u672C)
1001
- /snap/bin/google-chrome (Ubuntu snap \u5B89\u88C5)
580
+ \u63D0\u793A:
581
+ Chromium \u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF08\u7EA6150MB\uFF09\uFF0C\u65E0\u9700\u624B\u52A8\u5B89\u88C5 Chrome
582
+ Session \u4FDD\u5B58\u5728 ~/.yiyan-browser-agent/session \u76EE\u5F55
1002
583
  `);
1003
584
  }
1004
585
  function parseCliArgs(args) {
@@ -1027,33 +608,18 @@ function parseCliArgs(args) {
1027
608
  if (arg === "login") {
1028
609
  result.command = "login";
1029
610
  }
1030
- if (arg === "config") {
1031
- result.command = "config";
1032
- }
1033
611
  if (arg === "--timeout") {
1034
612
  if (i + 1 < args.length) {
1035
613
  result.timeout = parseInt(args[i + 1], 10);
1036
614
  i++;
1037
615
  }
1038
616
  }
1039
- if (arg === "--retry") {
1040
- if (i + 1 < args.length) {
1041
- result.retry = parseInt(args[i + 1], 10);
1042
- i++;
1043
- }
1044
- }
1045
617
  if (arg === "--headful") {
1046
618
  result.headful = true;
1047
619
  }
1048
620
  if (arg === "--verbose") {
1049
621
  result.verbose = true;
1050
622
  }
1051
- if (arg === "--chrome-path") {
1052
- if (i + 1 < args.length) {
1053
- result.chromePath = args[i + 1];
1054
- i++;
1055
- }
1056
- }
1057
623
  }
1058
624
  return result;
1059
625
  }
@@ -1070,39 +636,13 @@ async function runCli(args) {
1070
636
  if (parsed.timeout) {
1071
637
  options.timeout = parsed.timeout;
1072
638
  }
1073
- if (parsed.retry) {
1074
- options.retryCount = parsed.retry;
1075
- }
1076
639
  if (parsed.verbose) {
1077
640
  options.verbose = true;
1078
641
  }
1079
- if (parsed.chromePath) {
1080
- options.chromePath = parsed.chromePath;
1081
- }
1082
642
  const agent = new YiyanAgent(options);
1083
- if (parsed.command === "config") {
1084
- const status = agent.status();
1085
- const configInfo = {
1086
- ...status,
1087
- savedChromePath: parsed.chromePath || void 0
1088
- };
1089
- console.log(formatCliOutput({
1090
- success: true,
1091
- question: "config",
1092
- answer: JSON.stringify(configInfo, null, 2),
1093
- duration: 0
1094
- }));
1095
- return;
1096
- }
1097
643
  if (parsed.command === "login") {
1098
- if (parsed.verbose) {
1099
- console.log("Opening browser for login... Please login to Yiyan (\u6587\u5FC3\u4E00\u8A00) in the browser window.");
1100
- }
1101
644
  try {
1102
645
  await agent.login();
1103
- if (parsed.chromePath) {
1104
- agent.saveChromePath(parsed.chromePath);
1105
- }
1106
646
  console.log(formatCliOutput({
1107
647
  success: true,
1108
648
  question: "login",
@@ -1135,7 +675,7 @@ async function runCli(args) {
1135
675
  console.log(formatCliOutput({
1136
676
  success: true,
1137
677
  question: "reset",
1138
- answer: "Profile cleared successfully",
678
+ answer: "Session cleared successfully",
1139
679
  duration: 0
1140
680
  }));
1141
681
  return;