natureco-cli 5.16.0 → 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.0",
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"
@@ -1,138 +1,291 @@
1
- const { spawn, execSync } = require('child_process');
1
+ const { spawnSync } = require('child_process');
2
2
  const os = require('os');
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const PLATFORM = os.platform();
7
7
 
8
+ const KEY_MAP_DARWIN = {
9
+ enter: 'return',
10
+ return: 'return',
11
+ tab: 'tab',
12
+ escape: 'escape',
13
+ esc: 'escape',
14
+ up: 'up',
15
+ down: 'down',
16
+ left: 'left',
17
+ right: 'right',
18
+ backspace: 'delete',
19
+ delete: 'forwarddelete',
20
+ forwarddelete: 'forwarddelete',
21
+ home: 'home',
22
+ end: 'end',
23
+ pageup: 'page up',
24
+ pagedown: 'page down',
25
+ space: 'space',
26
+ ' ': 'space',
27
+ };
28
+
29
+ const MODIFIER_MAP = {
30
+ command: 'command down',
31
+ cmd: 'command down',
32
+ option: 'option down',
33
+ alt: 'option down',
34
+ control: 'control down',
35
+ ctrl: 'control down',
36
+ shift: 'shift down',
37
+ };
38
+
39
+ function escapeText(s) {
40
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
41
+ }
42
+
43
+ function osaScript(script, timeoutMs = 10000) {
44
+ const result = spawnSync('osascript', ['-e', script], {
45
+ timeout: timeoutMs,
46
+ encoding: 'utf8',
47
+ maxBuffer: 1024 * 1024,
48
+ });
49
+ if (result.error) {
50
+ if (result.error.code === 'ETIMEDOUT') {
51
+ return { success: false, error: 'osascript timed out after ' + timeoutMs + 'ms' };
52
+ }
53
+ return { success: false, error: result.error.message };
54
+ }
55
+ if (result.status !== 0) {
56
+ const msg = result.stderr || result.stdout || 'unknown error';
57
+ let friendly = msg;
58
+ if (msg.includes('yardımcı erişime izin verilmiyor') || msg.includes('access for assistive devices')) {
59
+ friendly = 'Accessibility izni gerekli. System Settings > Privacy & Security > Accessibility > Terminal/iTerm2\'ye izin verin.';
60
+ } else if (msg.includes('(-1700') || msg.includes('can\'t convert')) {
61
+ friendly = 'AppleScript hatasi: ' + msg.slice(0, 200);
62
+ }
63
+ return { success: false, error: friendly };
64
+ }
65
+ return { success: true, data: result.stdout };
66
+ }
67
+
68
+ function checkAccessibility() {
69
+ const r = osaScript('tell application "System Events" to get name of first process whose frontmost is true', 3000);
70
+ return r.success;
71
+ }
72
+
8
73
  async function computerUse(params) {
9
- const { action, x, y, key, text, button, clicks, query, file } = params;
74
+ const { action, x, y, key, text, button, clicks, file } = params;
10
75
 
11
76
  if (action === 'screenshot') {
12
77
  const outputFile = file || path.join(os.tmpdir(), 'natureco_screen_' + Date.now() + '.png');
13
- if (PLATFORM === 'darwin') {
14
- execSync('screencapture -x "' + outputFile + '"', { timeout: 5000 });
15
- } else if (PLATFORM === 'win32') {
16
- execSync('powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $bmp = [System.Drawing.Bitmap]::new([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height); $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen(0, 0, 0, 0, $bmp.Size); $bmp.Save(\\"' + outputFile + '\\", [System.Drawing.Imaging.ImageFormat]::Png)"', { timeout: 10000 });
17
- } else {
18
- execSync('import -window root "' + outputFile + '"', { timeout: 5000 });
78
+ try {
79
+ if (PLATFORM === 'darwin') {
80
+ spawnSync('screencapture', ['-x', outputFile], { timeout: 5000 });
81
+ } else if (PLATFORM === 'win32') {
82
+ spawnSync('powershell', ['-Command',
83
+ 'Add-Type -AssemblyName System.Windows.Forms; ' +
84
+ '$bmp = [System.Drawing.Bitmap]::new([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height); ' +
85
+ '$g = [System.Drawing.Graphics]::FromImage($bmp); ' +
86
+ '$g.CopyFromScreen(0, 0, 0, 0, $bmp.Size); ' +
87
+ '$bmp.Save("' + outputFile.replace(/"/g, '') + '", [System.Drawing.Imaging.ImageFormat]::Png)'
88
+ ], { timeout: 10000 });
89
+ } else {
90
+ spawnSync('import', ['-window', 'root', outputFile], { timeout: 5000 });
91
+ }
92
+ return { success: true, file: outputFile, platform: PLATFORM, note: 'Ekran goruntusu alindi' };
93
+ } catch (e) {
94
+ return { success: false, error: 'Screenshot hatasi: ' + e.message };
19
95
  }
20
- return { success: true, file: outputFile, platform: PLATFORM, note: 'Ekran goruntusu alindi: ' + outputFile };
21
96
  }
22
97
 
23
98
  if (action === 'click') {
24
99
  if (typeof x !== 'number' || typeof y !== 'number') return { success: false, error: 'x ve y gerekli' };
25
100
  const btn = button || 'left';
26
101
  if (PLATFORM === 'darwin') {
27
- execSync('osascript -e \'tell application "System Events" to click at {' + x + ', ' + y + '}\'', { timeout: 5000 });
28
- } else if (PLATFORM === 'win32') {
102
+ const acc = checkAccessibility();
103
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli. System Settings > Privacy & Security > Accessibility > Terminal\'e izin verin.' };
104
+ const r = osaScript('tell application "System Events" to click at {' + x + ', ' + y + '}');
105
+ if (!r.success) return r;
106
+ return { success: true, action: 'click', x, y, button: btn };
107
+ }
108
+ if (PLATFORM === 'win32') {
29
109
  const c = clicks || 1;
30
- execSync('powershell -Command "[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(' + x + ', ' + y + '); [System.Windows.Forms.SendKeys]::SendWait(\\"' + (c > 1 ? '{DOUBLECLICK}' : '{CLICK}') + '\\")"', { timeout: 5000 });
31
- } else {
32
- execSync('xdotool mousemove ' + x + ' ' + y + ' click 1', { timeout: 5000 });
110
+ spawnSync('powershell', ['-Command',
111
+ '[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(' + x + ', ' + y + '); ' +
112
+ '[System.Windows.Forms.SendKeys]::SendWait("' + (c > 1 ? '{DOUBLECLICK}' : '{CLICK}') + '")'
113
+ ], { timeout: 5000 });
114
+ return { success: true, action: 'click', x, y, button: btn };
33
115
  }
34
- return { success: true, action: 'click', x, y, button };
116
+ spawnSync('xdotool', ['mousemove', String(x), String(y), 'click', '1'], { timeout: 5000 });
117
+ return { success: true, action: 'click', x, y, button: btn };
35
118
  }
36
119
 
37
120
  if (action === 'type') {
38
121
  if (!text) return { success: false, error: 'text gerekli' };
39
122
  if (PLATFORM === 'darwin') {
40
- const safeText = text.replace(/"/g, '\\"');
41
- execSync('osascript -e \'tell application "System Events" to keystroke "' + safeText + '"\'', { timeout: 5000 });
42
- } else if (PLATFORM === 'win32') {
43
- execSync('powershell -Command "[System.Windows.Forms.SendKeys]::SendWait(\\"' + text.replace(/[{}()^+% ~]/g, '{$&}') + '\\")"', { timeout: 5000 });
44
- } else {
45
- execSync('xdotool type "' + text.replace(/"/g, '\\"') + '"', { timeout: 5000 });
123
+ const acc = checkAccessibility();
124
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli' };
125
+ const r = osaScript('tell application "System Events" to keystroke "' + escapeText(text) + '"');
126
+ if (!r.success) return r;
127
+ return { success: true, action: 'type', text };
128
+ }
129
+ if (PLATFORM === 'win32') {
130
+ spawnSync('powershell', ['-Command',
131
+ '[System.Windows.Forms.SendKeys]::SendWait("' + text.replace(/[{}()^+% ~]/g, '{$&}') + '")'
132
+ ], { timeout: 5000 });
133
+ return { success: true, action: 'type', text };
46
134
  }
135
+ spawnSync('xdotool', ['type', text], { timeout: 5000 });
47
136
  return { success: true, action: 'type', text };
48
137
  }
49
138
 
50
139
  if (action === 'keypress') {
51
140
  if (!key) return { success: false, error: 'key gerekli' };
52
141
  if (PLATFORM === 'darwin') {
53
- const keyMap = { enter: 'return', tab: 'tab', escape: 'escape', up: 'up', down: 'down', left: 'left', right: 'right', backspace: 'delete', delete: 'forwarddelete' };
54
- const k = keyMap[key.toLowerCase()] || key;
55
- execSync('osascript -e \'tell application "System Events" to key code ' + (isNaN(k) ? '"' + k + '"' : k) + '\'', { timeout: 5000 });
56
- } else if (PLATFORM === 'win32') {
57
- const keyMap = { enter: '{ENTER}', tab: '{TAB}', escape: '{ESC}', up: '{UP}', down: '{DOWN}', left: '{LEFT}', right: '{RIGHT}', backspace: '{BACKSPACE}', delete: '{DELETE}' };
58
- execSync('powershell -Command "[System.Windows.Forms.SendKeys]::SendWait(\\"' + (keyMap[key.toLowerCase()] || key) + '\\")"', { timeout: 5000 });
59
- } else {
60
- execSync('xdotool key ' + key, { timeout: 5000 });
142
+ const acc = checkAccessibility();
143
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli' };
144
+
145
+ const parts = key.toLowerCase().split('+').map(p => p.trim());
146
+ const mods = [];
147
+ let actualKey = '';
148
+ for (const p of parts) {
149
+ if (MODIFIER_MAP[p]) mods.push(MODIFIER_MAP[p]);
150
+ else actualKey = p;
151
+ }
152
+
153
+ if (KEY_MAP_DARWIN[actualKey]) {
154
+ const keyName = KEY_MAP_DARWIN[actualKey];
155
+ const usingClause = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
156
+ const r = osaScript('tell application "System Events" to keystroke ' + keyName + usingClause);
157
+ if (!r.success) return r;
158
+ } else if (actualKey) {
159
+ const usingClause = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
160
+ const r = osaScript('tell application "System Events" to keystroke "' + escapeText(actualKey) + '"' + usingClause);
161
+ if (!r.success) return r;
162
+ } else {
163
+ if (mods.length > 0) {
164
+ return { success: false, error: 'Modifier-only keypress not supported. Provide a key with modifiers (e.g. cmd+q).' };
165
+ }
166
+ }
167
+ return { success: true, action: 'keypress', key };
61
168
  }
169
+ if (PLATFORM === 'win32') {
170
+ const keyMap = {
171
+ enter: '{ENTER}', tab: '{TAB}', escape: '{ESC}', up: '{UP}', down: '{DOWN}',
172
+ left: '{LEFT}', right: '{RIGHT}', backspace: '{BACKSPACE}', delete: '{DELETE}',
173
+ home: '{HOME}', end: '{END}', pageup: '{PGUP}', pagedown: '{PGDN}',
174
+ };
175
+ spawnSync('powershell', ['-Command',
176
+ '[System.Windows.Forms.SendKeys]::SendWait("' + (keyMap[key.toLowerCase()] || key) + '")'
177
+ ], { timeout: 5000 });
178
+ return { success: true, action: 'keypress', key };
179
+ }
180
+ spawnSync('xdotool', ['key', key], { timeout: 5000 });
62
181
  return { success: true, action: 'keypress', key };
63
182
  }
64
183
 
65
184
  if (action === 'mouse_move') {
66
185
  if (typeof x !== 'number' || typeof y !== 'number') return { success: false, error: 'x ve y gerekli' };
67
186
  if (PLATFORM === 'darwin') {
68
- execSync('osascript -e \'tell application "System Events" to set position of mouse to {' + x + ', ' + y + '}\'', { timeout: 5000 });
69
- } else if (PLATFORM === 'win32') {
70
- execSync('powershell -Command "[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(' + x + ', ' + y + ')"', { timeout: 5000 });
71
- } else {
72
- execSync('xdotool mousemove ' + x + ' ' + y, { timeout: 5000 });
187
+ const acc = checkAccessibility();
188
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli' };
189
+ const r = osaScript('tell application "System Events" to set position of mouse to {' + x + ', ' + y + '}');
190
+ if (!r.success) return r;
191
+ return { success: true, action: 'mouse_move', x, y };
192
+ }
193
+ if (PLATFORM === 'win32') {
194
+ spawnSync('powershell', ['-Command',
195
+ '[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(' + x + ', ' + y + ')'
196
+ ], { timeout: 5000 });
197
+ return { success: true, action: 'mouse_move', x, y };
73
198
  }
199
+ spawnSync('xdotool', ['mousemove', String(x), String(y)], { timeout: 5000 });
74
200
  return { success: true, action: 'mouse_move', x, y };
75
201
  }
76
202
 
77
203
  if (action === 'mouse_position') {
78
204
  if (PLATFORM === 'darwin') {
79
- const out = execSync('osascript -e \'tell application "System Events" to return position of mouse\'', { timeout: 5000 }).toString().trim();
80
- const [mx, my] = out.split(', ').map(Number);
205
+ const r = osaScript('tell application "System Events" to return position of mouse');
206
+ if (!r.success) return r;
207
+ const [mx, my] = r.data.trim().split(', ').map(Number);
81
208
  return { success: true, x: mx, y: my };
82
- } else if (PLATFORM === 'win32') {
83
- const out = execSync('powershell -Command "[System.Windows.Forms.Cursor]::Position.X.ToString() + \\", \\" + [System.Windows.Forms.Cursor]::Position.Y.ToString()"', { timeout: 5000 }).toString().trim();
84
- const [mx, my] = out.split(', ').map(Number);
85
- return { success: true, x: mx, y: my };
86
- } else {
87
- const out = execSync('xdotool getmouselocation', { timeout: 5000 }).toString().trim();
88
- const mx = parseInt(out.match(/x:(\d+)/)?.[1] || '0');
89
- const my = parseInt(out.match(/y:(\d+)/)?.[1] || '0');
209
+ }
210
+ if (PLATFORM === 'win32') {
211
+ const result = spawnSync('powershell', ['-Command',
212
+ '[System.Windows.Forms.Cursor]::Position.X.ToString() + ", " + [System.Windows.Forms.Cursor]::Position.Y.ToString()'
213
+ ], { timeout: 5000, encoding: 'utf8' });
214
+ const [mx, my] = result.stdout.trim().split(', ').map(Number);
90
215
  return { success: true, x: mx, y: my };
91
216
  }
217
+ const result = spawnSync('xdotool', ['getmouselocation'], { timeout: 5000, encoding: 'utf8' });
218
+ const mx = parseInt(result.stdout.match(/x:(\d+)/)?.[1] || '0');
219
+ const my = parseInt(result.stdout.match(/y:(\d+)/)?.[1] || '0');
220
+ return { success: true, x: mx, y: my };
92
221
  }
93
222
 
94
223
  if (action === 'scroll') {
95
224
  if (typeof y !== 'number') return { success: false, error: 'y (pixels) gerekli' };
96
225
  if (PLATFORM === 'darwin') {
97
- execSync('osascript -e \'tell application "System Events" to scroll (current application)\' &', { timeout: 5000 }); // simplified
98
- return { success: true, action: 'scroll', y, note: 'macOS scroll icin Accessibility izni gerekli' };
99
- } else {
100
- execSync('xdotool click ' + (y < 0 ? '4' : '5') + ' --repeat ' + Math.abs(Math.ceil(y / 50)), { timeout: 5000 });
101
- return { success: true, action: 'scroll', y };
226
+ const acc = checkAccessibility();
227
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli' };
228
+ const direction = y > 0 ? 'up' : 'down';
229
+ const times = Math.abs(Math.ceil(y / 40));
230
+ const r = osaScript('tell application "System Events" to repeat ' + times + ' times\n key code 125\nend repeat');
231
+ if (!r.success) return r;
232
+
233
+ return { success: true, action: 'scroll', y, note: 'Scrolled ' + direction + ' ' + times + ' steps' };
234
+ }
235
+ spawnSync('xdotool', ['click', y < 0 ? '4' : '5', '--repeat', String(Math.abs(Math.ceil(y / 50)))], { timeout: 5000 });
236
+ return { success: true, action: 'scroll', y };
237
+ }
238
+
239
+ if (action === 'drag') {
240
+ if (typeof x !== 'number' || typeof y !== 'number') return { success: false, error: 'x ve y (baslangic) gerekli' };
241
+ const x2 = params.x2, y2 = params.y2;
242
+ if (typeof x2 !== 'number' || typeof y2 !== 'number') return { success: false, error: 'x2 ve y2 (bitis) gerekli' };
243
+ if (PLATFORM === 'darwin') {
244
+ const acc = checkAccessibility();
245
+ if (!acc) return { success: false, error: 'Accessibility izni gerekli' };
246
+ const r = osaScript('tell application "System Events"\n set mousePos to {' + x + ', ' + y + '}\n set mousePos2 to {' + x2 + ', ' + y2 + '}\n set position of mouse to mousePos\n delay 0.1\n mouse down\n set position of mouse to mousePos2\n delay 0.1\n mouse up\nend tell');
247
+ if (!r.success) return r;
248
+ return { success: true, action: 'drag', from: { x, y }, to: { x: x2, y: y2 } };
249
+ }
250
+ if (PLATFORM === 'linux') {
251
+ spawnSync('xdotool', ['mousemove', String(x), String(y), 'mousedown', '1', 'mousemove', String(x2), String(y2), 'mouseup', '1'], { timeout: 5000 });
252
+ return { success: true, action: 'drag', from: { x, y }, to: { x: x2, y: y2 } };
102
253
  }
254
+ return { success: false, error: 'drag only supported on macOS and Linux' };
103
255
  }
104
256
 
105
257
  if (action === 'info') {
106
258
  const displays = [];
107
259
  try {
108
260
  if (PLATFORM === 'darwin') {
109
- const out = execSync('system_profiler SPDisplaysDataType 2>/dev/null | grep Resolution', { timeout: 5000 }).toString();
110
- for (const line of out.trim().split('\n')) {
261
+ const result = spawnSync('system_profiler', ['SPDisplaysDataType'], { timeout: 5000, encoding: 'utf8' });
262
+ for (const line of result.stdout.split('\n')) {
111
263
  const m = line.match(/(\d+) x (\d+)/);
112
264
  if (m) displays.push({ width: parseInt(m[1]), height: parseInt(m[2]) });
113
265
  }
114
266
  }
115
267
  } catch {}
116
- return { success: true, platform: PLATFORM, displays: displays.length > 0 ? displays : undefined, note: 'Ekran bilgisi' };
268
+ return { success: true, platform: PLATFORM, displays: displays.length > 0 ? displays : undefined };
117
269
  }
118
270
 
119
- return { success: false, error: 'Gecersiz action: ' + action + ' (screenshot, click, type, keypress, mouse_move, mouse_position, scroll, info)' };
271
+ return { success: false, error: 'Gecersiz action: ' + action + ' (screenshot, click, type, keypress, mouse_move, mouse_position, scroll, drag, info)' };
120
272
  }
121
273
 
122
274
  module.exports = {
123
275
  name: 'computer_use',
124
- description: 'GUI otomasyonu: screenshot, click, type, keypress, mouse_move, mouse_position, scroll, info. macOS/Windows/Linux.',
276
+ description: 'GUI otomasyonu: screenshot, click, type, keypress, mouse_move, mouse_position, scroll, drag, info. macOS/Windows/Linux.',
125
277
  inputSchema: {
126
278
  type: 'object',
127
279
  properties: {
128
- action: { type: 'string', description: 'screenshot, click, type, keypress, mouse_move, mouse_position, scroll, info', enum: ['screenshot', 'click', 'type', 'keypress', 'mouse_move', 'mouse_position', 'scroll', 'info'] },
280
+ action: { type: 'string', description: 'screenshot, click, type, keypress, mouse_move, mouse_position, scroll, drag, info', enum: ['screenshot', 'click', 'type', 'keypress', 'mouse_move', 'mouse_position', 'scroll', 'drag', 'info'] },
129
281
  x: { type: 'number', description: 'X koordinati' },
130
282
  y: { type: 'number', description: 'Y koordinati' },
131
283
  button: { type: 'string', description: 'Fare tusu: left, right, middle (default: left)' },
132
284
  clicks: { type: 'number', description: 'Tiklama sayisi (default: 1)' },
133
- key: { type: 'string', description: '(keypress) Tus adi: enter, tab, escape, up, down, left, right, backspace, delete' },
285
+ key: { type: 'string', description: '(keypress) Tus adi veya kombinasyon: enter, tab, escape, up, down, left, right, backspace, delete, cmd+q, cmd+shift+z, alt+space' },
286
+ x2: { type: 'number', description: '(drag) Bitis X koordinati' },
287
+ y2: { type: 'number', description: '(drag) Bitis Y koordinati' },
134
288
  text: { type: 'string', description: '(type) Yazilacak metin' },
135
- query: { type: 'string', description: 'Arama sorgusu' },
136
289
  file: { type: 'string', description: '(screenshot) Kayit dosyasi' },
137
290
  },
138
291
  required: ['action'],
@@ -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
  };