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.
- package/LICENSE +1 -1
- package/README.md +241 -153
- package/package.json +25 -30
- package/src/agent.js +80 -0
- package/src/browser.js +589 -0
- package/src/calibrate.js +178 -0
- package/src/client.js +80 -0
- package/src/config.js +68 -0
- package/src/index.js +139 -0
- package/src/logger.js +118 -0
- package/src/parser.js +272 -0
- package/src/postinstall.js +26 -0
- package/src/prompt.js +188 -0
- package/src/server.js +144 -0
- package/src/tools.js +547 -0
- package/dist/cli.d.ts +0 -27
- package/dist/cli.js +0 -858
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts +0 -73
- package/dist/index.js +0 -667
- package/dist/index.js.map +0 -1
- package/dist/postinstall.d.ts +0 -2
- package/dist/postinstall.js +0 -70
- package/dist/postinstall.js.map +0 -1
- package/dist/types-BhQ78DYf.d.ts +0 -39
package/src/calibrate.js
ADDED
|
@@ -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;
|