natureco-cli 4.4.0 → 4.5.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 +8 -0
- package/package.json +1 -1
- package/src/commands/repl.js +333 -0
- package/src/commands/setup.js +7 -0
- package/src/utils/api.js +5 -1
package/bin/natureco.js
CHANGED
|
@@ -41,6 +41,7 @@ const medium = require('../src/commands/medium');
|
|
|
41
41
|
const seo = require('../src/commands/seo');
|
|
42
42
|
const xp = require('../src/commands/xp');
|
|
43
43
|
const team = require('../src/commands/team');
|
|
44
|
+
const repl = require('../src/commands/repl');
|
|
44
45
|
const capability = require('../src/commands/capability');
|
|
45
46
|
const commitments = require('../src/commands/commitments');
|
|
46
47
|
const completion = require('../src/commands/completion');
|
|
@@ -623,6 +624,13 @@ program
|
|
|
623
624
|
team(action ? [action, ...(params || [])] : []);
|
|
624
625
|
});
|
|
625
626
|
|
|
627
|
+
program
|
|
628
|
+
.command('repl [options...]')
|
|
629
|
+
.description('İnteraktif REPL — bizim bu konuşmamız gibi sohbet modu')
|
|
630
|
+
.action((options) => {
|
|
631
|
+
repl(options || []);
|
|
632
|
+
});
|
|
633
|
+
|
|
626
634
|
program
|
|
627
635
|
.command('memory [action] [params...]')
|
|
628
636
|
.description('Manage memory (status|list|search|show|clear|index|export|import|semantic|wiki)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"natureco": "bin/natureco.js"
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* natureco repl — Interactive REPL mode (bizim bu konuşmamız gibi)
|
|
3
|
+
*
|
|
4
|
+
* Kullanım:
|
|
5
|
+
* natureco repl # interaktif sohbet başlat
|
|
6
|
+
* natureco repl --model M2.5 # farklı model
|
|
7
|
+
* natureco repl --no-stream # streaming kapalı
|
|
8
|
+
*
|
|
9
|
+
* Özellikler:
|
|
10
|
+
* - Mesaj yaz → Enter → AI cevap verir
|
|
11
|
+
* - Streaming response (token token gelir)
|
|
12
|
+
* - Mesaj geçmişi (sıra) context olarak gönderilir
|
|
13
|
+
* - Slash komutlar: /help /clear /exit /model /system
|
|
14
|
+
* - Ctrl+C ile temiz çıkış
|
|
15
|
+
* - Konuşma ~/.natureco/repl/<timestamp>.json'a kaydedilir
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const readline = require('readline');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const https = require('https');
|
|
23
|
+
const chalk = require('chalk');
|
|
24
|
+
const tui = require('../utils/tui');
|
|
25
|
+
|
|
26
|
+
const REPL_DIR = path.join(os.homedir(), '.natureco', 'repl');
|
|
27
|
+
|
|
28
|
+
function getConfig() {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8'));
|
|
31
|
+
} catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isMiniMax(url) {
|
|
37
|
+
return url && (url.includes('minimax.io') || url.includes('minimaxi.com') || url.includes('minimax.cn'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function apiRequest(providerUrl, providerApiKey, body, stream = false) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const isMM = isMiniMax(providerUrl);
|
|
43
|
+
const endpoint = isMM
|
|
44
|
+
? `${providerUrl.replace(/\/+$/, '')}/v1/text/chatcompletion_v2`
|
|
45
|
+
: `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
|
|
46
|
+
const req = https.request(endpoint, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: {
|
|
49
|
+
'Authorization': `Bearer ${providerApiKey}`,
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
},
|
|
52
|
+
timeout: 60000,
|
|
53
|
+
}, (res) => {
|
|
54
|
+
if (stream) {
|
|
55
|
+
resolve(res); // Stream response
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let data = '';
|
|
59
|
+
res.on('data', c => data += c);
|
|
60
|
+
res.on('end', () => {
|
|
61
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
62
|
+
try {
|
|
63
|
+
resolve(JSON.parse(data));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
reject(new Error(`Parse hatası: ${e.message}`));
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
req.on('error', reject);
|
|
73
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
74
|
+
req.write(JSON.stringify(body));
|
|
75
|
+
req.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stripAnsi(str) {
|
|
80
|
+
return String(str || '').replace(/\x1b\[[0-9;]*m/g, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function sendStreaming(providerUrl, providerApiKey, messages, model, onChunk) {
|
|
84
|
+
const isMM = isMiniMax(providerUrl);
|
|
85
|
+
const body = {
|
|
86
|
+
model,
|
|
87
|
+
messages,
|
|
88
|
+
stream: !isMM, // MiniMax streaming farklı (bu basit versiyonda non-stream)
|
|
89
|
+
temperature: 0.7,
|
|
90
|
+
max_tokens: 2048,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (!body.stream) {
|
|
94
|
+
// Non-stream fallback (MiniMax için)
|
|
95
|
+
const res = await apiRequest(providerUrl, providerApiKey, body, false);
|
|
96
|
+
const content = res.choices?.[0]?.message?.content || '';
|
|
97
|
+
for (const char of content) {
|
|
98
|
+
onChunk(char);
|
|
99
|
+
await new Promise(r => setTimeout(r, 8)); // Typewriter efekti
|
|
100
|
+
}
|
|
101
|
+
return content;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// OpenAI-compatible streaming
|
|
105
|
+
const endpoint = `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const req = https.request(endpoint, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'Authorization': `Bearer ${providerApiKey}`,
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
timeout: 60000,
|
|
114
|
+
}, (res) => {
|
|
115
|
+
if (res.statusCode !== 200) {
|
|
116
|
+
let data = '';
|
|
117
|
+
res.on('data', c => data += c);
|
|
118
|
+
res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let buffer = '';
|
|
122
|
+
let full = '';
|
|
123
|
+
res.on('data', (chunk) => {
|
|
124
|
+
buffer += chunk.toString('utf8');
|
|
125
|
+
const lines = buffer.split('\n');
|
|
126
|
+
buffer = lines.pop() || '';
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
const trimmed = line.trim();
|
|
129
|
+
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
130
|
+
const data = trimmed.slice(5).trim();
|
|
131
|
+
if (data === '[DONE]') {
|
|
132
|
+
resolve(full);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const parsed = JSON.parse(data);
|
|
137
|
+
const delta = parsed.choices?.[0]?.delta?.content || '';
|
|
138
|
+
if (delta) {
|
|
139
|
+
full += delta;
|
|
140
|
+
onChunk(delta);
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
res.on('end', () => resolve(full));
|
|
146
|
+
});
|
|
147
|
+
req.on('error', reject);
|
|
148
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
149
|
+
req.write(JSON.stringify(body));
|
|
150
|
+
req.end();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function printHelp() {
|
|
155
|
+
console.log(chalk.cyan('\n 📚 REPL Komutları:\n'));
|
|
156
|
+
const cmds = [
|
|
157
|
+
['/help', 'Bu yardım mesajı'],
|
|
158
|
+
['/clear', 'Ekranı temizle'],
|
|
159
|
+
['/history', 'Konuşma geçmişini göster'],
|
|
160
|
+
['/system <text>', 'System prompt ayarla'],
|
|
161
|
+
['/model <name>', 'Model değiştir (örn: /model MiniMax-M3)'],
|
|
162
|
+
['/tokens', 'Token kullanımı'],
|
|
163
|
+
['/save', 'Konuşmayı kaydet'],
|
|
164
|
+
['/exit veya /quit', 'Çıkış (Ctrl+C de çalışır)'],
|
|
165
|
+
];
|
|
166
|
+
for (const [cmd, desc] of cmds) {
|
|
167
|
+
console.log(' ' + chalk.yellow(cmd.padEnd(20)) + chalk.gray(' ' + desc));
|
|
168
|
+
}
|
|
169
|
+
console.log('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function saveRepl(messages, provider, model) {
|
|
173
|
+
if (!fs.existsSync(REPL_DIR)) fs.mkdirSync(REPL_DIR, { recursive: true });
|
|
174
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
175
|
+
const file = path.join(REPL_DIR, `chat-${ts}.json`);
|
|
176
|
+
const data = {
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
provider,
|
|
179
|
+
model,
|
|
180
|
+
messages: messages.filter(m => m.role !== 'system' || !m._internal),
|
|
181
|
+
totalMessages: messages.length,
|
|
182
|
+
};
|
|
183
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
|
184
|
+
return file;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function startRepl(args) {
|
|
188
|
+
const cfg = getConfig();
|
|
189
|
+
let providerUrl = cfg.providerUrl;
|
|
190
|
+
let providerApiKey = cfg.providerApiKey;
|
|
191
|
+
let model = cfg.providerModel;
|
|
192
|
+
|
|
193
|
+
// Arg parse
|
|
194
|
+
for (let i = 0; i < args.length; i++) {
|
|
195
|
+
if (args[i] === '--model' && args[i + 1]) model = args[++i];
|
|
196
|
+
if (args[i] === '--no-stream') global.NO_STREAM = true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!providerUrl || !providerApiKey) {
|
|
200
|
+
console.log(chalk.red('\n ❌ Provider ayarlı değil. Önce: natureco setup\n'));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const messages = [];
|
|
205
|
+
let systemPrompt = 'Sen yardımcı bir AI asistansın. Türkçe konuş, kısa ve net cevap ver. Markdown kullanabilirsin.';
|
|
206
|
+
messages.push({ role: 'system', content: systemPrompt, _internal: true });
|
|
207
|
+
|
|
208
|
+
// Header
|
|
209
|
+
console.log('');
|
|
210
|
+
console.log(tui.styled(' 🌿 NatureCo REPL · İnteraktif Sohbet', { color: tui.PALETTE.primary, bold: true }));
|
|
211
|
+
console.log(tui.styled(' ' + '─'.repeat(50), { color: tui.PALETTE.border }));
|
|
212
|
+
console.log(tui.C.muted(' Provider: ') + tui.C.brand(providerUrl.replace(/https?:\/\//, '')));
|
|
213
|
+
console.log(tui.C.muted(' Model: ') + tui.C.brand(model));
|
|
214
|
+
console.log(tui.C.muted(' Komutlar için ') + tui.C.yellow('/help') + tui.C.muted(' · Çıkış için ') + tui.C.yellow('/exit') + tui.C.muted(' veya Ctrl+C'));
|
|
215
|
+
console.log('');
|
|
216
|
+
|
|
217
|
+
let totalInputTokens = 0;
|
|
218
|
+
let totalOutputTokens = 0;
|
|
219
|
+
|
|
220
|
+
const rl = readline.createInterface({
|
|
221
|
+
input: process.stdin,
|
|
222
|
+
output: process.stdout,
|
|
223
|
+
prompt: tui.styled('\n You ', { color: tui.PALETTE.primary, bold: true }),
|
|
224
|
+
terminal: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
rl.prompt();
|
|
228
|
+
|
|
229
|
+
const cleanup = () => {
|
|
230
|
+
if (messages.length > 1) {
|
|
231
|
+
const file = saveRepl(messages, providerUrl, model);
|
|
232
|
+
console.log(chalk.gray(`\n 💾 Konuşma kaydedildi: ${file}`));
|
|
233
|
+
}
|
|
234
|
+
console.log(chalk.gray('\n 👋 Görüşürüz!\n'));
|
|
235
|
+
process.exit(0);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
rl.on('SIGINT', cleanup);
|
|
239
|
+
process.on('SIGINT', cleanup);
|
|
240
|
+
process.on('SIGTERM', cleanup);
|
|
241
|
+
|
|
242
|
+
rl.on('line', async (input) => {
|
|
243
|
+
const line = input.trim();
|
|
244
|
+
if (!line) {
|
|
245
|
+
rl.prompt();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Slash komutlar
|
|
250
|
+
if (line.startsWith('/')) {
|
|
251
|
+
const [cmd, ...rest] = line.slice(1).split(/\s+/);
|
|
252
|
+
const arg = rest.join(' ');
|
|
253
|
+
|
|
254
|
+
switch (cmd) {
|
|
255
|
+
case 'help':
|
|
256
|
+
printHelp();
|
|
257
|
+
break;
|
|
258
|
+
case 'clear':
|
|
259
|
+
console.clear();
|
|
260
|
+
break;
|
|
261
|
+
case 'exit':
|
|
262
|
+
case 'quit':
|
|
263
|
+
case 'q':
|
|
264
|
+
cleanup();
|
|
265
|
+
return;
|
|
266
|
+
case 'history':
|
|
267
|
+
console.log(chalk.cyan('\n 📜 Konuşma Geçmişi:\n'));
|
|
268
|
+
for (const m of messages.filter(m => !m._internal)) {
|
|
269
|
+
const role = m.role === 'user' ? chalk.green('You') : chalk.blue('AI ');
|
|
270
|
+
const content = m.content.slice(0, 100) + (m.content.length > 100 ? '...' : '');
|
|
271
|
+
console.log(` ${role} ${content}`);
|
|
272
|
+
}
|
|
273
|
+
console.log('');
|
|
274
|
+
break;
|
|
275
|
+
case 'system':
|
|
276
|
+
if (!arg) {
|
|
277
|
+
console.log(chalk.yellow(' Kullanım: /system <text>'));
|
|
278
|
+
} else {
|
|
279
|
+
systemPrompt = arg;
|
|
280
|
+
messages[0] = { role: 'system', content: systemPrompt, _internal: true };
|
|
281
|
+
console.log(chalk.green(' ✓ System prompt güncellendi'));
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
case 'model':
|
|
285
|
+
if (!arg) {
|
|
286
|
+
console.log(chalk.yellow(' Kullanım: /model <name>'));
|
|
287
|
+
} else {
|
|
288
|
+
model = arg;
|
|
289
|
+
console.log(chalk.green(' ✓ Model: ') + chalk.cyan(model));
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case 'tokens':
|
|
293
|
+
console.log(chalk.gray(` Token: ~${totalInputTokens} in / ~${totalOutputTokens} out`));
|
|
294
|
+
break;
|
|
295
|
+
case 'save':
|
|
296
|
+
const f = saveRepl(messages, providerUrl, model);
|
|
297
|
+
console.log(chalk.green(' ✓ Kaydedildi: ') + chalk.cyan(f));
|
|
298
|
+
break;
|
|
299
|
+
default:
|
|
300
|
+
console.log(chalk.yellow(` Bilinmeyen komut: /${cmd}. /help yazın.`));
|
|
301
|
+
}
|
|
302
|
+
rl.prompt();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// User mesajı ekle
|
|
307
|
+
messages.push({ role: 'user', content: line });
|
|
308
|
+
|
|
309
|
+
// AI cevabı al (streaming)
|
|
310
|
+
process.stdout.write(tui.styled('\n AI ', { color: tui.PALETTE.secondary, bold: true }));
|
|
311
|
+
try {
|
|
312
|
+
const apiMessages = messages.filter(m => !m._internal);
|
|
313
|
+
const reply = await sendStreaming(providerUrl, providerApiKey, apiMessages, model, (chunk) => {
|
|
314
|
+
process.stdout.write(chunk);
|
|
315
|
+
});
|
|
316
|
+
process.stdout.write('\n');
|
|
317
|
+
// Reply'i geçmişe ekle
|
|
318
|
+
messages.push({ role: 'assistant', content: reply });
|
|
319
|
+
// Token tahmini (basit: 4 char ≈ 1 token)
|
|
320
|
+
totalInputTokens += apiMessages.reduce((s, m) => s + Math.ceil((m.content || '').length / 4), 0);
|
|
321
|
+
totalOutputTokens += Math.ceil((reply || '').length / 4);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
process.stdout.write('\n');
|
|
324
|
+
console.log(chalk.red(' ❌ Hata: ' + err.message));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
rl.prompt();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
rl.on('close', cleanup);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = startRepl;
|
package/src/commands/setup.js
CHANGED
|
@@ -18,6 +18,13 @@ const PROVIDER_PRESETS = {
|
|
|
18
18
|
anthropic:{ url: 'https://api.anthropic.com', model: 'claude-sonnet-4-6' },
|
|
19
19
|
deepseek: { url: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
20
20
|
ollama: { url: 'http://localhost:11434/v1', model: 'llama3.3' },
|
|
21
|
+
// MiniMax — özel endpoint gerekiyor (/v1/text/chatcompletion_v2)
|
|
22
|
+
// OpenAI uyumlu DEĞİL — llm_task ve api.js bunu tespit edip yönlendiriyor
|
|
23
|
+
minimax: { url: 'https://api.minimax.io', model: 'MiniMax-M2.5' },
|
|
24
|
+
// OpenRouter — 100+ model, tek key ile hepsine erişim
|
|
25
|
+
openrouter: { url: 'https://openrouter.ai/api/v1', model: 'meta-llama/llama-3.3-70b-instruct:free' },
|
|
26
|
+
// Together AI
|
|
27
|
+
together: { url: 'https://api.together.xyz/v1', model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo' },
|
|
21
28
|
};
|
|
22
29
|
|
|
23
30
|
function rlQuestion(query) {
|
package/src/utils/api.js
CHANGED
|
@@ -888,7 +888,11 @@ async function streamProviderCompletion(providerConfig, messages, tools) {
|
|
|
888
888
|
|
|
889
889
|
async function streamOpenAICompletion(providerConfig, messages, tools) {
|
|
890
890
|
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
891
|
-
|
|
891
|
+
// MiniMax özel endpoint tespiti (streaming için de aynı)
|
|
892
|
+
const isMiniMax = baseUrl.includes('minimax.io') || baseUrl.includes('minimaxi.com') || baseUrl.includes('minimax.cn');
|
|
893
|
+
const endpoint = isMiniMax
|
|
894
|
+
? `${baseUrl}/v1/text/chatcompletion_v2`
|
|
895
|
+
: `${baseUrl}/chat/completions`;
|
|
892
896
|
|
|
893
897
|
const requestBody = {
|
|
894
898
|
model: providerConfig.model,
|