natureco-cli 4.5.2 → 4.6.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.
Files changed (2) hide show
  1. package/package.json +79 -79
  2. package/src/commands/repl.js +338 -135
package/package.json CHANGED
@@ -1,79 +1,79 @@
1
- {
2
- "name": "natureco-cli",
3
- "version": "4.5.2",
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
- "bin": {
6
- "natureco": "bin/natureco.js"
7
- },
8
- "files": [
9
- "bin/",
10
- "src/",
11
- "skills/",
12
- "README.md",
13
- "CHANGELOG.md",
14
- "AUDIT.md",
15
- "DEPLOY_v2.0.0.md"
16
- ],
17
- "scripts": {
18
- "test": "node bin/natureco.js help",
19
- "test:unit": "node --check bin/natureco.js && node -e \"console.log('All files OK')\"",
20
- "test:watch": "node --watch bin/natureco.js",
21
- "test:coverage": "node --check bin/natureco.js",
22
- "postinstall": "node bin/natureco.js doctor || true",
23
- "prepublishOnly": "node --check bin/natureco.js"
24
- },
25
- "keywords": [
26
- "natureco",
27
- "ai",
28
- "ai-agent",
29
- "bot",
30
- "cli",
31
- "terminal",
32
- "openclaw-alternative",
33
- "claude-code-alternative",
34
- "multi-agent",
35
- "self-evolving-skills",
36
- "audit-log",
37
- "cost-optimization",
38
- "seo",
39
- "türkçe",
40
- "turkish",
41
- "whatsapp-bot",
42
- "telegram-bot",
43
- "discord-bot",
44
- "natureco-me"
45
- ],
46
- "author": {
47
- "name": "Gencay Olgun",
48
- "email": "hello@natureco.me",
49
- "url": "https://natureco.me"
50
- },
51
- "homepage": "https://natureco.me/cli",
52
- "repository": {
53
- "type": "git",
54
- "url": "git+https://github.com/natureco/natureco-cli.git"
55
- },
56
- "bugs": {
57
- "url": "https://github.com/natureco/natureco-cli/issues"
58
- },
59
- "license": "MIT",
60
- "engines": {
61
- "node": ">=18.0.0"
62
- },
63
- "dependencies": {
64
- "@inquirer/prompts": "^8.5.0",
65
- "@whiskeysockets/baileys": "^7.0.0-rc10",
66
- "chalk": "^4.1.2",
67
- "commander": "^11.1.0",
68
- "json5": "^2.2.3",
69
- "node-cron": "^4.2.1",
70
- "node-telegram-bot-api": "^0.67.0",
71
- "pino": "^8.21.0",
72
- "qrcode-terminal": "^0.12.0",
73
- "semver": "^7.8.1",
74
- "ws": "^8.20.0"
75
- },
76
- "devDependencies": {
77
- "vitest": "^4.1.9"
78
- }
79
- }
1
+ {
2
+ "name": "natureco-cli",
3
+ "version": "4.6.0",
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
+ "bin": {
6
+ "natureco": "bin/natureco.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "skills/",
12
+ "README.md",
13
+ "CHANGELOG.md",
14
+ "AUDIT.md",
15
+ "DEPLOY_v2.0.0.md"
16
+ ],
17
+ "scripts": {
18
+ "test": "node bin/natureco.js help",
19
+ "test:unit": "node --check bin/natureco.js && node -e \"console.log('All files OK')\"",
20
+ "test:watch": "node --watch bin/natureco.js",
21
+ "test:coverage": "node --check bin/natureco.js",
22
+ "postinstall": "node bin/natureco.js doctor || true",
23
+ "prepublishOnly": "node --check bin/natureco.js"
24
+ },
25
+ "keywords": [
26
+ "natureco",
27
+ "ai",
28
+ "ai-agent",
29
+ "bot",
30
+ "cli",
31
+ "terminal",
32
+ "openclaw-alternative",
33
+ "claude-code-alternative",
34
+ "multi-agent",
35
+ "self-evolving-skills",
36
+ "audit-log",
37
+ "cost-optimization",
38
+ "seo",
39
+ "türkçe",
40
+ "turkish",
41
+ "whatsapp-bot",
42
+ "telegram-bot",
43
+ "discord-bot",
44
+ "natureco-me"
45
+ ],
46
+ "author": {
47
+ "name": "Gencay Olgun",
48
+ "email": "hello@natureco.me",
49
+ "url": "https://natureco.me"
50
+ },
51
+ "homepage": "https://natureco.me/cli",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/natureco/natureco-cli.git"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/natureco/natureco-cli/issues"
58
+ },
59
+ "license": "MIT",
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "dependencies": {
64
+ "@inquirer/prompts": "^8.5.0",
65
+ "@whiskeysockets/baileys": "^7.0.0-rc10",
66
+ "chalk": "^4.1.2",
67
+ "commander": "^11.1.0",
68
+ "json5": "^2.2.3",
69
+ "node-cron": "^4.2.1",
70
+ "node-telegram-bot-api": "^0.67.0",
71
+ "pino": "^8.21.0",
72
+ "qrcode-terminal": "^0.12.0",
73
+ "semver": "^7.8.1",
74
+ "ws": "^8.20.0"
75
+ },
76
+ "devDependencies": {
77
+ "vitest": "^4.1.9"
78
+ }
79
+ }
@@ -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,47 +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
- ['/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));
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));
168
266
  }
