yiyan-browser-agent 1.1.3 → 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 os2 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,214 +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 fs from "fs";
27
- import fsp from "fs/promises";
28
- function getChromeExecutablePath(platform) {
29
- switch (platform) {
30
- case "win32":
31
- const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
32
- return path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe");
33
- case "darwin":
34
- return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
35
- case "linux":
36
- return "/usr/bin/google-chrome";
37
- default:
38
- return null;
39
- }
40
- }
41
- var PROFILE_TARGET_NAME = "chrome-profile";
42
- async function clearCopiedProfile(profileDir) {
43
- const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
44
- if (fs.existsSync(targetPath)) {
45
- await fsp.rm(targetPath, { recursive: true, force: true });
46
- }
47
- }
48
- function profileExists(profileDir) {
49
- const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
50
- return fs.existsSync(targetPath);
51
- }
52
-
53
30
  // src/browser.ts
54
- import { chromium } from "playwright-core";
55
- import http from "http";
56
- import { spawn, execSync } from "child_process";
57
- import path2 from "path";
58
- import os from "os";
59
- import fs2 from "fs";
60
- var CDP_PORT = 19222;
61
- var DEBUG_DIR = path2.join(os.homedir(), ".yiyan-browser-agent", "debug");
31
+ import { chromium } from "playwright";
32
+ import fs from "fs";
62
33
  function log(verbose, msg) {
63
34
  if (verbose) {
64
35
  process.stderr.write(`[yiyan-agent] ${msg}
65
36
  `);
66
37
  }
67
38
  }
68
- function ensureDebugDir() {
69
- if (!fs2.existsSync(DEBUG_DIR)) {
70
- fs2.mkdirSync(DEBUG_DIR, { recursive: true });
71
- }
72
- }
73
- function killProcessOnPort(port) {
74
- try {
75
- if (process.platform === "win32") {
76
- const output = execSync(
77
- `netstat -ano | findstr :${port} | findstr LISTENING`,
78
- { encoding: "utf-8", timeout: 5e3 }
79
- ).trim();
80
- if (output) {
81
- const lines = output.split("\n").filter((l) => l.trim());
82
- const pids = /* @__PURE__ */ new Set();
83
- for (const line of lines) {
84
- const parts = line.trim().split(/\s+/);
85
- const pid = parts[parts.length - 1];
86
- if (pid && /^\d+$/.test(pid)) pids.add(pid);
87
- }
88
- for (const pid of pids) {
89
- try {
90
- execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
91
- } catch {
92
- }
93
- }
94
- }
95
- } else {
96
- const output = execSync(
97
- `lsof -ti :${port}`,
98
- { encoding: "utf-8", timeout: 5e3 }
99
- ).trim();
100
- if (output) {
101
- const pids = output.split("\n").filter((l) => l.trim());
102
- for (const pid of pids) {
103
- try {
104
- process.kill(parseInt(pid, 10), "SIGKILL");
105
- } catch {
106
- }
107
- }
108
- }
109
- }
110
- } catch {
111
- }
112
- }
113
- function killProcessTree(pid) {
114
- try {
115
- if (process.platform === "win32") {
116
- execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
117
- } else {
118
- try {
119
- process.kill(-pid, "SIGKILL");
120
- } catch {
121
- process.kill(pid, "SIGKILL");
122
- }
123
- }
124
- } catch {
125
- }
126
- }
127
- function waitForCDP(port, timeout = 15e3) {
128
- return new Promise((resolve, reject) => {
129
- const start = Date.now();
130
- const check = () => {
131
- http.get(`http://localhost:${port}/json/version`, (res) => {
132
- let data = "";
133
- res.on("data", (chunk) => data += chunk);
134
- res.on("end", () => {
135
- try {
136
- resolve(JSON.parse(data));
137
- } catch (e) {
138
- reject(e);
139
- }
140
- });
141
- }).on("error", () => {
142
- if (Date.now() - start > timeout) reject(new Error("CDP connection timeout"));
143
- else setTimeout(check, 500);
144
- });
145
- };
146
- check();
147
- });
148
- }
149
39
  async function launchBrowser(options) {
150
- const { chromePath, profilePath, headless, timeout = 3e4, verbose = false } = options;
40
+ const { sessionDir, headless, timeout = 3e4, verbose = false } = options;
151
41
  try {
152
- log(verbose, "\u6E05\u7406\u6B8B\u7559 Chrome \u8FDB\u7A0B...");
153
- killProcessOnPort(CDP_PORT);
154
- await new Promise((resolve) => setTimeout(resolve, 500));
155
- const chromeArgs = [
156
- `--remote-debugging-port=${CDP_PORT}`,
157
- `--user-data-dir=${profilePath}`,
158
- "--no-first-run",
159
- "--no-default-browser-check",
160
- "--disable-blink-features=AutomationControlled"
161
- ];
162
- if (headless) {
163
- chromeArgs.push("--headless=new");
164
- }
165
- log(verbose, `\u542F\u52A8 Chrome: ${chromePath} ${headless ? "(headless)" : "(headed)"}`);
166
- const chromeProcess = spawn(chromePath, chromeArgs, {
167
- detached: true,
168
- stdio: "ignore"
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",
58
+ "--no-sandbox",
59
+ "--disable-setuid-sandbox",
60
+ "--disable-dev-shm-usage"
61
+ ],
62
+ ignoreDefaultArgs: ["--enable-automation"]
169
63
  });
170
- chromeProcess.unref();
171
- const spawnError = new Promise((_, reject) => {
172
- chromeProcess.on("error", (err) => {
173
- reject(new YiyanAgentError("BROWSER_LAUNCH", `Failed to spawn Chrome: ${err.message}`));
174
- });
175
- });
176
- log(verbose, "\u7B49\u5F85 CDP \u7AEF\u53E3\u5C31\u7EEA...");
177
- await Promise.race([
178
- waitForCDP(CDP_PORT, timeout),
179
- spawnError
180
- ]);
181
- log(verbose, "\u901A\u8FC7 CDP \u8FDE\u63A5\u6D4F\u89C8\u5668...");
182
- const cdpBrowser = await chromium.connectOverCDP(`http://localhost:${CDP_PORT}`);
183
- const contexts = cdpBrowser.contexts();
184
- const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
185
64
  context.setDefaultTimeout(timeout);
186
65
  const pages = context.pages();
187
66
  const page = pages.length > 0 ? pages[0] : await context.newPage();
67
+ await page.addInitScript(() => {
68
+ Object.defineProperty(navigator, "webdriver", { get: () => false });
69
+ });
188
70
  log(verbose, "\u6D4F\u89C8\u5668\u542F\u52A8\u5B8C\u6210");
189
- return { browser: context, cdpBrowser, page, chromeProcess };
71
+ return { context, page };
190
72
  } catch (error) {
191
- killProcessOnPort(CDP_PORT);
192
73
  throw new YiyanAgentError(
193
74
  "BROWSER_LAUNCH",
194
75
  `Failed to launch browser: ${error instanceof Error ? error.message : String(error)}`
195
76
  );
196
77
  }
