natureco-cli 2.17.10 → 2.18.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/bin/natureco.js CHANGED
@@ -53,6 +53,14 @@ program
53
53
  chat(botName, options);
54
54
  });
55
55
 
56
+ program
57
+ .command('code [file]')
58
+ .description('Agentic coding modu — dosya oku, değiştir, komut çalıştır')
59
+ .action((file) => {
60
+ const codeCmd = require('../src/commands/code');
61
+ codeCmd(file);
62
+ });
63
+
56
64
  program
57
65
  .command('init')
58
66
  .description('Initialize NatureCo project in current directory')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.17.10",
3
+ "version": "2.18.0",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@ const { getMemoryPrompt, extractMemoryFromMessage, loadMemory, clearMemory, addM
13
13
  const { getCommands, getCommandContent } = require('../utils/commands');
14
14
  const { runHooks } = require('../utils/hooks');
15
15
  const { createSession, loadSession, getLatestSession, addMessageToSession } = require('../utils/sessions');
16
- const { getToolDefinitions, executeToolCalls } = require('../utils/tool-runner');
16
+ const { getToolDefinitions, executeToolCalls, getSessionStats, resetSessionStats } = require('../utils/tool-runner');
17
17
  const { extractToolCalls } = require('../utils/tool-adapter');
18
18
 
19
19
  // ── ASCII Logo ────────────────────────────────────────────────────────────────
@@ -127,6 +127,8 @@ async function chat(botName, options = {}) {
127
127
  }
128
128
 
129
129
  let conversationId = null;
130
+ let messagesCount = 0;
131
+ resetSessionStats();
130
132
 
131
133
  // ── What's New kontrolü ─────────────────────────────────────────────────────
132
134
  const lastVersionFile = path.join(os.homedir(), '.natureco', 'lastVersion');
@@ -138,22 +140,22 @@ async function chat(botName, options = {}) {
138
140
  console.clear();
139
141
  console.log(centerText(ASCII_LOGO.map((line, i) => i < 5 ? chalk.green(line) : chalk.gray(line)).join('\n')));
140
142
  console.log();
141
- console.log(centerText(chalk.cyan(`(\\_/) `) + chalk.white(`Hoş geldin, ${userName}`) + chalk.gray(' · ') + chalk.cyan(`${displayBotName} hazır`) + chalk.gray(` · v${version}`)));
142
- console.log(sep());
143
+ console.log(centerText(chalk.cyan(`(\\_/) Hoş geldin, ${userName} · ${displayBotName} hazır · v${version}`)));
144
+ console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
143
145
  console.log();
144
146
 
145
147
  // ── What's New ──────────────────────────────────────────────────────────────
146
148
  if (isNewVersion) {
147
- console.log(chalk.yellow(`── v${version} yenilikleri ──`));
148
- CHANGELOG.forEach(c => console.log(chalk.gray(` · ${c}`)));
149
+ console.log(centerText(chalk.yellow(`── v${version} yenilikleri ──`)));
150
+ CHANGELOG.forEach(c => console.log(centerText(chalk.gray( ${c}`))));
149
151
  console.log();
150
152
  try { fs.writeFileSync(lastVersionFile, version); } catch {}
151
153
  } else {
152
154
  // Yeni versiyon yoksa günlük tip göster
153
155
  const dayIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % TIPS.length;
154
- console.log(sep());
155
- console.log(' ' + chalk.yellow(TIPS[dayIndex]));
156
- console.log(sep());
156
+ console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
157
+ console.log(centerText(chalk.yellow(TIPS[dayIndex])));
158
+ console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
157
159
  console.log();
158
160
  }
159
161
 
@@ -167,8 +169,8 @@ async function chat(botName, options = {}) {
167
169
  });
168
170
  }
169
171
 
170
- console.log(chalk.gray(` ${shortModel} · /help için yardım · Ctrl+C çıkış`));
171
- console.log(sep());
172
+ console.log(centerText(chalk.gray(`${shortModel} · /help için yardım · Ctrl+C çıkış`)));
173
+ console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
172
174
  console.log();
173
175
 
174
176
  // ── Yükleme animasyonu ──────────────────────────────────────────────────────
@@ -299,6 +301,7 @@ async function chat(botName, options = {}) {
299
301
  // Normal mesaj
300
302
  userMessage = await runHooks('pre-message', userMessage, { botId: bot.id, botName: bot.name });
301
303
  console.log(chalk.white('You ') + userMessage);
304
+ messagesCount++;
302
305
 
303
306
  startLoading();
304
307
 
@@ -350,7 +353,15 @@ async function chat(botName, options = {}) {
350
353
  process.on('SIGINT', async () => {
351
354
  await runHooks('on-exit', null, { botId: bot.id, botName: bot.name });
352
355
  rl.close();
353
- console.log(chalk.gray('\n👋 Goodbye!\n'));
356
+ const { filesChanged, commandsRun } = getSessionStats();
357
+ if (filesChanged > 0 || commandsRun > 0 || messagesCount > 0) {
358
+ console.log(chalk.gray('\n─── Session Özeti ───'));
359
+ if (filesChanged > 0) console.log(chalk.green(` ✓ ${filesChanged} dosya değiştirildi`));
360
+ if (commandsRun > 0) console.log(chalk.green(` ✓ ${commandsRun} komut çalıştırıldı`));
361
+ console.log(chalk.cyan(` ✓ ${messagesCount} mesaj gönderildi`));
362
+ console.log();
363
+ }
364
+ console.log(chalk.gray('👋 Goodbye!\n'));
354
365
  process.exit(0);
355
366
  });
356
367
 
@@ -0,0 +1,236 @@
1
+ const path = require('path');
2
+ const os = require('os');
3
+ const fs = require('fs');
4
+ const readline = require('readline');
5
+ const inquirer = require('inquirer');
6
+ const chalk = require('chalk');
7
+ const { getApiKey, getConfig } = require('../utils/config');
8
+ const { getBots, _sendMessage } = require('../utils/api');
9
+ const { getMemoryPrompt, loadMemory } = require('../utils/memory');
10
+ const { getAgentsPrompt } = require('../utils/agents');
11
+ const { createSession, addMessageToSession } = require('../utils/sessions');
12
+ const { addToHistory } = require('../utils/history');
13
+ const { getToolDefinitions, executeToolCalls, getSessionStats, resetSessionStats } = require('../utils/tool-runner');
14
+ const { extractToolCalls } = require('../utils/tool-adapter');
15
+
16
+ const sep = () => chalk.gray('─'.repeat(process.stdout.columns || 120));
17
+
18
+ function centerText(text) {
19
+ const width = process.stdout.columns || 120;
20
+ return text.split('\n').map(line => {
21
+ const padding = Math.max(0, Math.floor((width - line.length) / 2));
22
+ return ' '.repeat(padding) + line;
23
+ }).join('\n');
24
+ }
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()}`;
30
+
31
+ async function code(targetFile, options = {}) {
32
+ const apiKey = getApiKey();
33
+ const config = getConfig();
34
+ const version = require('../../package.json').version;
35
+
36
+ // ── Bot seçimi ──────────────────────────────────────────────────────────────
37
+ let botList;
38
+ try {
39
+ botList = await (require('../utils/api').getBots)(apiKey || config.providerApiKey || '');
40
+ } catch (err) {
41
+ console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
42
+ process.exit(1);
43
+ }
44
+
45
+ if (!botList?.bots?.length) {
46
+ console.log(chalk.gray('No bots found. Create one at https://developers.natureco.me\n'));
47
+ process.exit(1);
48
+ }
49
+
50
+ let bot;
51
+ if (config.botName) {
52
+ bot = botList.bots.find(b => b.name && b.name.toLowerCase() === config.botName.toLowerCase());
53
+ }
54
+ if (!bot) {
55
+ process.stdin.resume();
56
+ const { selectedBot } = await inquirer.prompt([{
57
+ type: 'list',
58
+ name: 'selectedBot',
59
+ message: 'Bot seçin:',
60
+ choices: botList.bots.map(b => ({ name: b.name, value: b.id })),
61
+ }]);
62
+ bot = botList.bots.find(b => b.id === selectedBot);
63
+ }
64
+
65
+ const mem = loadMemory(bot.id);
66
+ const displayBotName = mem.botName || bot.name || 'NatureCo';
67
+ const userName = mem.name || config.userName || 'User';
68
+ const providerModel = config.providerModel || 'unknown';
69
+ const shortModel = providerModel.split('/').pop().split('-').slice(0, 3).join('-');
70
+
71
+ const agentsPrompt = getAgentsPrompt();
72
+ const memoryPrompt = getMemoryPrompt(bot.id);
73
+
74
+ let systemPrompt = CODE_SYSTEM_PROMPT;
75
+ if (agentsPrompt) systemPrompt += `\n\n## Project Instructions\n${agentsPrompt}`;
76
+ if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
77
+
78
+ // Hedef dosya varsa context'e ekle
79
+ if (targetFile) {
80
+ try {
81
+ const content = fs.readFileSync(path.resolve(targetFile), 'utf-8');
82
+ systemPrompt += `\n\n## Odak Dosyası: ${targetFile}\n\`\`\`\n${content}\n\`\`\``;
83
+ } catch {
84
+ console.log(chalk.yellow(` ⚠️ ${targetFile} okunamadı, devam ediliyor...\n`));
85
+ }
86
+ }
87
+
88
+ const session = createSession(bot.id, bot.name);
89
+ let conversationId = null;
90
+ let messagesCount = 0;
91
+ resetSessionStats();
92
+
93
+ // ── Header ──────────────────────────────────────────────────────────────────
94
+ console.clear();
95
+ console.log(centerText(chalk.green('⚡ NatureCo Code') + chalk.gray(' · ') + chalk.cyan(`${displayBotName}`) + chalk.gray(` · v${version}`)));
96
+ 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ış`)));
102
+ console.log(sep());
103
+ console.log();
104
+
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
+ // ── 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();
132
+ }
133
+
134
+ // ── Mesaj gönderme ──────────────────────────────────────────────────────────
135
+ async function handleMessage(userMessage) {
136
+ userMessage = userMessage.trim();
137
+ if (!userMessage) return;
138
+
139
+ if (userMessage.startsWith('/')) {
140
+ const [cmd] = userMessage.slice(1).split(' ');
141
+ switch (cmd.toLowerCase()) {
142
+ case 'help':
143
+ console.log(chalk.yellow('Code Komutları:'));
144
+ [
145
+ ['/clear', 'Ekranı temizle'],
146
+ ['/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ış'));
150
+ console.log();
151
+ return;
152
+ case 'clear':
153
+ console.clear();
154
+ return;
155
+ case 'summary':
156
+ await showSummary();
157
+ return;
158
+ default:
159
+ console.log(chalk.red(`Bilinmeyen komut: /${cmd}`));
160
+ console.log();
161
+ return;
162
+ }
163
+ }
164
+
165
+ if (userMessage === 'exit' || userMessage === 'quit') {
166
+ await showSummary();
167
+ process.exit(0);
168
+ }
169
+
170
+ messagesCount++;
171
+ console.log(chalk.white('You ') + userMessage);
172
+ startLoading();
173
+
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 — agentMode: true ile onay + spinner
182
+ let iter = 0;
183
+ while (iter < 10) {
184
+ const toolCalls = extractToolCalls(response);
185
+ if (!toolCalls?.length) break;
186
+ console.log(chalk.yellow(`\n🔧 ${toolCalls.length} tool çalıştırılıyor...\n`));
187
+ const toolResults = await executeToolCalls(toolCalls, { agentMode: true });
188
+ const toolMsg = toolResults.map(tr =>
189
+ `Tool: ${tr.name}\nResult: ${tr.result.success ? (tr.result.output || JSON.stringify(tr.result)) : tr.result.error}`
190
+ ).join('\n\n');
191
+ startLoading();
192
+ response = await _sendMessage(apiKey || config.providerApiKey, bot.id, toolMsg, conversationId, systemPrompt, toolDefinitions);
193
+ stopLoading();
194
+ if (response.conversation_id) conversationId = response.conversation_id;
195
+ iter++;
196
+ }
197
+
198
+ const botReply = response.reply || response.message || 'No response';
199
+ console.log(chalk.cyan(`${displayBotName} `) + botReply);
200
+ console.log();
201
+
202
+ addToHistory(bot.id, userMessage, botReply, conversationId);
203
+ addMessageToSession(bot.id, session.id, userMessage, botReply);
204
+
205
+ } catch (err) {
206
+ stopLoading();
207
+ const errMsg = err.message.split('"message":"')[1]?.split('"')[0] || err.message;
208
+ console.log(chalk.red(`Error: ${errMsg}`));
209
+ console.log();
210
+ }
211
+ }
212
+
213
+ // ── Input loop ───────────────────────────────────────────────────────────────
214
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
215
+
216
+ process.on('SIGINT', async () => {
217
+ rl.close();
218
+ await showSummary();
219
+ console.log(chalk.gray('👋 Goodbye!\n'));
220
+ process.exit(0);
221
+ });
222
+
223
+ rl.on('close', () => process.exit(0));
224
+
225
+ async function promptLoop() {
226
+ rl.question('', async (msg) => {
227
+ process.stdout.write('\x1b[1A\x1b[2K');
228
+ await handleMessage(msg);
229
+ promptLoop();
230
+ });
231
+ }
232
+
233
+ promptLoop();
234
+ }
235
+
236
+ module.exports = code;
@@ -1,37 +1,78 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
+ const inquirer = require('inquirer');
4
5
 
5
- /**
6
- * Tool Runner - Execute tools locally
7
- */
6
+ // ── Spinner ───────────────────────────────────────────────────────────────────
7
+ const SPINNER_FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
8
8
 
9
- // Load all tools from src/tools/
9
+ function startSpinner(label) {
10
+ let i = 0;
11
+ const timer = setInterval(() => {
12
+ process.stdout.write(`\r${chalk.cyan(SPINNER_FRAMES[i++ % SPINNER_FRAMES.length])} ${chalk.gray(label)}`);
13
+ }, 80);
14
+ return timer;
15
+ }
16
+
17
+ function stopSpinner(timer, label, success = true) {
18
+ clearInterval(timer);
19
+ if (success) {
20
+ process.stdout.write(`\r${chalk.green('✓')} ${chalk.gray(label)}\n`);
21
+ } else {
22
+ process.stdout.write(`\r${chalk.red('✗')} ${chalk.gray(label)}\n`);
23
+ }
24
+ }
25
+
26
+ // ── Diff view ─────────────────────────────────────────────────────────────────
27
+ function showDiff(oldContent, newContent, filepath) {
28
+ const oldLines = (oldContent || '').split('\n');
29
+ const newLines = newContent.split('\n');
30
+ console.log(chalk.gray(`\n 📄 ${filepath}`));
31
+ newLines.forEach(line => {
32
+ if (line && !oldLines.includes(line)) {
33
+ console.log(chalk.green(' + ' + line));
34
+ }
35
+ });
36
+ oldLines.forEach(line => {
37
+ if (line && !newLines.includes(line)) {
38
+ console.log(chalk.red(' - ' + line));
39
+ }
40
+ });
41
+ console.log();
42
+ }
43
+
44
+ // ── Session stats (module-level counters) ─────────────────────────────────────
45
+ let filesChanged = 0;
46
+ let commandsRun = 0;
47
+
48
+ function getSessionStats() {
49
+ return { filesChanged, commandsRun };
50
+ }
51
+
52
+ function resetSessionStats() {
53
+ filesChanged = 0;
54
+ commandsRun = 0;
55
+ }
56
+
57
+ // ── Load tools ────────────────────────────────────────────────────────────────
10
58
  function loadTools() {
11
59
  const toolsDir = path.join(__dirname, '..', 'tools');
12
60
  const tools = {};
13
-
14
- if (!fs.existsSync(toolsDir)) {
15
- return tools;
16
- }
17
-
61
+
62
+ if (!fs.existsSync(toolsDir)) return tools;
63
+
18
64
  const files = fs.readdirSync(toolsDir).filter(f => f.endsWith('.js'));
19
-
20
65
  for (const file of files) {
21
66
  try {
22
67
  const tool = require(path.join(toolsDir, file));
23
- if (tool.name && tool.execute) {
24
- tools[tool.name] = tool;
25
- }
68
+ if (tool.name && tool.execute) tools[tool.name] = tool;
26
69
  } catch (err) {
27
70
  console.error(chalk.red(`Failed to load tool ${file}:`, err.message));
28
71
  }
29
72
  }
30
-
31
73
  return tools;
32
74
  }
33
75
 
34
- // Get tool definitions for API
35
76
  function getToolDefinitions() {
36
77
  const tools = loadTools();
37
78
  return Object.values(tools).map(tool => ({
@@ -41,47 +82,74 @@ function getToolDefinitions() {
41
82
  }));
42
83
  }
43
84
 
44
- // Execute a single tool
45
- async function executeTool(toolName, params) {
85
+ // ── Execute a single tool ─────────────────────────────────────────────────────
86
+ async function executeTool(toolName, params, opts = {}) {
46
87
  const tools = loadTools();
47
88
  const tool = tools[toolName];
48
-
89
+ const agentMode = opts.agentMode || false;
90
+
49
91
  if (!tool) {
50
- return {
51
- success: false,
52
- error: `Tool '${toolName}' not found`
53
- };
92
+ return { success: false, error: `Tool '${toolName}' not found` };
93
+ }
94
+
95
+ const label = `${toolName}${params.path ? ' — ' + params.path : params.command ? ' — ' + params.command : ''}`;
96
+
97
+ // ── Onay mekanizması (write_file ve tehlikeli bash) ───────────────────────
98
+ if (agentMode) {
99
+ const needsConfirm =
100
+ toolName === 'write_file' ||
101
+ (toolName === 'bash' && /\b(rm|mv|cp|chmod|chown|dd|mkfs|truncate)\b/.test(params.command || ''));
102
+
103
+ if (needsConfirm) {
104
+ if (toolName === 'write_file') {
105
+ // Diff göster
106
+ let oldContent = '';
107
+ try { oldContent = fs.readFileSync(path.resolve(params.path), 'utf-8'); } catch {}
108
+ showDiff(oldContent, params.content || '', params.path);
109
+ } else {
110
+ console.log(chalk.yellow(`\n 🖥️ Komut: ${chalk.white(params.command)}\n`));
111
+ }
112
+
113
+ const { confirm } = await inquirer.prompt([{
114
+ type: 'confirm',
115
+ name: 'confirm',
116
+ message: chalk.yellow(` ${toolName === 'write_file' ? `✏️ ${params.path} dosyası değiştirilecek` : '⚠️ Bu komut çalıştırılacak'}. Onaylıyor musun?`),
117
+ default: true,
118
+ }]);
119
+
120
+ if (!confirm) {
121
+ console.log(chalk.gray(' İptal edildi.\n'));
122
+ return { success: false, error: 'Kullanıcı iptal etti.' };
123
+ }
124
+ }
54
125
  }
55
-
56
- console.log(chalk.blue(`\n🔧 Executing tool: ${toolName}`));
57
- console.log(chalk.gray(` Params: ${JSON.stringify(params)}`));
58
-
126
+
127
+ // ── Spinner ile çalıştır ──────────────────────────────────────────────────
128
+ const spinner = startSpinner(label);
59
129
  try {
60
130
  const result = await tool.execute(params);
61
- console.log(chalk.green(` ✓ Success`));
131
+ stopSpinner(spinner, label, result.success !== false);
132
+
133
+ // İstatistik güncelle
134
+ if (result.success !== false) {
135
+ if (toolName === 'write_file') filesChanged++;
136
+ if (toolName === 'bash') commandsRun++;
137
+ }
138
+
62
139
  return result;
63
140
  } catch (error) {
64
- console.log(chalk.red(` ✗ Error: ${error.message}`));
65
- return {
66
- success: false,
67
- error: error.message
68
- };
141
+ stopSpinner(spinner, label, false);
142
+ return { success: false, error: error.message };
69
143
  }
70
144
  }
71
145
 
72
- // Execute multiple tool calls
73
- async function executeToolCalls(toolCalls) {
146
+ // ── Execute multiple tool calls ───────────────────────────────────────────────
147
+ async function executeToolCalls(toolCalls, opts = {}) {
74
148
  const results = [];
75
-
76
149
  for (const call of toolCalls) {
77
- const result = await executeTool(call.name, call.input);
78
- results.push({
79
- id: call.id,
80
- name: call.name,
81
- result
82
- });
150
+ const result = await executeTool(call.name, call.input, opts);
151
+ results.push({ id: call.id, name: call.name, result });
83
152
  }
84
-
85
153
  return results;
86
154
  }
87
155
 
@@ -89,5 +157,7 @@ module.exports = {
89
157
  loadTools,
90
158
  getToolDefinitions,
91
159
  executeTool,
92
- executeToolCalls
160
+ executeToolCalls,
161
+ getSessionStats,
162
+ resetSessionStats,
93
163
  };