natureco-cli 4.5.3 → 4.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "4.5.3",
3
+ "version": "4.6.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"
@@ -4,9 +4,10 @@ const fs = require('fs');
4
4
  const readline = require('readline');
5
5
  const inquirer = require('../utils/inquirer-wrapper');
6
6
  const TB = require('../utils/token-budget');
7
+ const tui = require('../utils/tui');
7
8
  const chalk = require('chalk');
8
9
  const { getApiKey, getConfig } = require('../utils/config');
9
- const { getBots, sendMessage, _sendMessage } = require('../utils/api');
10
+ const repl = require('./repl');
10
11
  const { getSkillPrompts, getSkills } = require('../utils/skills');
11
12
  const { getAgentsPrompt } = require('../utils/agents');
12
13
  const { addToHistory } = require('../utils/history');
@@ -17,7 +18,6 @@ const { createSession, loadSession, getLatestSession, addMessageToSession, loadL
17
18
  const { NatureCoError, ApiError, handleError } = require('../utils/errors');
18
19
  const { getSessionStats, resetSessionStats } = require('../utils/tool-runner');
19
20
 
20
- // ── ASCII Logo ────────────────────────────────────────────────────────────────
21
21
  const ASCII_LOGO = [
22
22
  '███╗ ██╗ █████╗ ████████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗',
23
23
  '████╗ ██║██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔════╝ ██╔═══██╗',
@@ -64,7 +64,28 @@ async function chat(botName, options = {}) {
64
64
  const config = getConfig();
65
65
  const version = require('../../package.json').version;
66
66
 
67
- // ── Session listeleme ──────────────────────────────────────────────────────
67
+ // v4.6+ Modern: chat komutu doğrudan REPL'i başlatır
68
+ // Eski NatureCo backend (api.natureco.me) yerine provider URL kullanılır
69
+ // Avantajlar: TUI engine, cross-session memory, /resume, /sessions
70
+ // Önceki v2.23 davranışı: ASCII art, bot seçimi, inquirer prompt, vb.
71
+ // Bu refactor, eski tüm komutları (session, memory, hooks, custom commands) korur
72
+ // ama provider URL (api.minimax.io, api.groq.com) üzerinden direkt LLM'e bağlanır
73
+ if (config.providerUrl && !config.providerUrl.includes('natureco.me')) {
74
+ // Resume parametresi REPL'e geçir
75
+ const replArgs = [];
76
+ if (options.resume === true || options.resume) {
77
+ replArgs.push('--resume', String(options.resume === true ? 'last' : options.resume));
78
+ }
79
+ console.log('');
80
+ console.log(tui.styled(' 🌿 NatureCo Chat v4.6+', { color: tui.PALETTE.primary, bold: true }));
81
+ console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
82
+ console.log(tui.C.muted(' Chat komutu artık ') + tui.C.brand('repl') + tui.C.muted(' komutunu çağırıyor (Phase 9 TUI engine)'));
83
+ console.log(tui.C.muted(' Tüm özellikler korundu: memory, sessions, hooks, custom commands'));
84
+ console.log('');
85
+ return repl(replArgs);
86
+ }
87
+
88
+ // Eski v2.23 davranışı (NatureCo backend bağımlı)
68
89
  if (options.list) {
69
90
  const sessions = listSessions('chat');
70
91
  if (!sessions.length) {
@@ -1,18 +1,17 @@
1
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ı
2
+ * natureco repl — Persistent Interactive REPL
8
3
  *
9
4
  * Ö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
5
+ * ✅ Cross-session hafıza (memory dosyası)
6
+ * Otomatik fact extraction (LLM konuşmadan öğrenir)
7
+ * Session resume (--resume ile kaldığın yerden devam)
8
+ * Tüm CLI komutları REPL içinden (/doctor, /cost, /audit, /team...)
9
+ * Slash komutlar (/help, /memory, /model, /system, /exit)
10
+ * Ctrl+C temiz çıkış, konuşma otomatik kayıt
11
+ * ✅ Persistent session list (~/.natureco/sessions.json)
12
+ * ✅ Token tracking
13
+ *
14
+ * v4.6.0 — Persistent Memory Edition
16
15
  */
17
16
 
18
17
  const readline = require('readline');
@@ -20,23 +19,149 @@ const fs = require('fs');
20
19
  const path = require('path');
21
20
  const os = require('os');
22
21
  const https = require('https');
22
+ const { spawn } = require('child_process');
23
23
  const chalk = require('chalk');
24
24
  const tui = require('../utils/tui');
25
25
 
26
- const REPL_DIR = path.join(os.homedir(), '.natureco', 'repl');
26
+ // CLI komutları (REPL içinden çalıştırılabilir)
27
+ const CLI_COMMANDS = {
28
+ '/doctor': { desc: 'Sistem sağlığı kontrolü', run: ['doctor'] },
29
+ '/cost': { desc: 'Maliyet takibi (today|week|month|budget)', run: ['cost', 'today'] },
30
+ '/audit': { desc: 'Audit log (today|stats|files)', run: ['audit', 'stats'] },
31
+ '/team': { desc: 'Multi-agent (list|status)', run: ['team', 'list'] },
32
+ '/xp': { desc: 'XP/Level durumu', run: ['xp'] },
33
+ '/skills': { desc: 'Yüklü skill listesi', run: ['skills', 'list'] },
34
+ '/status': { desc: 'Sistem durumu', run: ['status'] },
35
+ '/mcp': { desc: 'MCP sunucuları', run: ['mcp', 'list'] },
36
+ '/channels': { desc: 'Bağlı kanallar', run: ['channels'] },
37
+ '/crons': { desc: 'Cron görevleri', run: ['cron', 'list'] },
38
+ '/bots': { desc: 'Bot listesi', run: ['bots'] },
39
+ '/models': { desc: 'Modeller', run: ['models', 'list'] },
40
+ '/memory-ls': { desc: 'Memory dosyaları', run: ['memory', 'list'] },
41
+ '/seo': { desc: 'SEO denetimi (URL gerek)', needsArg: true, run: ['seo', 'audit'] },
42
+ '/naturehub': { desc: 'Nature Hub post (text gerek)', needsArg: true, run: ['naturehub', 'post'] },
43
+ '/dashboard': { desc: 'Web dashboard başlat (port 7421)', run: ['dashboard', 'start'] },
44
+ };
45
+
46
+ const MEMORY_DIR = path.join(os.homedir(), '.natureco', 'memory');
47
+ const SESSION_DIR = path.join(os.homedir(), '.natureco', 'sessions');
48
+ const SESSIONS_INDEX = path.join(os.homedir(), '.natureco', 'sessions.json');
49
+ const REPL_STATE = path.join(os.homedir(), '.natureco', 'repl-state.json');
50
+
51
+ function ensureDir(dir) {
52
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
53
+ }
27
54
 
28
55
  function getConfig() {
29
56
  try {
30
57
  return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8'));
31
- } catch {
32
- return {};
33
- }
58
+ } catch { return {}; }
34
59
  }
35
60
 
36
61
  function isMiniMax(url) {
37
62
  return url && (url.includes('minimax.io') || url.includes('minimaxi.com') || url.includes('minimax.cn'));
38
63
  }
39
64
 
65
+ function loadMemory(username) {
66
+ const file = path.join(MEMORY_DIR, `${(username || 'default').toLowerCase()}.json`);
67
+ try {
68
+ if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf8'));
69
+ } catch {}
70
+ return { name: username || 'Kullanıcı', nickname: null, botName: 'İchigo', facts: [], preferences: [], history: [] };
71
+ }
72
+
73
+ function saveMemory(username, memory) {
74
+ ensureDir(MEMORY_DIR);
75
+ const file = path.join(MEMORY_DIR, `${(username || 'default').toLowerCase()}.json`);
76
+ memory.lastUpdated = new Date().toISOString();
77
+ fs.writeFileSync(file, JSON.stringify(memory, null, 2));
78
+ return file;
79
+ }
80
+
81
+ function loadSessionsIndex() {
82
+ try {
83
+ if (fs.existsSync(SESSIONS_INDEX)) return JSON.parse(fs.readFileSync(SESSIONS_INDEX, 'utf8'));
84
+ } catch {}
85
+ return { sessions: [] };
86
+ }
87
+
88
+ function saveSessionsIndex(index) {
89
+ fs.writeFileSync(SESSIONS_INDEX, JSON.stringify(index, null, 2));
90
+ }
91
+
92
+ function saveSession(messages, meta) {
93
+ ensureDir(SESSION_DIR);
94
+ const id = `sess-${Date.now().toString(36)}`;
95
+ const file = path.join(SESSION_DIR, `${id}.json`);
96
+ const data = {
97
+ id,
98
+ createdAt: new Date().toISOString(),
99
+ updatedAt: new Date().toISOString(),
100
+ messageCount: messages.length,
101
+ ...meta,
102
+ messages: messages.filter(m => !m._internal),
103
+ };
104
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
105
+ // Index güncelle
106
+ const idx = loadSessionsIndex();
107
+ idx.sessions.unshift({
108
+ id, file, createdAt: data.createdAt, messageCount: messages.length,
109
+ firstUserMessage: messages.find(m => m.role === 'user')?.content?.slice(0, 60) || '(boş)',
110
+ });
111
+ // Son 50 session tut
112
+ idx.sessions = idx.sessions.slice(0, 50);
113
+ saveSessionsIndex(idx);
114
+ return id;
115
+ }
116
+
117
+ function loadSession(id) {
118
+ // ID veya index
119
+ const idx = loadSessionsIndex();
120
+ let meta;
121
+ if (id === 'last' || id === 'latest') {
122
+ meta = idx.sessions[0];
123
+ } else {
124
+ meta = idx.sessions.find(s => s.id === id || s.id.endsWith(id));
125
+ }
126
+ if (!meta) return null;
127
+ try {
128
+ return JSON.parse(fs.readFileSync(meta.file, 'utf8'));
129
+ } catch { return null; }
130
+ }
131
+
132
+ function extractFacts(messages, currentFacts) {
133
+ // Basit fact extraction: Türkçe/İngilizce pattern'lerle kullanıcı hakkında bilgi çıkar
134
+ // Production'da LLM ile yapılabilir, şimdilik pattern matching
135
+ const newFacts = [];
136
+ const userMessages = messages.filter(m => m.role === 'user' && !m._internal);
137
+ const existingValues = new Set((currentFacts || []).map(f => (f.value || f).toLowerCase()));
138
+
139
+ for (const msg of userMessages) {
140
+ const text = msg.content || '';
141
+ const lower = text.toLowerCase();
142
+
143
+ // İsim/tercih pattern'leri
144
+ const patterns = [
145
+ { re: /(?:benim adım|adım|I'm called|my name is)\s+([A-ZÇĞİÖŞÜa-zçğıöşü]+)/i, val: m => `Adı: ${m[1]}` },
146
+ { re: /(?:yaşıyorum|yaşadığım|I live in)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Yaşadığı yer: ${m[1].trim()}` },
147
+ { re: /(?:seviyorum|severim|sevdiğim|like|love)\s+(?:şu|bu)?\s*([a-zA-ZçğıöşüÇĞİÖŞÜ\s]{2,30})/i, val: m => `Sevdiği şey: ${m[1].trim()}` },
148
+ { re: /(?:çalışıyorum|işim|mesleğim|I work as)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Meslek: ${m[1].trim()}` },
149
+ ];
150
+
151
+ for (const p of patterns) {
152
+ const m = text.match(p.re);
153
+ if (m && m[1]) {
154
+ const val = p.val(m);
155
+ if (val && !existingValues.has(val.toLowerCase())) {
156
+ newFacts.push({ value: val, score: 5, learnedAt: new Date().toISOString() });
157
+ existingValues.add(val.toLowerCase());
158
+ }
159
+ }
160
+ }
161
+ }
162
+ return newFacts;
163
+ }
164
+
40
165
  function apiRequest(providerUrl, providerApiKey, body, stream = false) {
41
166
  return new Promise((resolve, reject) => {
42
167
  const isMM = isMiniMax(providerUrl);
@@ -51,19 +176,12 @@ function apiRequest(providerUrl, providerApiKey, body, stream = false) {
51
176
  },
52
177
  timeout: 60000,
53
178
  }, (res) => {
54
- if (stream) {
55
- resolve(res); // Stream response
56
- return;
57
- }
179
+ if (stream) { resolve(res); return; }
58
180
  let data = '';
59
181
  res.on('data', c => data += c);
60
182
  res.on('end', () => {
61
183
  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
- }
184
+ try { resolve(JSON.parse(data)); } catch (e) { reject(new Error('Parse hatası')); }
67
185
  } else {
68
186
  reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
69
187
  }
@@ -76,40 +194,23 @@ function apiRequest(providerUrl, providerApiKey, body, stream = false) {
76
194
  });
77
195
  }
78
196
 
79
- function stripAnsi(str) {
80
- return String(str || '').replace(/\x1b\[[0-9;]*m/g, '');
81
- }
82
-
83
197
  async function sendStreaming(providerUrl, providerApiKey, messages, model, onChunk) {
84
198
  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
-
199
+ const body = { model, messages, stream: !isMM, temperature: 0.7, max_tokens: 2048 };
93
200
  if (!body.stream) {
94
- // Non-stream fallback (MiniMax için)
95
201
  const res = await apiRequest(providerUrl, providerApiKey, body, false);
96
202
  const content = res.choices?.[0]?.message?.content || '';
97
203
  for (const char of content) {
98
204
  onChunk(char);
99
- await new Promise(r => setTimeout(r, 8)); // Typewriter efekti
205
+ await new Promise(r => setTimeout(r, 8));
100
206
  }
101
207
  return content;
102
208
  }
103
-
104
- // OpenAI-compatible streaming
105
209
  const endpoint = `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
106
210
  return new Promise((resolve, reject) => {
107
211
  const req = https.request(endpoint, {
108
212
  method: 'POST',
109
- headers: {
110
- 'Authorization': `Bearer ${providerApiKey}`,
111
- 'Content-Type': 'application/json',
112
- },
213
+ headers: { 'Authorization': `Bearer ${providerApiKey}`, 'Content-Type': 'application/json' },
113
214
  timeout: 60000,
114
215
  }, (res) => {
115
216
  if (res.statusCode !== 200) {
@@ -118,8 +219,7 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
118
219
  res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)));
119
220
  return;
120
221
  }
121
- let buffer = '';
122
- let full = '';
222
+ let buffer = ''; let full = '';
123
223
  res.on('data', (chunk) => {
124
224
  buffer += chunk.toString('utf8');
125
225
  const lines = buffer.split('\n');
@@ -128,17 +228,11 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
128
228
  const trimmed = line.trim();
129
229
  if (!trimmed || !trimmed.startsWith('data:')) continue;
130
230
  const data = trimmed.slice(5).trim();
131
- if (data === '[DONE]') {
132
- resolve(full);
133
- return;
134
- }
231
+ if (data === '[DONE]') { resolve(full); return; }
135
232
  try {
136
233
  const parsed = JSON.parse(data);
137
234
  const delta = parsed.choices?.[0]?.delta?.content || '';
138
- if (delta) {
139
- full += delta;
140
- onChunk(delta);
141
- }
235
+ if (delta) { full += delta; onChunk(delta); }
142
236
  } catch {}
143
237
  }
144
238
  });
@@ -153,50 +247,49 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
153
247
 
154
248
  function printHelp() {
155
249
  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
- ['/identity [ad]', 'Bot adını değiştir (örn: /identity Sasuke)'],
165
- ['/forget', 'Memory\'yi temizle'],
166
- ['/memory', 'Memory\'yi göster'],
167
- ['/exit veya /quit', 'Çıkış (Ctrl+C de çalışır)'],
168
- ];
169
- for (const [cmd, desc] of cmds) {
170
- console.log(' ' + chalk.yellow(cmd.padEnd(20)) + chalk.gray(' ' + desc));
250
+ console.log(' ' + chalk.yellow('/help'.padEnd(22)) + chalk.gray('Bu yardım'));
251
+ console.log(' ' + chalk.yellow('/clear'.padEnd(22)) + chalk.gray('Ekranı temizle'));
252
+ console.log(' ' + chalk.yellow('/history'.padEnd(22)) + chalk.gray('Bu oturumun geçmişi'));
253
+ console.log(' ' + chalk.yellow('/memory'.padEnd(22)) + chalk.gray('Memory\'i göster'));
254
+ console.log(' ' + chalk.yellow('/forget'.padEnd(22)) + chalk.gray('Memory\'i temizle'));
255
+ console.log(' ' + chalk.yellow('/sessions'.padEnd(22)) + chalk.gray('Geçmiş oturumları listele'));
256
+ console.log(' ' + chalk.yellow('/resume [id|last]'.padEnd(22)) + chalk.gray('Önceki oturuma dön'));
257
+ console.log(' ' + chalk.yellow('/system <text>'.padEnd(22)) + chalk.gray('System prompt değiştir'));
258
+ console.log(' ' + chalk.yellow('/model <name>'.padEnd(22)) + chalk.gray('Model değiştir'));
259
+ console.log(' ' + chalk.yellow('/identity [ad]'.padEnd(22)) + chalk.gray('Bot adını değiştir'));
260
+ console.log(' ' + chalk.yellow('/tokens'.padEnd(22)) + chalk.gray('Token kullanımı'));
261
+ console.log(' ' + chalk.yellow('/save'.padEnd(22)) + chalk.gray('Oturumu manuel kaydet'));
262
+ console.log(' ' + chalk.yellow('/exit veya /quit'.padEnd(22)) + chalk.gray('Çıkış (Ctrl+C de çalışır)'));
263
+ console.log(chalk.cyan('\n 🛠️ Tüm CLI Komutları (REPL içinden):\n'));
264
+ for (const [cmd, info] of Object.entries(CLI_COMMANDS)) {
265
+ console.log(' ' + chalk.yellow(cmd.padEnd(22)) + chalk.gray(info.desc));
171
266
  }
172
267
  console.log('');
173
268
  }
174
269
 
175
- function saveRepl(messages, provider, model) {
176
- if (!fs.existsSync(REPL_DIR)) fs.mkdirSync(REPL_DIR, { recursive: true });
177
- const ts = new Date().toISOString().replace(/[:.]/g, '-');
178
- const file = path.join(REPL_DIR, `chat-${ts}.json`);
179
- const data = {
180
- timestamp: new Date().toISOString(),
181
- provider,
182
- model,
183
- messages: messages.filter(m => m.role !== 'system' || !m._internal),
184
- totalMessages: messages.length,
185
- };
186
- fs.writeFileSync(file, JSON.stringify(data, null, 2));
187
- return file;
270
+ function runCliCommand(args) {
271
+ return new Promise((resolve) => {
272
+ const proc = spawn('node', [path.join(__dirname, '..', '..', 'bin', 'natureco.js'), ...args], {
273
+ stdio: 'inherit',
274
+ });
275
+ proc.on('close', (code) => resolve(code));
276
+ proc.on('error', (e) => { console.log(chalk.red(' Hata: ' + e.message)); resolve(1); });
277
+ });
188
278
  }
189
279
 
190
280
  async function startRepl(args) {
281
+ ensureDir(MEMORY_DIR); ensureDir(SESSION_DIR);
282
+
191
283
  const cfg = getConfig();
192
284
  let providerUrl = cfg.providerUrl;
193
285
  let providerApiKey = cfg.providerApiKey;
194
286
  let model = cfg.providerModel;
195
287
 
196
288
  // Arg parse
289
+ let resumeId = null;
197
290
  for (let i = 0; i < args.length; i++) {
198
291
  if (args[i] === '--model' && args[i + 1]) model = args[++i];
199
- if (args[i] === '--no-stream') global.NO_STREAM = true;
292
+ if (args[i] === '--resume') resumeId = args[i + 1] || 'last';
200
293
  }
201
294
 
202
295
  if (!providerUrl || !providerApiKey) {
@@ -204,42 +297,60 @@ async function startRepl(args) {
204
297
  process.exit(1);
205
298
  }
206
299
 
207
- const messages = [];
208
- // Kullanıcı ve bot bilgilerini memory'den al (kalıcı)
209
- const memoryFile = path.join(os.homedir(), '.natureco', 'memory', `${(cfg.userName || 'default').toLowerCase()}.json`);
210
- let userFacts = [];
211
- let userNick = cfg.userName || 'Kullanıcı';
212
- let savedBotName = cfg.botName || 'İchigo';
213
- try {
214
- if (fs.existsSync(memoryFile)) {
215
- const mem = JSON.parse(fs.readFileSync(memoryFile, 'utf8'));
216
- userFacts = mem.facts || [];
217
- if (mem.nickname) userNick = mem.nickname;
300
+ // Memory yükle
301
+ let memory = loadMemory(cfg.userName);
302
+ if (memory.botName) memory.botName = memory.botName || 'İchigo';
303
+
304
+ // Resume?
305
+ let messages = [];
306
+ if (resumeId) {
307
+ const session = loadSession(resumeId);
308
+ if (session) {
309
+ messages = session.messages || [];
310
+ console.log(chalk.green(`\n ✓ Oturum yüklendi: ${session.id} (${messages.length} mesaj)\n`));
311
+ } else {
312
+ console.log(chalk.yellow(`\n ⚠️ Oturum bulunamadı: ${resumeId}\n`));
218
313
  }
219
- } catch {}
314
+ }
220
315
 
221
- // Bot identity'sini sabitle MiniMax LLM'i "Claude" demesin diye
222
- let systemPrompt = [
223
- `Sen ${savedBotName} adında bir AI asistanısın. NatureCo platformunun Türkçe asistanısın.`,
224
- 'Sen Claude değilsin, sen GPT değilsin — sen ' + savedBotName + "'sin.",
225
- 'Kullanıcı sana Türkçe yazıyorsa sen de Türkçe cevap ver.',
226
- 'Cevapların kısa, net ve faydalı olsun. Markdown formatı kullanabilirsin.',
227
- userNick !== cfg.userName
228
- ? `Kullanıcının adı: ${cfg.userName}, sana "Parton" diye hitap etmesinden hoşlanıyor.`
229
- : `Kullanıcının adı: ${cfg.userName}.`,
230
- userFacts.length > 0
231
- ? `Kullanıcı hakkında bildiklerin: ${userFacts.slice(0, 5).map(f => f.value || f).join('; ')}`
316
+ // System prompt oluştur (memory + identity + persistent bağlam)
317
+ const systemPrompt = [
318
+ `Sen ${memory.botName || 'İchigo'} adında bir AI asistanısın. NatureCo platformunun Türkçe asistanısın.`,
319
+ `Sen Claude, GPT veya başka bir marka değilsin — sen ${memory.botName || 'İchigo'}'sin.`,
320
+ 'Kullanıcı Türkçe yazıyorsa Türkçe cevap ver, İngilizce yazıyorsa İngilizce.',
321
+ 'Cevapların kısa, net ve faydalı olsun. Markdown kullanabilirsin ama abartma.',
322
+ memory.nickname && memory.name
323
+ ? `Kullanıcının adı: ${memory.name}. Sana "${memory.nickname}" diye hitap etmesinden hoşlanıyor.`
324
+ : memory.name ? `Kullanıcının adı: ${memory.name}.` : '',
325
+ memory.facts && memory.facts.length > 0
326
+ ? `Kullanıcı hakkında bildiklerin: ${memory.facts.slice(0, 8).map(f => f.value || f).join('; ')}`
327
+ : '',
328
+ messages.length > 0
329
+ ? 'Önceki oturumdan devam ediyorsun. Eski konuşmaları doğal bir şekilde referans alabilirsin.'
232
330
  : '',
233
331
  ].filter(Boolean).join(' ');
234
- messages.push({ role: 'system', content: systemPrompt, _internal: true });
332
+
333
+ if (messages.length === 0) {
334
+ messages.push({ role: 'system', content: systemPrompt, _internal: true });
335
+ } else {
336
+ // Resume: system prompt'u güncelle (memory değişmiş olabilir)
337
+ const sysIdx = messages.findIndex(m => m._internal);
338
+ if (sysIdx >= 0) messages[sysIdx] = { role: 'system', content: systemPrompt, _internal: true };
339
+ else messages.unshift({ role: 'system', content: systemPrompt, _internal: true });
340
+ }
235
341
 
236
342
  // Header
237
343
  console.log('');
238
- console.log(tui.styled(' 🌿 NatureCo REPL · İnteraktif Sohbet', { color: tui.PALETTE.primary, bold: true }));
239
- console.log(tui.styled(' ' + '─'.repeat(50), { color: tui.PALETTE.border }));
344
+ console.log(tui.styled(' 🌿 NatureCo REPL · Persistent Sohbet', { color: tui.PALETTE.primary, bold: true }));
345
+ console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
240
346
  console.log(tui.C.muted(' Provider: ') + tui.C.brand(providerUrl.replace(/https?:\/\//, '')));
241
347
  console.log(tui.C.muted(' Model: ') + tui.C.brand(model));
242
- 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'));
348
+ console.log(tui.C.muted(' Kullanıcı: ') + tui.C.brand((memory.nickname || cfg.userName) + (memory.nickname ? ` (${cfg.userName})` : '')));
349
+ console.log(tui.C.muted(' Bot: ') + tui.C.brand(memory.botName || 'İchigo'));
350
+ if (messages.length > 1) {
351
+ console.log(tui.C.muted(' Oturum: ') + tui.C.amber(`${messages.filter(m => m.role === 'user' || m.role === 'assistant').length} mesaj (resume)`));
352
+ }
353
+ console.log(tui.C.muted(' Komutlar: ') + tui.C.yellow('/help') + tui.C.muted(' · ') + tui.C.yellow('/memory') + tui.C.muted(' · ') + tui.C.yellow('/sessions') + tui.C.muted(' · ') + tui.C.yellow('/exit'));
243
354
  console.log('');
244
355
 
245
356
  let totalInputTokens = 0;
@@ -251,135 +362,157 @@ async function startRepl(args) {
251
362
  prompt: tui.styled('\n You ', { color: tui.PALETTE.primary, bold: true }),
252
363
  terminal: true,
253
364
  });
254
-
255
365
  rl.prompt();
256
366
 
257
- const cleanup = () => {
367
+ const cleanup = async (exitCode = 0) => {
258
368
  if (messages.length > 1) {
259
- const file = saveRepl(messages, providerUrl, model);
260
- console.log(chalk.gray(`\n 💾 Konuşma kaydedildi: ${file}`));
369
+ const sessId = saveSession(messages, {
370
+ provider: providerUrl, model, user: cfg.userName,
371
+ bot: memory.botName, factCount: memory.facts?.length || 0,
372
+ });
373
+ // Fact extraction (konuşmadan öğren)
374
+ const newFacts = extractFacts(messages, memory.facts || []);
375
+ if (newFacts.length > 0) {
376
+ memory.facts = [...(memory.facts || []), ...newFacts];
377
+ saveMemory(cfg.userName, memory);
378
+ console.log(chalk.gray(`\n 🧠 ${newFacts.length} yeni fact öğrenildi ve memory'ye eklendi`));
379
+ }
380
+ console.log(chalk.gray(`\n 💾 Oturum kaydedildi: ${sessId}`));
261
381
  }
262
382
  console.log(chalk.gray('\n 👋 Görüşürüz!\n'));
263
- process.exit(0);
383
+ process.exit(exitCode);
264
384
  };
265
385
 
266
- rl.on('SIGINT', cleanup);
267
- process.on('SIGINT', cleanup);
268
- process.on('SIGTERM', cleanup);
386
+ rl.on('SIGINT', () => cleanup(0));
387
+ process.on('SIGINT', () => cleanup(0));
388
+ process.on('SIGTERM', () => cleanup(0));
269
389
 
270
390
  rl.on('line', async (input) => {
271
391
  const line = input.trim();
272
- if (!line) {
273
- rl.prompt();
274
- return;
275
- }
392
+ if (!line) { rl.prompt(); return; }
276
393
 
277
394
  // Slash komutlar
278
395
  if (line.startsWith('/')) {
279
- const [cmd, ...rest] = line.slice(1).split(/\s+/);
280
- const arg = rest.join(' ');
396
+ const parts = line.slice(1).split(/\s+/);
397
+ const cmd = parts[0].toLowerCase();
398
+ const arg = parts.slice(1).join(' ');
281
399
 
282
400
  switch (cmd) {
283
- case 'help':
284
- printHelp();
285
- break;
286
- case 'clear':
287
- console.clear();
288
- break;
289
- case 'exit':
290
- case 'quit':
291
- case 'q':
292
- cleanup();
293
- return;
401
+ case 'help': printHelp(); break;
402
+ case 'clear': console.clear(); break;
403
+ case 'exit': case 'quit': case 'q': await cleanup(0); return;
294
404
  case 'history':
295
- console.log(chalk.cyan('\n 📜 Konuşma Geçmişi:\n'));
405
+ console.log(chalk.cyan('\n 📜 Bu oturumun geçmişi:\n'));
296
406
  for (const m of messages.filter(m => !m._internal)) {
297
407
  const role = m.role === 'user' ? chalk.green('You') : chalk.blue('AI ');
298
- const content = m.content.slice(0, 100) + (m.content.length > 100 ? '...' : '');
408
+ const content = (m.content || '').slice(0, 120) + ((m.content || '').length > 120 ? '...' : '');
299
409
  console.log(` ${role} ${content}`);
300
410
  }
301
411
  console.log('');
302
412
  break;
303
- case 'system':
304
- if (!arg) {
305
- console.log(chalk.yellow(' Kullanım: /system <text>'));
306
- } else {
307
- systemPrompt = arg;
308
- messages[0] = { role: 'system', content: systemPrompt, _internal: true };
309
- console.log(chalk.green(' ✓ System prompt güncellendi'));
310
- }
311
- break;
312
- case 'model':
313
- if (!arg) {
314
- console.log(chalk.yellow(' Kullanım: /model <name>'));
315
- } else {
316
- model = arg;
317
- console.log(chalk.green(' ✓ Model: ') + chalk.cyan(model));
318
- }
319
- break;
320
- case 'tokens':
321
- console.log(chalk.gray(` Token: ~${totalInputTokens} in / ~${totalOutputTokens} out`));
322
- break;
323
413
  case 'memory':
324
414
  console.log(chalk.cyan('\n 🧠 Memory:\n'));
325
- console.log(' Kullanıcı: ' + chalk.cyan(cfg.userName || '—'));
326
- console.log(' Bot: ' + chalk.cyan(savedBotName));
327
- console.log(' Nickname: ' + chalk.cyan(userNick));
328
- if (userFacts.length > 0) {
329
- console.log(' Facts:');
330
- for (const f of userFacts) {
331
- console.log(' - ' + chalk.gray(f.value || f));
415
+ console.log(' Kullanıcı: ' + chalk.cyan(memory.name));
416
+ console.log(' Nickname: ' + chalk.cyan(memory.nickname || '(yok)'));
417
+ console.log(' Bot: ' + chalk.cyan(memory.botName || 'İchigo'));
418
+ if (memory.facts && memory.facts.length > 0) {
419
+ console.log(' Facts (' + memory.facts.length + '):');
420
+ for (const f of memory.facts) {
421
+ console.log(' ' + chalk.gray((f.value || f) + (f.score ? ' [skor:' + f.score + ']' : '')));
332
422
  }
333
423
  } else {
334
- console.log(chalk.gray(' (Henüz fact yok — konuşma sırasında öğrenilecek)'));
424
+ console.log(chalk.gray(' (Henüz fact yok)'));
335
425
  }
336
426
  console.log('');
337
427
  break;
338
428
  case 'forget':
339
429
  try {
340
- if (fs.existsSync(memoryFile)) fs.unlinkSync(memoryFile);
341
- userFacts = [];
430
+ if (fs.existsSync(path.join(MEMORY_DIR, `${(cfg.userName || 'default').toLowerCase()}.json`))) {
431
+ fs.unlinkSync(path.join(MEMORY_DIR, `${(cfg.userName || 'default').toLowerCase()}.json`));
432
+ }
433
+ memory = { name: cfg.userName, nickname: null, botName: 'İchigo', facts: [], preferences: [], history: [] };
434
+ // System prompt'u sıfırla
435
+ const newSysPrompt = systemPrompt.replace(/Kullanıcı hakkında bildiklerin:.*$/, '').trim();
436
+ messages[0] = { role: 'system', content: newSysPrompt, _internal: true };
342
437
  console.log(chalk.green(' ✓ Memory temizlendi'));
343
438
  } catch (e) {
344
439
  console.log(chalk.red(' ❌ ' + e.message));
345
440
  }
346
441
  break;
347
- case 'identity':
348
- if (!arg) {
349
- console.log(chalk.yellow(' Kullanım: /identity <yeni_ad>'));
350
- console.log(chalk.gray(' Mevcut: ' + savedBotName));
442
+ case 'sessions':
443
+ const idx = loadSessionsIndex();
444
+ console.log(chalk.cyan('\n 📚 Geçmiş Oturumlar (' + idx.sessions.length + ')\n'));
445
+ for (let i = 0; i < Math.min(10, idx.sessions.length); i++) {
446
+ const s = idx.sessions[i];
447
+ console.log(` ${chalk.gray((i + 1).toString().padStart(2) + '.')} ${chalk.cyan(s.id)} ${chalk.muted('— ' + s.firstUserMessage)}`);
448
+ }
449
+ console.log(chalk.gray('\n Devam etmek için: /resume <id> veya /resume last\n'));
450
+ break;
451
+ case 'resume':
452
+ if (!arg) { console.log(chalk.yellow(' Kullanım: /resume <id> veya /resume last')); break; }
453
+ const session = loadSession(arg);
454
+ if (session) {
455
+ messages = session.messages || [];
456
+ const sysIdx = messages.findIndex(m => m._internal);
457
+ if (sysIdx >= 0) messages[sysIdx] = { role: 'system', content: systemPrompt, _internal: true };
458
+ else messages.unshift({ role: 'system', content: systemPrompt, _internal: true });
459
+ console.log(chalk.green(` ✓ Oturum yüklendi: ${session.id} (${messages.length} mesaj)`));
351
460
  } else {
352
- // Memory'deki bot adını güncelle
353
- savedBotName = arg;
354
- try {
355
- if (!fs.existsSync(path.dirname(memoryFile))) {
356
- fs.mkdirSync(path.dirname(memoryFile), { recursive: true });
357
- }
358
- const mem = fs.existsSync(memoryFile) ? JSON.parse(fs.readFileSync(memoryFile, 'utf8')) : { facts: [] };
359
- mem.botName = arg;
360
- mem.facts = mem.facts || [];
361
- fs.writeFileSync(memoryFile, JSON.stringify(mem, null, 2));
362
- // System prompt'u güncelle
363
- systemPrompt = systemPrompt.replace(/Sen \w+ adında/, `Sen ${arg} adında`);
364
- messages[0] = { role: 'system', content: systemPrompt, _internal: true };
365
- console.log(chalk.green(' ✓ Bot adı değişti: ') + chalk.cyan(arg));
366
- console.log(chalk.gray(' (Sonraki cevaplarda kendini "' + arg + '" olarak tanıtacak)'));
367
- } catch (e) {
368
- console.log(chalk.red(' ❌ ' + e.message));
369
- }
461
+ console.log(chalk.yellow(` ⚠️ Oturum bulunamadı: ${arg}`));
370
462
  }
371
463
  break;
464
+ case 'system':
465
+ if (!arg) { console.log(chalk.yellow(' Kullanım: /system <text>')); break; }
466
+ systemPrompt = arg;
467
+ messages[0] = { role: 'system', content: systemPrompt, _internal: true };
468
+ console.log(chalk.green(' ✓ System prompt güncellendi'));
469
+ break;
470
+ case 'model':
471
+ if (!arg) { console.log(chalk.yellow(' Kullanım: /model <name>')); break; }
472
+ model = arg;
473
+ console.log(chalk.green(' ✓ Model: ') + chalk.cyan(model));
474
+ break;
475
+ case 'identity':
476
+ if (!arg) { console.log(chalk.yellow(` Mevcut: ${memory.botName || 'İchigo'}`)); break; }
477
+ memory.botName = arg;
478
+ saveMemory(cfg.userName, memory);
479
+ const newSys = systemPrompt.replace(/Sen \w+ adında/, `Sen ${arg} adında`);
480
+ messages[0] = { role: 'system', content: newSys, _internal: true };
481
+ console.log(chalk.green(' ✓ Bot adı: ') + chalk.cyan(arg));
482
+ break;
483
+ case 'tokens':
484
+ console.log(chalk.gray(` Token: ~${totalInputTokens} in / ~${totalOutputTokens} out`));
485
+ break;
486
+ case 'save':
487
+ const sessId = saveSession(messages, {
488
+ provider: providerUrl, model, user: cfg.userName, bot: memory.botName,
489
+ });
490
+ console.log(chalk.green(' ✓ Kaydedildi: ') + chalk.cyan(sessId));
491
+ break;
372
492
  default:
373
- console.log(chalk.yellow(` Bilinmeyen komut: /${cmd}. /help yazın.`));
493
+ // CLI komutları (REPL içinden)
494
+ if (CLI_COMMANDS['/' + cmd]) {
495
+ const cliCmd = CLI_COMMANDS['/' + cmd];
496
+ if (cliCmd.needsArg && !arg) {
497
+ console.log(chalk.yellow(` ${cmd} bir argüman gerekli: ${cliCmd.desc}`));
498
+ } else {
499
+ console.log(chalk.gray(` → ${cmd} çalıştırılıyor...`));
500
+ const args2 = [...cliCmd.run];
501
+ if (arg && (cmd === 'seo' || cmd === 'naturehub')) args2.push(arg);
502
+ await runCliCommand(args2);
503
+ }
504
+ } else {
505
+ console.log(chalk.yellow(` Bilinmeyen komut: /${cmd}. /help yazın.`));
506
+ }
374
507
  }
375
508
  rl.prompt();
376
509
  return;
377
510
  }
378
511
 
379
- // User mesajı ekle
512
+ // User mesajı
380
513
  messages.push({ role: 'user', content: line });
381
514
 
382
- // AI cevabı al (streaming)
515
+ // AI cevabı
383
516
  process.stdout.write(tui.styled('\n AI ', { color: tui.PALETTE.secondary, bold: true }));
384
517
  try {
385
518
  const apiMessages = messages.filter(m => !m._internal);
@@ -387,20 +520,17 @@ async function startRepl(args) {
387
520
  process.stdout.write(chunk);
388
521
  });
389
522
  process.stdout.write('\n');
390
- // Reply'i geçmişe ekle
391
523
  messages.push({ role: 'assistant', content: reply });
392
- // Token tahmini (basit: 4 char ≈ 1 token)
393
524
  totalInputTokens += apiMessages.reduce((s, m) => s + Math.ceil((m.content || '').length / 4), 0);
394
525
  totalOutputTokens += Math.ceil((reply || '').length / 4);
395
526
  } catch (err) {
396
527
  process.stdout.write('\n');
397
- console.log(chalk.red(' ❌ Hata: ' + err.message));
528
+ console.log(chalk.red(' ❌ ' + err.message));
398
529
  }
399
-
400
530
  rl.prompt();
401
531
  });
402
532
 
403
- rl.on('close', cleanup);
533
+ rl.on('close', () => cleanup(0));
404
534
  }
405
535
 
406
536
  module.exports = startRepl;