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 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.4.0",
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;
@@ -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
- const endpoint = `${baseUrl}/chat/completions`;
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,