natureco-cli 4.5.3 → 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.
- package/package.json +1 -1
- package/src/commands/repl.js +328 -198
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.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"
|
package/src/commands/repl.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* natureco repl — Interactive REPL
|
|
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
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
|
|
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));
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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] === '--
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
}
|
|
314
|
+
}
|
|
220
315
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
`Sen ${
|
|
224
|
-
|
|
225
|
-
'Kullanıcı
|
|
226
|
-
'Cevapların kısa, net ve faydalı olsun. Markdown
|
|
227
|
-
|
|
228
|
-
? `Kullanıcının adı: ${
|
|
229
|
-
: `Kullanıcının adı: ${
|
|
230
|
-
|
|
231
|
-
? `Kullanıcı hakkında bildiklerin: ${
|
|
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
|
-
|
|
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 ·
|
|
239
|
-
console.log(tui.styled(' ' + '─'.repeat(
|
|
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('
|
|
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
|
|
260
|
-
|
|
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(
|
|
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
|
|
280
|
-
const
|
|
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
|
-
|
|
285
|
-
|
|
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 📜
|
|
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,
|
|
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(
|
|
326
|
-
console.log('
|
|
327
|
-
console.log('
|
|
328
|
-
if (
|
|
329
|
-
console.log(' Facts:');
|
|
330
|
-
for (const f of
|
|
331
|
-
console.log('
|
|
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
|
|
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(
|
|
341
|
-
|
|
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 '
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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ı
|
|
512
|
+
// User mesajı
|
|
380
513
|
messages.push({ role: 'user', content: line });
|
|
381
514
|
|
|
382
|
-
// AI cevabı
|
|
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(' ❌
|
|
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;
|