169
267
  console.log('');
170
268
  }
171
269
 
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;
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
+ });
185
278
  }
186
279
 
187
280
  async function startRepl(args) {
281
+ ensureDir(MEMORY_DIR); ensureDir(SESSION_DIR);
282
+
188
283
  const cfg = getConfig();
189
284
  let providerUrl = cfg.providerUrl;
190
285
  let providerApiKey = cfg.providerApiKey;
191
286
  let model = cfg.providerModel;
192
287
 
193
288
  // Arg parse
289
+ let resumeId = null;
194
290
  for (let i = 0; i < args.length; i++) {
195
291
  if (args[i] === '--model' && args[i + 1]) model = args[++i];
196
- if (args[i] === '--no-stream') global.NO_STREAM = true;
292
+ if (args[i] === '--resume') resumeId = args[i + 1] || 'last';
197
293
  }
198
294
 
199
295
  if (!providerUrl || !providerApiKey) {
@@ -201,17 +297,60 @@ async function startRepl(args) {
201
297
  process.exit(1);
202
298
  }
203
299
 
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 });
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`));
313
+ }
314
+ }
315
+
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.'
330
+ : '',
331
+ ].filter(Boolean).join(' ');
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
+ }
207
341
 
208
342
  // Header
209
343
  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 }));
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 }));
212
346
  console.log(tui.C.muted(' Provider: ') + tui.C.brand(providerUrl.replace(/https?:\/\//, '')));
213
347
  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'));
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'));
215
354
  console.log('');
216
355
 
217
356
  let totalInputTokens = 0;
@@ -223,90 +362,157 @@ async function startRepl(args) {
223
362
  prompt: tui.styled('\n You ', { color: tui.PALETTE.primary, bold: true }),
224
363
  terminal: true,
225
364
  });
226
-
227
365
  rl.prompt();
228
366
 
229
- const cleanup = () => {
367
+ const cleanup = async (exitCode = 0) => {
230
368
  if (messages.length > 1) {
231
- const file = saveRepl(messages, providerUrl, model);
232
- 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}`));
233
381
  }
234
382
  console.log(chalk.gray('\n 👋 Görüşürüz!\n'));
235
- process.exit(0);
383
+ process.exit(exitCode);
236
384
  };
237
385
 
238
- rl.on('SIGINT', cleanup);
239
- process.on('SIGINT', cleanup);
240
- process.on('SIGTERM', cleanup);
386
+ rl.on('SIGINT', () => cleanup(0));
387
+ process.on('SIGINT', () => cleanup(0));
388
+ process.on('SIGTERM', () => cleanup(0));
241
389
 
