natureco-cli 1.0.44 → 1.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "1.0.44",
3
+ "version": "1.0.48",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -13,55 +13,383 @@ function getConfig() {
13
13
  catch { return {}; }
14
14
  }
15
15
 
16
+ function getSkills() {
17
+ try {
18
+ const skillsUtil = require('../utils/skills');
19
+ return skillsUtil.getSkills().map(s => s.name || s.slug);
20
+ } catch { return []; }
21
+ }
22
+
23
+ function getMemory(botId) {
24
+ try {
25
+ const memFile = path.join(CONFIG_DIR, 'memory', `${botId}.json`);
26
+ return JSON.parse(fs.readFileSync(memFile, 'utf8'));
27
+ } catch { return {}; }
28
+ }
29
+
16
30
  function openBrowser(url) {
17
31
  const { exec } = require('child_process');
18
32
  const p = process.platform;
19
- exec(p === 'darwin' ? 'open ' + url : p === 'win32' ? 'start ' + url : 'xdg-open ' + url);
33
+ exec(p === 'darwin' ? `open ${url}` : p === 'win32' ? `start ${url}` : `xdg-open ${url}`);
34
+ }
35
+
36
+ const HTML = `<!DOCTYPE html>
37
+ <html lang="tr">
38
+ <head>
39
+ <meta charset="UTF-8">
40
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
41
+ <title>NatureCo Dashboard</title>
42
+ <style>
43
+ *{margin:0;padding:0;box-sizing:border-box;font-family:system-ui,-apple-system,sans-serif}
44
+ html,body{height:100%;overflow:hidden}
45
+ body{
46
+ background:#0a1628;
47
+ background-image:
48
+ radial-gradient(ellipse 80% 50% at 50% -20%, rgba(0,230,118,0.15) 0%, transparent 60%),
49
+ radial-gradient(ellipse 60% 40% at 80% 80%, rgba(0,188,212,0.08) 0%, transparent 50%),
50
+ radial-gradient(ellipse 40% 30% at 20% 60%, rgba(0,230,118,0.06) 0%, transparent 40%);
51
+ animation:bgShift 15s ease-in-out infinite alternate;
52
+ }
53
+ @keyframes bgShift{
54
+ 0%{
55
+ background-image:
56
+ radial-gradient(ellipse 80% 50% at 50% -20%, rgba(0,230,118,0.15) 0%, transparent 60%),
57
+ radial-gradient(ellipse 60% 40% at 80% 80%, rgba(0,188,212,0.08) 0%, transparent 50%),
58
+ radial-gradient(ellipse 40% 30% at 20% 60%, rgba(0,230,118,0.06) 0%, transparent 40%);
59
+ }
60
+ 100%{
61
+ background-image:
62
+ radial-gradient(ellipse 80% 50% at 30% 120%, rgba(0,230,118,0.12) 0%, transparent 60%),
63
+ radial-gradient(ellipse 60% 40% at 90% 20%, rgba(0,188,212,0.1) 0%, transparent 50%),
64
+ radial-gradient(ellipse 40% 30% at 10% 80%, rgba(0,230,118,0.08) 0%, transparent 40%);
65
+ }
66
+ }
67
+ .app{display:flex;height:100vh}
68
+ .sidebar{
69
+ width:240px;min-width:240px;
70
+ background:rgba(0,10,5,0.75);
71
+ backdrop-filter:blur(20px);
72
+ border-right:1px solid rgba(0,230,118,0.12);
73
+ display:flex;flex-direction:column;
74
+ overflow-y:auto;
75
+ }
76
+ .sidebar::-webkit-scrollbar{width:3px}
77
+ .sidebar::-webkit-scrollbar-thumb{background:rgba(0,230,118,0.25);border-radius:2px}
78
+ .logo-area{padding:18px 16px;border-bottom:1px solid rgba(0,230,118,0.08);flex-shrink:0}
79
+ .logo{color:#00E676;font-size:17px;font-weight:700;letter-spacing:-0.4px}
80
+ .logo-sub{color:rgba(0,230,118,0.35);font-size:10px;margin-top:2px}
81
+ .section{padding:12px 14px}
82
+ .section-label{color:rgba(0,230,118,0.35);font-size:9px;font-weight:600;letter-spacing:1.8px;text-transform:uppercase;margin-bottom:8px}
83
+ .bot-card{background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.2);border-radius:8px;padding:10px;display:flex;align-items:center;gap:8px;cursor:pointer;transition:all 0.2s}
84
+ .bot-card:hover{background:rgba(0,230,118,0.1);border-color:rgba(0,230,118,0.35)}
85
+ .avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#00C853,#00E676);display:flex;align-items:center;justify-content:center;color:#000;font-weight:700;font-size:13px;flex-shrink:0}
86
+ .bot-info .name{color:#e8f5e9;font-size:13px;font-weight:500}
87
+ .bot-info .model{color:rgba(0,230,118,0.45);font-size:10px;margin-top:1px}
88
+ .online-dot{width:7px;height:7px;background:#00E676;border-radius:50%;margin-left:auto;flex-shrink:0;box-shadow:0 0 6px rgba(0,230,118,0.8);animation:pulse 2s infinite}
89
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
90
+ .other-bot{padding:5px 0;color:rgba(0,230,118,0.4);font-size:12px;cursor:pointer;transition:color 0.2s;display:flex;align-items:center;gap:6px}
91
+ .other-bot:hover{color:#a5d6a7}
92
+ .other-avatar{width:22px;height:22px;border-radius:50%;background:rgba(0,230,118,0.1);border:1px solid rgba(0,230,118,0.15);display:flex;align-items:center;justify-content:center;color:rgba(0,230,118,0.5);font-size:9px;font-weight:700}
93
+ .ch-row{display:flex;align-items:center;gap:7px;padding:4px 0}
94
+ .ch-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
95
+ .ch-dot.on{background:#00E676;box-shadow:0 0 5px rgba(0,230,118,0.7)}
96
+ .ch-dot.off{background:rgba(255,255,255,0.15)}
97
+ .ch-name{font-size:12px}
98
+ .ch-name.on{color:#a5d6a7}
99
+ .ch-name.off{color:rgba(255,255,255,0.25)}
100
+ .skill-tag{display:inline-block;background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.15);border-radius:4px;padding:3px 8px;color:#81c784;font-size:10px;margin:2px 2px 2px 0}
101
+ .mem-row{font-size:11px;padding:2px 0}
102
+ .mem-key{color:rgba(0,230,118,0.4);font-size:9px;letter-spacing:0.5px;text-transform:uppercase;margin-top:5px}
103
+ .mem-val{color:#a5d6a7}
104
+ .sidebar-footer{margin-top:auto;padding:12px 14px;border-top:1px solid rgba(0,230,118,0.06);flex-shrink:0}
105
+ .footer-link{color:rgba(0,230,118,0.25);font-size:10px;text-decoration:none}
106
+ .footer-link:hover{color:rgba(0,230,118,0.5)}
107
+ .chat-area{flex:1;display:flex;flex-direction:column;min-width:0}
108
+ .chat-header{
109
+ padding:14px 20px;
110
+ border-bottom:1px solid rgba(0,230,118,0.08);
111
+ display:flex;align-items:center;gap:12px;
112
+ background:rgba(0,10,5,0.6);
113
+ backdrop-filter:blur(10px);
114
+ flex-shrink:0;
115
+ }
116
+ .header-bot-name{color:#e8f5e9;font-size:14px;font-weight:500}
117
+ .header-bot-model{color:rgba(0,230,118,0.4);font-size:11px}
118
+ .version-badge{margin-left:auto;background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.15);border-radius:20px;padding:3px 10px;color:rgba(0,230,118,0.5);font-size:10px}
119
+ .messages{flex:1;padding:16px 20px;overflow-y:auto;display:flex;flex-direction:column;gap:12px}
120
+ .messages::-webkit-scrollbar{width:3px}
121
+ .messages::-webkit-scrollbar-thumb{background:rgba(0,230,118,0.2);border-radius:2px}
122
+ .msg{display:flex;gap:8px;align-items:flex-end;animation:fadeIn 0.3s ease}
123
+ @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
124
+ .msg.user{flex-direction:row-reverse}
125
+ .msg-avatar{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;flex-shrink:0}
126
+ .msg-avatar.bot{background:linear-gradient(135deg,#00C853,#00E676);color:#000}
127
+ .msg-avatar.user{background:rgba(0,230,118,0.15);border:1px solid rgba(0,230,118,0.3);color:#00E676}
128
+ .bubble{padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.6;max-width:72%}
129
+ .bubble.user{background:linear-gradient(135deg,#00C853,#00E676);color:#000;font-weight:500;border-radius:12px 12px 0 12px}
130
+ .bubble.bot{background:rgba(255,255,255,0.04);border:1px solid rgba(0,230,118,0.1);color:#c8e6c9;border-radius:12px 12px 12px 0}
131
+ .typing{display:flex;gap:4px;padding:10px 14px;background:rgba(255,255,255,0.04);border:1px solid rgba(0,230,118,0.1);border-radius:12px 12px 12px 0;width:fit-content}
132
+ .typing span{width:6px;height:6px;background:rgba(0,230,118,0.5);border-radius:50%;animation:typing 1.2s infinite}
133
+ .typing span:nth-child(2){animation-delay:0.2s}
134
+ .typing span:nth-child(3){animation-delay:0.4s}
135
+ @keyframes typing{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}
136
+ .input-area{
137
+ padding:14px 20px;
138
+ border-top:1px solid rgba(0,230,118,0.08);
139
+ display:flex;gap:10px;
140
+ background:rgba(0,10,5,0.6);
141
+ backdrop-filter:blur(10px);
142
+ flex-shrink:0;
143
+ }
144
+ .input-box{
145
+ flex:1;
146
+ background:rgba(255,255,255,0.04);
147
+ border:1px solid rgba(0,230,118,0.15);
148
+ border-radius:8px;
149
+ padding:10px 14px;
150
+ color:#e8f5e9;
151
+ font-size:13px;
152
+ outline:none;
153
+ transition:border-color 0.2s;
154
+ }
155
+ .input-box:focus{border-color:rgba(0,230,118,0.4)}
156
+ .input-box::placeholder{color:rgba(0,230,118,0.25)}
157
+ .send-btn{
158
+ background:linear-gradient(135deg,#00C853,#00E676);
159
+ border:none;border-radius:8px;
160
+ padding:10px 18px;
161
+ color:#000;font-weight:600;font-size:13px;
162
+ cursor:pointer;transition:opacity 0.2s,transform 0.1s;
163
+ white-space:nowrap;
164
+ }
165
+ .send-btn:hover{opacity:0.9}
166
+ .send-btn:active{transform:scale(0.97)}
167
+ </style>
168
+ </head>
169
+ <body>
170
+ <div class="app">
171
+ <div class="sidebar" id="sidebar">
172
+ <div class="logo-area">
173
+ <div class="logo">NatureCo</div>
174
+ <div class="logo-sub" id="ver-label">Terminal</div>
175
+ </div>
176
+ <div class="section" id="active-bot-section">
177
+ <div class="section-label">Aktif Bot</div>
178
+ <div class="bot-card">
179
+ <div class="avatar">N</div>
180
+ <div class="bot-info">
181
+ <div class="name" id="active-bot-name">Yükleniyor...</div>
182
+ <div class="model" id="active-bot-model">NatureCo</div>
183
+ </div>
184
+ <div class="online-dot"></div>
185
+ </div>
186
+ </div>
187
+ <div class="section" id="other-bots-section" style="padding-top:0">
188
+ <div class="section-label">Diğer Botlar</div>
189
+ <div id="other-bots-list"></div>
190
+ </div>
191
+ <div class="section" style="padding-top:0">
192
+ <div class="section-label">Kanallar</div>
193
+ <div id="channels-list"></div>
194
+ </div>
195
+ <div class="section" style="padding-top:0">
196
+ <div class="section-label">Skill'ler</div>
197
+ <div id="skills-list"></div>
198
+ </div>
199
+ <div class="section" style="padding-top:0">
200
+ <div class="section-label">Hafıza</div>
201
+ <div id="memory-section"></div>
202
+ </div>
203
+ <div class="sidebar-footer">
204
+ <a href="https://natureco.me" class="footer-link">natureco.me</a>
205
+ </div>
206
+ </div>
207
+ <div class="chat-area">
208
+ <div class="chat-header">
209
+ <div class="avatar" style="width:36px;height:36px;font-size:14px">N</div>
210
+ <div>
211
+ <div class="header-bot-name" id="header-bot-name">Nature Bot</div>
212
+ <div class="header-bot-model" id="header-bot-model">NatureCo</div>
213
+ </div>
214
+ <div class="version-badge" id="version-badge">v1.0.48</div>
215
+ </div>
216
+ <div class="messages" id="messages"></div>
217
+ <div class="input-area">
218
+ <input class="input-box" id="input" placeholder="Mesaj yaz..." />
219
+ <button class="send-btn" onclick="send()">Gönder</button>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ <script>
224
+ let cfg = {}, activeBotId = null;
225
+
226
+ async function init() {
227
+ try {
228
+ const r = await fetch('/config');
229
+ cfg = await r.json();
230
+ activeBotId = cfg.defaultBotId;
231
+ document.getElementById('active-bot-name').textContent = cfg.defaultBot || 'Bot';
232
+ document.getElementById('header-bot-name').textContent = cfg.defaultBot || 'Bot';
233
+ document.getElementById('ver-label').textContent = 'Terminal ' + (cfg.version || '');
234
+
235
+ const others = (cfg.bots || []).filter(b => b.id !== activeBotId);
236
+ const ol = document.getElementById('other-bots-list');
237
+ ol.innerHTML = others.map(b => \`<div class="other-bot" onclick="switchBot('\${b.id}','\${b.name}')"><div class="other-avatar">\${b.name[0]}</div>\${b.name}</div>\`).join('');
238
+
239
+ const channels = [
240
+ { name: 'Telegram', key: 'telegramToken' },
241
+ { name: 'WhatsApp', key: 'whatsappConnected' },
242
+ { name: 'Discord', key: 'discordToken' },
243
+ { name: 'Gateway', key: 'gatewayRunning' },
244
+ ];
245
+ document.getElementById('channels-list').innerHTML = channels.map(c => {
246
+ const on = !!(cfg[c.key]);
247
+ return \`<div class="ch-row"><div class="ch-dot \${on?'on':'off'}"></div><div class="ch-name \${on?'on':'off'}">\${c.name}</div></div>\`;
248
+ }).join('');
249
+ } catch(e) { console.error(e); }
250
+
251
+ try {
252
+ const sr = await fetch('/skills');
253
+ const skills = await sr.json();
254
+ document.getElementById('skills-list').innerHTML = skills.map(s => \`<span class="skill-tag">\${s}</span>\`).join('');
255
+ } catch {}
256
+
257
+ try {
258
+ const mr = await fetch('/memory');
259
+ const mem = await mr.json();
260
+ let html = '';
261
+ if (mem.name) html += \`<div class="mem-key">İsim</div><div class="mem-row mem-val">\${mem.name}</div>\`;
262
+ if (mem.facts && mem.facts.length) html += \`<div class="mem-key">Bilgiler</div><div class="mem-row mem-val">\${mem.facts.map(f=>typeof f==='object'?f.value:f).join(' · ')}</div>\`;
263
+ document.getElementById('memory-section').innerHTML = html || \`<div class="mem-row" style="color:rgba(0,230,118,0.25)">Hafıza boş</div>\`;
264
+ } catch {}
265
+ }
266
+
267
+ function switchBot(id, name) {
268
+ activeBotId = id;
269
+ document.getElementById('active-bot-name').textContent = name;
270
+ document.getElementById('header-bot-name').textContent = name;
271
+ document.getElementById('messages').innerHTML = '';
272
+ }
273
+
274
+ function addMsg(type, text) {
275
+ const m = document.getElementById('messages');
276
+ const initials = type === 'user' ? 'G' : 'N';
277
+ m.innerHTML += \`<div class="msg \${type}"><div class="msg-avatar \${type}">\${initials}</div><div class="bubble \${type}">\${text}</div></div>\`;
278
+ m.scrollTop = m.scrollHeight;
20
279
  }
21
280
 
22
- const HTML = '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>NatureCo Dashboard</title><style>*{margin:0;padding:0;box-sizing:border-box}body{background:#050a0e;color:#eceff1;font-family:system-ui,sans-serif;display:flex;height:100vh}.sidebar{width:260px;background:rgba(255,255,255,0.03);border-right:1px solid rgba(0,230,118,0.1);padding:20px;display:flex;flex-direction:column}.logo{color:#00E676;font-size:20px;font-weight:700;margin-bottom:8px;letter-spacing:1px}.chat{flex:1;display:flex;flex-direction:column}.header{padding:16px 20px;border-bottom:1px solid rgba(0,230,118,0.1);display:flex;align-items:center;gap:8px}.messages{flex:1;padding:20px;overflow-y:auto}.input-area{padding:20px;border-top:1px solid rgba(0,230,118,0.1);display:flex;gap:10px}.input-area input{flex:1;background:rgba(255,255,255,0.05);border:1px solid rgba(0,230,118,0.2);border-radius:8px;padding:12px;color:#eceff1;outline:none;font-size:14px}.input-area button{background:linear-gradient(135deg,#00E676,#00BCD4);border:none;border-radius:8px;padding:12px 24px;color:#000;font-weight:700;cursor:pointer;font-size:14px}.msg{margin:8px 0;display:flex}.msg.user{justify-content:flex-end}.msg.user .bubble{background:linear-gradient(135deg,#00E676,#00BCD4);color:#000;border-radius:12px 12px 0 12px;padding:10px 14px;max-width:70%;font-size:14px}.msg.bot .bubble{background:rgba(255,255,255,0.05);border:1px solid rgba(0,230,118,0.1);border-radius:12px 12px 12px 0;padding:10px 14px;max-width:70%;font-size:14px}</style></head><body><div class="sidebar"><div class="logo">NatureCo</div><div id="botName" style="color:#546e7a;font-size:14px;margin-top:4px">Loading...</div><div style="margin-top:auto;color:#546e7a;font-size:12px">natureco.me</div></div><div class="chat"><div class="header"><span id="chatBot" style="color:#eceff1;font-weight:600">Dashboard</span><span style="width:8px;height:8px;background:#00E676;border-radius:50%;display:inline-block"></span><span style="color:#00E676;font-size:12px">Online</span></div><div class="messages" id="messages"></div><div class="input-area"><input id="input" placeholder="Mesaj yaz..." onkeydown="if(event.key===\')send()"><button onclick="send()">Gonder</button></div></div><script>var cfg={};fetch("/config").then(function(r){return r.json()}).then(function(d){cfg=d;document.getElementById("botName").textContent=d.defaultBot||"-";document.getElementById("chatBot").textContent=d.defaultBot||"-"});function send(){var i=document.getElementById("input");var m=i.value.trim();if(!m)return;i.value="";addMsg("user",m);fetch("https://api.natureco.me/api/agent/chat",{method:"POST",headers:{"Content-Type":"application/json","Authorization":"Bearer "+cfg.apiKey,"X-User-ID":"dashboard-user"},body:JSON.stringify({agent_id:cfg.defaultBotId,message:m,platform:"dashboard",user_id:"dashboard-user"})}).then(function(r){return r.json()}).then(function(d){addMsg("bot",d.reply||d.message||"Hata")}).catch(function(){addMsg("bot","Baglanti hatasi")})}function addMsg(t,m){var d=document.getElementById("messages");var el=document.createElement("div");el.className="msg "+t;el.innerHTML="<div class=bubble>"+m+"</div>";d.appendChild(el);d.scrollTop=d.scrollHeight}</script></body></html>';
281
+ function showTyping() {
282
+ const m = document.getElementById('messages');
283
+ m.innerHTML += \`<div class="msg" id="typing-row"><div class="msg-avatar bot">N</div><div class="typing"><span></span><span></span><span></span></div></div>\`;
284
+ m.scrollTop = m.scrollHeight;
285
+ }
286
+
287
+ function hideTyping() {
288
+ const t = document.getElementById('typing-row');
289
+ if (t) t.remove();
290
+ }
291
+
292
+ async function send() {
293
+ const inp = document.getElementById('input');
294
+ const msg = inp.value.trim();
295
+ if (!msg || !cfg.apiKey) return;
296
+ inp.value = '';
297
+ addMsg('user', msg);
298
+ showTyping();
299
+ try {
300
+ const r = await fetch('https://api.natureco.me/api/agent/chat', {
301
+ method: 'POST',
302
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + cfg.apiKey, 'X-User-ID': 'dashboard-user' },
303
+ body: JSON.stringify({ agent_id: activeBotId, message: msg, platform: 'dashboard', user_id: 'dashboard-user' })
304
+ });
305
+ const d = await r.json();
306
+ hideTyping();
307
+ addMsg('bot', d.reply || d.message || 'Hata');
308
+ } catch { hideTyping(); addMsg('bot', 'Bağlantı hatası'); }
309
+ }
310
+
311
+ document.getElementById('input').addEventListener('keydown', e => { if (e.key === 'Enter') send(); });
312
+ init();
313
+ </script>
314
+ </body>
315
+ </html>`;
23
316
 