197
78
  }
198
- async function closeBrowser(browser, chromeProcess, cdpBrowser, verbose) {
79
+ async function closeBrowser(context, verbose) {
199
80
  log(!!verbose, "\u5173\u95ED\u6D4F\u89C8\u5668...");
200
- if (cdpBrowser) {
201
- try {
202
- await cdpBrowser.close();
203
- } catch {
204
- }
205
- }
206
81
  try {
207
- const pages = browser.pages();
208
- for (const page of pages) {
209
- await page.close().catch(() => {
210
- });
211
- }
82
+ await context.close();
83
+ log(!!verbose, "\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED");
212
84
  } catch {
213
85
  }
214
- if (chromeProcess && chromeProcess.pid) {
215
- killProcessTree(chromeProcess.pid);
216
- }
217
- killProcessOnPort(CDP_PORT);
218
- log(!!verbose, "\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED");
219
86
  }
220
87
  async function navigateToYiyan(page, verbose = false) {
221
88
  log(verbose, "\u5BFC\u822A\u5230\u6587\u5FC3\u4E00\u8A00\u804A\u5929\u9875\u9762...");
222
- await page.goto(YIYAN_CHAT_URL, {
223
- waitUntil: "networkidle",
224
- timeout: 6e4
225
- });
226
- await page.waitForTimeout(5e3);
227
89
  try {
228
- await page.waitForSelector('[contenteditable="true"]', { timeout: 15e3 });
229
- log(verbose, "\u9875\u9762\u52A0\u8F7D\u5B8C\u6210\uFF0C\u8F93\u5165\u6846\u5DF2\u5C31\u7EEA");
230
- } catch {
231
- 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)}`);
232
98
  }
233
99
  }
234
100
  async function checkCaptcha(page, verbose = false) {
@@ -284,7 +150,7 @@ async function waitForUserAction(page, reason, verbose = false) {
284
150
  const maxWait = 18e4;
285
151
  const startTime = Date.now();
286
152
  while (Date.now() - startTime < maxWait) {
287
- await new Promise((resolve) => setTimeout(resolve, 2e3));
153
+ await page.waitForTimeout(2e3);
288
154
  if (reason === "captcha") {
289
155
  const stillHasCaptcha = await checkCaptcha(page, false);
290
156
  if (!stillHasCaptcha) {
@@ -313,24 +179,38 @@ async function waitForUserAction(page, reason, verbose = false) {
313
179
  log(verbose, "\u26A0 \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\u8D85\u65F6\uFF083\u5206\u949F\uFF09");
314
180
  }
315
181
  async function sendMessage(page, message, verbose = false, headful = false) {
316
- const inputSelector = '[contenteditable="true"]';
182
+ const inputSelectors = [
183
+ '[contenteditable="true"][role="textbox"]',
184
+ '[contenteditable="true"]',
185
+ "textarea[placeholder]",
186
+ "textarea"
187
+ ];
317
188
  log(verbose, "\u7B49\u5F85\u8F93\u5165\u6846\u51FA\u73B0...");
318
- 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
+ }
319
197
  if (!inputElement) {
320
- log(verbose, "\u26A0 \u8F93\u5165\u6846\u672A\u627E\u5230\uFF01");
321
- throw new YiyanAgentError("NETWORK", "Input element not found on page");
198
+ throw new YiyanAgentError("NETWORK", "Cannot find the Yiyan chat input box");
322
199
  }
323
200
  log(verbose, "\u2713 \u8F93\u5165\u6846\u5DF2\u627E\u5230");
324
- await inputElement.click();
325
- 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);
326
212
  log(verbose, `\u8F93\u5165\u95EE\u9898: "${message}"`);
327
- await page.keyboard.type(message, { delay: 30 });
328
- const filledValue = await inputElement.innerText();
329
- if (filledValue.includes(message)) {
330
- log(verbose, "\u2713 \u95EE\u9898\u5DF2\u6210\u529F\u586B\u5165\u8F93\u5165\u6846");
331
- } else {
332
- log(verbose, `\u26A0 \u8F93\u5165\u6846\u5185\u5BB9: "${filledValue.substring(0, 50)}"`);
333
- }
213
+ await page.waitForTimeout(400);
334
214
  log(verbose, "\u6309 Enter \u53D1\u9001\u6D88\u606F...");
335
215
  await page.keyboard.press("Enter");
336
216
  await page.waitForTimeout(3e3);
@@ -338,47 +218,32 @@ async function sendMessage(page, message, verbose = false, headful = false) {
338
218
  if (headful) {
339
219
  await waitForUserAction(page, "captcha", verbose);
340
220
  log(verbose, "\u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF0C\u91CD\u65B0\u53D1\u9001\u6D88\u606F...");
341
- const inputEl = await page.waitForSelector(inputSelector, { timeout: 1e4 });
342
- if (inputEl) {
343
- await inputEl.click();
344
- await page.waitForTimeout(300);
345
- await page.keyboard.type(message, { delay: 30 });
346
- await page.keyboard.press("Enter");
347
- 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
+ }
348
239
  }
349
240
  } else {
350
241
  throw new YiyanAgentError(
351
242
  "CAPTCHA",
352
- '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."
353
244
  );
354
245
  }
355
246
  }
356
- const url = page.url();
357
- log(verbose, `\u5F53\u524D URL: ${url}`);
358
- if (url.includes("/chat/")) {
359
- log(verbose, "\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165\u5BF9\u8BDD\u9875\u9762");
360
- } else if (url === "https://yiyan.baidu.com/" || url === "https://yiyan.baidu.com") {
361
- log(verbose, "\u4ECD\u5728\u6B22\u8FCE\u9875\uFF0C\u5C1D\u8BD5\u70B9\u51FB\u53D1\u9001\u6309\u94AE...");
362
- try {
363
- const sendBtn = page.locator('[class*="send__"]').first();
364
- await sendBtn.click({ timeout: 5e3 });
365
- await page.waitForTimeout(3e3);
366
- log(verbose, "\u2713 \u5DF2\u70B9\u51FB\u53D1\u9001\u6309\u94AE");
367
- } catch {
368
- log(verbose, "\u26A0 \u70B9\u51FB\u53D1\u9001\u6309\u94AE\u5931\u8D25\uFF0C\u7EE7\u7EED\u7B49\u5F85\u56DE\u590D...");
369
- }
370
- } else {
371
- log(verbose, `\u5F53\u524D\u9875\u9762: ${url}`);
372
- }
373
- }
374
- async function saveDebugScreenshot(page, name, verbose) {
375
- try {
376
- ensureDebugDir();
377
- const screenshotPath = path2.join(DEBUG_DIR, `${name}.png`);
378
- await page.screenshot({ path: screenshotPath, fullPage: true });
379
- log(verbose, `\u8C03\u8BD5\u622A\u56FE\u5DF2\u4FDD\u5B58: ${screenshotPath}`);
380
- } catch {
381
- }
382
247
  }
383
248
  async function waitForReply(page, timeout, verbose = false, headful = false) {
384
249
  const maxWait = Math.min(timeout, 6e4);
@@ -414,13 +279,13 @@ async function waitForReply(page, timeout, verbose = false, headful = false) {
414
279
  });
415
280
  if (state.hasAiReply && state.aiTextLen > 0) {
416
281
  if (!replyStarted) {
417
- 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`);
418
283
  replyStarted = true;
