natureco-cli 4.4.1 → 4.5.1

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.1",
3
+ "version": "4.5.1",
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"
@@ -238,16 +238,33 @@ function runCheck(name) {
238
238
 
239
239
  case 'secretsClean': {
240
240
  try {
241
- // Mevcut çalışma dizinini tara — secret var mı?
242
- const findings = secrets.scanDir(process.cwd());
243
- const critical = findings.filter(f => f.severity === 'critical' || f.severity === 'high');
244
- if (critical.length === 0) {
245
- return { pass: true, message: 'Çalışma dizininde secret bulunamadı ✓' };
241
+ // Mevcut çalışma dizinini tara — sadece kritik bulguları rapor et
242
+ // Whitelist: .git, node_modules, .DS_Store, dist, build, *.md (dokümanlar),
243
+ // *.example, *.test, package-lock.json, audit-*.jsonl
244
+ // SKIP_DIRS secret-scanner.js'de zaten var (.git, node_modules, dist, build)
245
+ // Ama .DS_Store, .env.example gibi dosyaları atlamamız gerek
246
+ const findings = secrets.scanDir(process.cwd(), { maxFiles: 500 });
247
+ // False positive azaltma: sadece severity critical VEYA (.env/.key/secret içeren dosyalar)
248
+ const realSecrets = findings.filter(f => {
249
+ // .DS_Store, .md, .txt gibi dokümanları atla
250
+ const fname = (f.file || '').toLowerCase();
251
+ if (fname.endsWith('.md') || fname.endsWith('.txt')) return false;
252
+ if (fname.includes('.ds_store') || fname.includes('package-lock')) return false;
253
+ if (fname.includes('changelog') || fname.includes('readme')) return false;
254
+ // 'high' severity çoğunlukla false positive (40-char hex gibi)
255
+ // Sadece 'critical' VEYA bilinen provider pattern'i kabul et
256
+ if (f.severity === 'critical') return true;
257
+ // .env dosyalarında yüksek severity kabul
258
+ if (fname.includes('.env') && !fname.includes('.example')) return true;
259
+ return false;
260
+ });
261
+ if (realSecrets.length === 0) {
262
+ return { pass: true, message: 'Çalışma dizininde gerçek secret bulunamadı ✓' };
246
263
  }
247
- const sample = critical.slice(0, 3).map(f => `${f.type}@${path.basename(f.file || '?')}`).join(', ');
264
+ const sample = realSecrets.slice(0, 3).map(f => `${f.type}@${path.basename(f.file || '?')}`).join(', ');
248
265
  return {
249
266
  pass: false,
250
- message: `${critical.length} potansiyel secret: ${sample}${critical.length > 3 ? '...' : ''}`,
267
+ message: `${realSecrets.length} gerçek secret: ${sample}${realSecrets.length > 3 ? '...' : ''}`,
251
268
  };
252
269
  } catch (e) {
253
270
  return { pass: false, message: e.message };
@@ -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/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,