yiyan-browser-agent 1.4.6 โ†’ 1.4.8

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.
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ // src/calibrate.js โ€” Auto-detect Yiyan UI selectors and update browser.js
3
+ 'use strict';
4
+
5
+ const { chromium } = require('playwright');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const config = require('./config');
9
+
10
+ console.log('\n๐Ÿ”ฌ Yiyan Agent โ€” Selector Calibration Tool\n');
11
+ console.log('This tool opens Yiyan (ๆ–‡ๅฟƒไธ€่จ€), inspects the DOM, and prints out');
12
+ console.log('the selectors that your browser.js should use.\n');
13
+
14
+ async function calibrate() {
15
+ const context = await chromium.launchPersistentContext(config.SESSION_DIR, {
16
+ headless : false,
17
+ viewport : { width: 1280, height: 900 },
18
+ });
19
+
20
+ const pages = context.pages();
21
+ const page = pages.length > 0 ? pages[0] : await context.newPage();
22
+
23
+ console.log('โ†’ Navigating to', config.YIYAN_URL, '...');
24
+ await page.goto(config.YIYAN_URL, { waitUntil: 'domcontentloaded', timeout: 30_000 });
25
+ await page.waitForTimeout(3_000);
26
+
27
+ console.log('โ†’ Inspecting DOM...\n');
28
+
29
+ const report = await page.evaluate(() => {
30
+ function isVisible(el) {
31
+ const s = window.getComputedStyle(el);
32
+ return s.display !== 'none' && s.visibility !== 'hidden' && s.opacity !== '0' && el.offsetParent !== null;
33
+ }
34
+
35
+ function classify(el) {
36
+ return {
37
+ tag : el.tagName.toLowerCase(),
38
+ id : el.id || null,
39
+ classes : el.className?.slice?.(0, 120) || null,
40
+ placeholder : el.placeholder || null,
41
+ ariaLabel : el.getAttribute('aria-label') || null,
42
+ dataTestId : el.dataset?.testid || null,
43
+ role : el.getAttribute('role') || null,
44
+ visible : isVisible(el),
45
+ text : (el.innerText || '').slice(0, 40).replace(/\n/g, ' ') || null,
46
+ type : el.getAttribute('type') || null,
47
+ };
48
+ }
49
+
50
+ // โ”€โ”€ Inputs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
51
+ const inputs = Array.from(document.querySelectorAll('textarea, [contenteditable="true"]'))
52
+ .map(classify);
53
+
54
+ // โ”€โ”€ Buttons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
55
+ const buttons = Array.from(document.querySelectorAll('button, [role="button"]'))
56
+ .filter(isVisible)
57
+ .map(classify)
58
+ .slice(0, 30);
59
+
60
+ // โ”€โ”€ All named classes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
61
+ const classFreq = {};
62
+ document.querySelectorAll('*').forEach(el => {
63
+ (el.getAttribute('class') || '').split(/\s+/).forEach(c => {
64
+ if (c.length > 2 && c.length < 50) {
65
+ classFreq[c] = (classFreq[c] || 0) + 1;
66
+ }
67
+ });
68
+ });
69
+ const topClasses = Object.entries(classFreq)
70
+ .sort((a, b) => b[1] - a[1])
71
+ .slice(0, 80)
72
+ .map(([cls, n]) => ({ cls, n }));
73
+
74
+ // โ”€โ”€ Suggested selectors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
75
+ const suggestedInput = (
76
+ inputs.find(i => i.placeholder?.toLowerCase().includes('message'))?.id ||
77
+ inputs.find(i => i.placeholder?.toLowerCase().includes('ask'))?.id ||
78
+ inputs.find(i => i.visible)?.id ||
79
+ null
80
+ );
81
+
82
+ const sendBtn = buttons.find(b =>
83
+ /send/i.test(b.ariaLabel || '') ||
84
+ /send/i.test(b.text || '') ||
85
+ /send/i.test(b.classes || '')
86
+ );
87
+
88
+ const stopBtn = buttons.find(b =>
89
+ /stop/i.test(b.ariaLabel || '') ||
90
+ /stop/i.test(b.text || '') ||
91
+ /stop/i.test(b.classes || '')
92
+ );
93
+
94
+ const newChatBtn = buttons.find(b =>
95
+ /new chat/i.test(b.ariaLabel || '') ||
96
+ /new chat/i.test(b.text || '') ||
97
+ /new.?chat/i.test(b.classes || '')
98
+ );
99
+
100
+ return {
101
+ url : window.location.href,
102
+ title : document.title,
103
+ inputs,
104
+ buttons,
105
+ topClasses,
106
+ suggestions: { suggestedInput, sendBtn, stopBtn, newChatBtn },
107
+ };
108
+ });
109
+
110
+ // โ”€โ”€ Print report โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
111
+ const sep = 'โ”€'.repeat(60);
112
+
113
+ console.log(sep);
114
+ console.log('URL :', report.url);
115
+ console.log('Title :', report.title);
116
+ console.log(sep);
117
+
118
+ console.log('\n๐Ÿ“ฅ INPUT ELEMENTS:');
119
+ if (report.inputs.length === 0) {
120
+ console.log(' (none found โ€” are you logged in?)');
121
+ }
122
+ report.inputs.forEach((el, i) => {
123
+ console.log(` [${i}] ${JSON.stringify(el)}`);
124
+ });
125
+
126
+ console.log('\n๐Ÿ”˜ BUTTONS (visible, first 30):');
127
+ report.buttons.forEach((el, i) => {
128
+ console.log(` [${i}] ${JSON.stringify(el)}`);
129
+ });
130
+
131
+ console.log('\n๐Ÿท๏ธ TOP CSS CLASSES:');
132
+ report.topClasses.slice(0, 40).forEach(({ cls, n }) => {
133
+ console.log(` ${String(n).padStart(4)}x .${cls}`);
134
+ });
135
+
136
+ console.log('\n' + sep);
137
+ console.log('๐ŸŽฏ SUGGESTED SELECTORS (update browser.js SEL object):');
138
+ console.log(sep);
139
+
140
+ const s = report.suggestions;
141
+
142
+ if (s.suggestedInput) {
143
+ console.log(` chatInput : '#${s.suggestedInput}' (or use the id above)`);
144
+ }
145
+ if (s.sendBtn) {
146
+ const sel = s.sendBtn.ariaLabel
147
+ ? `button[aria-label="${s.sendBtn.ariaLabel}"]`
148
+ : s.sendBtn.id ? `#${s.sendBtn.id}` : `.${s.sendBtn.classes?.split(' ')[0]}`;
149
+ console.log(` sendButton : '${sel}'`);
150
+ }
151
+ if (s.stopBtn) {
152
+ const sel = s.stopBtn.ariaLabel
153
+ ? `button[aria-label="${s.stopBtn.ariaLabel}"]`
154
+ : `.${s.stopBtn.classes?.split(' ')[0]}`;
155
+ console.log(` stopButton : '${sel}'`);
156
+ }
157
+ if (s.newChatBtn) {
158
+ const sel = s.newChatBtn.ariaLabel
159
+ ? `button[aria-label="${s.newChatBtn.ariaLabel}"]`
160
+ : `.${s.newChatBtn.classes?.split(' ')[0]}`;
161
+ console.log(` newChat : '${sel}'`);
162
+ }
163
+
164
+ console.log(sep);
165
+ console.log('\n๐Ÿ“ธ Taking screenshot โ†’ /tmp/yiyan-calibrate.png');
166
+ await page.screenshot({ path: '/tmp/yiyan-calibrate.png', fullPage: false });
167
+
168
+ console.log('\nโœ… Calibration complete! Update src/browser.js SEL object with the selectors above.');
169
+ console.log(' Press Ctrl+C to exit.\n');
170
+
171
+ // Keep browser open so user can inspect manually
172
+ await new Promise(() => {}); // wait forever
173
+ }
174
+
175
+ calibrate().catch(err => {
176
+ console.error('Calibration error:', err.message);
177
+ process.exit(1);
178
+ });
package/src/client.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ // src/client.js โ€” TCP client for sending tasks to running server
3
+ 'use strict';
4
+
5
+ const net = require('net');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const { DEFAULT_PORT, LOCK_FILE } = require('./server');
11
+
12
+ class AgentClient {
13
+ constructor() {
14
+ this.port = DEFAULT_PORT;
15
+ }
16
+
17
+ // ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰ๆœๅŠกๅ™จ่ฟ่กŒ
18
+ isServerAvailable() {
19
+ if (fs.existsSync(LOCK_FILE)) {
20
+ try {
21
+ const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf8'));
22
+ // ๆฃ€ๆŸฅ่ฟ›็จ‹ๆ˜ฏๅฆๆดป็€
23
+ try {
24
+ process.kill(lock.pid, 0);
25
+ this.port = lock.port || DEFAULT_PORT;
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ return false;
35
+ }
36
+
37
+ // ๅ‘้€ไปปๅŠกๅˆฐๆœๅŠกๅ™จ
38
+ async sendTask(task, options = {}) {
39
+ return new Promise((resolve, reject) => {
40
+ const socket = net.connect(this.port, 'localhost');
41
+ const timeout = options.timeout || 180000; // 3ๅˆ†้’Ÿ
42
+
43
+ let data = '';
44
+ let timer = setTimeout(() => {
45
+ socket.destroy();
46
+ reject(new Error('Connection timeout'));
47
+ }, timeout);
48
+
49
+ socket.on('connect', () => {
50
+ clearTimeout(timer);
51
+ // ๅ‘้€ไปปๅŠก
52
+ const request = JSON.stringify({
53
+ task,
54
+ newChat: options.newChat !== false
55
+ });
56
+ socket.end(request);
57
+ });
58
+
59
+ socket.on('data', (chunk) => {
60
+ data += chunk.toString();
61
+ });
62
+
63
+ socket.on('end', () => {
64
+ try {
65
+ const result = JSON.parse(data);
66
+ resolve(result);
67
+ } catch (err) {
68
+ reject(new Error(`Invalid response: ${data}`));
69
+ }
70
+ });
71
+
72
+ socket.on('error', (err) => {
73
+ clearTimeout(timer);
74
+ reject(err);
75
+ });
76
+ });
77
+ }
78
+ }
79
+
80
+ module.exports = AgentClient;
package/src/config.js ADDED
@@ -0,0 +1,68 @@
1
+ // src/config.js โ€” Central configuration for Yiyan Agent
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+
6
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
+ // Default configuration
8
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9
+ const defaults = {
10
+ // Browser
11
+ YIYAN_URL : 'https://yiyan.baidu.com/',
12
+ SESSION_DIR : path.join(os.homedir(), '.yiyan-agent', 'session'),
13
+ HEADLESS : false,
14
+
15
+ // Timing (configurable for speed vs stability tradeoff)
16
+ RESPONSE_TIMEOUT : 180_000,
17
+ STABLE_DELAY : 1_000, // 1s stability check (faster, may cut off long responses)
18
+ SEND_DELAY : 100, // Reduced for faster operation
19
+
20
+ // Agent
21
+ MAX_ITERATIONS : 40,
22
+ WORKING_DIR : process.cwd(),
23
+
24
+ // Output
25
+ MAX_OUTPUT_LENGTH : 8_000,
26
+ DEBUG : false,
27
+ };
28
+
29
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
30
+ // Config loading priority (highest wins):
31
+ //
32
+ // 1. ~/.yiyan-agent/config.json โ€” global user config
33
+ // 2. ./yiyan-agent.config.json โ€” per-project config
34
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
35
+
36
+ function loadJson(filePath) {
37
+ try {
38
+ if (fs.existsSync(filePath)) {
39
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
40
+ }
41
+ } catch {
42
+ console.warn('[yiyan-agent] Could not parse config file: ' + filePath);
43
+ }
44
+ return {};
45
+ }
46
+
47
+ const globalConfigPath = path.join(os.homedir(), '.yiyan-agent', 'config.json');
48
+ const projectConfigPath = path.join(process.cwd(), 'yiyan-agent.config.json');
49
+
50
+ const config = {
51
+ ...defaults,
52
+ ...loadJson(globalConfigPath), // global overrides defaults
53
+ ...loadJson(projectConfigPath), // project overrides global
54
+ };
55
+
56
+ // Remove comment keys from JSON files
57
+ delete config._comment;
58
+
59
+ // Resolve session dir to absolute path
60
+ if (!path.isAbsolute(config.SESSION_DIR)) {
61
+ config.SESSION_DIR = path.resolve(process.cwd(), config.SESSION_DIR);
62
+ }
63
+
64
+ // Ensure required directories exist
65
+ fs.mkdirSync(config.SESSION_DIR, { recursive: true });
66
+ fs.mkdirSync(path.join(os.homedir(), '.yiyan-agent', 'logs'), { recursive: true });
67
+
68
+ module.exports = config;
package/src/index.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ // src/index.js โ€” CLI entry point
3
+ 'use strict';
4
+
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const config = require('./config');
8
+ const logger = require('./logger');
9
+ const YiyanAgent = require('./agent');
10
+ const AgentClient = require('./client');
11
+
12
+ function parseArgs(argv) {
13
+ const args = argv.slice(2);
14
+ const opts = { task: null, interactive: false, debug: false, showBrowser: false, workingDir: null, calibrate: false, help: false };
15
+
16
+ for (let i = 0; i < args.length; i++) {
17
+ const a = args[i];
18
+ if (a === '-i' || a === '--interactive') opts.interactive = true;
19
+ else if (a === '--debug') opts.debug = true;
20
+ else if (a === '--show-browser') opts.showBrowser = true;
21
+ else if (a === '--calibrate') opts.calibrate = true;
22
+ else if (a === '-h' || a === '--help') opts.help = true;
23
+ else if (a === '-d' || a === '--dir') opts.workingDir = args[++i];
24
+ else if (a === '-t' || a === '--task') opts.task = args[++i];
25
+ else if (!a.startsWith('-')) { opts.task = args.slice(i).join(' '); break; }
26
+ }
27
+ return opts;
28
+ }
29
+
30
+ function printHelp() {
31
+ console.log(`
32
+ yiyan-agent โ€” AI Agent via Yiyan Browser
33
+
34
+ USAGE
35
+ yiyan-agent "ไปปๅŠก" # ๆ‰ง่กŒไปปๅŠก๏ผŒๅช่พ“ๅ‡บJSON
36
+ yiyan-agent -i # ไบคไบ’ๆจกๅผ๏ผˆๅฏๅŠจๆœๅŠกๅ™จ๏ผŒๆŽฅๆ”ถ่ฟœ็จ‹ไปปๅŠก๏ผ‰
37
+
38
+ OPTIONS
39
+ -i, --interactive ไบคไบ’ๆจกๅผ๏ผˆๅฏๅŠจ TCP ๆœๅŠก๏ผŒ็ซฏๅฃ 9527๏ผ‰
40
+ --show-browser ๆ˜พ็คบๆต่งˆๅ™จ
41
+ --debug ่ฐƒ่ฏ•ๆจกๅผ
42
+ -h, --help ๅธฎๅŠฉ
43
+
44
+ ่ฟ›็จ‹้—ด้€šไฟก
45
+ ๅฝ“ไบคไบ’ๆจกๅผ่ฟ่กŒๆ—ถ๏ผŒๅ…ถไป– yiyan-agent ่ฟ›็จ‹ไผš่‡ชๅŠจๆฃ€ๆต‹ๅนถ่ฝฌๅ‘ไปปๅŠก๏ผŒ
46
+ ้ฟๅ…ๆฏๆฌก้‡ๆ–ฐๅฏๅŠจๆต่งˆๅ™จใ€‚
47
+
48
+ ้”ๆ–‡ไปถ: ~/.yiyan-agent/server.lock
49
+ `);
50
+ }
51
+
52
+ async function main() {
53
+ const opts = parseArgs(process.argv);
54
+
55
+ if (opts.help) { printHelp(); process.exit(0); }
56
+
57
+ if (opts.debug) config.DEBUG = true;
58
+
59
+ // Headless: ๅ•ไปปๅŠก้ป˜่ฎค้š่—๏ผŒไบคไบ’ๆจกๅผๆ˜พ็คบ
60
+ config.HEADLESS = !(opts.interactive || opts.calibrate || opts.showBrowser);
61
+
62
+ if (opts.workingDir) {
63
+ const resolved = path.resolve(opts.workingDir);
64
+ if (!fs.existsSync(resolved)) {
65
+ if (opts.interactive) logger.error(`Dir not found: ${resolved}`);
66
+ else console.log(JSON.stringify({ question: '', answer: `Dir not found: ${resolved}`, status: 'error' }));
67
+ process.exit(1);
68
+ }
69
+ config.WORKING_DIR = resolved;
70
+ }
71
+
72
+ // ๅชๅœจไบคไบ’/่ฐƒ่ฏ•ๆจกๅผๆ˜พ็คบๆ—ฅๅฟ—
73
+ const silent = !opts.interactive && !opts.debug && !opts.calibrate;
74
+ if (!silent) {
75
+ logger.banner();
76
+ logger.info(`Working dir: ${config.WORKING_DIR}`);
77
+ }
78
+
79
+ const agent = new YiyanAgent();
80
+
81
+ const shutdown = async (code = 0) => {
82
+ if (!silent) logger.info('Shutting down...');
83
+ try { await agent.shutdown(); } catch {}
84
+ process.exit(code);
85
+ };
86
+
87
+ process.on('SIGINT', () => shutdown(0));
88
+ process.on('SIGTERM', () => shutdown(0));
89
+
90
+ if (opts.calibrate) {
91
+ await agent.init();
92
+ await agent.browser.dumpDebugInfo();
93
+ await agent.browser.screenshot();
94
+ await shutdown(0);
95
+ }
96
+
97
+ if (!opts.interactive && !opts.task) opts.interactive = true;
98
+
99
+ try {
100
+ await agent.init();
101
+ } catch (err) {
102
+ if (silent) console.log(JSON.stringify({ question: opts.task || '', answer: `Error: ${err.message}`, status: 'error' }));
103
+ else logger.error(`Failed: ${err.message}`);
104
+ process.exit(1);
105
+ }
106
+
107
+ try {
108
+ if (opts.interactive) {
109
+ await agent.runInteractive();
110
+ } else {
111
+ // ๅ•ไปปๅŠกๆจกๅผ๏ผšๅ…ˆๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰ๆœๅŠกๅ™จ่ฟ่กŒ
112
+ const client = new AgentClient();
113
+
114
+ if (client.isServerAvailable()) {
115
+ // ๆœ‰ๆœๅŠกๅ™จ่ฟ่กŒ๏ผŒ่ฝฌๅ‘ไปปๅŠก
116
+ logger.info(`Found running server, forwarding task...`);
117
+ try {
118
+ const result = await client.sendTask(opts.task);
119
+ console.log(JSON.stringify(result, null, 2));
120
+ // ็›ดๆŽฅ้€€ๅ‡บ๏ผŒไธๅ…ณ้—ญๆต่งˆๅ™จ๏ผˆๆœๅŠกๅ™จ็ฎก็†๏ผ‰
121
+ process.exit(0);
122
+ } catch (err) {
123
+ // ่ฝฌๅ‘ๅคฑ่ดฅ๏ผŒfallback ๅˆฐๆœฌๅœฐๆ‰ง่กŒ
124
+ logger.warn(`Server connection failed: ${err.message}, running locally...`);
125
+ }
126
+ }
127
+
128
+ // ๆ— ๆœๅŠกๅ™จๆˆ–่ฝฌๅ‘ๅคฑ่ดฅ๏ผŒๆœฌๅœฐๆ‰ง่กŒ
129
+ const result = await agent.run(opts.task);
130
+ console.log(JSON.stringify(result, null, 2));
131
+ }
132
+ } catch (err) {
133
+ console.log(JSON.stringify({ question: opts.task || '', answer: `Error: ${err.message}`, status: 'error' }));
134
+ }
135
+
136
+ await shutdown(0);
137
+ }
138
+
139
+ main();
package/src/logger.js ADDED
@@ -0,0 +1,118 @@
1
+ // src/logger.js โ€” ANSI-colored terminal output (no dependencies)
2
+ 'use strict';
3
+
4
+ const A = {
5
+ reset : '\x1b[0m',
6
+ bold : '\x1b[1m',
7
+ dim : '\x1b[2m',
8
+ red : '\x1b[31m',
9
+ green : '\x1b[32m',
10
+ yellow : '\x1b[33m',
11
+ blue : '\x1b[34m',
12
+ magenta : '\x1b[35m',
13
+ cyan : '\x1b[36m',
14
+ white : '\x1b[37m',
15
+ gray : '\x1b[90m',
16
+ lred : '\x1b[91m',
17
+ lgreen : '\x1b[92m',
18
+ lyellow : '\x1b[93m',
19
+ lblue : '\x1b[94m',
20
+ lmagenta: '\x1b[95m',
21
+ lcyan : '\x1b[96m',
22
+ };
23
+
24
+ const c = (code, text) => `${A[code]}${text}${A.reset}`;
25
+ const cb = (code, text) => `${A.bold}${A[code]}${text}${A.reset}`;
26
+
27
+ // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
28
+ function truncDisplay(str, max = 400) {
29
+ if (!str) return '';
30
+ const s = String(str);
31
+ if (s.length <= max) return s;
32
+ return s.slice(0, max) + c('gray', `โ€ฆ (+${s.length - max} chars)`);
33
+ }
34
+
35
+ function jsonPreview(obj, max = 350) {
36
+ const s = JSON.stringify(obj, null, 2);
37
+ return truncDisplay(s, max);
38
+ }
39
+
40
+ // โ”€โ”€ Public logger API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
41
+ const logger = {
42
+ banner() {
43
+ console.log(`
44
+ ${c('cyan','โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—')}
45
+ ${c('cyan','โ•‘')} ${cb('lcyan','๐Ÿค– Yiyan Browser Agent (ๆ–‡ๅฟƒไธ€่จ€)')} ${c('cyan','โ•‘')}
46
+ ${c('cyan','โ•‘')} ${c('gray','AI Coding Agent via Browser Automation')} ${c('cyan','โ•‘')}
47
+ ${c('cyan','โ•‘')} ${c('gray','No API key needed โ€” uses yiyan.baidu.com')} ${c('cyan','โ•‘')}
48
+ ${c('cyan','โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')}
49
+ `);
50
+ },
51
+
52
+ header(msg) {
53
+ const line = 'โ”€'.repeat(50);
54
+ console.log(`\n${c('blue', line)}`);
55
+ console.log(`${c('bold','๐Ÿ“‹ ')}${cb('white', msg)}`);
56
+ console.log(`${c('blue', line)}\n`);
57
+ },
58
+
59
+ info(msg) { console.log(`${c('lblue',' โ„น ')} ${msg}`); },
60
+ success(msg) { console.log(`${c('lgreen',' โœ“ ')} ${c('lgreen', msg)}`); },
61
+ warn(msg) { console.log(`${c('lyellow',' โš  ')} ${c('lyellow', msg)}`); },
62
+ error(msg) { console.log(`${c('lred',' โœ— ')} ${c('lred', msg)}`); },
63
+ dim(msg) { console.log(`${A.dim} ${msg}${A.reset}`); },
64
+
65
+ /** Spinner-style line (overwrites itself with \r) */
66
+ thinking(msg) {
67
+ process.stdout.write(` ${c('cyan','โŸณ')} ${c('gray', msg)}\r`);
68
+ },
69
+
70
+ /** Clear the current line */
71
+ clearLine() {
72
+ process.stdout.write(`\r${' '.repeat(80)}\r`);
73
+ },
74
+
75
+ // โ”€โ”€ Tool call display โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
76
+ toolCall(name, args) {
77
+ console.log(`\n ${cb('magenta','โšก TOOL CALL')} ${c('cyan', `โ†’ ${name}`)}`);
78
+ const preview = jsonPreview(args);
79
+ if (preview.trim()) {
80
+ preview.split('\n').forEach(l => console.log(` ${c('gray', l)}`));
81
+ }
82
+ },
83
+
84
+ toolResult(result, isError = false) {
85
+ const icon = isError ? c('lred',' โœ— Result:') : c('lgreen',' โœ“ Result:');
86
+ const text = truncDisplay(String(result), 300);
87
+ const color = isError ? 'lred' : 'gray';
88
+ console.log(`${icon}`);
89
+ text.split('\n').slice(0, 12).forEach(l => console.log(` ${c(color, l)}`));
90
+ if (String(result).split('\n').length > 12) {
91
+ console.log(` ${c('gray',' โ€ฆ (truncated for display)')}`);
92
+ }
93
+ console.log('');
94
+ },
95
+
96
+ // โ”€โ”€ Final agent output โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
+ finalOutput(msg) {
98
+ const line = 'โ”'.repeat(50);
99
+ console.log(`\n${c('lgreen', line)}`);
100
+ console.log(`${cb('lgreen','โœ… TASK COMPLETE')}`);
101
+ console.log(`${c('lgreen', line)}\n`);
102
+ console.log(msg);
103
+ console.log('');
104
+ },
105
+
106
+ // โ”€โ”€ Section separator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
107
+ separator(label = '') {
108
+ const pad = label ? ` ${label} ` : '';
109
+ console.log(`\n${c('gray', 'ยท'.repeat(20) + pad + 'ยท'.repeat(20))}\n`);
110
+ },
111
+
112
+ // โ”€โ”€ Iteration marker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
113
+ iteration(n, max) {
114
+ console.log(`\n${c('gray',' โ”„')} ${c('dim',`Step ${n}/${max}`)} ${c('gray','โ”„')}`);
115
+ },
116
+ };
117
+
118
+ module.exports = logger;