natureco-cli 5.64.1 → 5.64.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to NatureCo CLI will be documented in this file.
4
4
 
5
+ ## [5.64.2] - 2026-07-13 — Visible and reliable macOS GUI automation
6
+
7
+ ### Fixed
8
+ - REPL workflow tool activity no longer disappears when the next thinking indicator starts; every tool name and its success/failure marker remains visible in the transcript.
9
+ - Multi-step visual desktop tasks are routed to the screenshot-driven `computer_use_loop` instead of blind single click/type calls.
10
+ - The visual loop now uses the canonical provider endpoint builder, avoiding duplicated `/v1/v1` paths with MiniMax configurations.
11
+ - macOS special keys such as Enter, Tab and Escape now use correct AppleScript key codes.
12
+ - Screenshot capture and GUI-loop failures can no longer be reported as successful completion; missing files, failed actions and unverified max-step exits return explicit failures.
13
+
14
+ ### Tests
15
+ - Added regression coverage for GUI-loop action failures and MiniMax endpoint construction with and without a trailing `/v1`.
16
+
5
17
  ## [5.64.1] - 2026-07-13 — Reliable coding context and token-budgeted follow-ups
6
18
 
7
19
  ### Fixed
package/README.md CHANGED
@@ -51,6 +51,7 @@ natureco code
51
51
 
52
52
  | Version | Highlights |
53
53
  |---------|-----------|
54
+ | **v5.64.2** | **Reliable macOS GUI automation:** tool names stay visible in REPL transcripts, visual tasks use a verified screenshot loop, MiniMax GUI routing avoids duplicate paths, and failed/unverified actions no longer claim success. |
54
55
  | **v5.64.1** | **Reliable follow-ups + lower token cost:** `natureco code` now preserves same-session context across workflow calls and caps repeated history by token budget (1,024 / 2,048 / 8,192). Provider labels are rendered correctly. |
55
56
  | **v5.64.0** | **Unified secure agent foundation:** one execution gateway, hard-stop guardrails, schema-validated tools, rollback/checkpoints, sourced memory, resilient channel delivery, OS keychains and encrypted sync. |
56
57
  | **v5.43.0** | **Security:** 9 vulnerabilities fixed in a 3-round audit (RCE chain, admin-rpc auth, cron persistence, channel access control). See [`SECURITY_AUDIT_SUMMARY.md`](SECURITY_AUDIT_SUMMARY.md). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.64.1",
3
+ "version": "5.64.2",
4
4
  "description": "Terminal-native AI agent CLI with bilingual TR/EN UI, multi-agent orchestration, persistent memory, secure tools and messaging integrations.",
