natureco-cli 5.20.4 → 5.22.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.
@@ -101,7 +101,6 @@ function rebuildSystemPrompt(opts) {
101
101
 
102
102
  // ── Tool Guardrails instance (Hermes-style) ─────────────────────────────
103
103
  const guardrails = new ToolGuardrails();
104
- const noopCallback = () => {};
105
104
 
106
105
  // CLI komutları (REPL içinden çalıştırılabilir)
107
106
  const CLI_COMMANDS = {
@@ -119,31 +118,31 @@ const CLI_COMMANDS = {
119
118
  '/models': { desc: 'Modeller', run: ['models', 'list'] },
120
119
  '/memory-ls': { desc: 'Memory dosyaları', run: ['memory', 'list'] },
121
120
  '/seo': { desc: 'SEO denetimi (URL gerek)', needsArg: true, run: ['seo', 'audit'] },
122
- '/naturehub': { desc: 'NatureHub işlemleri (post|dm|inbox|forum|list)', needsArg: true, run: ['naturehub', 'post'] },
121
+ '/naturehub': { desc: 'Bota mesaj gönder (text gerek)', needsArg: true, run: ['naturehub', 'post'] },
123
122
  '/dashboard': { desc: 'Web dashboard başlat (port 7421)', run: ['dashboard', 'start'] },
124
123
  };
125
124
 
126
- const MEMORY_DIR = path.join(os.homedir(), '.natureco', 'memory');
127
- const SESSION_DIR = path.join(os.homedir(), '.natureco', 'sessions');
128
- const SESSIONS_INDEX = path.join(os.homedir(), '.natureco', 'sessions.json');
129
- const REPL_STATE = path.join(os.homedir(), '.natureco', 'repl-state.json');
125
+ // Profil desteği: --profile <ad> ile ~/.natureco-<ad> kullanılır (config ile tutarlı)
126
+ const { CONFIG_DIR: PROFILE_DIR } = require('../utils/config');
127
+ const MEMORY_DIR = path.join(PROFILE_DIR, 'memory');
128
+ const SESSION_DIR = path.join(PROFILE_DIR, 'sessions');
129
+ const SESSIONS_INDEX = path.join(PROFILE_DIR, 'sessions.json');
130
+ const REPL_STATE = path.join(PROFILE_DIR, 'repl-state.json');
130
131
 
131
132
  function ensureDir(dir) {
132
133
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
133
134
  }
134
135
 
135
136
  function getConfig() {
137
+ // Merkezi config'e delege — --profile desteği, backup ve validation
138
+ // yerel kopyada yoktu; REPL kullanıcının gerçek config'ini okuyordu
136
139
  try {
137
- return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.natureco', 'config.json'), 'utf8'));
140
+ return require('../utils/config').getConfig() || {};
138
141
  } catch { return {}; }
139
142
  }
140
143
 
141
- function isMiniMax(url) {
142
- return url && (url.includes('minimax.io') || url.includes('minimaxi.com') || url.includes('minimax.cn'));
143
- }
144
- function isGemini(url) {
145
- return url && (url.includes('generativelanguage.googleapis.com') || url.includes('gemini'));
146
- }
144
+ // Tek doğruluk kaynağı: provider-detect (MiniMax /v1 toleransı dahil)
145
+ const { isMiniMax, isGemini, buildChatEndpoint } = require('../utils/provider-detect');
147
146
 
148
147
  function loadMemory(username) {
149
148
  const file = path.join(MEMORY_DIR, `${(username || 'default').toLowerCase()}.json`);
@@ -197,8 +196,6 @@ function saveSession(messages, meta) {
197
196
  return id;
198
197
  }
199
198
 
200
-
201
-
202
199
  function loadSession(id) {
203
200
  // ID veya index
204
201
  const idx = loadSessionsIndex();
@@ -227,12 +224,10 @@ function extractFacts(messages, currentFacts) {
227
224
 
228
225
  // İsim/tercih pattern'leri
229
226
  const patterns = [
230
- { re: /(?:benim ad[ıi]m|ad[ıi]m|ad[ıi]n|ad[ıi]n[ıi]z|ismim|isim|I'm called|my name is)\s+([A-ZÇĞİÖŞÜa-zçğıöşü]+)/i, val: m => `Adı: ${m[1]}` },
231
- { re: /(?:yaşıyorum|yaşadığım|oturuyorum|I live in)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Yaşadığı yer: ${m[1].trim()}` },
232
- { re: /([A-ZÇĞİÖŞÜa-zçğıöşü]{2,})[''](?:da|de|ta|te)\s+(?:yaşıyorum|yaşadığım|oturuyorum|bulunuyorum|kalıyorum)/i, val: m => `Yaşadığı yer: ${m[1]}` },
233
- { re: /(?:seviyorum|severim|sevdiğim|hoşlanırım|beğenirim|like|love)\s+(?:şu|bu)?\s*([a-zA-ZçğıöşüÇĞİÖŞÜ\s]{2,30})/i, val: m => `Sevdiği şey: ${m[1].trim()}` },
234
- { re: /(?:çalışıyorum|işim|mesleğim|çalışırım|I work as)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Meslek: ${m[1].trim()}` },
235
- { re: /(\d{1,3})\s*(?:yaş[ıi]nday[ıi]m|yaş[ıi]nda)/i, val: m => `Yaş: ${m[1]}` },
227
+ { re: /(?:benim adım|adım|I'm called|my name is)\s+([A-ZÇĞİÖŞÜa-zçğıöşü]+)/i, val: m => `Adı: ${m[1]}` },
228
+ { re: /(?:yaşıyorum|yaşadığım|I live in)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Yaşadığı yer: ${m[1].trim()}` },
229
+ { 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()}` },
230
+ { re: /(?:çalışıyorum|işim|mesleğim|I work as)\s+([A-ZÇĞİÖŞÜa-zçğıöşü\s]+)/i, val: m => `Meslek: ${m[1].trim()}` },
236
231
  ];
237
232
 
238
233
  for (const p of patterns) {
@@ -251,12 +246,7 @@ function extractFacts(messages, currentFacts) {
251
246
 
252
247
  function apiRequest(providerUrl, providerApiKey, body, stream = false, retries = 3) {
253
248
  return new Promise((resolve, reject) => {
254
- const isMM = isMiniMax(providerUrl);
255
- const endpoint = isMM
256
- ? `${providerUrl.replace(/\/+$/, '')}/v1/text/chatcompletion_v2`
257
- : isGemini(providerUrl)
258
- ? `${providerUrl.replace(/\/+$/, '')}/openai/chat/completions`
259
- : `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
249
+ const endpoint = buildChatEndpoint(providerUrl);
260
250
  const doRequest = (attempt) => {
261
251
  const req = https.request(endpoint, {
262
252
  method: 'POST',
@@ -612,11 +602,9 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
612
602
  const res = await apiRequest(providerUrl, providerApiKey, body, false);
613
603
  const msg = res.choices?.[0]?.message || {};
614
604
  const content = msg.content || '';
615
- if (onChunk !== noopCallback) {
616
- for (const char of content) {
617
- onChunk(char);
618
- await new Promise(r => setTimeout(r, 8));
619
- }
605
+ for (const char of content) {
606
+ onChunk(char);
607
+ await new Promise(r => setTimeout(r, 8));
620
608
  }
621
609
  fullText = content;
622
610
  // Non-stream tool call desteği
@@ -629,13 +617,8 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
629
617
  break;
630
618
  }
631
619
 
632
- // OpenAI uyumlu streaming (veya MiniMax /v1/text/chatcompletion_v2)
633
- // v5.9.5: Gemini /openai/chat/completions — provider-detect.js buildChatEndpoint
634
- const endpoint = isMM
635
- ? `${providerUrl.replace(/\/+$/, '')}/v1/text/chatcompletion_v2`
636
- : isGemini(providerUrl)
637
- ? `${providerUrl.replace(/\/+$/, '')}/openai/chat/completions`
638
- : `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
620
+ // OpenAI uyumlu streaming tek doğruluk kaynağı buildChatEndpoint
621
+ const endpoint = buildChatEndpoint(providerUrl);
639
622
  let result;
640
623
  try {
641
624
  result = await new Promise((resolve, reject) => {
@@ -803,7 +786,7 @@ async function processToolCalls(toolCalls, onToolCall, onAsk) {
803
786
  const name = tc.function?.name || tc.name;
804
787
  const argsStr = tc.function?.arguments || tc.args || '{}';
805
788
  const id = tc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
806
- let args = {};
789
+ let args;
807
790
  try {
808
791
  args = typeof argsStr === 'string' ? JSON.parse(argsStr) : argsStr;
809
792
  } catch (e) {
@@ -1107,6 +1090,8 @@ async function startRepl(args) {
1107
1090
  memorySnapshotBlock, skillsIndexBlock, projectRules,
1108
1091
  crossSessionContext: crossSessionContext || '',
1109
1092
  userHome: cfg.userHome || '',
1093
+ platform: process.platform,
1094
+ desktopPath: cfg.userHome ? path.join(cfg.userHome, 'Desktop') : '',
1110
1095
  hasHistory: messages.length > 0,
1111
1096
  memoryFacts: memory.facts || [],
1112
1097
  };
@@ -1127,7 +1112,8 @@ async function startRepl(args) {
1127
1112
  console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
1128
1113
  console.log(tui.C.muted(' Provider: ') + tui.C.brand(providerUrl.replace(/https?:\/\//, '')));
1129
1114
  console.log(tui.C.muted(' Model: ') + tui.C.brand(model));
1130
- console.log(tui.C.muted(' Kullanıcı: ') + tui.C.brand((memory.nickname || cfg.userName) + (memory.nickname ? ` (${cfg.userName})` : '')));
1115
+ const displayUser = memory.nickname || cfg.userName || require('os').userInfo().username || 'Kullanıcı';
1116
+ console.log(tui.C.muted(' Kullanıcı: ') + tui.C.brand(displayUser + (memory.nickname && cfg.userName ? ` (${cfg.userName})` : '')));
1131
1117
  console.log(tui.C.muted(' Bot: ') + tui.C.brand(memory.botName || 'Asistan'));
1132
1118
  if (messages.length > 1) {
1133
1119
  console.log(tui.C.muted(' Oturum: ') + tui.C.amber(`${messages.filter(m => m.role === 'user' || m.role === 'assistant').length} mesaj (resume)`));
@@ -1162,9 +1148,6 @@ async function startRepl(args) {
1162
1148
  let totalInputTokens = 0;
1163
1149
  let totalOutputTokens = 0;
1164
1150
 
1165
- let _pendingCount = 0;
1166
- let _closeRequested = false;
1167
-
1168
1151
  enableBracketedPaste(process.stdout);
1169
1152
 
1170
1153
  const rl = readline.createInterface({
@@ -1173,7 +1156,12 @@ async function startRepl(args) {
1173
1156
  prompt: tui.styled('\n You ', { color: tui.PALETTE.primary, bold: true }),
1174
1157
  terminal: true,
1175
1158
  });
1176
- rl.prompt();
1159
+ // Pipe/script kullanımında EOF, yanıt hâlâ üretilirken gelir —
1160
+ // aktif işlem varken kapanışı bekletmek için sayaç + kapalı-rl koruması
1161
+ let _busy = 0;
1162
+ let _rlClosed = false;
1163
+ const safePrompt = () => { if (!_rlClosed) rl.prompt(); };
1164
+ safePrompt();
1177
1165
 
1178
1166
  const cleanup = async (exitCode = 0) => {
1179
1167
  if (messages.length > 1) {
@@ -1273,10 +1261,18 @@ async function startRepl(args) {
1273
1261
  process.on('SIGTERM', () => cleanup(0));
1274
1262
 
1275
1263
  rl.on('line', async (input) => {
1276
- _pendingCount++;
1264
+ // BOM ve benzeri görünmez karakterleri temizle (PowerShell echo BOM'lu gönderir)
1265
+ const line = restoreNewlines(input).replace(/[\u200B\u200C\u200D\uFEFF]/g, '').trim();
1266
+ if (!line) { safePrompt(); return; }
1267
+ _busy++;
1277
1268
  try {
1278
- const line = restoreNewlines(input).trim();
1279
- if (!line) { rl.prompt(); return; }
1269
+ await handleLine(line);
1270
+ } finally {
1271
+ _busy--;
1272
+ }
1273
+ });
1274
+
1275
+ async function handleLine(line) {
1280
1276
 
1281
1277
  // Çok satırlı paste: output filter'a echo'yu durdurma sinyalini ver
1282
1278
  clearPasteContext();
@@ -1435,7 +1431,7 @@ async function startRepl(args) {
1435
1431
  console.log(chalk.yellow(` Bilinmeyen komut: /${cmd}. /help yazın.`));
1436
1432
  }
1437
1433
  }
1438
- rl.prompt();
1434
+ safePrompt();
1439
1435
  return;
1440
1436
  }
1441
1437
 
@@ -1474,7 +1470,8 @@ async function startRepl(args) {
1474
1470
  // v5.13.0: Run workflow FIRST for every request
1475
1471
  process.stdout.write(tui.styled('\r 🔧 workflow... ', { color: tui.PALETTE.muted }));
1476
1472
  const wfToolDefs = getToolDefs();
1477
- const wfResult = await executeTool('workflow', { action: 'run', task: line }, wfToolDefs);
1473
+ const recentHistory = messages.length > 1 ? messages.slice(-10) : [];
1474
+ const wfResult = await executeTool('workflow', { action: 'run', task: line, conversationHistory: recentHistory }, wfToolDefs);
1478
1475
  const wf = wfResult?.result || {};
1479
1476
  if (wf.success !== false) {
1480
1477
  const loaded = wf.skillsLoaded && wf.skillsLoaded.length > 0 ? ` [skill: ${wf.skillsLoaded.join(', ')}]` : '';
@@ -1530,7 +1527,7 @@ async function startRepl(args) {
1530
1527
  messages,
1531
1528
  model,
1532
1529
  // v5.6.12: Callback bos - tam metin 'reply' olarak gelecek (non-stream mode)
1533
- noopCallback,
1530
+ () => {},
1534
1531
  // Tool call callback — Hermes-style per-tool status line
1535
1532
  ((toolEvent) => {
1536
1533
  const name = toolEvent.name;
@@ -1583,21 +1580,17 @@ async function startRepl(args) {
1583
1580
  process.stdout.write('\n');
1584
1581
  console.log(chalk.red(' ❌ ' + err.message));
1585
1582
  }
1586
- rl.prompt();
1587
- } finally {
1588
- _pendingCount--;
1589
- if (_closeRequested && _pendingCount === 0) {
1590
- await cleanup(0);
1591
- }
1592
- }
1593
- });
1583
+ safePrompt();
1584
+ }
1594
1585
 
1595
1586
  rl.on('close', () => {
1596
- if (_pendingCount > 0) {
1597
- _closeRequested = true;
1598
- } else {
1587
+ _rlClosed = true;
1588
+ // EOF (pipe/script): süren yanıt varsa bitmesini bekle, sonra kapan
1589
+ const wait = () => {
1590
+ if (_busy > 0) { setTimeout(wait, 200); return; }
1599
1591
  cleanup(0);
1600
- }
1592
+ };
1593
+ wait();
1601
1594
  });
1602
1595
  }
1603
1596
 
@@ -1,121 +1,121 @@
1
- /**
2
- * mem0-memory.js — Mem0 memory provider
3
- *
4
- * Uses the mem0ai npm package (platform API) or local OSS Memory class.
5
- * Requires MEM0_API_KEY env var or config.mem0.apiKey.
6
- * npm install mem0ai
7
- */
8
-
9
- const { MemoryProvider, registerProvider } = require('../utils/memory-provider');
10
-
11
- class Mem0MemoryProvider extends MemoryProvider {
12
- constructor(config = {}) {
13
- super(config);
14
- this.name = 'mem0';
15
- this._client = null;
16
- this._mode = 'platform'; // or 'oss'
17
- }
18
-
19
- _getConfig() {
20
- return this.config.mem0 || this.config.Mem0 || {};
21
- }
22
-
23
- _apiKey() {
24
- return process.env.MEM0_API_KEY || this._getConfig().apiKey || '';
25
- }
26
-
27
- async _ensureClient() {
28
- if (this._client) return;
29
- try {
30
- const apiKey = this._apiKey();
31
- if (apiKey) {
32
- const { MemoryClient } = await import('mem0ai');
33
- this._client = new MemoryClient({ apiKey });
34
- this._mode = 'platform';
35
- } else {
36
- const { Memory } = await import('mem0ai/oss');
37
- this._client = new Memory(this._getConfig().oss || {});
38
- this._mode = 'oss';
39
- }
40
- } catch (e) {
41
- throw new Error('mem0ai paketi yuklu degil. npm install mem0ai');
42
- }
43
- }
44
-
45
- async add(userId, content, metadata = {}) {
46
- await this._ensureClient();
47
- const uid = userId || 'default';
48
- if (this._mode === 'platform') {
49
- const messages = [{ role: 'user', content }];
50
- await this._client.add(messages, { userId: uid, metadata });
51
- } else {
52
- const messages = [
53
- { role: 'user', content },
54
- { role: 'assistant', content: 'Bilgi kaydedildi.' },
55
- ];
56
- await this._client.add(messages, { userId: uid, metadata });
57
- }
58
- return { success: true, id: content, message: 'Mem0 memory added', provider: 'mem0' };
59
- }
60
-
61
- async search(query, options = {}) {
62
- await this._ensureClient();
63
- const uid = options.userId;
64
- const filters = uid ? { userId: uid } : {};
65
- if (options.limit) filters.limit = options.limit;
66
- try {
67
- const results = await this._client.search(query, { filters });
68
- return { success: true, results: (results.results || results || []).map(r => ({
69
- id: r.id || r.memory,
70
- content: r.memory || r.content || '',
71
- score: r.score || 0,
72
- metadata: r.metadata || {},
73
- })) };
74
- } catch (e) {
75
- return { success: false, error: e.message, results: [] };
76
- }
77
- }
78
-
79
- async list(userId) {
80
- await this._ensureClient();
81
- const uid = userId || 'default';
82
- try {
83
- const memories = await this._client.getAll({ userId: uid });
84
- return {
85
- success: true,
86
- memories: (memories || []).map(r => ({
87
- id: r.id || r.memory,
88
- content: r.memory || r.content || '',
89
- score: r.score || 0,
90
- metadata: r.metadata || {},
91
- })),
92
- };
93
- } catch (e) {
94
- return { success: false, error: e.message, memories: [] };
95
- }
96
- }
97
-
98
- async remove(id) {
99
- await this._ensureClient();
100
- try {
101
- await this._client.delete(id);
102
- return { success: true, message: 'Mem0 memory removed' };
103
- } catch (e) {
104
- return { success: false, error: e.message };
105
- }
106
- }
107
-
108
- async clear(userId) {
109
- await this._ensureClient();
110
- const uid = userId || 'default';
111
- try {
112
- await this._client.deleteAll({ userId: uid });
113
- return { success: true, message: `Mem0 memory cleared for ${uid}` };
114
- } catch (e) {
115
- return { success: false, error: e.message };
116
- }
117
- }
118
- }
119
-
120
- registerProvider('mem0', Mem0MemoryProvider);
121
- module.exports = Mem0MemoryProvider;
1
+ /**
2
+ * mem0-memory.js — Mem0 memory provider
3
+ *
4
+ * Uses the mem0ai npm package (platform API) or local OSS Memory class.
5
+ * Requires MEM0_API_KEY env var or config.mem0.apiKey.
6
+ * npm install mem0ai
7
+ */
8
+
9
+ const { MemoryProvider, registerProvider } = require('../utils/memory-provider');
10
+
11
+ class Mem0MemoryProvider extends MemoryProvider {
12
+ constructor(config = {}) {
13
+ super(config);
14
+ this.name = 'mem0';
15
+ this._client = null;
16
+ this._mode = 'platform'; // or 'oss'
17
+ }
18
+
19
+ _getConfig() {
20
+ return this.config.mem0 || this.config.Mem0 || {};
21
+ }
22
+
23
+ _apiKey() {
24
+ return process.env.MEM0_API_KEY || this._getConfig().apiKey || '';
25
+ }
26
+
27
+ async _ensureClient() {
28
+ if (this._client) return;
29
+ try {
30
+ const apiKey = this._apiKey();
31
+ if (apiKey) {
32
+ const { MemoryClient } = await import('mem0ai');
33
+ this._client = new MemoryClient({ apiKey });
34
+ this._mode = 'platform';
35
+ } else {
36
+ const { Memory } = await import('mem0ai/oss');
37
+ this._client = new Memory(this._getConfig().oss || {});
38
+ this._mode = 'oss';
39
+ }
40
+ } catch (e) {
41
+ throw new Error('mem0ai paketi yuklu degil. npm install mem0ai', { cause: e });
42
+ }
43
+ }
44
+
45
+ async add(userId, content, metadata = {}) {
46
+ await this._ensureClient();
47
+ const uid = userId || 'default';
48
+ if (this._mode === 'platform') {
49
+ const messages = [{ role: 'user', content }];
50
+ await this._client.add(messages, { userId: uid, metadata });
51
+ } else {
52
+ const messages = [
53
+ { role: 'user', content },
54
+ { role: 'assistant', content: 'Bilgi kaydedildi.' },
55
+ ];
56
+ await this._client.add(messages, { userId: uid, metadata });
57
+ }
58
+ return { success: true, id: content, message: 'Mem0 memory added', provider: 'mem0' };
59
+ }
60
+
61
+ async search(query, options = {}) {
62
+ await this._ensureClient();
63
+ const uid = options.userId;
64
+ const filters = uid ? { userId: uid } : {};
65
+ if (options.limit) filters.limit = options.limit;
66
+ try {
67
+ const results = await this._client.search(query, { filters });
68
+ return { success: true, results: (results.results || results || []).map(r => ({
69
+ id: r.id || r.memory,
70
+ content: r.memory || r.content || '',
71
+ score: r.score || 0,
72
+ metadata: r.metadata || {},
73
+ })) };
74
+ } catch (e) {
75
+ return { success: false, error: e.message, results: [] };
76
+ }
77
+ }
78
+
79
+ async list(userId) {
80
+ await this._ensureClient();
81
+ const uid = userId || 'default';
82
+ try {
83
+ const memories = await this._client.getAll({ userId: uid });
84
+ return {
85
+ success: true,
86
+ memories: (memories || []).map(r => ({
87
+ id: r.id || r.memory,
88
+ content: r.memory || r.content || '',
89
+ score: r.score || 0,
90
+ metadata: r.metadata || {},
91
+ })),
92
+ };
93
+ } catch (e) {
94
+ return { success: false, error: e.message, memories: [] };
95
+ }
96
+ }
97
+
98
+ async remove(id) {
99
+ await this._ensureClient();
100
+ try {
101
+ await this._client.delete(id);
102
+ return { success: true, message: 'Mem0 memory removed' };
103
+ } catch (e) {
104
+ return { success: false, error: e.message };
105
+ }
106
+ }
107
+
108
+ async clear(userId) {
109
+ await this._ensureClient();
110
+ const uid = userId || 'default';
111
+ try {
112
+ await this._client.deleteAll({ userId: uid });
113
+ return { success: true, message: `Mem0 memory cleared for ${uid}` };
114
+ } catch (e) {
115
+ return { success: false, error: e.message };
116
+ }
117
+ }
118
+ }
119
+
120
+ registerProvider('mem0', Mem0MemoryProvider);
121
+ module.exports = Mem0MemoryProvider;