natureco-cli 2.18.1 → 2.19.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": "2.18.1",
3
+ "version": "2.19.0",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -312,11 +312,10 @@ async function chat(botName, options = {}) {
312
312
 
313
313
  if (response.conversation_id) conversationId = response.conversation_id;
314
314
 
315
- // Tool loop — DEBUG: api.js zaten tool loop yapıyor, bu loop çalışmamalı
315
+ // Tool loop — api.js zaten tool loop yapıyor, bu loop çalışmamalı
316
316
  let iter = 0;
317
317
  while (iter < 5) {
318
318
  const toolCalls = extractToolCalls(response);
319
- console.log('[DEBUG] Tool calls:', JSON.stringify(toolCalls));
320
319
  if (!toolCalls?.length) break;
321
320
  console.log(chalk.yellow(`🔧 ${toolCalls.length} tool çalıştırılıyor...`));
322
321
  const toolResults = await executeToolCalls(toolCalls);
@@ -5,14 +5,14 @@ const readline = require('readline');
5
5
  const inquirer = require('inquirer');
6
6
  const chalk = require('chalk');
7
7
  const { getApiKey, getConfig } = require('../utils/config');
8
- const { getBots, _sendMessage } = require('../utils/api');
8
+ const { getBots, getProviderConfig, startMcpServers } = require('../utils/api');
9
9
  const { getMemoryPrompt, loadMemory } = require('../utils/memory');
10
10
  const { getAgentsPrompt } = require('../utils/agents');
11
11
  const { createSession, addMessageToSession } = require('../utils/sessions');
12
12
  const { addToHistory } = require('../utils/history');
13
- const { getToolDefinitions, executeToolCalls, getSessionStats, resetSessionStats } = require('../utils/tool-runner');
14
- const { extractToolCalls } = require('../utils/tool-adapter');
13
+ const { getToolDefinitions, executeTool } = require('../utils/tool-runner');
15
14
 
15
+ // ── Helpers ───────────────────────────────────────────────────────────────────
16
16
  const sep = () => chalk.gray('─'.repeat(process.stdout.columns || 120));
17
17
 
18
18
  function centerText(text) {
@@ -23,11 +23,141 @@ function centerText(text) {
23
23
  }).join('\n');
24
24
  }
25
25
 
26
- const CODE_SYSTEM_PROMPT = `Sen bir AI coding asistanısın. Kullanıcının projesinde dosyaları oku, değiştir, komut çalıştır.
27
- Her adımı açıkça belirt. Değişiklik yapmadan önce plan sun.
28
- Türkçe konuş.
29
- Mevcut çalışma dizini: ${process.cwd()}`;
26
+ const SPINNER_FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
30
27
 
28
+ function startSpinner(label) {
29
+ let i = 0;
30
+ return setInterval(() => {
31
+ process.stdout.write(`\r ${chalk.cyan(SPINNER_FRAMES[i++ % SPINNER_FRAMES.length])} ${chalk.gray(label)}`);
32
+ }, 80);
33
+ }
34
+
35
+ function stopSpinner(timer, label, success = true) {
36
+ clearInterval(timer);
37
+ process.stdout.write(`\r ${success ? chalk.green('✓') : chalk.red('✗')} ${chalk.cyan(label)}\n`);
38
+ }
39
+
40
+ // ── Streaming fetch ───────────────────────────────────────────────────────────
41
+ async function streamMessage(providerConfig, messages, tools) {
42
+ const endpoint = `${providerConfig.url}/chat/completions`;
43
+ const body = {
44
+ model: providerConfig.model,
45
+ messages,
46
+ tools,
47
+ tool_choice: 'auto',
48
+ temperature: 0.7,
49
+ max_tokens: 4000,
50
+ stream: true,
51
+ };
52
+
53
+ const response = await fetch(endpoint, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Authorization': `Bearer ${providerConfig.apiKey}`,
57
+ 'Content-Type': 'application/json',
58
+ },
59
+ body: JSON.stringify(body),
60
+ });
61
+
62
+ if (!response.ok) {
63
+ const err = await response.text();
64
+ throw new Error(`API error: ${response.status} — ${err}`);
65
+ }
66
+
67
+ const reader = response.body.getReader();
68
+ const decoder = new TextDecoder();
69
+ let fullText = '';
70
+ let toolCallsBuffer = {}; // id -> { name, arguments }
71
+
72
+ process.stdout.write('\n');
73
+
74
+ while (true) {
75
+ const { done, value } = await reader.read();
76
+ if (done) break;
77
+
78
+ const chunk = decoder.decode(value);
79
+ const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
80
+
81
+ for (const line of lines) {
82
+ const data = line.slice(6).trim();
83
+ if (data === '[DONE]') continue;
84
+ try {
85
+ const json = JSON.parse(data);
86
+ const delta = json.choices?.[0]?.delta;
87
+ if (!delta) continue;
88
+
89
+ // Text content
90
+ if (delta.content) {
91
+ process.stdout.write(delta.content);
92
+ fullText += delta.content;
93
+ }
94
+
95
+ // Tool call deltas
96
+ if (delta.tool_calls) {
97
+ for (const tc of delta.tool_calls) {
98
+ const idx = tc.index ?? 0;
99
+ if (!toolCallsBuffer[idx]) {
100
+ toolCallsBuffer[idx] = { id: tc.id || '', name: '', arguments: '' };
101
+ }
102
+ if (tc.id) toolCallsBuffer[idx].id = tc.id;
103
+ if (tc.function?.name) toolCallsBuffer[idx].name += tc.function.name;
104
+ if (tc.function?.arguments) toolCallsBuffer[idx].arguments += tc.function.arguments;
105
+ }
106
+ }
107
+ } catch {}
108
+ }
109
+ }
110
+
111
+ process.stdout.write('\n');
112
+
113
+ const toolCalls = Object.values(toolCallsBuffer)
114
+ .filter(tc => tc.name)
115
+ .map(tc => ({
116
+ id: tc.id,
117
+ name: tc.name,
118
+ input: (() => { try { return JSON.parse(tc.arguments); } catch { return {}; } })(),
119
+ }));
120
+
121
+ return { text: fullText, toolCalls };
122
+ }
123
+
124
+ // ── Tool execution with spinner + confirm ─────────────────────────────────────
125
+ const DANGEROUS = ['rm ', 'rmdir', 'delete', 'DROP', 'truncate'];
126
+
127
+ async function runToolCall(toolCall, stats) {
128
+ const inputPreview = JSON.stringify(toolCall.input).slice(0, 60);
129
+ const label = `${toolCall.name} ${chalk.gray(inputPreview)}`;
130
+
131
+ const needsConfirm =
132
+ toolCall.name === 'write_file' ||
133
+ (toolCall.name === 'bash' && DANGEROUS.some(d => toolCall.input.command?.includes(d)));
134
+
135
+ if (needsConfirm) {
136
+ console.log(chalk.yellow(`\n ⚠️ ${toolCall.name}: ${inputPreview}`));
137
+ const { ok } = await inquirer.prompt([{
138
+ type: 'confirm', name: 'ok',
139
+ message: ' Devam edilsin mi?', default: true,
140
+ }]);
141
+ if (!ok) {
142
+ console.log(chalk.gray(' İptal edildi.\n'));
143
+ return { success: false, output: 'Kullanıcı iptal etti.' };
144
+ }
145
+ }
146
+
147
+ const spinner = startSpinner(`${toolCall.name} ${inputPreview}`);
148
+ const result = await executeTool(toolCall.name, toolCall.input);
149
+ stopSpinner(spinner, toolCall.name, result.success !== false);
150
+
151
+ if (result.success !== false) {
152
+ if (toolCall.name === 'write_file') stats.filesChanged++;
153
+ if (toolCall.name === 'bash') stats.commandsRun++;
154
+ stats.toolCallCount++;
155
+ }
156
+
157
+ return result;
158
+ }
159
+
160
+ // ── Main ──────────────────────────────────────────────────────────────────────
31
161
  async function code(targetFile, options = {}) {
32
162
  const apiKey = getApiKey();
33
163
  const config = getConfig();
@@ -36,27 +166,25 @@ async function code(targetFile, options = {}) {
36
166
  // ── Bot seçimi ──────────────────────────────────────────────────────────────
37
167
  let botList;
38
168
  try {
39
- botList = await (require('../utils/api').getBots)(apiKey || config.providerApiKey || '');
169
+ botList = await getBots(apiKey || config.providerApiKey || '');
40
170
  } catch (err) {
41
171
  console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
42
172
  process.exit(1);
43
173
  }
44
174
 
45
175
  if (!botList?.bots?.length) {
46
- console.log(chalk.gray('No bots found. Create one at https://developers.natureco.me\n'));
176
+ console.log(chalk.gray('No bots found.\n'));
47
177
  process.exit(1);
48
178
  }
49
179
 
50
180
  let bot;
51
181
  if (config.botName) {
52
- bot = botList.bots.find(b => b.name && b.name.toLowerCase() === config.botName.toLowerCase());
182
+ bot = botList.bots.find(b => b.name?.toLowerCase() === config.botName.toLowerCase());
53
183
  }
54
184
  if (!bot) {
55
185
  process.stdin.resume();
56
186
  const { selectedBot } = await inquirer.prompt([{
57
- type: 'list',
58
- name: 'selectedBot',
59
- message: 'Bot seçin:',
187
+ type: 'list', name: 'selectedBot', message: 'Bot seçin:',
60
188
  choices: botList.bots.map(b => ({ name: b.name, value: b.id })),
61
189
  }]);
62
190
  bot = botList.bots.find(b => b.id === selectedBot);
@@ -65,13 +193,35 @@ async function code(targetFile, options = {}) {
65
193
  const mem = loadMemory(bot.id);
66
194
  const displayBotName = mem.botName || bot.name || 'NatureCo';
67
195
  const userName = mem.name || config.userName || 'User';
68
- const providerModel = config.providerModel || 'unknown';
69
- const shortModel = providerModel.split('/').pop().split('-').slice(0, 3).join('-');
196
+ const providerConfig = getProviderConfig();
70
197
 
198
+ if (!providerConfig) {
199
+ console.log(chalk.red('\n❌ Provider yapılandırılmamış. natureco config set providerUrl ...\n'));
200
+ process.exit(1);
201
+ }
202
+
203
+ const shortModel = providerConfig.model.split('/').pop().split('-').slice(0, 3).join('-');
204
+
205
+ // ── Proje context — dizini otomatik tara ────────────────────────────────────
206
+ let projectContext = '';
207
+ try {
208
+ const dirResult = await executeTool('list_dir', { path: process.cwd() });
209
+ if (dirResult.success !== false) {
210
+ projectContext = `\nMevcut proje dizini: ${process.cwd()}\nDosyalar:\n${(dirResult.output || '').slice(0, 800)}`;
211
+ }
212
+ } catch {}
213
+
214
+ // ── Sistem prompt ────────────────────────────────────────────────────────────
71
215
  const agentsPrompt = getAgentsPrompt();
72
216
  const memoryPrompt = getMemoryPrompt(bot.id);
73
217
 
74
- let systemPrompt = CODE_SYSTEM_PROMPT;
218
+ let systemPrompt = `Sen NatureCo Code Agent'sın — güçlü bir AI coding asistanı.
219
+ Kullanıcının projesinde gerçekten çalış: dosyaları oku, değiştir, komutları çalıştır.
220
+ Her adımı Türkçe açıkla. Değişiklik yapmadan önce ne yapacağını söyle.
221
+ Hataları yakala ve düzelt. İş bitince özet sun.
222
+ Mevcut dizin: ${process.cwd()}
223
+ Kullanıcı: ${userName}${projectContext}`;
224
+
75
225
  if (agentsPrompt) systemPrompt += `\n\n## Project Instructions\n${agentsPrompt}`;
76
226
  if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
77
227
 
@@ -79,144 +229,170 @@ async function code(targetFile, options = {}) {
79
229
  if (targetFile) {
80
230
  try {
81
231
  const content = fs.readFileSync(path.resolve(targetFile), 'utf-8');
82
- systemPrompt += `\n\n## Odak Dosyası: ${targetFile}\n\`\`\`\n${content}\n\`\`\``;
232
+ systemPrompt += `\n\n## Odak Dosyası: ${targetFile}\n\`\`\`\n${content.slice(0, 2000)}\n\`\`\``;
83
233
  } catch {
84
- console.log(chalk.yellow(` ⚠️ ${targetFile} okunamadı, devam ediliyor...\n`));
234
+ console.log(chalk.yellow(` ⚠️ ${targetFile} okunamadı\n`));
85
235
  }
86
236
  }
87
237
 
238
+ // ── Session ──────────────────────────────────────────────────────────────────
88
239
  const session = createSession(bot.id, bot.name);
89
- let conversationId = null;
90
- let messagesCount = 0;
91
- resetSessionStats();
240
+ const conversationMessages = [{ role: 'system', content: systemPrompt }];
241
+ const stats = { filesChanged: 0, commandsRun: 0, toolCallCount: 0, messageCount: 0 };
242
+
243
+ // ── MCP sunucularını başlat ──────────────────────────────────────────────────
244
+ await startMcpServers().catch(() => {});
245
+
246
+ // ── Tool definitions ─────────────────────────────────────────────────────────
247
+ const localTools = getToolDefinitions();
248
+ const tools = localTools.map(t => ({
249
+ type: 'function',
250
+ function: {
251
+ name: t.name,
252
+ description: t.description,
253
+ parameters: t.inputSchema || { type: 'object', properties: {} },
254
+ },
255
+ }));
92
256
 
93
257
  // ── Header ──────────────────────────────────────────────────────────────────
94
258
  console.clear();
95
- console.log(centerText(chalk.green('⚡ NatureCo Code') + chalk.gray(' · ') + chalk.cyan(`${displayBotName}`) + chalk.gray(` · v${version}`)));
259
+ const codeLogo = [
260
+ '███╗ ██╗ █████╗ ████████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗',
261
+ '████╗ ██║██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔════╝ ██╔═══██╗',
262
+ '██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝█████╗ ██║ ██║ ██║',
263
+ '██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║',
264
+ '██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗╚██████╗ ╚██████╔╝',
265
+ '╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝',
266
+ '',
267
+ '█▀▀ █▀█ █▀▄ █▀▀',
268
+ '█▄▄ █▄█ █▄▀ ██▄',
269
+ ].join('\n');
270
+ console.log(centerText(chalk.green(codeLogo)));
271
+ console.log();
272
+ console.log(centerText(chalk.cyan(`(\\_/) ${displayBotName} hazır`) + chalk.gray(` · v${version}`)));
96
273
  console.log(sep());
97
- if (targetFile) {
98
- console.log(centerText(chalk.gray(`📄 ${targetFile}`)));
99
- console.log(sep());
100
- }
101
- console.log(centerText(chalk.gray(`${shortModel} · /help için yardım · Ctrl+C çıkış`)));
274
+ if (targetFile) console.log(centerText(chalk.gray(`📄 ${targetFile}`)));
275
+ console.log(centerText(chalk.gray(`${shortModel} · /help · Ctrl+C çıkış`)));
102
276
  console.log(sep());
103
277
  console.log();
104
278
 
105
- // ── Yükleme animasyonu ──────────────────────────────────────────────────────
106
- const loadingFrames = ['●○○', '○●○', '○○●'];
107
- let loadingFrame = 0;
108
- let loadingTimer = null;
109
-
110
- function startLoading() {
111
- loadingFrame = 0;
112
- process.stdout.write(chalk.gray(' ' + loadingFrames[0]));
113
- loadingTimer = setInterval(() => {
114
- process.stdout.write('\r' + chalk.gray(' ' + loadingFrames[loadingFrame]));
115
- loadingFrame = (loadingFrame + 1) % loadingFrames.length;
116
- }, 300);
117
- }
118
-
119
- function stopLoading() {
120
- if (loadingTimer) { clearInterval(loadingTimer); loadingTimer = null; }
121
- process.stdout.write('\r\x1b[2K');
122
- }
123
-
124
279
  // ── Session özeti ─────────────────────────────────────────────────────────
125
- async function showSummary() {
126
- const { filesChanged, commandsRun } = getSessionStats();
127
- console.log(chalk.gray('\n─── Session Özeti ───'));
128
- console.log(chalk.green(` ✓ ${filesChanged} dosya değiştirildi`));
129
- console.log(chalk.green(` ✓ ${commandsRun} komut çalıştırıldı`));
130
- console.log(chalk.cyan(` ✓ ${messagesCount} mesaj gönderildi`));
131
- console.log();
280
+ function showSummary() {
281
+ console.log(chalk.gray('\n─── Agent Session Özeti ───'));
282
+ console.log(` ${chalk.green('✓')} ${stats.filesChanged} dosya değiştirildi`);
283
+ console.log(` ${chalk.green('')} ${stats.commandsRun} komut çalıştırıldı`);
284
+ console.log(` ${chalk.green('')} ${stats.toolCallCount} tool çağrısı yapıldı`);
285
+ console.log(` ${chalk.cyan('◉')} ${stats.messageCount} mesaj\n`);
132
286
  }
133
287
 
134
- // ── Mesaj gönderme ──────────────────────────────────────────────────────────
288
+ // ── Mesaj gönder + tool loop ──────────────────────────────────────────────
135
289
  async function handleMessage(userMessage) {
136
290
  userMessage = userMessage.trim();
137
291
  if (!userMessage) return;
138
292
 
293
+ // Slash komutları
139
294
  if (userMessage.startsWith('/')) {
140
295
  const [cmd] = userMessage.slice(1).split(' ');
141
296
  switch (cmd.toLowerCase()) {
142
297
  case 'help':
143
- console.log(chalk.yellow('Code Komutları:'));
298
+ console.log(chalk.yellow('Code Agent Komutları:'));
144
299
  [
145
- ['/clear', 'Ekranı temizle'],
300
+ ['/clear', 'Ekranı temizle'],
146
301
  ['/summary', 'Session özetini göster'],
147
- ['/help', 'Bu yardım'],
148
- ].forEach(([c, d]) => console.log(' ' + chalk.cyan(c.padEnd(16)) + chalk.gray(d)));
149
- console.log(chalk.gray(' Ctrl+C'.padEnd(18) + 'Çıkış'));
302
+ ['/done', 'Bitir ve özet göster'],
303
+ ['/help', 'Bu yardım'],
304
+ ].forEach(([c, d]) => console.log(' ' + chalk.cyan(c.padEnd(12)) + chalk.gray(d)));
305
+ console.log(chalk.gray(' Ctrl+C'.padEnd(14) + 'Çıkış'));
150
306
  console.log();
151
307
  return;
152
308
  case 'clear':
153
309
  console.clear();
154
310
  return;
155
311
  case 'summary':
156
- await showSummary();
312
+ case 'done':
313
+ showSummary();
314
+ if (cmd === 'done') process.exit(0);
157
315
  return;
158
316
  default:
159
- console.log(chalk.red(`Bilinmeyen komut: /${cmd}`));
160
- console.log();
317
+ console.log(chalk.red(`Bilinmeyen komut: /${cmd}\n`));
161
318
  return;
162
319
  }
163
320
  }
164
321
 
165
322
  if (userMessage === 'exit' || userMessage === 'quit') {
166
- await showSummary();
323
+ showSummary();
167
324
  process.exit(0);
168
325
  }
169
326
 
170
- messagesCount++;
327
+ stats.messageCount++;
171
328
  console.log(chalk.white('You ') + userMessage);
172
- startLoading();
173
329
 
174
- try {
175
- const toolDefinitions = getToolDefinitions();
176
- let response = await _sendMessage(apiKey || config.providerApiKey, bot.id, userMessage, conversationId, systemPrompt, toolDefinitions);
177
- stopLoading();
178
-
179
- if (response.conversation_id) conversationId = response.conversation_id;
180
-
181
- // Tool loop — DEBUG: api.js zaten tool loop yapıyor, bu loop çalışmamalı
182
- let iter = 0;
183
- while (iter < 10) {
184
- const toolCalls = extractToolCalls(response);
185
- console.log('[DEBUG] Tool calls:', JSON.stringify(toolCalls));
186
- if (!toolCalls?.length) break;
187
- console.log(chalk.yellow(`\n🔧 ${toolCalls.length} tool çalıştırılıyor...\n`));
188
- const toolResults = await executeToolCalls(toolCalls, { agentMode: true });
189
- const toolMsg = toolResults.map(tr =>
190
- `Tool: ${tr.name}\nResult: ${tr.result.success ? (tr.result.output || JSON.stringify(tr.result)) : tr.result.error}`
191
- ).join('\n\n');
192
- startLoading();
193
- response = await _sendMessage(apiKey || config.providerApiKey, bot.id, toolMsg, conversationId, systemPrompt, toolDefinitions);
194
- stopLoading();
195
- if (response.conversation_id) conversationId = response.conversation_id;
196
- iter++;
330
+ // Mesajı history'e ekle
331
+ conversationMessages.push({ role: 'user', content: userMessage });
332
+
333
+ // ── Streaming + tool loop (max 20 iter) ──────────────────────────────────
334
+ let iter = 0;
335
+ while (iter < 20) {
336
+ iter++;
337
+
338
+ let streamResult;
339
+ try {
340
+ process.stdout.write(chalk.cyan(`${displayBotName} `));
341
+ streamResult = await streamMessage(providerConfig, conversationMessages, tools);
342
+ } catch (err) {
343
+ console.log(chalk.red(`\nError: ${err.message}\n`));
344
+ conversationMessages.pop(); // başarısız user mesajını geri al
345
+ return;
197
346
  }
198
347
 
199
- const botReply = response.reply || response.message || 'No response';
200
- console.log(chalk.cyan(`${displayBotName} `) + botReply);
201
- console.log();
202
-
203
- addToHistory(bot.id, userMessage, botReply, conversationId);
204
- addMessageToSession(bot.id, session.id, userMessage, botReply);
348
+ // Assistant mesajını history'e ekle
349
+ const assistantMsg = { role: 'assistant', content: streamResult.text || '' };
350
+ if (streamResult.toolCalls?.length) {
351
+ assistantMsg.tool_calls = streamResult.toolCalls.map(tc => ({
352
+ id: tc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2)}`,
353
+ type: 'function',
354
+ function: { name: tc.name, arguments: JSON.stringify(tc.input) },
355
+ }));
356
+ }
357
+ conversationMessages.push(assistantMsg);
358
+
359
+ // Tool call yoksa bitti
360
+ if (!streamResult.toolCalls?.length) break;
361
+
362
+ // Tool call'ları çalıştır
363
+ console.log(chalk.yellow(`\n🔧 ${streamResult.toolCalls.length} tool çalıştırılıyor...\n`));
364
+
365
+ for (const toolCall of streamResult.toolCalls) {
366
+ const result = await runToolCall(toolCall, stats);
367
+ const resultStr = result.success !== false
368
+ ? (result.output || JSON.stringify(result))
369
+ : `Hata: ${result.error}`;
370
+
371
+ conversationMessages.push({
372
+ role: 'tool',
373
+ tool_call_id: assistantMsg.tool_calls?.find(tc => tc.function.name === toolCall.name)?.id || toolCall.id,
374
+ name: toolCall.name,
375
+ content: resultStr.slice(0, 3000),
376
+ });
377
+ }
205
378
 
206
- } catch (err) {
207
- stopLoading();
208
- const errMsg = err.message.split('"message":"')[1]?.split('"')[0] || err.message;
209
- console.log(chalk.red(`Error: ${errMsg}`));
210
379
  console.log();
211
380
  }
381
+
382
+ // Session'a kaydet
383
+ const lastAssistant = [...conversationMessages].reverse().find(m => m.role === 'assistant');
384
+ if (lastAssistant?.content) {
385
+ addToHistory(bot.id, userMessage, lastAssistant.content, null);
386
+ addMessageToSession(bot.id, session.id, userMessage, lastAssistant.content);
387
+ }
212
388
  }
213
389
 
214
390
  // ── Input loop ───────────────────────────────────────────────────────────────
215
391
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
216
392
 
217
- process.on('SIGINT', async () => {
393
+ process.on('SIGINT', () => {
218
394
  rl.close();
219
- await showSummary();
395
+ showSummary();
220
396
  console.log(chalk.gray('👋 Goodbye!\n'));
221
397
  process.exit(0);
222
398
  });
package/src/utils/api.js CHANGED
@@ -4,6 +4,7 @@
4
4
  const fs = require('fs');
5
5
  const os = require('os');
6
6
  const path = require('path');
7
+ const chalk = require('chalk');
7
8
  const { getConfig } = require('./config');
8
9
  const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
9
10
  const { MCPClient } = require('./mcp-client');
@@ -428,8 +429,7 @@ function formatToolsForAnthropic() {
428
429
  */
429
430
  async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
430
431
  const endpoint = `${providerConfig.url}/chat/completions`;
431
-
432
- // Log request size before sending
432
+
433
433
  const requestBody = {
434
434
  model: providerConfig.model,
435
435
  messages: messages,
@@ -438,8 +438,12 @@ async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
438
438
  temperature: 0.7,
439
439
  max_tokens: 2000,
440
440
  };
441
-
442
- const bodyStr = JSON.stringify(requestBody);
441
+
442
+ // NatureCo backend proxy — tool call'ları Groq'a iletmek için explicitly set
443
+ if (providerConfig.url.includes('api.natureco.me')) {
444
+ requestBody.tool_choice = 'auto';
445
+ requestBody.tools = tools;
446
+ }
443
447
 
444
448
  const response = await fetch(endpoint, {
445
449
  method: 'POST',
@@ -447,7 +451,7 @@ async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
447
451
  'Authorization': `Bearer ${providerConfig.apiKey}`,
448
452
  'Content-Type': 'application/json',
449
453
  },
450
- body: bodyStr,
454
+ body: JSON.stringify(requestBody),
451
455
  });
452
456
 
453
457
  if (!response.ok) {
@@ -588,27 +592,36 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
588
592
  }));
589
593
 
590
594
  const toolResults = [];
595
+ const SPINNER_FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
591
596
 
592
597
  for (const toolCall of toolCalls) {
598
+ // Spinner başlat
599
+ let frameIdx = 0;
600
+ const inputPreview = JSON.stringify(toolCall.input).slice(0, 50);
601
+ const spinner = setInterval(() => {
602
+ process.stdout.write(`\r ${chalk.cyan(SPINNER_FRAMES[frameIdx++ % SPINNER_FRAMES.length])} ${chalk.gray(toolCall.name + ' — ' + inputPreview)}`);
603
+ }, 80);
604
+
593
605
  // Check if this is an MCP tool
594
606
  const mcpTools = getMcpTools();
595
607
  const isMcpTool = mcpTools.find(t => t.name === toolCall.name);
608
+ let result;
596
609
 
597
610
  if (isMcpTool) {
598
- // Execute MCP tool
599
611
  debugLog(`[MCP] Executing tool: ${toolCall.name}`);
600
- const result = await executeMcpTool(toolCall.name, toolCall.input);
601
- toolResults.push({
602
- id: toolCall.id,
603
- name: toolCall.name,
604
- result: result
605
- });
612
+ result = await executeMcpTool(toolCall.name, toolCall.input);
613
+ toolResults.push({ id: toolCall.id, name: toolCall.name, result });
606
614
  } else {
607
- // Execute local tool
608
615
  debugLog(`[Local] Executing tool: ${toolCall.name}`);
609
616
  const localResults = await executeToolCalls([toolCall]);
610
617
  toolResults.push(...localResults);
618
+ result = localResults[0]?.result;
611
619
  }
620
+
621
+ // Spinner durdur, sonucu göster
622
+ clearInterval(spinner);
623
+ const success = result?.success !== false;
624
+ process.stdout.write(`\r ${success ? chalk.green('✓') : chalk.red('✗')} ${chalk.cyan(toolCall.name)} ${chalk.gray('— ' + inputPreview)}\n`);
612
625
  }
613
626
 
614
627
  // Add tool results to messages (base64 encoded for safety)