natureco-cli 5.17.0 → 5.18.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/package.json +1 -1
- package/src/tools/browser_use.js +279 -0
- package/src/utils/tools.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.18.0",
|
|
4
4
|
"description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"natureco": "bin/natureco.js"
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser_use — Cloud browser automation via Browser Use
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* 1) API mode (default): Browser Use cloud API — create sessions with task descriptions,
|
|
6
|
+
* autonomous browsing, screenshots, structured output
|
|
7
|
+
* 2) CLI mode: wraps `browser-use` CLI for direct terminal-driven browser control
|
|
8
|
+
*
|
|
9
|
+
* API: https://api.browser-use.com/api/v3
|
|
10
|
+
* Docs: https://docs.browser-use.com
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const https = require('https');
|
|
14
|
+
const { spawnSync } = require('child_process');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
const BASE_URL = 'https://api.browser-use.com/api/v3';
|
|
20
|
+
|
|
21
|
+
function loadConfig() {
|
|
22
|
+
try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8')); } catch { return {}; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getApiKey() {
|
|
26
|
+
const cfg = loadConfig();
|
|
27
|
+
return cfg.browserUseApiKey || process.env.BROWSER_USE_API_KEY || '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function apiRequest(method, endpoint, body) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const apiKey = getApiKey();
|
|
33
|
+
if (!apiKey) return reject(new Error('BROWSER_USE_API_KEY gerekli. Sign up: https://cloud.browser-use.com'));
|
|
34
|
+
|
|
35
|
+
const url = new URL(endpoint, BASE_URL);
|
|
36
|
+
const options = {
|
|
37
|
+
method,
|
|
38
|
+
hostname: url.hostname,
|
|
39
|
+
path: url.pathname + url.search,
|
|
40
|
+
headers: {
|
|
41
|
+
'X-Browser-Use-API-Key': apiKey,
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
},
|
|
44
|
+
timeout: 120000,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const req = https.request(options, (res) => {
|
|
48
|
+
let data = '';
|
|
49
|
+
res.on('data', c => data += c);
|
|
50
|
+
res.on('end', () => {
|
|
51
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
52
|
+
try { resolve(JSON.parse(data)); } catch { reject(new Error('Parse error: ' + data.slice(0, 200))); }
|
|
53
|
+
} else {
|
|
54
|
+
let detail = data;
|
|
55
|
+
try { detail = JSON.parse(data)?.detail || data; } catch {}
|
|
56
|
+
reject(new Error('HTTP ' + res.statusCode + ': ' + JSON.stringify(detail).slice(0, 300)));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
62
|
+
if (body) req.write(JSON.stringify(body));
|
|
63
|
+
req.end();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkCli() {
|
|
68
|
+
const r = spawnSync('which', ['browser-use'], { timeout: 3000, encoding: 'utf8' });
|
|
69
|
+
return r.status === 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cliExec(args) {
|
|
73
|
+
const r = spawnSync('browser-use', args, { timeout: 60000, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 });
|
|
74
|
+
if (r.error) return { success: false, error: r.error.message };
|
|
75
|
+
if (r.status !== 0) return { success: false, error: r.stderr || r.stdout || 'CLI error' };
|
|
76
|
+
return { success: true, stdout: r.stdout.trim(), stderr: r.stderr.trim() };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleApiAction(params) {
|
|
80
|
+
const { action } = params;
|
|
81
|
+
|
|
82
|
+
if (action === 'browse') {
|
|
83
|
+
if (!params.task) return { success: false, error: 'task gerekli (ornek: "github.com/ac, en populer repo\'lari listele")' };
|
|
84
|
+
|
|
85
|
+
const result = await apiRequest('POST', '/sessions', {
|
|
86
|
+
task: params.task,
|
|
87
|
+
...(params.persistentSession ? { persistent_session: params.persistentSession } : {}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const sessionId = result.session_id || result.id || result.data?.session_id || result.data?.id;
|
|
91
|
+
if (!sessionId) return { success: false, error: 'Session ID alinamadi: ' + JSON.stringify(result).slice(0, 300) };
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < 60; i++) {
|
|
94
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
95
|
+
const status = await apiRequest('GET', '/sessions/' + sessionId);
|
|
96
|
+
if (status.status === 'completed' || status.status === 'finished' || status.data?.status === 'completed') {
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
mode: 'api',
|
|
100
|
+
sessionId,
|
|
101
|
+
result: status.result || status.data?.result || status,
|
|
102
|
+
summary: status.summary || status.data?.summary || '',
|
|
103
|
+
steps: status.steps || status.data?.steps || [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (status.status === 'failed' || status.status === 'error') {
|
|
107
|
+
return { success: false, error: 'Session failed: ' + JSON.stringify(status.error || status) };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { success: false, error: 'Session timeout after 120s', sessionId };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (action === 'status') {
|
|
114
|
+
if (!params.sessionId) return { success: false, error: 'sessionId gerekli' };
|
|
115
|
+
const status = await apiRequest('GET', '/sessions/' + params.sessionId);
|
|
116
|
+
return { success: true, sessionId: params.sessionId, status };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (action === 'cancel' || action === 'delete') {
|
|
120
|
+
if (!params.sessionId) return { success: false, error: 'sessionId gerekli' };
|
|
121
|
+
await apiRequest('DELETE', '/sessions/' + params.sessionId);
|
|
122
|
+
return { success: true, action: 'cancel', sessionId: params.sessionId };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (action === 'self_signup') {
|
|
126
|
+
const r = cliExec(['cloud', 'signup']);
|
|
127
|
+
if (!r.success) return r;
|
|
128
|
+
const challengeMatch = r.stdout.match(/Challenge ID:\s*(\S+)/);
|
|
129
|
+
const challengeMatch2 = r.stdout.match(/Challenge:\s*(.+)/);
|
|
130
|
+
if (!challengeMatch || !challengeMatch2) return { success: false, error: 'Signup baslatilamadi', cliOutput: r.stdout };
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
action: 'challenge',
|
|
134
|
+
challengeId: challengeMatch[1],
|
|
135
|
+
challenge: challengeMatch2[1],
|
|
136
|
+
note: 'Challenge\'i LLM ile coz, sonra verify icin: browser_use(action="self_verify", challengeId="...", answer="...")',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (action === 'self_verify') {
|
|
141
|
+
if (!params.challengeId || !params.answer) return { success: false, error: 'challengeId ve answer gerekli' };
|
|
142
|
+
const r = cliExec(['cloud', 'signup', '--verify', params.challengeId, String(params.answer)]);
|
|
143
|
+
if (!r.success) return r;
|
|
144
|
+
return { success: true, action: 'verified', note: 'API key kaydedildi. browser_use kullanima hazir.' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { success: false, error: 'Bilinmeyen action: ' + action + ' (browse, status, cancel, self_signup, self_verify)' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handleCliAction(params) {
|
|
151
|
+
const { action } = params;
|
|
152
|
+
|
|
153
|
+
if (action === 'navigate') {
|
|
154
|
+
if (!params.url) return { success: false, error: 'url gerekli' };
|
|
155
|
+
return cliExec(['navigate', params.url]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (action === 'click') {
|
|
159
|
+
if (params.selector) return cliExec(['click', params.selector]);
|
|
160
|
+
if (typeof params.x === 'number' && typeof params.y === 'number') {
|
|
161
|
+
return cliExec(['click', '--x', String(params.x), '--y', String(params.y)]);
|
|
162
|
+
}
|
|
163
|
+
return { success: false, error: 'selector veya x,y gerekli' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (action === 'type') {
|
|
167
|
+
if (!params.selector || !params.text) return { success: false, error: 'selector ve text gerekli' };
|
|
168
|
+
return cliExec(['type', params.selector, params.text]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (action === 'screenshot') {
|
|
172
|
+
const outFile = params.file || path.join(os.tmpdir(), 'browser_use_' + Date.now() + '.png');
|
|
173
|
+
const r = cliExec(['screenshot', '--output', outFile]);
|
|
174
|
+
if (!r.success) return r;
|
|
175
|
+
const b64 = fs.readFileSync(outFile).toString('base64');
|
|
176
|
+
if (!params.file) fs.unlinkSync(outFile);
|
|
177
|
+
return { success: true, action: 'screenshot', file: outFile, base64: b64 };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (action === 'extract') {
|
|
181
|
+
if (!params.selector) return { success: false, error: 'selector gerekli' };
|
|
182
|
+
return cliExec(['extract', params.selector]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (action === 'evaluate') {
|
|
186
|
+
if (!params.script) return { success: false, error: 'script gerekli' };
|
|
187
|
+
return cliExec(['evaluate', params.script]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (action === 'html') {
|
|
191
|
+
const r = cliExec(['html']);
|
|
192
|
+
return { success: true, html: r.stdout };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (action === 'current_url') {
|
|
196
|
+
const r = cliExec(['current-url']);
|
|
197
|
+
return { success: true, url: r.stdout };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (action === 'session') {
|
|
201
|
+
if (params.sessionAction === 'list') return cliExec(['session', 'list']);
|
|
202
|
+
if (params.sessionAction === 'save') return cliExec(['session', 'save', params.name || 'default']);
|
|
203
|
+
if (params.sessionAction === 'load') return cliExec(['session', 'load', params.name || 'default']);
|
|
204
|
+
if (params.sessionAction === 'close') return cliExec(['session', 'close']);
|
|
205
|
+
return cliExec(['session']);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (action === 'cloud_login') {
|
|
209
|
+
if (!params.apiKey) return { success: false, error: 'apiKey gerekli' };
|
|
210
|
+
return cliExec(['cloud', 'login', params.apiKey]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (action === 'cloud_status') {
|
|
214
|
+
return cliExec(['cloud', 'status']);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (action === 'doctor') {
|
|
218
|
+
return cliExec(['doctor']);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (action === 'help') {
|
|
222
|
+
const r = cliExec(['--help']);
|
|
223
|
+
return { success: true, help: r.stdout };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { success: false, error: 'Bilinmeyen CLI action: ' + action };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const name = 'browser_use';
|
|
230
|
+
const description = 'Bulut tarayici otomasyonu — Browser Use altyapisi ile web islemleri. API modu: task tanimla, otonom yapilsin. CLI modu: direkt tarayici kontrolu (navigate, click, type, screenshot).';
|
|
231
|
+
const parameters = {
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: {
|
|
234
|
+
action: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'API actions: browse (task ile otonom), status, cancel, self_signup, self_verify. CLI actions: navigate, click, type, screenshot, extract, evaluate, html, current_url, session, cloud_login, cloud_status, doctor, help',
|
|
237
|
+
enum: ['browse', 'status', 'cancel', 'self_signup', 'self_verify', 'navigate', 'click', 'type', 'screenshot', 'extract', 'evaluate', 'html', 'current_url', 'session', 'cloud_login', 'cloud_status', 'doctor', 'help'],
|
|
238
|
+
},
|
|
239
|
+
task: { type: 'string', description: '(API browse) Yapilacak is — dogal dilde, ornek: "github.com/trending ac, en populer 3 repoyu ozetle"' },
|
|
240
|
+
sessionId: { type: 'string', description: '(API status/cancel) Session ID' },
|
|
241
|
+
persistentSession: { type: 'string', description: '(API browse) Persistent session ID — login/cerezleri korur' },
|
|
242
|
+
url: { type: 'string', description: '(CLI navigate) Hedef URL' },
|
|
243
|
+
selector: { type: 'string', description: '(CLI click/type/extract) CSS selector, ornek: #username, button[type=submit]' },
|
|
244
|
+
text: { type: 'string', description: '(CLI type) Yazilacak metin' },
|
|
245
|
+
script: { type: 'string', description: '(CLI evaluate) Calistirilacak JavaScript' },
|
|
246
|
+
x: { type: 'number', description: '(CLI click) X koordinati' },
|
|
247
|
+
y: { type: 'number', description: '(CLI click) Y koordinati' },
|
|
248
|
+
file: { type: 'string', description: '(CLI screenshot) Kayit dosyasi' },
|
|
249
|
+
name: { type: 'string', description: '(CLI session save/load) Session adi' },
|
|
250
|
+
sessionAction: { type: 'string', description: '(CLI session) list, save, load, close', enum: ['list', 'save', 'load', 'close'] },
|
|
251
|
+
apiKey: { type: 'string', description: '(CLI cloud_login) Browser Use API key' },
|
|
252
|
+
challengeId: { type: 'string', description: '(API self_verify) Challenge ID from self_signup' },
|
|
253
|
+
answer: { type: 'string', description: '(API self_verify) Challenge cozumu (2 ondalikli sayi)' },
|
|
254
|
+
},
|
|
255
|
+
required: ['action'],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
async function execute(params) {
|
|
259
|
+
const { action } = params;
|
|
260
|
+
|
|
261
|
+
const apiActions = ['browse', 'status', 'cancel', 'self_signup', 'self_verify'];
|
|
262
|
+
const cliActions = ['navigate', 'click', 'type', 'screenshot', 'extract', 'evaluate', 'html', 'current_url', 'session', 'cloud_login', 'cloud_status', 'doctor', 'help'];
|
|
263
|
+
|
|
264
|
+
if (apiActions.includes(action)) {
|
|
265
|
+
return await handleApiAction(params);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (cliActions.includes(action)) {
|
|
269
|
+
if (!checkCli()) {
|
|
270
|
+
return { success: false, error: 'browser-use CLI bulunamadi. Kur: curl -fsSL https://browser-use.com/cli/install.sh | bash', cliRequired: true };
|
|
271
|
+
}
|
|
272
|
+
const result = await handleCliAction(params);
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { success: false, error: 'Bilinmeyen action: ' + action };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = { name, description, parameters, execute };
|
package/src/utils/tools.js
CHANGED
|
@@ -20,7 +20,7 @@ const EMOJI_MAP = {
|
|
|
20
20
|
// Web
|
|
21
21
|
duckduckgo: '🦆', duckduckgo_search: '🦆', web_search: '🌐', web_readability: '📄', firecrawl: '🔥', searxng: '🔬', searxng_search: '🔬', http_request: '🌍', http: '🌍', exa_search: '🔬', parallel_search: '⚡',
|
|
22
22
|
// Browser
|
|
23
|
-
browser: '🖥️',
|
|
23
|
+
browser: '🖥️', browser_use: '🌐',
|
|
24
24
|
// Memory
|
|
25
25
|
memory: '🧠', memory_write: '🧠', memory_search: '🔍',
|
|
26
26
|
// Skills
|
|
@@ -84,7 +84,7 @@ const TOOLSET_MAP = {
|
|
|
84
84
|
parallel_search: 'web',
|
|
85
85
|
duckduckgo_search: 'web', searxng_search: 'web',
|
|
86
86
|
// Browser
|
|
87
|
-
browser: 'browser',
|
|
87
|
+
browser: 'browser', browser_use: 'browser',
|
|
88
88
|
// Memory
|
|
89
89
|
memory: 'memory', memory_write: 'memory', memory_search: 'memory',
|
|
90
90
|
// Skills
|