24
317
  function dashboard(action) {
25
318
  action = action || 'start';
26
319
  if (action === 'start') {
27
320
  const cfg = getConfig();
28
- const server = http.createServer((req, res) => {
321
+ const skills = getSkills();
322
+ const fs2 = require('fs');
323
+
324
+ let gatewayRunning = false;
325
+ try {
326
+ const pid = parseInt(fs2.readFileSync(path.join(CONFIG_DIR, 'gateway.pid'), 'utf8'));
327
+ process.kill(pid, 0);
328
+ gatewayRunning = true;
329
+ } catch {}
330
+
331
+ const bots = [];
332
+
333
+ const server = http.createServer(async (req, res) => {
334
+ const cors = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,DELETE', 'Access-Control-Allow-Headers': 'Content-Type' };
335
+
336
+ if (req.method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }
337
+
29
338
  if (req.url === '/config') {
30
- res.writeHead(200, { 'Content-Type': 'application/json' });
31
- res.end(JSON.stringify({ apiKey: cfg.apiKey, defaultBot: cfg.defaultBot, defaultBotId: cfg.defaultBotId }));
339
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
340
+ res.end(JSON.stringify({
341
+ apiKey: cfg.apiKey,
342
+ defaultBot: cfg.defaultBot,
343
+ defaultBotId: cfg.defaultBotId,
344
+ version: 'v1.0.48',
345
+ bots: cfg.bots || [],
346
+ telegramToken: cfg.telegramToken || null,
347
+ whatsappConnected: cfg.whatsappConnected || false,
348
+ discordToken: cfg.discordToken || null,
349
+ gatewayRunning,
350
+ }));
351
+ } else if (req.url === '/skills') {
352
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
353
+ res.end(JSON.stringify(skills));
354
+ } else if (req.url === '/memory') {
355
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
356
+ res.end(JSON.stringify(getMemory(cfg.defaultBotId || '')));
32
357
  } else {
33
358
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
34
359
  res.end(HTML);
35
360
  }
36
361
  });
362
+
37
363
  server.listen(PORT, () => {
38
364
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
39
365
  fs.writeFileSync(PID_FILE, String(process.pid));
40
- console.log(chalk.green('Dashboard started!'));
41
- console.log(chalk.cyan('URL: ') + 'http://localhost:' + PORT);
42
- openBrowser('http://localhost:' + PORT);
43
- console.log(chalk.gray('Press Ctrl+C to stop'));
366
+ console.log(chalk.green('\n✅ Dashboard başlatıldı!'));
367
+ console.log(chalk.cyan('URL: ') + `http://localhost:${PORT}`);
368
+ console.log(chalk.gray('Durdurmak için: Ctrl+C\n'));
369
+ openBrowser(`http://localhost:${PORT}`);
44
370
  });
371
+
45
372
  process.on('SIGINT', () => {
46
373
  try { fs.unlinkSync(PID_FILE); } catch {}
47
374
  server.close();
48
- console.log(chalk.green('Dashboard stopped'));
49
375
  process.exit(0);
50
376
  });
377
+
51
378
  } else if (action === 'stop') {
52
379
  try {
53
380
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
54
381
  process.kill(pid);
55
382
  fs.unlinkSync(PID_FILE);
56
- console.log(chalk.green('Dashboard stopped'));
57
- } catch { console.log(chalk.red('Dashboard not running')); }
383
+ console.log(chalk.green('Dashboard durduruldu'));
384
+ } catch { console.log(chalk.red('Dashboard çalışmıyor')); }
58
385
  } else if (action === 'status') {
59
386
  try {
60
387
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
61
- console.log(chalk.green('Dashboard running'));
388
+ process.kill(pid, 0);
389
+ console.log(chalk.green('✅ Dashboard çalışıyor'));
62
390
  console.log('PID:', pid);
63
391
  console.log('URL: http://localhost:' + PORT);
64
- } catch { console.log(chalk.gray('Dashboard not running')); }
392
+ } catch { console.log(chalk.gray('Dashboard çalışmıyor')); }
65
393
  }
66
394
  }