5
5
  "bin": {
6
6
  "natureco": "bin/natureco.js"
@@ -77,7 +77,9 @@ async function computerUse(params) {
77
77
  const outputFile = file || path.join(os.tmpdir(), 'natureco_screen_' + Date.now() + '.png');
78
78
  try {
79
79
  if (PLATFORM === 'darwin') {
80
- spawnSync('screencapture', ['-x', outputFile], { timeout: 5000 });
80
+ const capture = spawnSync('screencapture', ['-x', outputFile], { timeout: 5000, encoding: 'utf8' });
81
+ if (capture.error) throw capture.error;
82
+ if (capture.status !== 0) throw new Error(capture.stderr || `screencapture exit ${capture.status}`);
81
83
  } else if (PLATFORM === 'win32') {
82
84
  spawnSync('powershell', ['-Command',
83
85
  'Add-Type -AssemblyName System.Windows.Forms; ' +
@@ -89,7 +91,13 @@ async function computerUse(params) {
89
91
  } else {
90
92
  spawnSync('import', ['-window', 'root', outputFile], { timeout: 5000 });
91
93
  }
92
- return { success: true, file: outputFile, platform: PLATFORM, note: 'Ekran goruntusu alindi' };
94
+ if (!fs.existsSync(outputFile)) throw new Error('Screenshot file was not created');
95
+ return {
96
+ success: true,
97
+ file: outputFile,
98
+ platform: PLATFORM,
99
+ note: 'Screenshot saved. This tool returns a file path, not visual analysis; use computer_use_loop for autonomous visual interaction.',
100
+ };
93
101
  } catch (e) {
94
102
  return { success: false, error: 'Screenshot hatasi: ' + e.message };
95
103
  }
@@ -151,9 +159,10 @@ async function computerUse(params) {
151
159
  }
152
160
 
153
161
  if (KEY_MAP_DARWIN[actualKey]) {
162
+ const keyCodes = { return: 36, tab: 48, escape: 53, up: 126, down: 125, left: 123, right: 124, delete: 51, forwarddelete: 117, home: 115, end: 119, 'page up': 116, 'page down': 121, space: 49 };
154
163
  const keyName = KEY_MAP_DARWIN[actualKey];
155
164
  const usingClause = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
156
- const r = osaScript('tell application "System Events" to keystroke ' + keyName + usingClause);
165
+ const r = osaScript('tell application "System Events" to key code ' + keyCodes[keyName] + usingClause);
157
166
  if (!r.success) return r;
158
167
  } else if (actualKey) {
159
168
  const usingClause = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
@@ -9,22 +9,15 @@ const https = require('https');
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
  const os = require('os');
12
+ const { buildChatEndpoint } = require('../utils/provider-detect');
12
13
 
13
14
  function loadConfig() {
14
15
  try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8')); } catch { return {}; }
15
16
  }
16
17
 
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
18
  function apiCall(providerUrl, apiKey, body) {
21
19
  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';
20
+ const endpoint = buildChatEndpoint(providerUrl);
28
21
  const req = https.request(endpoint, {
29
22
  method: 'POST',
30
23
  headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' },
@@ -46,6 +39,7 @@ function apiCall(providerUrl, apiKey, body) {
46
39
  }
47
40
 
48
41
  function screenshotBase64() {
42
+ if (os.platform() !== 'darwin') throw new Error('computer_use_loop currently requires macOS');
49
43
  const file = path.join(os.tmpdir(), 'ncloop_' + Date.now() + '.png');
50
44
  require('child_process').execSync('screencapture -x "' + file + '"', { timeout: 5000 });
51
45
  const buf = fs.readFileSync(file);
@@ -73,7 +67,7 @@ function executeAction(action, params) {
73
67
  case 'type':
74
68
  return osaScript('tell application "System Events" to keystroke "' + ESC(params.text) + '"');
75
69
  case 'keypress': {
76
- const KEY_MAP = { enter: 'return', tab: 'tab', escape: 'escape', up: 'up', down: 'down', left: 'left', right: 'right', backspace: 'delete', space: 'space' };
70
+ const KEY_CODES = { enter: 36, return: 36, tab: 48, escape: 53, up: 126, down: 125, left: 123, right: 124, backspace: 51, delete: 117, space: 49 };
77
71
  const MOD_MAP = { cmd: 'command down', command: 'command down', option: 'option down', alt: 'option down', ctrl: 'control down', shift: 'shift down' };
78
72
  const parts = params.key.toLowerCase().split('+').map(p => p.trim());
79
73
  const mods = [];
@@ -82,9 +76,12 @@ function executeAction(action, params) {
82
76
  if (MOD_MAP[p]) mods.push(MOD_MAP[p]);
83
77
  else actual = p;
84
78
  }
85
- const k = KEY_MAP[actual] || actual;
86
79
  const using = mods.length > 0 ? ' using {' + mods.join(', ') + '}' : '';
87
- return osaScript('tell application "System Events" to keystroke ' + k + using);
80
+ if (Object.prototype.hasOwnProperty.call(KEY_CODES, actual)) {
81
+ return osaScript('tell application "System Events" to key code ' + KEY_CODES[actual] + using);
82
+ }
83
+ if (!actual) return { success: false, error: 'A key is required with the modifier' };
84
+ return osaScript('tell application "System Events" to keystroke "' + ESC(actual) + '"' + using);
88
85
  }
89
86
  case 'mouse_move':
90
87
  return osaScript('tell application "System Events" to set position of mouse to {' + params.x + ', ' + params.y + '}');
@@ -122,10 +119,15 @@ async function loop(goal, maxSteps) {
122
119
  const model = cfg.providerModel || 'default';
123
120
 
124
121
  if (!providerUrl || !apiKey) {
125
- return JSON.stringify({ success: false, error: 'Provider not configured' });
122
+ return { success: false, error: 'Provider not configured' };
123
+ }
124
+
125
+ if (os.platform() !== 'darwin') {
126
+ return { success: false, error: 'computer_use_loop currently requires macOS' };
126
127
  }
127
128
 
128
129
  const steps = [];
130
+ let completed = false;
129
131
  for (let i = 0; i < maxSteps; i++) {
130
132
  try {
131
133
  // 1. Screenshot
@@ -175,6 +177,7 @@ async function loop(goal, maxSteps) {
175
177
  // 3. Execute
176
178
  if (decision.action === 'done') {
177
179
  steps.push({ step: i + 1, action: 'done', reason: decision.reason || 'Goal achieved' });
180
+ completed = true;
178
181
  break;
179
182
  }
180
183
 
@@ -187,6 +190,7 @@ async function loop(goal, maxSteps) {
187
190
  const retryResult = executeAction(decision.action, decision);
188
191
  if (!retryResult.success) {
189
192
  steps.push({ step: i + 1, action: 'error', error: execResult.error });
193
+ return { success: false, error: execResult.error, goal, totalSteps: steps.length, steps };
190
194
  }
191
195
  }
192
196
 
@@ -195,7 +199,7 @@ async function loop(goal, maxSteps) {
195
199
 
196
200
  // Safety: if too many steps, break
197
201
  if (i >= maxSteps - 1) {
198
- steps.push({ step: i + 1, action: 'done', reason: 'Max steps reached' });
202
+ steps.push({ step: i + 1, action: 'error', error: 'Max steps reached before the goal was verified' });
199
203
  }
200
204
  } catch (e) {
201
205
  steps.push({ step: i + 1, action: 'error', error: e.message });
@@ -203,7 +207,10 @@ async function loop(goal, maxSteps) {
203
207
  }
204
208
  }
205
209
 
206
- return JSON.stringify({ success: true, goal, totalSteps: steps.length, steps });
210
+ if (!completed) {
211
+ return { success: false, error: 'Goal was not verified', goal, totalSteps: steps.length, steps };
212
+ }
213
+ return { success: true, goal, totalSteps: steps.length, steps };
207
214
  }
208
215
 
209
216
  const name = 'computer_use_loop';
@@ -221,4 +228,4 @@ async function execute(params) {
221
228
  return await loop(params.goal, params.maxSteps || 30);
222
229
  }
223
230
 
224
- module.exports = { name, description, parameters, execute };
231
+ module.exports = { name, description, parameters, execute, executeAction };
@@ -300,7 +300,7 @@ async function workflow(params) {
300
300
  '- Dosya degistirmeden ONCE read_file ile oku, edit_file ile hedefli degistir. Yerini bilmiyorsan file_search/grep_search ile kesfet.',
301
301
  '- Kod yazinca/degistirince bash ile calistir/test et, hata varsa duzelt. Coklu dosya = her biri icin AYRI write_file. Hep TAM yol; "masaustu" = ' + desktop + '.',
302
302
  '- Arac sonuclari <tool_results> icinde doner; is bitince arac cagirmadan tek cumlelik ozet yaz. Basit sohbette arac cagirma, dogrudan yanitla.',
303
- execFull ? '- Kullanici uygulama ac / tarayici kontrol / muzik cal / ekran / GUI istediyse yukaridaki computer-use araclarini KULLAN — "yapamam/engellendi" DEME, dogrudan ilgili araci cagir.' : '',
303
+ execFull ? '- Kullanici uygulama ac / tarayici kontrol / muzik cal / ekran / GUI istediyse yukaridaki computer-use araclarini KULLAN — "yapamam/engellendi" DEME, dogrudan ilgili araci cagir. Gorsel geri bildirim gerektiren cok adimli GUI gorevlerinde tekil screenshot/click yerine computer_use_loop kullan; basariyi arac dogrulamadan tamamlandi deme.' : '',
304
304
  fullToolsBlock,
305
305
  treeDigest ? ('\n\nBILDIGIN KALICI HAFIZA (bu kullaniciya ait, onceki oturumlardan hatirladiklarin; kullaniciya ozel bir sey sorulursa ONCE BUNU KULLAN — dosya arama, uydurma):\n' + treeDigest) : '',
306
306
  treeIndex ? ('\n\nHafiza agaci yapisi (yukarida olmayan detay icin memory_tree(action:read/search) ile ilgili kok/dali oku):\n' + treeIndex) : '',
@@ -337,7 +337,7 @@ async function workflow(params) {
337
337
  }
338
338
 
339
339
  // Araç aktivitesi gösterimi (TTY streaming): her araç icin "🔧 label · hint ✓/✗"
340
- const TOOL_LABEL = { write_file: 'dosya yaz', read_file: 'oku', edit_file: 'düzenle', bash: 'komut', file_search: 'ara', list_dir: 'listele', skill_view: 'skill', browser: 'tarayıcı', browser_use: 'tarayıcı', mac_app_open: 'uygulama aç', mac_app_quit: 'uygulama kapat', computer_use: 'GUI', social_open: 'medya aç', macos_screenshot: 'ekran görüntüsü' };
340
+ const TOOL_LABEL = { write_file: 'dosya yaz', read_file: 'oku', edit_file: 'düzenle', bash: 'komut', file_search: 'ara', list_dir: 'listele', skill_view: 'skill', browser: 'tarayıcı', browser_use: 'tarayıcı', mac_app_open: 'uygulama aç', mac_app_quit: 'uygulama kapat', computer_use: 'GUI', computer_use_loop: 'GUI görsel döngü', social_open: 'medya aç', macos_screenshot: 'ekran görüntüsü' };
341
341
  function briefHint(args) {
342
342
  if (!args || typeof args !== 'object') return '';
343
343
  const v = args.appName || args.query || args.name || args.url || args.command || args.pattern || args.path || args.action;
@@ -350,7 +350,10 @@ async function workflow(params) {
350
350
  process.stdout.write('\n\x1b[2m 🔧 ' + label + (hint ? ' · ' + hint : '') + '\x1b[0m');
351
351
  } else {
352
352
  const rec = (ev.records || [])[0] || {};
353
- process.stdout.write(rec.status === 'done' ? ' \x1b[32m✓\x1b[0m' : ' \x1b[31m✗\x1b[0m');
353
+ // Satırı burada bitir. Sonraki model çağrısının thinking göstergesi
354
+ // mevcut satırı \r + erase-line ile temizlediği için newline yoksa
355
+ // kullanıcı araç adını ve sonucunu hiç göremiyordu.
356
+ process.stdout.write((rec.status === 'done' ? ' \x1b[32m✓\x1b[0m' : ' \x1b[31m✗\x1b[0m') + '\n');
354
357
  }
355
358
  } : null;
356
359