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.
- package/package.json +79 -79
- package/src/commands/repl.js +338 -135
package/package.json
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "natureco-cli",
|
|
3
|
-
"version": "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
|
-
"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
|
+
}
|
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,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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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] === '--
|
|
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
|
-
|
|
205
|
-
let
|
|
206
|
-
|
|
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 ·
|
|
211
|
-
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 }));
|
|
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('
|
|
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
|
|
232
|
-
|
|
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(
|
|
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
|
|
252
|
-
const
|
|
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
|
-
|
|
257
|
-
|
|
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 📜
|
|
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,
|
|
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 '
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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 '
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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
|
|
297
|
-
|
|
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
|
-
|
|
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ı
|
|
512
|
+
// User mesajı
|
|
307
513
|
messages.push({ role: 'user', content: line });
|
|
308
514
|
|
|
309
|
-
// AI cevabı
|
|
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(' ❌
|
|
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;
|