natureco-cli 5.16.1 → 5.17.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.16.1",
3
+ "version": "5.17.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,224 @@
1
+ /**
2
+ * computer_use_loop — Autonomous GUI interaction with visual feedback loop
3
+ *
4
+ * Takes a goal → loops: screenshot → LLM vision analysis → execute action →
5
+ * screenshot → verify → repeat until goal achieved.
6
+ */
7
+
8
+ const https = require('https');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ function loadConfig() {
14
+ try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8')); } catch { return {}; }
15
+ }
16
+
17
+ function isMiniMax(url) { return url && (url.includes('minimax.io') || url.includes('minimaxi.com') || url.includes('minimax.cn')); }
18
+ function isGemini(url) { return url && (url.includes('generativelanguage.googleapis.com') || url.includes('gemini')); }
19
+
20
+ function apiCall(providerUrl, apiKey, body) {
21
+ return new Promise((resolve, reject) => {
22
+ const base = providerUrl.replace(/\/+$/, '');
23
+ const endpoint = isMiniMax(base)
24
+ ? base + '/v1/text/chatcompletion_v2'
25
+ : isGemini(base)
26
+ ? base + '/openai/chat/completions'
27
+ : base + '/chat/completions';
28
+ const req = https.request(endpoint, {
29
+ method: 'POST',
30
+ headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' },
31
+ timeout: 120000,
32
+ }, (res) => {
33
+ let data = '';
34
+ res.on('data', c => data += c);
35
+ res.on('end', () => {
36
+ if (res.statusCode >= 200 && res.statusCode < 300) {
37
+ try { resolve(JSON.parse(data)); } catch { reject(new Error('Parse error')); }
38
+ } else reject(new Error('HTTP ' + res.statusCode + ': ' + data.slice(0, 500)));
39
+ });
40
+ });
41
+ req.on('error', reject);
42
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
43
+ req.write(JSON.stringify(body));
44
+ req.end();
45
+ });
46
+ }
47
+
48
+ function screenshotBase64() {
49
+ const file = path.join(os.tmpdir(), 'ncloop_' + Date.now() + '.png');
50
+ require('child_process').execSync('screencapture -x "' + file + '"', { timeout: 5000 });
51
+ const buf = fs.readFileSync(file);
52
+ fs.unlinkSync(file);
53
+ return buf.toString('base64');
54
+ }
55
+
56
+ function executeAction(action, params) {
57
+ const { spawnSync } = require('child_process');
58
+ const PLATFORM = os.platform();
59
+
60
+ const ESC = (s) => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
61
+
62
+ function osaScript(script, timeoutMs = 10000) {
63
+ const r = spawnSync('osascript', ['-e', script], { timeout: timeoutMs, encoding: 'utf8', maxBuffer: 1024 * 1024 });
64
+ if (r.error) return { success: false, error: r.error.code === 'ETIMEDOUT' ? 'osascript timed out' : r.error.message };
65
+ if (r.status !== 0) return { success: false, error: r.stderr || r.stdout || 'error' };
66
+ return { success: true };
67
+ }
68
+
69
+ try {
70
+ switch (action) {
71
+ case 'click':
72
+ return osaScript('tell application "System Events" to click at {' + params.x + ', ' + params.y + '}');
73
+ case 'type':
74
+ return osaScript('tell application "System Events" to keystroke "' + ESC(params.text) + '"');
75
+ case 'keypress': {
76
+ const KEY_MAP = { enter: 'return', tab: 'tab', escape: 'escape', up: 'up', down: 'down', left: 'left', right: 'right', backspace: 'delete', space: 'space' };
77
+ const MOD_MAP = { cmd: 'command down', command: 'command down', option: 'option down', alt: 'option down', ctrl: 'control down', shift: 'shift down' };
78
+ const parts = params.key.toLowerCase().split('+').map(p => p.trim());
79
+ const mods = [];
80
+ let actual = '';
81
+ for (const p of parts) {
82
+ if (MOD_MAP[p]) mods.push(MOD_MAP[p]);
83
+ else actual = p;
84
+ }
85
+ const k = KEY_MAP[actual] || actual;
86
+ const using = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
87
+ return osaScript('tell application "System Events" to keystroke ' + k + using);
88
+ }
89
+ case 'mouse_move':
90
+ return osaScript('tell application "System Events" to set position of mouse to {' + params.x + ', ' + params.y + '}');
91
+ case 'scroll': {
92
+ const times = Math.abs(Math.ceil((params.y || 0) / 40));
93
+ return osaScript('tell application "System Events"\n repeat ' + times + ' times\n key code 125\n end repeat\nend tell');
94
+ }
95
+ case 'wait':
96
+ return { success: true };
97
+ default:
98
+ return { success: false, error: 'Unknown action: ' + action };
99
+ }
100
+ } catch (e) {
101
+ return { success: false, error: e.message };
102
+ }
103
+ }
104
+
105
+ const SYSTEM_PROMPT = `Sen bir GUI otomasyon asistanisin. Gorev: Ekran goruntusunu analiz et ve siradaki action'i JSON olarak belirle.
106
+
107
+ Actions:
108
+ - click: { "action": "click", "x": sayi, "y": sayi }
109
+ - type: { "action": "type", "text": "yazilacak metin" }
110
+ - keypress: { "action": "keypress", "key": "enter|tab|escape|up|down|left|right|backspace|cmd+q|alt+space" }
111
+ - mouse_move: { "action": "mouse_move", "x": sayi, "y": sayi }
112
+ - scroll: { "action": "scroll", "y": piksel_sayisi (positive=down, negative=up) }
113
+ - wait: { "action": "wait" }
114
+ - done: { "action": "done", "reason": "gorev tamamlandi" }
115
+
116
+ Sadece JSON yanit ver, baska metin ekleme. Her action'dan sonra otomatik screenshot alinir, sen sadece bir sonraki en mantikli adimi soyle.`;
117
+
118
+ async function loop(goal, maxSteps) {
119
+ const cfg = loadConfig();
120
+ const providerUrl = cfg.providerUrl;
121
+ const apiKey = cfg.providerApiKey;
122
+ const model = cfg.providerModel || 'default';
123
+
124
+ if (!providerUrl || !apiKey) {
125
+ return JSON.stringify({ success: false, error: 'Provider not configured' });
126
+ }
127
+
128
+ const steps = [];
129
+ for (let i = 0; i < maxSteps; i++) {
130
+ try {
131
+ // 1. Screenshot
132
+ const b64 = screenshotBase64();
133
+ steps.push({ step: i + 1, action: 'screenshot' });
134
+
135
+ // 2. LLM vision analysis
136
+ const messages = [
137
+ { role: 'system', content: SYSTEM_PROMPT },
138
+ ];
139
+
140
+ const history = steps.filter(s => s.action && s.action !== 'screenshot' && s.action !== 'done').slice(-5);
141
+ if (history.length > 0) {
142
+ messages.push({ role: 'user', content: 'Onceki adimlar:\n' + history.map(h =>
143
+ 'Adim ' + h.step + ': ' + JSON.stringify(h)
144
+ ).join('\n') });
145
+ }
146
+
147
+ messages.push({
148
+ role: 'user',
149
+ content: [
150
+ { type: 'text', text: 'Gorev: ' + goal + '\n\nEkran goruntusunu analiz et. Siradaki action ne?' },
151
+ { type: 'image_url', image_url: { url: 'data:image/png;base64,' + b64 } },
152
+ ],
153
+ });
154
+
155
+ const body = {
156
+ model,
157
+ messages,
158
+ stream: false,
159
+ temperature: 0.2,
160
+ max_tokens: 500,
161
+ };
162
+
163
+ const result = await apiCall(providerUrl, apiKey, body);
164
+ const reply = result.choices?.[0]?.message?.content || '';
165
+ let decision;
166
+ try {
167
+ decision = JSON.parse(reply);
168
+ } catch {
169
+ const m = reply.match(/\{[\s\S]*\}/);
170
+ decision = m ? JSON.parse(m[0]) : { action: 'wait' };
171
+ }
172
+
173
+ if (!decision.action) decision.action = 'wait';
174
+
175
+ // 3. Execute
176
+ if (decision.action === 'done') {
177
+ steps.push({ step: i + 1, action: 'done', reason: decision.reason || 'Goal achieved' });
178
+ break;
179
+ }
180
+
181
+ const execResult = executeAction(decision.action, decision);
182
+ steps.push({ step: i + 1, action: decision.action, params: decision, result: execResult.success });
183
+
184
+ if (!execResult.success) {
185
+ // Retry once with wait
186
+ require('child_process').execSync('sleep 1');
187
+ const retryResult = executeAction(decision.action, decision);
188
+ if (!retryResult.success) {
189
+ steps.push({ step: i + 1, action: 'error', error: execResult.error });
190
+ }
191
+ }
192
+
193
+ // Small delay between actions
194
+ require('child_process').execSync('sleep 0.5');
195
+
196
+ // Safety: if too many steps, break
197
+ if (i >= maxSteps - 1) {
198
+ steps.push({ step: i + 1, action: 'done', reason: 'Max steps reached' });
199
+ }
200
+ } catch (e) {
201
+ steps.push({ step: i + 1, action: 'error', error: e.message });
202
+ break;
203
+ }
204
+ }
205
+
206
+ return JSON.stringify({ success: true, goal, totalSteps: steps.length, steps });
207
+ }
208
+
209
+ const name = 'computer_use_loop';
210
+ const description = 'Otonom GUI etkilesimi: bir hedef ver, loop halinde screenshot → LLM analizi → action → screenshot... hedef tamamlanana kadar devam eder. Web formlari, login, menus islemleri icin ideal.';
211
+ const parameters = {
212
+ type: 'object',
213
+ properties: {
214
+ goal: { type: 'string', description: 'Yapilacak islem (ornek: "natureco.me/login sayfasinda kullanici adi ve sifre girerek giris yap")' },
215
+ maxSteps: { type: 'number', description: 'Maksimum adim sayisi (default: 30)' },
216
+ },
217
+ required: ['goal'],
218
+ };
219
+
220
+ async function execute(params) {
221
+ return await loop(params.goal, params.maxSteps || 30);
222
+ }
223
+
224
+ module.exports = { name, description, parameters, execute };
@@ -64,7 +64,7 @@ const EMOJI_MAP = {
64
64
  url_safety: '🛡️', approval: '✅', checkpoint: '💾', file_state: '🔍',
65
65
  pii_redact: '🔒', clarify: '❓', session_search: '🔎', x_search: '🐦',
66
66
  discord: '💬', send_message: '📨', async_delegation: '⏳', blueprint: '📐',
67
- spotify: '🎧', homeassistant: '🏠', microsoft_graph: '📊', computer_use: '🖱️',
67
+ spotify: '🎧', homeassistant: '🏠', microsoft_graph: '📊', computer_use: '🖱️', computer_use_loop: '🔄',
68
68
  google_meet: '📹',
69
69
  // Orchestrator
70
70
  workflow: '⚙️',
@@ -122,7 +122,7 @@ const TOOLSET_MAP = {
122
122
  x_search: 'web', discord: 'communication', send_message: 'communication',
123
123
  async_delegation: 'agent', blueprint: 'planning', workflow: 'orchestrator',
124
124
  spotify: 'media', homeassistant: 'iot', microsoft_graph: 'office',
125
- computer_use: 'automation', google_meet: 'communication',
125
+ computer_use: 'automation', computer_use_loop: 'automation', google_meet: 'communication',
126
126
  social_open: 'communication', youtube_ac: 'media',
127
127
  sub_agent: 'agent', memory_provider: 'memory',
128
128
  };