natureco-cli 2.18.2 → 2.19.1

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/bin/natureco.js CHANGED
@@ -21,6 +21,55 @@ program
21
21
  .description('NatureCo AI Bot Terminal Interface')
22
22
  .version(packageJson.version);
23
23
 
24
+ program.addHelpText('after', `
25
+ ${chalk.yellow('🤖 AI & Chat')}
26
+ ${chalk.cyan('chat')} AI sohbet başlat
27
+ ${chalk.cyan('code')} Code agent — dosya oku, yaz, komut çalıştır
28
+ ${chalk.cyan('ask')} Tek soru sor
29
+ ${chalk.cyan('run')} Markdown script çalıştır
30
+ ${chalk.cyan('bots')} Bot listesi
31
+
32
+ ${chalk.yellow('⚙️ Kurulum & Ayarlar')}
33
+ ${chalk.cyan('setup')} İlk kurulum sihirbazı
34
+ ${chalk.cyan('login')} API key ile giriş
35
+ ${chalk.cyan('logout')} Çıkış
36
+ ${chalk.cyan('config')} Ayarları yönet
37
+ ${chalk.cyan('doctor')} Sistem sağlık kontrolü
38
+ ${chalk.cyan('update')} Güncelleme kontrol
39
+
40
+ ${chalk.yellow('🔌 Entegrasyonlar')}
41
+ ${chalk.cyan('telegram')} Telegram bağlantısı
42
+ ${chalk.cyan('whatsapp')} WhatsApp bağlantısı
43
+ ${chalk.cyan('discord')} Discord bağlantısı
44
+ ${chalk.cyan('slack')} Slack bağlantısı
45
+ ${chalk.cyan('gateway')} WebSocket gateway
46
+
47
+ ${chalk.yellow('🛠️ Geliştirici Araçları')}
48
+ ${chalk.cyan('git')} Git entegrasyonu
49
+ ${chalk.cyan('skills')} Skill yönetimi
50
+ ${chalk.cyan('mcp')} MCP sunucuları
51
+ ${chalk.cyan('hooks')} Hook yönetimi
52
+ ${chalk.cyan('cron')} Zamanlanmış görevler
53
+ ${chalk.cyan('sessions')} Oturum yönetimi
54
+ ${chalk.cyan('ultrareview')} Detaylı kod inceleme
55
+
56
+ ${chalk.yellow('📊 Yönetim')}
57
+ ${chalk.cyan('dashboard')} Web kontrol paneli
58
+ ${chalk.cyan('memory')} Hafıza yönetimi
59
+ ${chalk.cyan('logs')} Gateway logları
60
+ ${chalk.cyan('status')} Sistem durumu
61
+ ${chalk.cyan('agents')} Agent yönetimi
62
+ ${chalk.cyan('plugins')} Plugin yönetimi
63
+ ${chalk.cyan('security')} Güvenlik denetimi
64
+ ${chalk.cyan('reset')} Sıfırlama
65
+
66
+ ${chalk.yellow('Örnekler:')}
67
+ ${chalk.gray('$ natureco setup')} İlk kurulum
68
+ ${chalk.gray('$ natureco chat')} Sohbet başlat
69
+ ${chalk.gray('$ natureco code')} Code agent
70
+ ${chalk.gray('$ natureco doctor')} Sistem kontrolü
71
+ `);
72
+
24
73
  program
25
74
  .command('login')
26
75
  .description('Login with your NatureCo API key')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.18.2",
3
+ "version": "2.19.1",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -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();
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
+ }
70
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,16 +229,30 @@ 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();
@@ -99,136 +263,136 @@ async function code(targetFile, options = {}) {
99
263
  '██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║',
100
264
  '██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗╚██████╗ ╚██████╔╝',
101
265
  '╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝',
102
- ].join('\n');
103
- const codeSubLogo = [
266
+ '',
104
267
  '█▀▀ █▀█ █▀▄ █▀▀',
105
268
  '█▄▄ █▄█ █▄▀ ██▄',
106
269
  ].join('\n');
107
270
  console.log(centerText(chalk.green(codeLogo)));
108
- console.log(centerText(chalk.green(codeSubLogo)));
271
+ console.log();
272
+ console.log(centerText(chalk.cyan(`(\\_/) ${displayBotName} hazır`) + chalk.gray(` · v${version}`)));
109
273
  console.log(sep());
110
- if (targetFile) {
111
- console.log(centerText(chalk.gray(`📄 ${targetFile}`)));
112
- console.log(sep());
113
- }
114
- 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ış`)));
115
276
  console.log(sep());
116
277
  console.log();
117
278
 
118
- // ── Yükleme animasyonu ──────────────────────────────────────────────────────
119
- const loadingFrames = ['●○○', '○●○', '○○●'];
120
- let loadingFrame = 0;
121
- let loadingTimer = null;
122
-
123
- function startLoading() {
124
- loadingFrame = 0;
125
- process.stdout.write(chalk.gray(' ' + loadingFrames[0]));
126
- loadingTimer = setInterval(() => {
127
- process.stdout.write('\r' + chalk.gray(' ' + loadingFrames[loadingFrame]));
128
- loadingFrame = (loadingFrame + 1) % loadingFrames.length;
129
- }, 300);
130
- }
131
-
132
- function stopLoading() {
133
- if (loadingTimer) { clearInterval(loadingTimer); loadingTimer = null; }
134
- process.stdout.write('\r\x1b[2K');
135
- }
136
-
137
279
  // ── Session özeti ─────────────────────────────────────────────────────────
138
- async function showSummary() {
139
- const { filesChanged, commandsRun } = getSessionStats();
140
- console.log(chalk.gray('\n─── Session Özeti ───'));
141
- console.log(chalk.green(` ✓ ${filesChanged} dosya değiştirildi`));
142
- console.log(chalk.green(` ✓ ${commandsRun} komut çalıştırıldı`));
143
- console.log(chalk.cyan(` ✓ ${messagesCount} mesaj gönderildi`));
144
- 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`);
145
286
  }
