natureco-cli 1.0.17 → 1.0.18

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.17",
3
+ "version": "1.0.18",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -1,698 +0,0 @@
1
- const chalk = require('chalk');
2
- const http = require('http');
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { exec } = require('child_process');
6
- const { getConfig, CONFIG_DIR } = require('../utils/config');
7
-
8
- const PORT = 3848;
9
- const PID_FILE = path.join(CONFIG_DIR, 'dashboard.pid');
10
-
11
- async function dashboard(action) {
12
- if (!action || action === 'start') {
13
- return startDashboard();
14
- }
15
-
16
- if (action === 'stop') {
17
- return stopDashboard();
18
- }
19
-
20
- if (action === 'status') {
21
- return statusDashboard();
22
- }
23
-
24
- console.log(chalk.red('\n❌ Unknown action\n'));
25
- console.log(chalk.gray('Available actions: start, stop, status\n'));
26
- process.exit(1);
27
- }
28
-
29
- function startDashboard() {
30
- // Check if already running
31
- if (fs.existsSync(PID_FILE)) {
32
- const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
33
- try {
34
- process.kill(pid, 0);
35
- console.log(chalk.yellow('\n⚠️ Dashboard is already running\n'));
36
- console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
37
- console.log(chalk.gray('\nStop with: natureco dashboard stop\n'));
38
- return;
39
- } catch {
40
- // Process not running, remove stale PID file
41
- fs.unlinkSync(PID_FILE);
42
- }
43
- }
44
-
45
- const config = getConfig();
46
-
47
- if (!config.apiKey) {
48
- console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
49
- process.exit(1);
50
- }
51
-
52
- console.log(chalk.cyan('🚀 Starting NatureCo Dashboard...\n'));
53
-
54
- const server = http.createServer((req, res) => {
55
- // CORS headers
56
- res.setHeader('Access-Control-Allow-Origin', '*');
57
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
58
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
59
-
60
- if (req.method === 'OPTIONS') {
61
- res.writeHead(200);
62
- res.end();
63
- return;
64
- }
65
-
66
- if (req.url === '/' || req.url === '/index.html') {
67
- res.writeHead(200, { 'Content-Type': 'text/html' });
68
- res.end(getHTML(config));
69
- } else if (req.url === '/config') {
70
- res.writeHead(200, { 'Content-Type': 'application/json' });
71
- res.end(JSON.stringify({
72
- apiKey: config.apiKey,
73
- defaultBot: config.defaultBot,
74
- defaultBotId: config.defaultBotId,
75
- skills: config.skills || { enabled: true, list: [] },
76
- mcpServers: config.mcpServers || {},
77
- }));
78
- } else {
79
- res.writeHead(404);
80
- res.end('Not Found');
81
- }
82
- });
83
-
84
- server.listen(PORT, () => {
85
- // Save PID
86
- fs.writeFileSync(PID_FILE, process.pid.toString());
87
-
88
- console.log(chalk.green('✅ Dashboard started!\n'));
89
- console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
90
- console.log(chalk.gray('\nOpening browser...\n'));
91
-
92
- // Open browser
93
- const url = `http://localhost:${PORT}`;
94
- const platform = process.platform;
95
-
96
- if (platform === 'darwin') {
97
- exec(`open ${url}`);
98
- } else if (platform === 'win32') {
99
- exec(`start ${url}`);
100
- } else {
101
- exec(`xdg-open ${url}`);
102
- }
103
-
104
- console.log(chalk.gray('Press Ctrl+C to stop the server\n'));
105
- });
106
-
107
- // Handle shutdown
108
- process.on('SIGINT', () => {
109
- console.log(chalk.yellow('\n\n⏳ Shutting down dashboard...\n'));
110
- server.close(() => {
111
- if (fs.existsSync(PID_FILE)) {
112
- fs.unlinkSync(PID_FILE);
113
- }
114
- console.log(chalk.green('✅ Dashboard stopped\n'));
115
- process.exit(0);
116
- });
117
- });
118
- }
119
-
120
- function stopDashboard() {
121
- if (!fs.existsSync(PID_FILE)) {
122
- console.log(chalk.gray('\n⚠️ Dashboard is not running\n'));
123
- return;
124
- }
125
-
126
- const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
127
-
128
- try {
129
- process.kill(pid, 'SIGTERM');
130
- fs.unlinkSync(PID_FILE);
131
- console.log(chalk.green('\n✅ Dashboard stopped\n'));
132
- } catch (err) {
133
- console.log(chalk.red(`\n❌ Failed to stop dashboard: ${err.message}\n`));
134
- // Remove stale PID file
135
- if (fs.existsSync(PID_FILE)) {
136
- fs.unlinkSync(PID_FILE);
137
- }
138
- }
139
- }
140
-
141
- function statusDashboard() {
142
- if (!fs.existsSync(PID_FILE)) {
143
- console.log(chalk.gray('\n⚠️ Dashboard is not running\n'));
144
- console.log(chalk.gray('Start with: natureco dashboard start\n'));
145
- return;
146
- }
147
-
148
- const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
149
-
150
- try {
151
- process.kill(pid, 0);
152
- console.log(chalk.green('\n✅ Dashboard is running\n'));
153
- console.log(chalk.cyan('PID:'), chalk.white(pid));
154
- console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
155
- console.log(chalk.gray('\nStop with: natureco dashboard stop\n'));
156
- } catch {
157
- console.log(chalk.gray('\n⚠️ Dashboard is not running (stale PID file)\n'));
158
- fs.unlinkSync(PID_FILE);
159
- }
160
- }
161
-
162
- function getHTML(config) {
163
- return `<!DOCTYPE html>
164
- <html lang="en">
165
- <head>
166
- <meta charset="UTF-8">
167
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
168
- <title>NatureCo Dashboard</title>
169
- <style>
170
- * {
171
- margin: 0;
172
- padding: 0;
173
- box-sizing: border-box;
174
- }
175
-
176
- body {
177
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
178
- background: #0d1117;
179
- color: #c9d1d9;
180
- height: 100vh;
181
- overflow: hidden;
182
- }
183
-
184
- .container {
185
- display: flex;
186
- height: 100vh;
187
- }
188
-
189
- .sidebar {
190
- width: 280px;
191
- background: #161b22;
192
- border-right: 1px solid #30363d;
193
- display: flex;
194
- flex-direction: column;
195
- }
196
-
197
- .sidebar-header {
198
- padding: 20px;
199
- border-bottom: 1px solid #30363d;
200
- }
201
-
202
- .logo {
203
- font-size: 20px;
204
- font-weight: bold;
205
- color: #00E676;
206
- display: flex;
207
- align-items: center;
208
- gap: 8px;
209
- }
210
-
211
- .sidebar-content {
212
- flex: 1;
213
- overflow-y: auto;
214
- padding: 16px;
215
- }
216
-
217
- .section {
218
- margin-bottom: 24px;
219
- }
220
-
221
- .section-title {
222
- font-size: 12px;
223
- font-weight: 600;
224
- color: #8b949e;
225
- text-transform: uppercase;
226
- margin-bottom: 8px;
227
- letter-spacing: 0.5px;
228
- }
229
-
230
- .bot-item, .item {
231
- padding: 10px 12px;
232
- background: #0d1117;
233
- border: 1px solid #30363d;
234
- border-radius: 6px;
235
- margin-bottom: 8px;
236
- cursor: pointer;
237
- transition: all 0.2s;
238
- }
239
-
240
- .bot-item:hover, .item:hover {
241
- background: #161b22;
242
- border-color: #00E676;
243
- }
244
-
245
- .bot-item.active {
246
- background: #00E676;
247
- color: #0d1117;
248
- border-color: #00E676;
249
- }
250
-
251
- .bot-name {
252
- font-weight: 600;
253
- font-size: 14px;
254
- }
255
-
256
- .bot-id {
257
- font-size: 11px;
258
- color: #8b949e;
259
- margin-top: 4px;
260
- }
261
-
262
- .bot-item.active .bot-id {
263
- color: #0d1117;
264
- opacity: 0.7;
265
- }
266
-
267
- .main {
268
- flex: 1;
269
- display: flex;
270
- flex-direction: column;
271
- }
272
-
273
- .header {
274
- padding: 20px 24px;
275
- border-bottom: 1px solid #30363d;
276
- background: #161b22;
277
- }
278
-
279
- .header-title {
280
- font-size: 18px;
281
- font-weight: 600;
282
- color: #00E676;
283
- }
284
-
285
- .chat-container {
286
- flex: 1;
287
- display: flex;
288
- flex-direction: column;
289
- overflow: hidden;
290
- }
291
-
292
- .messages {
293
- flex: 1;
294
- overflow-y: auto;
295
- padding: 24px;
296
- }
297
-
298
- .message {
299
- margin-bottom: 16px;
300
- display: flex;
301
- gap: 12px;
302
- }
303
-
304
- .message-avatar {
305
- width: 32px;
306
- height: 32px;
307
- border-radius: 50%;
308
- background: #00E676;
309
- display: flex;
310
- align-items: center;
311
- justify-content: center;
312
- font-weight: bold;
313
- color: #0d1117;
314
- flex-shrink: 0;
315
- }
316
-
317
- .message-content {
318
- flex: 1;
319
- }
320
-
321
- .message-author {
322
- font-weight: 600;
323
- font-size: 14px;
324
- margin-bottom: 4px;
325
- }
326
-
327
- .message-text {
328
- font-size: 14px;
329
- line-height: 1.6;
330
- white-space: pre-wrap;
331
- }
332
-
333
- .message.user .message-avatar {
334
- background: #58a6ff;
335
- }
336
-
337
- .input-container {
338
- padding: 16px 24px;
339
- border-top: 1px solid #30363d;
340
- background: #161b22;
341
- }
342
-
343
- .input-wrapper {
344
- display: flex;
345
- gap: 12px;
346
- }
347
-
348
- .input {
349
- flex: 1;
350
- padding: 12px 16px;
351
- background: #0d1117;
352
- border: 1px solid #30363d;
353
- border-radius: 6px;
354
- color: #c9d1d9;
355
- font-size: 14px;
356
- font-family: inherit;
357
- outline: none;
358
- transition: border-color 0.2s;
359
- }
360
-
361
- .input:focus {
362
- border-color: #00E676;
363
- }
364
-
365
- .send-btn {
366
- padding: 12px 24px;
367
- background: #00E676;
368
- color: #0d1117;
369
- border: none;
370
- border-radius: 6px;
371
- font-weight: 600;
372
- cursor: pointer;
373
- transition: all 0.2s;
374
- font-size: 14px;
375
- }
376
-
377
- .send-btn:hover {
378
- background: #00c853;
379
- }
380
-
381
- .send-btn:disabled {
382
- opacity: 0.5;
383
- cursor: not-allowed;
384
- }
385
-
386
- .loading {
387
- display: inline-block;
388
- width: 16px;
389
- height: 16px;
390
- border: 2px solid #30363d;
391
- border-top-color: #00E676;
392
- border-radius: 50%;
393
- animation: spin 0.8s linear infinite;
394
- }
395
-
396
- @keyframes spin {
397
- to { transform: rotate(360deg); }
398
- }
399
-
400
- .empty-state {
401
- display: flex;
402
- flex-direction: column;
403
- align-items: center;
404
- justify-content: center;
405
- height: 100%;
406
- color: #8b949e;
407
- text-align: center;
408
- padding: 24px;
409
- }
410
-
411
- .empty-icon {
412
- font-size: 48px;
413
- margin-bottom: 16px;
414
- }
415
-
416
- .empty-title {
417
- font-size: 18px;
418
- font-weight: 600;
419
- margin-bottom: 8px;
420
- color: #c9d1d9;
421
- }
422
-
423
- .empty-text {
424
- font-size: 14px;
425
- }
426
-
427
- .stat {
428
- display: flex;
429
- align-items: center;
430
- gap: 8px;
431
- padding: 8px 12px;
432
- background: #0d1117;
433
- border: 1px solid #30363d;
434
- border-radius: 6px;
435
- font-size: 13px;
436
- margin-bottom: 8px;
437
- }
438
-
439
- .stat-icon {
440
- color: #00E676;
441
- }
442
-
443
- ::-webkit-scrollbar {
444
- width: 8px;
445
- }
446
-
447
- ::-webkit-scrollbar-track {
448
- background: #0d1117;
449
- }
450
-
451
- ::-webkit-scrollbar-thumb {
452
- background: #30363d;
453
- border-radius: 4px;
454
- }
455
-
456
- ::-webkit-scrollbar-thumb:hover {
457
- background: #484f58;
458
- }
459
- </style>
460
- </head>
461
- <body>
462
- <div class="container">
463
- <div class="sidebar">
464
- <div class="sidebar-header">
465
- <div class="logo">
466
- 🌿 NatureCo
467
- </div>
468
- </div>
469
- <div class="sidebar-content">
470
- <div class="section">
471
- <div class="section-title">Bots</div>
472
- <div id="bots-list">
473
- <div class="loading"></div>
474
- </div>
475
- </div>
476
- <div class="section">
477
- <div class="section-title">Stats</div>
478
- <div class="stat">
479
- <span class="stat-icon">✨</span>
480
- <span id="skills-count">Skills: 0</span>
481
- </div>
482
- <div class="stat">
483
- <span class="stat-icon">🔌</span>
484
- <span id="mcp-count">MCP: 0</span>
485
- </div>
486
- <div class="stat">
487
- <span class="stat-icon">💬</span>
488
- <span id="sessions-count">Sessions: 0</span>
489
- </div>
490
- </div>
491
- </div>
492
- </div>
493
-
494
- <div class="main">
495
- <div class="header">
496
- <div class="header-title" id="current-bot">Select a bot</div>
497
- </div>
498
-
499
- <div class="chat-container">
500
- <div class="messages" id="messages">
501
- <div class="empty-state">
502
- <div class="empty-icon">💬</div>
503
- <div class="empty-title">Start a conversation</div>
504
- <div class="empty-text">Select a bot and send a message</div>
505
- </div>
506
- </div>
507
-
508
- <div class="input-container">
509
- <div class="input-wrapper">
510
- <input
511
- type="text"
512
- class="input"
513
- id="message-input"
514
- placeholder="Type your message..."
515
- disabled
516
- />
517
- <button class="send-btn" id="send-btn" disabled>Send</button>
518
- </div>
519
- </div>
520
- </div>
521
- </div>
522
- </div>
523
-
524
- <script>
525
- const API_BASE = 'https://api.natureco.me';
526
- let apiKey = '';
527
- let currentBotId = null;
528
- let currentBotName = '';
529
- let sessionId = null;
530
-
531
- // Load config
532
- fetch('/config')
533
- .then(r => r.json())
534
- .then(config => {
535
- apiKey = config.apiKey;
536
- loadBots();
537
- loadStats();
538
- });
539
-
540
- async function loadBots() {
541
- try {
542
- const response = await fetch(API_BASE + '/api/v1/bots', {
543
- headers: {
544
- 'Authorization': 'Bearer ' + apiKey,
545
- },
546
- });
547
-
548
- const data = await response.json();
549
- const botsList = document.getElementById('bots-list');
550
-
551
- if (data.bots && data.bots.length > 0) {
552
- botsList.innerHTML = data.bots.map(bot => \`
553
- <div class="bot-item" onclick="selectBot('\${bot.id}', '\${bot.name}')">
554
- <div class="bot-name">\${bot.name}</div>
555
- <div class="bot-id">\${bot.id.slice(0, 8)}...</div>
556
- </div>
557
- \`).join('');
558
- } else {
559
- botsList.innerHTML = '<div class="empty-text">No bots found</div>';
560
- }
561
- } catch (err) {
562
- console.error('Failed to load bots:', err);
563
- }
564
- }
565
-
566
- function selectBot(botId, botName) {
567
- currentBotId = botId;
568
- currentBotName = botName;
569
- sessionId = null;
570
-
571
- // Update UI
572
- document.getElementById('current-bot').textContent = botName;
573
- document.getElementById('message-input').disabled = false;
574
- document.getElementById('send-btn').disabled = false;
575
-
576
- // Clear messages
577
- document.getElementById('messages').innerHTML = \`
578
- <div class="empty-state">
579
- <div class="empty-icon">💬</div>
580
- <div class="empty-title">Start a conversation with \${botName}</div>
581
- <div class="empty-text">Type a message below</div>
582
- </div>
583
- \`;
584
-
585
- // Update active state
586
- document.querySelectorAll('.bot-item').forEach(item => {
587
- item.classList.remove('active');
588
- });
589
- event.target.closest('.bot-item').classList.add('active');
590
- }
591
-
592
- async function sendMessage() {
593
- const input = document.getElementById('message-input');
594
- const message = input.value.trim();
595
-
596
- if (!message || !currentBotId) return;
597
-
598
- // Add user message
599
- addMessage('user', 'You', message);
600
- input.value = '';
601
-
602
- // Disable input
603
- input.disabled = true;
604
- document.getElementById('send-btn').disabled = true;
605
-
606
- try {
607
- const response = await fetch('https://api.natureco.me/api/agent/chat', {
608
- method: 'POST',
609
- headers: {
610
- 'Authorization': 'Bearer ' + apiKey,
611
- 'Content-Type': 'application/json',
612
- 'X-User-ID': 'dashboard-user',
613
- },
614
- body: JSON.stringify({
615
- agent_id: currentBotId,
616
- message: message,
617
- platform: 'dashboard',
618
- user_id: 'dashboard-user',
619
- }),
620
- });
621
-
622
- const data = await response.json();
623
-
624
- if (data.reply) {
625
- addMessage('bot', currentBotName, data.reply);
626
- sessionId = data.session_id;
627
- }
628
- } catch (err) {
629
- addMessage('system', 'System', 'Error: ' + err.message);
630
- } finally {
631
- input.disabled = false;
632
- document.getElementById('send-btn').disabled = false;
633
- input.focus();
634
- }
635
- }
636
-
637
- function addMessage(type, author, text) {
638
- const messagesDiv = document.getElementById('messages');
639
-
640
- // Remove empty state
641
- const emptyState = messagesDiv.querySelector('.empty-state');
642
- if (emptyState) {
643
- emptyState.remove();
644
- }
645
-
646
- const messageDiv = document.createElement('div');
647
- messageDiv.className = 'message ' + type;
648
-
649
- const avatar = type === 'user' ? 'U' : type === 'bot' ? '🤖' : 'ℹ️';
650
-
651
- messageDiv.innerHTML = \`
652
- <div class="message-avatar">\${avatar}</div>
653
- <div class="message-content">
654
- <div class="message-author">\${author}</div>
655
- <div class="message-text">\${text}</div>
656
- </div>
657
- \`;
658
-
659
- messagesDiv.appendChild(messageDiv);
660
- messagesDiv.scrollTop = messagesDiv.scrollHeight;
661
- }
662
-
663
- async function loadStats() {
664
- // Load skills count
665
- try {
666
- const config = await fetch('/config').then(r => r.json());
667
-
668
- // Skills count
669
- const skillsCount = config.skills && config.skills.list ? config.skills.list.length : 0;
670
- document.getElementById('skills-count').textContent = 'Skills: ' + skillsCount;
671
-
672
- // MCP count
673
- const mcpCount = config.mcpServers ? Object.keys(config.mcpServers).length : 0;
674
- document.getElementById('mcp-count').textContent = 'MCP: ' + mcpCount;
675
-
676
- // Sessions count (placeholder)
677
- document.getElementById('sessions-count').textContent = 'Sessions: 0';
678
- } catch (err) {
679
- console.error('Failed to load stats:', err);
680
- }
681
- }
682
-
683
- // Event listeners
684
- document.getElementById('send-btn').addEventListener('click', sendMessage);
685
- document.getElementById('message-input').addEventListener('keypress', (e) => {
686
- if (e.key === 'Enter') {
687
- sendMessage();
688
- }
689
- });
690
-
691
- // Make selectBot global
692
- window.selectBot = selectBot;
693
- </script>
694
- </body>
695
- </html>`;
696
- }
697
-
698
- module.exports = dashboard;