419
284
  }
420
285
  if (state.aiTextLen === lastLen) {
421
286
  stableCount++;
422
287
  if (stableCount >= 3) {
423
- 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`);
424
289
  break;
425
290
  }
426
291
  } else {
@@ -428,134 +293,31 @@ async function waitForReply(page, timeout, verbose = false, headful = false) {
428
293
  lastLen = state.aiTextLen;
429
294
  }
430
295
  }
431
- await page.waitForTimeout(1500);
296
+ await page.waitForTimeout(500);
432
297
  }
433
298
  if (!replyStarted) {
434
299
  if (await checkCaptcha(page, verbose)) {
435
300
  if (headful) {
436
301
  await waitForUserAction(page, "captcha", verbose);
437
- const retryStart = Date.now();
438
- const retryMaxWait = 3e4;
439
- while (Date.now() - retryStart < retryMaxWait) {
440
- const retryState = await page.evaluate(() => {
441
- const answerBox = document.querySelector('[class*="answerBox"]');
442
- if (answerBox) {
443
- const text = answerBox.innerText?.trim() || "";
444
- return { hasAiReply: text.length > 0, aiTextLen: text.length };
445
- }
446
- return { hasAiReply: false, aiTextLen: 0 };
447
- });
448
- if (retryState.hasAiReply) {
449
- log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u901A\u8FC7\u540E AI \u5DF2\u5F00\u59CB\u56DE\u590D");
450
- replyStarted = true;
451
- break;
452
- }
453
- await page.waitForTimeout(1500);
454
- }
455
302
  } else {
456
303
  throw new YiyanAgentError(
457
304
  "CAPTCHA",
458
- '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."
459
306
  );
460
307
  }
461
308
  } else if (headful) {
462
309
  await waitForUserAction(page, "no-reply", verbose);
463
- const retryStart = Date.now();
464
- const retryMaxWait = 6e4;
465
- while (Date.now() - retryStart < retryMaxWait) {
466
- const retryState = await page.evaluate(() => {
467
- const answerBox = document.querySelector('[class*="answerBox"]');
468
- if (answerBox) {
469
- const text = answerBox.innerText?.trim() || "";
470
- return { hasAiReply: text.length > 0, aiTextLen: text.length };
471
- }
472
- return { hasAiReply: false, aiTextLen: 0 };
473
- });
474
- if (retryState.hasAiReply) {
475
- if (retryState.aiTextLen === lastLen) {
476
- stableCount++;
477
- if (stableCount >= 3) {
478
- log(verbose, `\u2713 \u7528\u6237\u64CD\u4F5C\u540E AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${retryState.aiTextLen}\u5B57\uFF09`);
479
- replyStarted = true;
480
- break;
481
- }
482
- } else {
483
- stableCount = 0;
484
- lastLen = retryState.aiTextLen;
485
- }
486
- }
487
- await page.waitForTimeout(1500);
488
- }
489
310
  } else {
490
311
  throw new YiyanAgentError(
491
312
  "TIMEOUT",
492
- '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."
493
314
  );
494
315
  }
495
316
  }
496
- await page.waitForTimeout(2e3);
497
- const pageDump = await page.evaluate(() => {
498
- const result = {};
499
- const selectors = [
500
- '[class*="answerBox"]',
501
- '[class*="dialogueCardList"]',
502
- '[class*="dialogue_card_item"]',
503
- '[class*="chatViewer"]',
504
- '[class*="flowBox"]',
505
- '[class*="roleSystem"]',
506
- '[class*="mdRenderContainer"]',
507
- '[class*="agent-markdown"]',
508
- '[class*="markdown"]',
509
- '[class*="content"]',
510
- '[class*="chat"]',
511
- '[class*="message"]'
512
- ];
513
- for (const sel of selectors) {
514
- result[`selector:${sel}`] = document.querySelectorAll(sel).length;
515
- }
516
- const answerBox = document.querySelector('[class*="answerBox"]');
517
- if (answerBox) {
518
- result["answerBox"] = {
519
- textLen: answerBox.innerText?.trim().length || 0,
520
- preview: (answerBox.innerText?.trim() || "").substring(0, 200),
521
- classes: answerBox.className?.toString().substring(0, 100) || ""
522
- };
523
- }
524
- result["bodyText"] = document.body.innerText?.substring(0, 1e3) || "";
525
- const longTextEls = [];
526
- const mainContent = document.querySelector('[class*="chatViewer"]') || document.querySelector('[class*="chat"]') || document.body;
527
- for (const el of Array.from(mainContent.querySelectorAll("div, section, article"))) {
528
- const text = el.innerText?.trim() || "";
529
- if (text.length > 50 && text.length < 1e4) {
530
- longTextEls.push({
531
- tag: el.tagName,
532
- classes: el.className?.toString().split(" ").slice(0, 3).join(" ") || "",
533
- textLen: text.length,
534
- preview: text.substring(0, 80)
535
- });
536
- }
537
- }
538
- result["longTextElements"] = longTextEls.sort((a, b) => b.textLen - a.textLen).slice(0, 10);
539
- return result;
540
- });
541
- log(verbose, "=== \u9875\u9762\u8BCA\u65AD ===");
542
- for (const [key, value] of Object.entries(pageDump)) {
543
- if (key === "bodyText" || key === "longTextElements") continue;
544
- log(verbose, ` ${key}: ${JSON.stringify(value)}`);
545
- }
546
- if (pageDump["longTextElements"] && pageDump["longTextElements"].length > 0) {
547
- log(verbose, " \u957F\u6587\u672C\u5143\u7D20 TOP 10:");
548
- for (const el of pageDump["longTextElements"]) {
549
- log(verbose, ` <${el.tag}> .${el.classes} (${el.textLen}\u5B57): ${el.preview}...`);
550
- }
551
- }
552
- if (pageDump["bodyText"]) {
553
- log(verbose, ` \u9875\u9762\u6587\u672C(\u524D1000\u5B57): ${pageDump["bodyText"].substring(0, 1e3)}`);
554
- }
317
+ await page.waitForTimeout(1e3);
555
318
  }
556
319
  async function extractReply(page, question, verbose = false) {
557
320
  log(verbose, "\u63D0\u53D6 AI \u56DE\u590D\u5185\u5BB9...");
558
- await saveDebugScreenshot(page, "before-extract", verbose);
559
321
  const reply = await page.evaluate((userQuestion) => {
560
322
  const debugInfo = [];
561
323
  const answerBox = document.querySelector('[class*="answerBox"]');
@@ -563,19 +325,12 @@ async function extractReply(page, question, verbose = false) {
563
325
  if (answerBox) {
564
326
  const clone = answerBox.cloneNode(true);
565
327
  const processItems = clone.querySelectorAll('[class*="processItem"], [class*="processContent"]');
566
- for (const item of processItems) {
567
- item.remove();
568
- }
328
+ for (const item of processItems) item.remove();
569
329
  const toolMessages = clone.querySelectorAll('[class*="toolMessage"]');
570
- for (const msg of toolMessages) {
571
- msg.remove();
572
- }
330
+ for (const msg of toolMessages) msg.remove();
573
331
  const thinkHeaders = clone.querySelectorAll('[class*="headerMask"], [class*="topHeader"]');
574
- for (const h of thinkHeaders) {
575
- h.remove();
576
- }
332
+ for (const h of thinkHeaders) h.remove();
577
333
  const text = clone.innerText?.trim() || "";
578
- debugInfo.push(`answerBox after removing thinking: ${text.length} chars`);
579
334
  const cleanedText = text.replace(/^参考\d+个网页\s*/, "").replace(/^深度思考已完成\s*/, "").replace(/^思考完成[::]\s*/, "").replace(/^准备输出结果\s*/, "").trim();
580
335
  if (cleanedText.length > 0) {
581
336
  debugInfo.push(`method1 success: ${cleanedText.length} chars`);
@@ -587,12 +342,10 @@ async function extractReply(page, question, verbose = false) {
587
342
  for (const container of allMdContainers) {
588
343
  const parent = container.parentElement;
589
344
  const parentClass = parent?.className?.toString() || "";
590
- if (parentClass.includes("toolMessage") || parentClass.includes("process")) {
591
- continue;
592
- }
345
+ if (parentClass.includes("toolMessage") || parentClass.includes("process")) continue;
593
346
  const text = container.innerText?.trim() || "";
594
347
  if (text.length > 10) {
595
- debugInfo.push(`method2 success: ${text.length} chars (parent: ${parentClass.substring(0, 50)})`);
348
+ debugInfo.push(`method2 success: ${text.length} chars`);
596
349
  return JSON.stringify({ text, debug: debugInfo });
597
350
  }
598
351
  }
@@ -611,21 +364,6 @@ async function extractReply(page, question, verbose = false) {
611
364
  }
612
365
  }
613
366
  }
614
- const flowBoxes = document.querySelectorAll('[class*="flowBox"]');
615
- debugInfo.push(`flowBoxes count: ${flowBoxes.length}`);
616
- for (let i = flowBoxes.length - 1; i >= 0; i--) {
617
- const box = flowBoxes[i];
618
- const boxClass = box.className?.toString() || "";
619
- if (boxClass.includes("questionBox")) continue;
620
- const clone = box.cloneNode(true);
621
- const thinkEls = clone.querySelectorAll('[class*="processItem"], [class*="processContent"], [class*="toolMessage"]');
622
- for (const el of thinkEls) el.remove();
623
- const text = clone.innerText?.trim() || "";
624
- if (text.length > 10) {
625
- debugInfo.push(`method4 success: flowBox[${i}] ${text.length} chars`);
626
- return JSON.stringify({ text, debug: debugInfo });
627
- }
628
- }
629
367
  const fullText = document.body.innerText;
630
368
  const lines = fullText.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
631
369
  const uiWords = /* @__PURE__ */ new Set([
@@ -642,27 +380,15 @@ async function extractReply(page, question, verbose = false) {
642
380
  "\u672A\u767B\u5F55",
643
381
  "\u767B\u5F55",
644
382
  "\u5185\u5BB9\u7531AI\u751F\u6210\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u8BF7\u4ED4\u7EC6\u7504\u522B",
645
- "\u53C2\u8003",
646
- "\u6DF1\u5EA6\u5206\u6790\u9700\u6C42\u5E76\u89E3\u7B54",
647
- "\u901A\u7528\u5199\u4F5C",
648
- "\u9605\u8BFB\u5206\u6790",
649
- "\u7F51\u9875\u5DE5\u574A",
650
- "\u667A\u80FD\u7FFB\u8BD1",
651
- "\u4EE3\u7801\u7F16\u7A0B"
383
+ "\u53C2\u8003"
652
384
  ]);
653
385
  const thinkingKeywords = [
654
386
  "\u6DF1\u5EA6\u601D\u8003\u5DF2\u5B8C\u6210",
655
387
  "\u601D\u8003\u5B8C\u6210",
656
- "\u51C6\u5907\u8F93\u51FA\u7ED3\u679C",
657
- "picaole\u5728\u8BE2\u95EE",
658
- "picaole\u95EE\u4E86\u4E00\u4E2A",
659
- "picaole\u5728\u641C\u7D22",
660
- "\u76F4\u63A5\u56DE\u7B54\u5373\u53EF",
661
- "\u4E0D\u9700\u8981\u590D\u6742\u5206\u6790",
662
- "\u6309\u7167\u56DE\u7B54\u65B9\u5F0F"
388
+ "\u51C6\u5907\u8F93\u51FA\u7ED3\u679C"
663
389
  ];
664
390
  const userQIdx = lines.findIndex((l) => l === userQuestion || l.includes(userQuestion));
665
- debugInfo.push(`userQ idx in body text: ${userQIdx}, total lines: ${lines.length}`);
391
+ debugInfo.push(`userQ idx: ${userQIdx}, lines: ${lines.length}`);
666
392
  if (userQIdx >= 0) {
667
393
  const replyLines = [];
668
394
  for (let i = userQIdx + 1; i < lines.length; i++) {
@@ -671,34 +397,15 @@ async function extractReply(page, question, verbose = false) {
671
397
  if (thinkingKeywords.some((kw) => line.includes(kw))) continue;
672
398
  if (line.startsWith("\u641C\u7D22") || line.includes("\u7BC7\u8D44\u6599")) continue;
673
399
  if (line.match(/^参考\d+个网页/)) continue;
674
- if (line.length > 0) {
675
- replyLines.push(line);
676
- }
400
+ if (line.length > 0) replyLines.push(line);
677
401
  if (line === "\u5FEB\u901F" || line === "\u66F4\u591A" || line.includes("\u5185\u5BB9\u7531AI\u751F\u6210")) break;
678
402
  }
679
403
  if (replyLines.length > 0) {
680
- debugInfo.push(`method5 success: ${replyLines.length} lines`);
404
+ debugInfo.push(`method4 success: ${replyLines.length} lines`);
681
405
  return JSON.stringify({ text: replyLines.join("\n"), debug: debugInfo });
682
406
  }
683
407
  }
684
- const allTextBlocks = [];
685
- const blockElements = document.querySelectorAll("div, section, article, p");
686
- for (const el of blockElements) {
687
- const elText = el.innerText?.trim() || "";
688
- if (elText.length > 50 && elText.length < 1e4) {
689
- if (uiWords.has(elText)) continue;
690
- if (thinkingKeywords.some((kw) => elText.includes(kw))) continue;
691
- allTextBlocks.push({ text: elText, length: elText.length });
692
- }
693
- }
694
- allTextBlocks.sort((a, b) => b.length - a.length);
695
- if (allTextBlocks.length > 0) {
696
- debugInfo.push(`method6 fallback: longest text block ${allTextBlocks[0].length} chars`);
697
- return JSON.stringify({ text: allTextBlocks[0].text, debug: debugInfo });
698
- }
699
- debugInfo.push(`ALL METHODS FAILED`);
700
- debugInfo.push(`body text length: ${fullText.length}`);
701
- debugInfo.push(`body text first 500: ${fullText.substring(0, 500)}`);
408
+ debugInfo.push("ALL METHODS FAILED");
702
409
  return JSON.stringify({ text: "", debug: debugInfo });
703
410
  }, question);
704
411
  let parsed;
@@ -714,68 +421,39 @@ async function extractReply(page, question, verbose = false) {
714
421
  log(verbose, `\u63D0\u53D6\u6210\u529F\uFF0C\u56DE\u590D\u957F\u5EA6: ${parsed.text.length} \u5B57\u7B26`);
715
422
  return parsed.text;
716
423
  }
717
- log(verbose, "\u63D0\u53D6\u5931\u8D25\uFF0C\u6240\u6709\u65B9\u6CD5\u5747\u672A\u627E\u5230\u56DE\u590D\u5185\u5BB9");
718
424
  throw new YiyanAgentError("TIMEOUT", "Failed to extract reply content");
719
425
  }
720
426
 
721
427
  // src/agent.ts
722
- var DEFAULT_PROFILE_BASE_DIR = path3.join(
723
- os2.homedir(),
724
- ".yiyan-browser-agent"
725
- );
428
+ var DEFAULT_SESSION_DIR = path.join(os.homedir(), ".yiyan-browser-agent", "session");
726
429
  var YiyanAgent = class {
727
430
  options;
728
- profileDir;
431
+ sessionDir;
729
432
  verbose;
730
433
  constructor(options) {
731
434
  this.options = {
732
435
  ...DEFAULT_OPTIONS,
733
436
  ...options
734
437
  };
735
- this.profileDir = this.options.profileDir || DEFAULT_PROFILE_BASE_DIR;
438
+ this.sessionDir = this.options.profileDir || DEFAULT_SESSION_DIR;
736
439
  this.verbose = options?.verbose ?? false;
737
440
  }
738
441
  /**
739
442
  * 发送问题并获取答案
740
443
  * @param question 问题文本
741
- * @param headful 是否使用有头浏览器(可见窗口),默认为 false(优先 headless,登录后无需弹窗)
742
- *
743
- * 策略:先尝试 headless(不弹窗),如果失败(验证码/未登录)自动回退到有头模式
444
+ * @param headful 是否使用有头浏览器(可见窗口),默认为 falseheadless
744
445
  */
745
446
  async ask(question, headful = false) {
746
- if (headful) {
747
- return this.executeAsk(question, true);
748
- }
749
- try {
750
- return await this.executeAsk(question, false);
751
- } catch (error) {
752
- if (error instanceof YiyanAgentError && (error.type === "CAPTCHA" || error.type === "TIMEOUT")) {
753
- if (this.verbose) {
754
- process.stderr.write("[yiyan-agent] headless \u6A21\u5F0F\u5931\u8D25\uFF0C\u81EA\u52A8\u5207\u6362\u5230\u6709\u5934\u6A21\u5F0F...\n");
755
- }
756
- return this.executeAsk(question, true);
757
- }
758
- throw error;
759
- }
447
+ return this.executeAsk(question, headful);
760
448
  }
761
449
  /**
762
450
  * 执行单次问答
763
451
  */
764
452
  async executeAsk(question, headful) {
765
- const platform = os2.platform();
766
- const chromePath = this.options.chromePath || getChromeExecutablePath(platform);
767
- if (!chromePath) {
768
- throw new YiyanAgentError(
769
- "BROWSER_LAUNCH",
770
- `Unsupported platform: ${platform}`
771
- );
772
- }
773
- const profilePath = path3.join(this.profileDir, "chrome-profile");
774
- const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
775
- chromePath,
776
- profilePath,
453
+ const { context, page } = await launchBrowser({
454
+ sessionDir: this.sessionDir,
777
455
  headless: !headful,
778
- timeout: 3e4,
456
+ timeout: this.options.timeout,
779
457
  verbose: this.verbose
780
458
  });
781
459
  try {
@@ -796,7 +474,7 @@ var YiyanAgent = class {
796
474
  const reply = await extractReply(page, question, this.verbose);
797
475
  return reply;
798
476
  } finally {
799
- await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
477
+ await closeBrowser(context, this.verbose);
800
478
  }
801
479
  }
802
480
  /**
@@ -804,59 +482,61 @@ var YiyanAgent = class {
804
482
  * 登录成功后,后续的 ask 调用将自动使用登录状态
805
483
  */
806
484
  async login() {
807
- const platform = os2.platform();
808
- const chromePath = this.options.chromePath || getChromeExecutablePath(platform);
809
- if (!chromePath) {
810
- throw new YiyanAgentError(
811
- "BROWSER_LAUNCH",
812
- `Unsupported platform: ${platform}`
813
- );
814
- }
815
- const profilePath = path3.join(this.profileDir, "chrome-profile");
816
- const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
817
- chromePath,
818
- profilePath,
485
+ const { context, page } = await launchBrowser({
486
+ sessionDir: this.sessionDir,
819
487
  headless: false,
820
488
  timeout: 6e4,
821
489
  verbose: this.verbose
822
490
  });
823
491
  try {
824
- await page.goto("https://yiyan.baidu.com/", {
825
- waitUntil: "networkidle",
826
- timeout: 6e4
827
- });
828
- await page.waitForTimeout(5e3);
829
- process.stderr.write("\n[yiyan-agent] ============================================\n");
830
- process.stderr.write("[yiyan-agent] \u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\n");
831
- process.stderr.write("[yiyan-agent] \u767B\u5F55\u6210\u529F\u540E\uFF0C\u56DE\u5230\u6B64\u5904\u6309 Enter \u952E\u7EE7\u7EED\n");
832
- process.stderr.write("[yiyan-agent] ============================================\n\n");
833
- const readline = await import("readline");
834
- await new Promise((resolve) => {
835
- const rl = readline.createInterface({
836
- input: process.stdin,
837
- output: process.stderr
838
- });
839
- rl.question("\u6309 Enter \u7EE7\u7EED...", () => {
840
- rl.close();
841
- resolve();
842
- });
843
- });
844
- 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");
845
501
  await new Promise((resolve) => setTimeout(resolve, 3e3));
846
502
  } finally {
847
- await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
503
+ await closeBrowser(context, this.verbose);
848
504
  }
849
505
  }
850
- sleep(ms) {
851
- 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
+ });
852
521
  }
522
+ /**
523
+ * 清除保存的 session
524
+ */
853
525
  async reset() {
854
- 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
+ }
855
531
  }
532
+ /**
533
+ * 检查状态
534
+ */
856
535
  status() {
536
+ const fs2 = __require("fs");
857
537
  return {
858
- loggedIn: profileExists(this.profileDir),
859
- profilePath: path3.join(this.profileDir, "chrome-profile")
538
+ loggedIn: fs2.existsSync(this.sessionDir),
539
+ sessionPath: this.sessionDir
860
540
  };
861
541
  }
862
542
  };
@@ -868,19 +548,18 @@ yiyan-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
868
548
 
869
549
  \u7528\u6CD5:
870
550
  yiyan-agent login \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
871
- yiyan-agent ask "\u95EE\u9898" [--timeout ms] [--retry n] [--headful] [--verbose]
551
+ yiyan-agent ask "\u95EE\u9898" [--timeout ms] [--headful] [--verbose]
872
552
  yiyan-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
873
- yiyan-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
553
+ yiyan-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 session
874
554
 
875
555
  \u547D\u4EE4:
876
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
877
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
878
558
  status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
879
- reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
559
+ reset \u6E05\u9664\u4FDD\u5B58\u7684 session
880
560
 
881
561
  \u9009\u9879:
882
562
  --timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
883
- --retry <n> \u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3
884
563
  --headful \u4F7F\u7528\u6709\u5934\u6D4F\u89C8\u5668\uFF08\u53EF\u89C1\u7A97\u53E3\uFF0C\u7528\u4E8E\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
885
564
  --verbose \u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7\uFF08\u8C03\u8BD5\u7528\uFF09
886
565
  --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
@@ -897,6 +576,10 @@ yiyan-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
897
576
  1. \u5148\u8FD0\u884C yiyan-agent login \u767B\u5F55\uFF08\u53EA\u9700\u4E00\u6B21\uFF0C\u767B\u5F55\u72B6\u6001\u4F1A\u4FDD\u5B58\uFF09
898
577
  2. \u4E4B\u540E\u76F4\u63A5 yiyan-agent ask "\u95EE\u9898" \u5373\u53EF\uFF0C\u65E0\u9700\u518D\u767B\u5F55
899
578
  3. \u5982\u679C\u9047\u5230\u9A8C\u8BC1\u7801\uFF0C\u52A0 --headful \u5207\u6362\u6709\u5934\u6A21\u5F0F\u624B\u52A8\u5904\u7406
579
+
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
900
583
  `);
901
584
  }