67
395
 
@@ -194,6 +194,9 @@ async function startWhatsAppProvider(sessionDir, config) {
194
194
  const { state, saveCreds } = await useMultiFileAuthState(sessionDir);
195
195
  const { version } = await fetchLatestBaileysVersion();
196
196
 
197
+ // Track last bot reply to prevent infinite loop
198
+ let lastBotReply = '';
199
+
197
200
  const sock = makeWASocket({
198
201
  version,
199
202
  auth: state,
@@ -240,43 +243,47 @@ async function startWhatsAppProvider(sessionDir, config) {
240
243
  const remoteJid = msg.key.remoteJid || '';
241
244
  const isLID = remoteJid.includes('@lid');
242
245
 
243
- // Handle LID format (new WhatsApp format)
244
- if (isLID) {
245
- if (msg.key.fromMe) {
246
- log('whatsapp', 'LID format detected, fromMe=true, accepting message', 'cyan');
247
- } else {
248
- log('whatsapp', 'LID format detected, fromMe=false, blocked', 'yellow');
249
- continue;
250
- }
246
+ // Skip bot's own replies (fromMe=true and not LID, or matches last bot reply)
247
+ const messageText = msg.message?.conversation ||
248
+ msg.message?.extendedTextMessage?.text ||
249
+ msg.message?.imageMessage?.caption ||
250
+ msg.message?.videoMessage?.caption ||
251
+ msg.message?.buttonsResponseMessage?.selectedDisplayText ||
252
+ msg.message?.listResponseMessage?.title ||
253
+ msg.message?.ephemeralMessage?.message?.conversation ||
254
+ msg.message?.viewOnceMessage?.message?.conversation ||
255
+ '';
256
+
257
+ // Skip if this is the bot's own reply
258
+ if (msg.key.fromMe && !isLID) {
259
+ log('whatsapp', 'Skipping bot own reply (fromMe=true, not LID)', 'gray');
260
+ continue;
261
+ }
262
+
263
+ // Skip if message matches last bot reply (prevent loop)
264
+ if (messageText === lastBotReply && lastBotReply !== '') {
265
+ log('whatsapp', 'Skipping duplicate bot reply (matches last sent)', 'gray');
266
+ continue;
251
267
  }
252
268
 
253
- // If fromMe and LID, accept without further checks (own conversation)
254
- if (msg.key.fromMe && isLID) {
255
- log('whatsapp', 'Own conversation (fromMe + LID), processing...', 'cyan');
269
+ // Handle LID format (new WhatsApp format)
270
+ if (isLID && !msg.key.fromMe) {
271
+ log('whatsapp', 'LID format detected, fromMe=false, blocked', 'yellow');
272
+ continue;
256
273
  }
257
274
 
258
275
  const sender = remoteJid.split('@')[0].split(':')[0];
259
276
  const allowedNumbers = config.whatsappAllowedNumbers || [];
260
277
 
261
- // Log incoming number before access control
278
+ // Log incoming number
262
279
  log('whatsapp', `Incoming from: +${sender}, allowed: ${JSON.stringify(allowedNumbers)}`, 'gray');
263
280
 
264
- // Access control - compare last 10 digits (skip if fromMe + LID)
281
+ // Access control - skip if fromMe + LID (own conversation)
265
282
  if (!(msg.key.fromMe && isLID) && allowedNumbers.length > 0 && !allowedNumbers.some(n => numberMatches(n, sender))) {
266
283
  log('whatsapp', `blocked message from +${sender} (not in allowed list)`, 'yellow');
267
284
  continue;
268
285
  }
269
286
 
270
- const messageText = msg.message?.conversation ||
271
- msg.message?.extendedTextMessage?.text ||
272
- msg.message?.imageMessage?.caption ||
273
- msg.message?.videoMessage?.caption ||
274
- msg.message?.buttonsResponseMessage?.selectedDisplayText ||
275
- msg.message?.listResponseMessage?.title ||
276
- msg.message?.ephemeralMessage?.message?.conversation ||
277
- msg.message?.viewOnceMessage?.message?.conversation ||
278
- '';
279
-
280
287
  if (!messageText) {
281
288
  log('whatsapp', `Message without text content. Keys: ${Object.keys(msg.message || {}).join(', ')}`, 'gray');
282
289
  continue;
@@ -284,7 +291,6 @@ async function startWhatsAppProvider(sessionDir, config) {
284
291
 
285
292
  const ownNumber = sock.user?.id?.split(':')[0].replace('@s.whatsapp.net', '') || 'unknown';
286
293
  log('whatsapp', `Inbound message +${sender} -> +${ownNumber} (${messageText.length} chars)`, 'cyan');
287
- log('whatsapp', `Message: "${messageText.substring(0, 100)}${messageText.length > 100 ? '...' : ''}"`, 'gray');
288
294
 
289
295
  try {
290
296
  const { sendMessage } = require('../utils/api');
@@ -294,10 +300,13 @@ async function startWhatsAppProvider(sessionDir, config) {
294
300
  const reply = response?.reply || response?.message || '';
295
301
 
296
302
  if (reply) {
297
- log('whatsapp', `API response: "${reply.substring(0, 100)}${reply.length > 100 ? '...' : ''}"`, 'gray');
298
303
  log('whatsapp', 'Sending reply...', 'cyan');
299
304
 
300
305
  await sock.sendMessage(msg.key.remoteJid, { text: reply });
306
+
307
+ // Store last bot reply to prevent loop
308
+ lastBotReply = reply;
309
+
301
310
  log('whatsapp', `Reply sent (${reply.length} chars)`, 'green');
302
311
  } else {
303
312
  log('whatsapp', 'No reply from API', 'yellow');