146
287
 
147
- // ── Mesaj gönderme ──────────────────────────────────────────────────────────
288
+ // ── Mesaj gönder + tool loop ──────────────────────────────────────────────
148
289
  async function handleMessage(userMessage) {
149
290
  userMessage = userMessage.trim();
150
291
  if (!userMessage) return;
151
292
 
293
+ // Slash komutları
152
294
  if (userMessage.startsWith('/')) {
153
295
  const [cmd] = userMessage.slice(1).split(' ');
154
296
  switch (cmd.toLowerCase()) {
155
297
  case 'help':
156
- console.log(chalk.yellow('Code Komutları:'));
298
+ console.log(chalk.yellow('Code Agent Komutları:'));
157
299
  [
158
- ['/clear', 'Ekranı temizle'],
300
+ ['/clear', 'Ekranı temizle'],
159
301
  ['/summary', 'Session özetini göster'],
160
- ['/help', 'Bu yardım'],
161
- ].forEach(([c, d]) => console.log(' ' + chalk.cyan(c.padEnd(16)) + chalk.gray(d)));
162
- 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ış'));
163
306
  console.log();
164
307
  return;
165
308
  case 'clear':
166
309
  console.clear();
167
310
  return;
168
311
  case 'summary':
169
- await showSummary();
312
+ case 'done':
313
+ showSummary();
314
+ if (cmd === 'done') process.exit(0);
170
315
  return;
171
316
  default:
172
- console.log(chalk.red(`Bilinmeyen komut: /${cmd}`));
173
- console.log();
317
+ console.log(chalk.red(`Bilinmeyen komut: /${cmd}\n`));
174
318
  return;
175
319
  }
176
320
  }
177
321
 
178
322
  if (userMessage === 'exit' || userMessage === 'quit') {
179
- await showSummary();
323
+ showSummary();
180
324
  process.exit(0);
181
325
  }
182
326
 
183
- messagesCount++;
327
+ stats.messageCount++;
184
328
  console.log(chalk.white('You ') + userMessage);
185
- startLoading();
186
329
 
187
- try {
188
- const toolDefinitions = getToolDefinitions();
189
- let response = await _sendMessage(apiKey || config.providerApiKey, bot.id, userMessage, conversationId, systemPrompt, toolDefinitions);
190
- stopLoading();
191
-
192
- if (response.conversation_id) conversationId = response.conversation_id;
193
-
194
- // Tool loop — api.js zaten tool loop yapıyor, bu loop çalışmamalı
195
- let iter = 0;
196
- while (iter < 10) {
197
- const toolCalls = extractToolCalls(response);
198
- if (!toolCalls?.length) break;
199
- console.log(chalk.yellow(`\n🔧 ${toolCalls.length} tool çalıştırılıyor...\n`));
200
- const toolResults = await executeToolCalls(toolCalls, { agentMode: true });
201
- const toolMsg = toolResults.map(tr =>
202
- `Tool: ${tr.name}\nResult: ${tr.result.success ? (tr.result.output || JSON.stringify(tr.result)) : tr.result.error}`
203
- ).join('\n\n');
204
- startLoading();
205
- response = await _sendMessage(apiKey || config.providerApiKey, bot.id, toolMsg, conversationId, systemPrompt, toolDefinitions);
206
- stopLoading();
207
- if (response.conversation_id) conversationId = response.conversation_id;
208
- 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;
209
346
  }
210
347
 
211
- const botReply = response.reply || response.message || 'No response';
212
- console.log(chalk.cyan(`${displayBotName} `) + botReply);
213
- console.log();
214
-
215
- addToHistory(bot.id, userMessage, botReply, conversationId);
216
- 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
+ }
217
378
 
218
- } catch (err) {
219
- stopLoading();
220
- const errMsg = err.message.split('"message":"')[1]?.split('"')[0] || err.message;
221
- console.log(chalk.red(`Error: ${errMsg}`));
222
379
  console.log();
223
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
+ }
224
388
  }
225
389
 
226
390
  // ── Input loop ───────────────────────────────────────────────────────────────
227
391
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
228
392
 
229
- process.on('SIGINT', async () => {
393
+ process.on('SIGINT', () => {
230
394
  rl.close();
231
- await showSummary();
395
+ showSummary();
232
396
  console.log(chalk.gray('👋 Goodbye!\n'));
233
397
  process.exit(0);
234
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)