902
585
  function parseCliArgs(args) {
@@ -931,12 +614,6 @@ function parseCliArgs(args) {
931
614
  i++;
932
615
  }
933
616
  }
934
- if (arg === "--retry") {
935
- if (i + 1 < args.length) {
936
- result.retry = parseInt(args[i + 1], 10);
937
- i++;
938
- }
939
- }
940
617
  if (arg === "--headful") {
941
618
  result.headful = true;
942
619
  }
@@ -959,15 +636,11 @@ async function runCli(args) {
959
636
  if (parsed.timeout) {
960
637
  options.timeout = parsed.timeout;
961
638
  }
962
- if (parsed.retry) {
963
- options.retryCount = parsed.retry;
964
- }
965
639
  if (parsed.verbose) {
966
640
  options.verbose = true;
967
641
  }
968
642
  const agent = new YiyanAgent(options);
969
643
  if (parsed.command === "login") {
970
- console.log("Opening browser for login... Please login to Yiyan (\u6587\u5FC3\u4E00\u8A00) in the browser window.");
971
644
  try {
972
645
  await agent.login();
973
646
  console.log(formatCliOutput({
@@ -1002,7 +675,7 @@ async function runCli(args) {
1002
675
  console.log(formatCliOutput({
1003
676
  success: true,
1004
677
  question: "reset",
1005
- answer: "Profile cleared successfully",
678
+ answer: "Session cleared successfully",
1006
679
  duration: 0
1007
680
  }));
1008
681
  return;