natureco-cli 2.21.1 → 2.22.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.21.1",
3
+ "version": "2.22.0",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -729,9 +729,9 @@ ${indexPrompt}`;
729
729
  // ── Input loop ───────────────────────────────────────────────────────────────
730
730
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
731
731
 
732
- process.on('SIGINT', async () => {
732
+ rl.on('SIGINT', async () => {
733
733
  console.log('\n');
734
- if (rl) rl.pause();
734
+ rl.pause();
735
735
  if (conversationMessages.filter(m => m.role === 'user').length > 0) {
736
736
  try {
737
737
  const { save } = await inquirer.prompt([{
@@ -196,7 +196,34 @@ async function startWhatsAppProvider(sessionDir, config) {
196
196
  if (messageText) {
197
197
  const ownNumber = sock.user?.id?.split(':')[0].replace('@s.whatsapp.net', '') || 'unknown';
198
198
  console.log(chalk.cyan('[whatsapp]'), chalk.white(`Inbound message +${sender} -> +${ownNumber}`));
199
-
199
+
200
+ // ── !code komutu — headless code agent ─────────────────────────────
201
+ if (messageText.startsWith('!code ')) {
202
+ const task = messageText.replace('!code ', '').trim();
203
+ console.log(chalk.cyan('[whatsapp]'), chalk.yellow(`!code komutu: ${task.slice(0, 60)}`));
204
+
205
+ try {
206
+ const { runCodeAgent } = require('../utils/headless');
207
+ await sock.sendMessage(msg.key.remoteJid, { text: `⚙️ Çalışıyor: ${task.slice(0, 80)}...` });
208
+
209
+ const result = await runCodeAgent(task, process.cwd(), (progress) => {
210
+ console.log(chalk.cyan('[whatsapp/code]'), chalk.gray(progress));
211
+ });
212
+
213
+ let reply = `✅ Tamamlandı!\n\n${result.reply}`;
214
+ if (result.filesChanged?.length) {
215
+ reply += `\n\n📝 Değiştirilen dosyalar:\n${result.filesChanged.map(f => `• ${f}`).join('\n')}`;
216
+ }
217
+
218
+ await sock.sendMessage(msg.key.remoteJid, { text: reply.slice(0, 4000) });
219
+ console.log(chalk.cyan('[whatsapp]'), chalk.green(`!code tamamlandı (${result.iterations} iterasyon)`));
220
+ } catch (err) {
221
+ console.log(chalk.red('[whatsapp/code]'), chalk.gray(`hata: ${err.message}`));
222
+ await sock.sendMessage(msg.key.remoteJid, { text: `❌ Hata: ${err.message}` });
223
+ }
224
+ continue;
225
+ }
226
+
200
227
  try {
201
228
  const { sendMessage } = require('../utils/api');
202
229
  console.log(chalk.cyan('[whatsapp]'), chalk.gray('Sending reply...'));
@@ -0,0 +1,180 @@
1
+ /**
2
+ * headless.js — Code agent'ı terminal olmadan çalıştır
3
+ * WhatsApp, API ve diğer entegrasyonlardan çağrılabilir
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { getProviderConfig } = require('./config');
9
+ const { getToolDefinitions, executeTool } = require('./tool-runner');
10
+
11
+ // ── Proje indexing (code.js'den paylaşılan) ───────────────────────────────────
12
+ const IGNORE_DIRS = new Set([
13
+ 'node_modules', '.git', 'dist', 'build', '.next',
14
+ '__pycache__', '.venv', 'venv', 'target', '.wrangler',
15
+ ]);
16
+
17
+ function scanDir(dir, maxDepth, depth = 0) {
18
+ const results = [];
19
+ if (depth > maxDepth) return results;
20
+ let entries;
21
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return results; }
22
+ for (const entry of entries) {
23
+ if (entry.name.startsWith('.') && depth > 0) continue;
24
+ if (IGNORE_DIRS.has(entry.name)) continue;
25
+ if (entry.isDirectory()) {
26
+ const sub = scanDir(path.join(dir, entry.name), maxDepth, depth + 1);
27
+ results.push(...sub.map(f => entry.name + '/' + f));
28
+ } else {
29
+ results.push(entry.name);
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+
35
+ async function indexProject(projectDir) {
36
+ const files = scanDir(projectDir, 2);
37
+ const fileSet = new Set(files);
38
+ let type = 'unknown';
39
+ let packageInfo = null;
40
+
41
+ if (fileSet.has('package.json')) {
42
+ try {
43
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf8'));
44
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
45
+ packageInfo = { name: pkg.name, version: pkg.version, scripts: pkg.scripts || {} };
46
+ if (deps.react) type = 'react';
47
+ else if (deps.next) type = 'nextjs';
48
+ else if (deps.express || deps.fastify) type = 'node-server';
49
+ else type = 'node';
50
+ } catch {}
51
+ } else if (files.some(f => f.endsWith('.py'))) {
52
+ type = 'python';
53
+ } else if (fileSet.has('Cargo.toml')) {
54
+ type = 'rust';
55
+ }
56
+
57
+ return { dir: projectDir, files, type, packageInfo };
58
+ }
59
+
60
+ // ── Provider API çağrısı (non-streaming) ─────────────────────────────────────
61
+ async function callProvider(providerConfig, messages, tools = []) {
62
+ const body = {
63
+ model: providerConfig.model,
64
+ messages,
65
+ temperature: 0.7,
66
+ max_tokens: 2000,
67
+ stream: false,
68
+ };
69
+ if (tools.length) {
70
+ body.tools = tools;
71
+ body.tool_choice = 'auto';
72
+ }
73
+
74
+ const res = await fetch(`${providerConfig.url}/chat/completions`, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Authorization': `Bearer ${providerConfig.apiKey}`,
78
+ 'Content-Type': 'application/json',
79
+ },
80
+ body: JSON.stringify(body),
81
+ });
82
+
83
+ if (!res.ok) {
84
+ const err = await res.text();
85
+ throw new Error(`Provider error: ${res.status} — ${err}`);
86
+ }
87
+
88
+ const data = await res.json();
89
+ const msg = data.choices?.[0]?.message;
90
+ return {
91
+ content: msg?.content || '',
92
+ tool_calls: (msg?.tool_calls || []).map(tc => ({
93
+ id: tc.id,
94
+ name: tc.function.name,
95
+ input: (() => { try { return JSON.parse(tc.function.arguments); } catch { return {}; } })(),
96
+ })),
97
+ };
98
+ }
99
+
100
+ // ── Headless code agent ───────────────────────────────────────────────────────
101
+ async function runCodeAgent(task, projectDir = process.cwd(), onProgress = null) {
102
+ const providerConfig = getProviderConfig();
103
+ if (!providerConfig) throw new Error('Provider yapılandırılmamış.');
104
+
105
+ const projectIndex = await indexProject(projectDir);
106
+
107
+ const systemPrompt = `Sen bir code agent'sın. Görevi sessizce tamamla ve kısa özet ver.
108
+ Proje tipi: ${projectIndex.type}
109
+ Proje dizini: ${projectDir}
110
+ Dosyalar: ${projectIndex.files.slice(0, 25).join(', ')}`;
111
+
112
+ const localTools = getToolDefinitions();
113
+ const tools = localTools.map(t => ({
114
+ type: 'function',
115
+ function: {
116
+ name: t.name,
117
+ description: t.description,
118
+ parameters: t.inputSchema || { type: 'object', properties: {} },
119
+ },
120
+ }));
121
+
122
+ const messages = [
123
+ { role: 'system', content: systemPrompt },
124
+ { role: 'user', content: task },
125
+ ];
126
+
127
+ const filesChanged = [];
128
+ let iteration = 0;
129
+
130
+ while (iteration < 10) {
131
+ iteration++;
132
+
133
+ const response = await callProvider(providerConfig, messages, tools);
134
+
135
+ const assistantMsg = { role: 'assistant', content: response.content };
136
+ if (response.tool_calls?.length) {
137
+ assistantMsg.tool_calls = response.tool_calls.map(tc => ({
138
+ id: tc.id || `call_${Date.now()}`,
139
+ type: 'function',
140
+ function: { name: tc.name, arguments: JSON.stringify(tc.input) },
141
+ }));
142
+ }
143
+ messages.push(assistantMsg);
144
+
145
+ if (!response.tool_calls?.length) {
146
+ // Final cevap
147
+ return {
148
+ reply: response.content || 'Görev tamamlandı.',
149
+ filesChanged,
150
+ iterations: iteration,
151
+ };
152
+ }
153
+
154
+ // Tool'ları çalıştır
155
+ for (const toolCall of response.tool_calls) {
156
+ if (onProgress) onProgress(`🔧 ${toolCall.name}`);
157
+
158
+ const result = await executeTool(toolCall.name, toolCall.input);
159
+
160
+ if (toolCall.name === 'write_file' && result.success !== false) {
161
+ filesChanged.push(toolCall.input.path || '?');
162
+ }
163
+
164
+ const resultStr = result.success !== false
165
+ ? (result.output || JSON.stringify(result))
166
+ : `Hata: ${result.error}`;
167
+
168
+ messages.push({
169
+ role: 'tool',
170
+ tool_call_id: assistantMsg.tool_calls?.find(tc => tc.function.name === toolCall.name)?.id || toolCall.id,
171
+ name: toolCall.name,
172
+ content: resultStr.slice(0, 3000),
173
+ });
174
+ }
175
+ }
176
+
177
+ return { reply: 'Görev tamamlandı (max iterasyon).', filesChanged, iterations: iteration };
178
+ }
179
+
180
+ module.exports = { runCodeAgent, indexProject };