242
390
  rl.on('line', async (input) => {
243
391
  const line = input.trim();
244
- if (!line) {
245
- rl.prompt();
246
- return;
247
- }
392
+ if (!line) { rl.prompt(); return; }
248
393
 
249
394
  // Slash komutlar
250
395
  if (line.startsWith('/')) {
251
- const [cmd, ...rest] = line.slice(1).split(/\s+/);
252
- 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(' ');
253
399
 
254
400
  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;
401
+ case 'help': printHelp(); break;
402
+ case 'clear': console.clear(); break;
403
+ case 'exit': case 'quit': case 'q': await cleanup(0); return;
266
404
  case 'history':
267
- console.log(chalk.cyan('\n 📜 Konuşma Geçmişi:\n'));
405
+ console.log(chalk.cyan('\n 📜 Bu oturumun geçmişi:\n'));
268
406
  for (const m of messages.filter(m => !m._internal)) {
269
407
  const role = m.role === 'user' ? chalk.green('You') : chalk.blue('AI ');
270
- const content = m.content.slice(0, 100) + (m.content.length > 100 ? '...' : '');
408
+ const content = (m.content || '').slice(0, 120) + ((m.content || '').length > 120 ? '...' : '');
271
409
  console.log(` ${role} ${content}`);
272
410
  }
273
411
  console.log('');
274
412
  break;
275
- case 'system':
276
- if (!arg) {
277
- console.log(chalk.yellow(' Kullanım: /system <text>'));
413
+ case 'memory':
414
+ console.log(chalk.cyan('\n 🧠 Memory:\n'));
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 + ']' : '')));
422
+ }
278
423
  } else {
279
- systemPrompt = arg;
280
- messages[0] = { role: 'system', content: systemPrompt, _internal: true };
281
- console.log(chalk.green(' ✓ System prompt güncellendi'));
424
+ console.log(chalk.gray(' (Henüz fact yok)'));
425
+ }
426
+ console.log('');
427
+ break;
428
+ case 'forget':
429
+ try {
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 };
437
+ console.log(chalk.green(' ✓ Memory temizlendi'));
438
+ } catch (e) {
439
+ console.log(chalk.red(' ❌ ' + e.message));
282
440
  }
283
441
  break;
284
- case 'model':
285
- if (!arg) {
286
- console.log(chalk.yellow(' Kullanım: /model <name>'));
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)`));
287
460
  } else {
288
- model = arg;
289
- console.log(chalk.green(' ✓ Model: ') + chalk.cyan(model));
461
+ console.log(chalk.yellow(` ⚠️ Oturum bulunamadı: ${arg}`));
290
462
  }
291
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;
292
483
  case 'tokens':
293
484
  console.log(chalk.gray(` Token: ~${totalInputTokens} in / ~${totalOutputTokens} out`));
294
485
  break;
295
486
  case 'save':
296
- const f = saveRepl(messages, providerUrl, model);
297
- console.log(chalk.green(' ✓ Kaydedildi: ') + chalk.cyan(f));
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));
298
491
  break;
299
492
  default:
300
- 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
+ }
301
507
  }
302
508
  rl.prompt();
303
509
  return;
304
510
  }
305
511
 
306
- // User mesajı ekle
512
+ // User mesajı
307
513
  messages.push({ role: 'user', content: line });
308
514
 
309
- // AI cevabı al (streaming)
515
+ // AI cevabı
310
516
  process.stdout.write(tui.styled('\n AI ', { color: tui.PALETTE.secondary, bold: true }));
311
517
  try {
312
518
  const apiMessages = messages.filter(m => !m._internal);
@@ -314,20 +520,17 @@ async function startRepl(args) {
314
520
  process.stdout.write(chunk);
315
521
  });
316
522
  process.stdout.write('\n');
317
- // Reply'i geçmişe ekle
318
523
  messages.push({ role: 'assistant', content: reply });
319
- // Token tahmini (basit: 4 char ≈ 1 token)
320
524
  totalInputTokens += apiMessages.reduce((s, m) => s + Math.ceil((m.content || '').length / 4), 0);
321
525
  totalOutputTokens += Math.ceil((reply || '').length / 4);
322
526
  } catch (err) {
323
527
  process.stdout.write('\n');
324
- console.log(chalk.red(' ❌ Hata: ' + err.message));
528
+ console.log(chalk.red(' ❌ ' + err.message));
325
529
  }
326
-
327
530
  rl.prompt();
328
531
  });
329
532
 
330
- rl.on('close', cleanup);
533
+ rl.on('close', () => cleanup(0));
331
534
  }
332
535
 
333
536
  module.exports = startRepl;