natureco-cli 2.21.2 → 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 +1 -1
- package/src/commands/gateway.js +28 -1
- package/src/utils/headless.js +180 -0
package/package.json
CHANGED
package/src/commands/gateway.js
CHANGED
|
@@ -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 };
|