navada-edge-cli 2.0.0 → 2.1.1
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/lib/agent.js +328 -0
- package/lib/cli.js +15 -7
- package/lib/commands/ai.js +33 -43
- package/lib/commands/setup.js +57 -88
- package/lib/commands/system.js +85 -10
- package/lib/config.js +14 -1
- package/lib/registry.js +32 -17
- package/package.json +3 -2
package/lib/agent.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const navada = require('navada-edge-sdk');
|
|
8
|
+
const ui = require('./ui');
|
|
9
|
+
const config = require('./config');
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// NAVADA Edge Agent — personality + tools + routing
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const IDENTITY = {
|
|
16
|
+
name: 'NAVADA Edge',
|
|
17
|
+
role: 'AI Infrastructure Agent',
|
|
18
|
+
personality: `You are NAVADA Edge — an AI agent that operates inside the user's terminal.
|
|
19
|
+
You are professional, technical, concise, and helpful. You speak with authority about distributed systems, Docker, AI, and cloud infrastructure.
|
|
20
|
+
You have full access to the user's computer: you can read/write files, run shell commands, manage processes, and connect to the NAVADA Edge Network.
|
|
21
|
+
You can invoke two sub-agents:
|
|
22
|
+
- Lucas CTO: runs bash, SSH, Docker commands on remote NAVADA Edge nodes
|
|
23
|
+
- Claude CoS: sends emails, generates images, manages the network
|
|
24
|
+
When users ask you to do something, DO it — don't just describe how. Use your tools.
|
|
25
|
+
When you don't have a tool for something, say so clearly and suggest an alternative.
|
|
26
|
+
Keep responses short. Code blocks when needed. No fluff.`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Local tools — run on the USER's machine
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
const localTools = {
|
|
33
|
+
shell: {
|
|
34
|
+
description: 'Execute a shell command on this machine',
|
|
35
|
+
execute: (cmd) => {
|
|
36
|
+
try {
|
|
37
|
+
const output = execSync(cmd, { timeout: 30000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
+
return output.trim();
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return `Error: ${e.stderr?.trim() || e.message}`;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
readFile: {
|
|
46
|
+
description: 'Read a file from this machine',
|
|
47
|
+
execute: (filePath) => {
|
|
48
|
+
try {
|
|
49
|
+
return fs.readFileSync(path.resolve(filePath), 'utf-8');
|
|
50
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
writeFile: {
|
|
55
|
+
description: 'Write content to a file on this machine',
|
|
56
|
+
execute: (filePath, content) => {
|
|
57
|
+
try {
|
|
58
|
+
const resolved = path.resolve(filePath);
|
|
59
|
+
const dir = path.dirname(resolved);
|
|
60
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
fs.writeFileSync(resolved, content);
|
|
62
|
+
return `Written: ${resolved}`;
|
|
63
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
listFiles: {
|
|
68
|
+
description: 'List files in a directory on this machine',
|
|
69
|
+
execute: (dir) => {
|
|
70
|
+
try {
|
|
71
|
+
const items = fs.readdirSync(path.resolve(dir || '.'), { withFileTypes: true });
|
|
72
|
+
return items.map(i => `${i.isDirectory() ? 'd' : 'f'} ${i.name}`).join('\n');
|
|
73
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
systemInfo: {
|
|
78
|
+
description: 'Get system information',
|
|
79
|
+
execute: () => {
|
|
80
|
+
return JSON.stringify({
|
|
81
|
+
hostname: os.hostname(),
|
|
82
|
+
platform: os.platform(),
|
|
83
|
+
arch: os.arch(),
|
|
84
|
+
cpus: os.cpus().length,
|
|
85
|
+
totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(1)} GB`,
|
|
86
|
+
freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(1)} GB`,
|
|
87
|
+
uptime: `${(os.uptime() / 3600).toFixed(1)} hours`,
|
|
88
|
+
user: os.userInfo().username,
|
|
89
|
+
home: os.homedir(),
|
|
90
|
+
cwd: process.cwd(),
|
|
91
|
+
nodeVersion: process.version,
|
|
92
|
+
}, null, 2);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Anthropic Claude API — conversational agent
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
async function chat(userMessage, conversationHistory = []) {
|
|
101
|
+
// Smart key detection — check all possible locations
|
|
102
|
+
const anthropicKey = config.get('anthropicKey')
|
|
103
|
+
|| config.getApiKey() // /setup saves here
|
|
104
|
+
|| process.env.ANTHROPIC_API_KEY
|
|
105
|
+
|| '';
|
|
106
|
+
|
|
107
|
+
// Auto-detect: if the key starts with sk-ant, it's Anthropic
|
|
108
|
+
const apiKey = config.getApiKey();
|
|
109
|
+
const effectiveKey = anthropicKey
|
|
110
|
+
|| (apiKey && apiKey.startsWith('sk-ant') ? apiKey : '')
|
|
111
|
+
|| '';
|
|
112
|
+
|
|
113
|
+
if (!effectiveKey) {
|
|
114
|
+
return fallbackChat(userMessage);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const tools = [
|
|
118
|
+
{
|
|
119
|
+
name: 'shell',
|
|
120
|
+
description: 'Execute a shell command on the user\'s local machine. Use for: file operations, git, npm, docker, system commands.',
|
|
121
|
+
input_schema: { type: 'object', properties: { command: { type: 'string', description: 'The shell command to run' } }, required: ['command'] },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'read_file',
|
|
125
|
+
description: 'Read the contents of a file on the user\'s local machine.',
|
|
126
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File path to read' } }, required: ['path'] },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'write_file',
|
|
130
|
+
description: 'Write content to a file on the user\'s local machine.',
|
|
131
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, content: { type: 'string', description: 'Content to write' } }, required: ['path', 'content'] },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'list_files',
|
|
135
|
+
description: 'List files and directories.',
|
|
136
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Directory path (default: current dir)' } } },
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'system_info',
|
|
140
|
+
description: 'Get local system information (CPU, RAM, disk, OS, hostname).',
|
|
141
|
+
input_schema: { type: 'object', properties: {} },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'network_status',
|
|
145
|
+
description: 'Ping all NAVADA Edge Network nodes and cloud services.',
|
|
146
|
+
input_schema: { type: 'object', properties: {} },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'lucas_exec',
|
|
150
|
+
description: 'Run a bash command on EC2 via Lucas CTO agent.',
|
|
151
|
+
input_schema: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] },
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'lucas_ssh',
|
|
155
|
+
description: 'SSH to a NAVADA Edge node (hp, ec2, oracle) and run a command via Lucas CTO.',
|
|
156
|
+
input_schema: { type: 'object', properties: { node: { type: 'string' }, command: { type: 'string' } }, required: ['node', 'command'] },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'lucas_docker',
|
|
160
|
+
description: 'Run a command inside a Docker container on EC2 via Lucas CTO.',
|
|
161
|
+
input_schema: { type: 'object', properties: { container: { type: 'string' }, command: { type: 'string' } }, required: ['container', 'command'] },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'mcp_call',
|
|
165
|
+
description: 'Call a tool on the NAVADA Edge MCP server (18 tools: docker, ssh, files, database, monitoring).',
|
|
166
|
+
input_schema: { type: 'object', properties: { tool: { type: 'string' }, args: { type: 'object' } }, required: ['tool'] },
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'docker_registry',
|
|
170
|
+
description: 'List images or tags in the NAVADA private Docker registry.',
|
|
171
|
+
input_schema: { type: 'object', properties: { image: { type: 'string', description: 'Image name for tags (optional — omit to list all)' } } },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'send_email',
|
|
175
|
+
description: 'Send an email via the NAVADA Edge MCP email tool.',
|
|
176
|
+
input_schema: { type: 'object', properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } }, required: ['to', 'subject', 'body'] },
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'generate_image',
|
|
180
|
+
description: 'Generate an image using Cloudflare Flux (FREE) or DALL-E.',
|
|
181
|
+
input_schema: { type: 'object', properties: { prompt: { type: 'string' }, provider: { type: 'string', description: 'flux (default, free) or dalle' } }, required: ['prompt'] },
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const messages = [
|
|
186
|
+
...conversationHistory,
|
|
187
|
+
{ role: 'user', content: userMessage },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Call Anthropic API
|
|
191
|
+
let response = await callAnthropic(effectiveKey, messages, tools);
|
|
192
|
+
|
|
193
|
+
// Handle tool use loop
|
|
194
|
+
let iterations = 0;
|
|
195
|
+
while (response.stop_reason === 'tool_use' && iterations < 10) {
|
|
196
|
+
iterations++;
|
|
197
|
+
const toolBlocks = response.content.filter(b => b.type === 'tool_use');
|
|
198
|
+
const results = [];
|
|
199
|
+
|
|
200
|
+
for (const block of toolBlocks) {
|
|
201
|
+
// Print what the agent is doing
|
|
202
|
+
console.log(ui.dim(` [${block.name}] ${JSON.stringify(block.input).slice(0, 80)}`));
|
|
203
|
+
const result = await executeTool(block.name, block.input);
|
|
204
|
+
results.push({ type: 'tool_result', tool_use_id: block.id, content: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Print any text blocks
|
|
208
|
+
for (const block of response.content) {
|
|
209
|
+
if (block.type === 'text' && block.text) console.log(` ${block.text}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Continue conversation with tool results
|
|
213
|
+
messages.push({ role: 'assistant', content: response.content });
|
|
214
|
+
messages.push({ role: 'user', content: results });
|
|
215
|
+
response = await callAnthropic(anthropicKey, messages, tools);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Extract final text
|
|
219
|
+
const textBlocks = response.content?.filter(b => b.type === 'text') || [];
|
|
220
|
+
return textBlocks.map(b => b.text).join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function callAnthropic(key, messages, tools) {
|
|
224
|
+
const r = await navada.request('https://api.anthropic.com/v1/messages', {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
body: {
|
|
227
|
+
model: 'claude-sonnet-4-20250514',
|
|
228
|
+
max_tokens: 4096,
|
|
229
|
+
system: IDENTITY.personality,
|
|
230
|
+
messages,
|
|
231
|
+
tools,
|
|
232
|
+
},
|
|
233
|
+
headers: {
|
|
234
|
+
'x-api-key': key,
|
|
235
|
+
'anthropic-version': '2023-06-01',
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
},
|
|
238
|
+
timeout: 60000,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (r.status !== 200) {
|
|
242
|
+
throw new Error(`Anthropic API error ${r.status}: ${JSON.stringify(r.data).slice(0, 200)}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return r.data;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function executeTool(name, input) {
|
|
249
|
+
try {
|
|
250
|
+
switch (name) {
|
|
251
|
+
case 'shell': return localTools.shell.execute(input.command);
|
|
252
|
+
case 'read_file': return localTools.readFile.execute(input.path);
|
|
253
|
+
case 'write_file': return localTools.writeFile.execute(input.path, input.content);
|
|
254
|
+
case 'list_files': return localTools.listFiles.execute(input.path);
|
|
255
|
+
case 'system_info': return localTools.systemInfo.execute();
|
|
256
|
+
case 'network_status': return JSON.stringify(await navada.network.ping());
|
|
257
|
+
case 'lucas_exec': return JSON.stringify(await navada.lucas.exec(input.command));
|
|
258
|
+
case 'lucas_ssh': return JSON.stringify(await navada.lucas.ssh(input.node, input.command));
|
|
259
|
+
case 'lucas_docker': return JSON.stringify(await navada.lucas.docker(input.container, input.command));
|
|
260
|
+
case 'mcp_call': return JSON.stringify(await navada.mcp.call(input.tool, input.args || {}));
|
|
261
|
+
case 'docker_registry':
|
|
262
|
+
if (input.image) return JSON.stringify(await navada.registry.tags(input.image));
|
|
263
|
+
return JSON.stringify(await navada.registry.catalog());
|
|
264
|
+
case 'send_email': return JSON.stringify(await navada.mcp.call('send-email', input));
|
|
265
|
+
case 'generate_image':
|
|
266
|
+
if (input.provider === 'dalle') return JSON.stringify(await navada.ai.openai.image(input.prompt));
|
|
267
|
+
const { size } = await navada.cloudflare.flux.generate(input.prompt, { savePath: `navada-${Date.now()}.png` });
|
|
268
|
+
return `Image generated: ${size} bytes`;
|
|
269
|
+
default: return `Unknown tool: ${name}`;
|
|
270
|
+
}
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return `Tool error: ${e.message}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function fallbackChat(msg) {
|
|
277
|
+
// Try MCP → Qwen → OpenAI → helpful error
|
|
278
|
+
if (navada.config.mcp) {
|
|
279
|
+
try {
|
|
280
|
+
const r = await navada.mcp.call('chat', { message: msg });
|
|
281
|
+
return typeof r === 'object' ? JSON.stringify(r) : r;
|
|
282
|
+
} catch {}
|
|
283
|
+
}
|
|
284
|
+
if (navada.config.hfToken) {
|
|
285
|
+
try {
|
|
286
|
+
const r = await navada.ai.huggingface.qwen(msg);
|
|
287
|
+
return typeof r === 'object' ? JSON.stringify(r) : r;
|
|
288
|
+
} catch {}
|
|
289
|
+
}
|
|
290
|
+
if (navada.config.openaiKey) {
|
|
291
|
+
try { return await navada.ai.openai.chat(msg); } catch {}
|
|
292
|
+
}
|
|
293
|
+
return `I need an API key to chat. Quick fix:
|
|
294
|
+
|
|
295
|
+
/login sk-ant-your-anthropic-key (recommended — enables full agent with tool use)
|
|
296
|
+
/login sk-your-openai-key (GPT-4o)
|
|
297
|
+
/init hfToken hf_your_token (Qwen Coder — FREE)
|
|
298
|
+
/setup (guided wizard)
|
|
299
|
+
|
|
300
|
+
/commands still work without a key — try /help`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Telemetry — track installs + usage
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
async function reportTelemetry(event, data = {}) {
|
|
307
|
+
if (!navada.config.dashboard) return;
|
|
308
|
+
try {
|
|
309
|
+
await navada.request(navada.config.dashboard + '/api/agent-heartbeat', {
|
|
310
|
+
method: 'POST',
|
|
311
|
+
body: {
|
|
312
|
+
agent: 'navada-edge-cli',
|
|
313
|
+
event,
|
|
314
|
+
version: require('../package.json').version,
|
|
315
|
+
hostname: os.hostname(),
|
|
316
|
+
platform: os.platform(),
|
|
317
|
+
arch: os.arch(),
|
|
318
|
+
nodeVersion: process.version,
|
|
319
|
+
apiKey: config.getApiKey()?.slice(0, 8) || 'none',
|
|
320
|
+
ts: new Date().toISOString(),
|
|
321
|
+
...data,
|
|
322
|
+
},
|
|
323
|
+
timeout: 5000,
|
|
324
|
+
});
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { IDENTITY, chat, localTools, reportTelemetry, fallbackChat };
|
package/lib/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ const history = require('./history');
|
|
|
8
8
|
const { completer } = require('./completer');
|
|
9
9
|
const { execute, getCompletions } = require('./registry');
|
|
10
10
|
const { loadAll } = require('./commands/index');
|
|
11
|
+
const { reportTelemetry } = require('./agent');
|
|
11
12
|
|
|
12
13
|
function applyConfig() {
|
|
13
14
|
const cfg = config.getAll();
|
|
@@ -84,14 +85,21 @@ async function run(argv) {
|
|
|
84
85
|
applyConfig();
|
|
85
86
|
|
|
86
87
|
if (argv.length === 0) {
|
|
87
|
-
//
|
|
88
|
-
if (config.isFirstRun())
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
// Interactive mode
|
|
88
|
+
// Report telemetry
|
|
89
|
+
if (config.isFirstRun()) reportTelemetry('install');
|
|
90
|
+
else reportTelemetry('session_start');
|
|
91
|
+
|
|
92
|
+
// Interactive mode — always start. User can /setup if needed.
|
|
94
93
|
showWelcome();
|
|
94
|
+
|
|
95
|
+
// Hint if no key configured
|
|
96
|
+
const hasKey = config.getApiKey() || config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY;
|
|
97
|
+
if (!hasKey) {
|
|
98
|
+
console.log(ui.warn('No API key set. Type /login <key> or /setup to configure.'));
|
|
99
|
+
console.log(ui.dim('You can still use /commands. Natural chat requires an API key.'));
|
|
100
|
+
console.log('');
|
|
101
|
+
}
|
|
102
|
+
|
|
95
103
|
startRepl();
|
|
96
104
|
} else if (argv[0] === '--version' || argv[0] === '-v') {
|
|
97
105
|
const pkg = require('../package.json');
|
package/lib/commands/ai.js
CHANGED
|
@@ -3,48 +3,39 @@
|
|
|
3
3
|
const navada = require('navada-edge-sdk');
|
|
4
4
|
const ui = require('../ui');
|
|
5
5
|
const config = require('../config');
|
|
6
|
+
const { chat: agentChat, reportTelemetry } = require('../agent');
|
|
6
7
|
|
|
7
8
|
module.exports = function(reg) {
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
// Conversation history for multi-turn
|
|
11
|
+
const conversationHistory = [];
|
|
12
|
+
|
|
13
|
+
reg('chat', 'Chat with NAVADA Edge AI agent', async (args) => {
|
|
10
14
|
const msg = args.join(' ');
|
|
11
|
-
if (!msg) { console.log(ui.dim('
|
|
15
|
+
if (!msg) { console.log(ui.dim('Just type naturally — no /command needed.')); return; }
|
|
12
16
|
const ora = require('ora');
|
|
13
|
-
const
|
|
17
|
+
const spinner = ora({ text: ' NAVADA thinking...', color: 'white' }).start();
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const spinner = ora({ text: ' Qwen thinking...', color: 'white' }).start();
|
|
18
|
-
const result = await navada.ai.huggingface.qwen(msg);
|
|
19
|
-
spinner.stop();
|
|
20
|
-
console.log(ui.header('QWEN CODER'));
|
|
21
|
-
console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
|
|
22
|
-
} else if (navada.config.openaiKey) {
|
|
23
|
-
// Use OpenAI
|
|
24
|
-
const spinner = ora({ text: ' Thinking...', color: 'white' }).start();
|
|
25
|
-
const response = await navada.ai.openai.chat(msg);
|
|
19
|
+
try {
|
|
20
|
+
const response = await agentChat(msg, conversationHistory);
|
|
26
21
|
spinner.stop();
|
|
27
|
-
|
|
22
|
+
|
|
23
|
+
// Update conversation history
|
|
24
|
+
conversationHistory.push({ role: 'user', content: msg });
|
|
25
|
+
conversationHistory.push({ role: 'assistant', content: response });
|
|
26
|
+
|
|
27
|
+
// Keep history manageable (last 20 turns)
|
|
28
|
+
while (conversationHistory.length > 40) conversationHistory.splice(0, 2);
|
|
29
|
+
|
|
30
|
+
console.log(ui.header('NAVADA'));
|
|
28
31
|
console.log(` ${response}`);
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log(ui.header('NAVADA'));
|
|
37
|
-
console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
|
|
38
|
-
} catch {
|
|
39
|
-
spinner.stop();
|
|
40
|
-
console.log(ui.warn('No AI provider configured. Set OPENAI_API_KEY, HF_TOKEN, or connect to MCP.'));
|
|
41
|
-
console.log(ui.dim('Run /setup to configure, or /model to choose a provider.'));
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
console.log(ui.warn('No AI provider configured.'));
|
|
45
|
-
console.log(ui.dim('Options: set OPENAI_API_KEY, HF_TOKEN, or connect to MCP server.'));
|
|
46
|
-
console.log(ui.dim('Run /setup to configure, or /model to choose a provider.'));
|
|
47
|
-
}
|
|
32
|
+
|
|
33
|
+
// Track usage
|
|
34
|
+
reportTelemetry('chat', { messageLength: msg.length });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
spinner.stop();
|
|
37
|
+
console.log(ui.error(e.message));
|
|
38
|
+
console.log(ui.dim('Check: /config to see which providers are set, or /setup to configure.'));
|
|
48
39
|
}
|
|
49
40
|
}, { category: 'AI', aliases: ['ask'] });
|
|
50
41
|
|
|
@@ -56,7 +47,7 @@ module.exports = function(reg) {
|
|
|
56
47
|
const result = await navada.ai.huggingface.qwen(prompt);
|
|
57
48
|
spinner.stop();
|
|
58
49
|
console.log(ui.header('QWEN CODER'));
|
|
59
|
-
console.log(typeof result === 'object' ? ui.jsonColorize(result) :
|
|
50
|
+
console.log(` ${typeof result === 'object' ? ui.jsonColorize(result) : result}`);
|
|
60
51
|
}, { category: 'AI' });
|
|
61
52
|
|
|
62
53
|
reg('yolo', 'YOLO object detection', async (args) => {
|
|
@@ -88,12 +79,12 @@ module.exports = function(reg) {
|
|
|
88
79
|
const ora = require('ora');
|
|
89
80
|
|
|
90
81
|
if (useDalle && navada.config.openaiKey) {
|
|
91
|
-
const spinner = ora({ text:
|
|
82
|
+
const spinner = ora({ text: ' DALL-E generating...', color: 'white' }).start();
|
|
92
83
|
const result = await navada.ai.openai.image(prompt);
|
|
93
84
|
spinner.stop();
|
|
94
85
|
console.log(ui.success(`Generated: ${result.url || result.revised_prompt || 'done'}`));
|
|
95
86
|
} else {
|
|
96
|
-
const spinner = ora({ text:
|
|
87
|
+
const spinner = ora({ text: ' Flux generating (FREE)...', color: 'white' }).start();
|
|
97
88
|
const savePath = `navada-image-${Date.now()}.png`;
|
|
98
89
|
const { size } = await navada.cloudflare.flux.generate(prompt, { savePath });
|
|
99
90
|
spinner.stop();
|
|
@@ -103,7 +94,7 @@ module.exports = function(reg) {
|
|
|
103
94
|
|
|
104
95
|
reg('model', 'Show/set default AI model', (args) => {
|
|
105
96
|
if (args[0]) {
|
|
106
|
-
const valid = ['auto', 'gpt-4o', 'gpt-4o-mini', 'qwen'
|
|
97
|
+
const valid = ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen'];
|
|
107
98
|
if (!valid.includes(args[0])) { console.log(ui.error(`Invalid model. Options: ${valid.join(', ')}`)); return; }
|
|
108
99
|
config.setModel(args[0]);
|
|
109
100
|
console.log(ui.success(`Model set to: ${args[0]}`));
|
|
@@ -113,15 +104,14 @@ module.exports = function(reg) {
|
|
|
113
104
|
console.log(ui.label('Current', current));
|
|
114
105
|
console.log('');
|
|
115
106
|
console.log(ui.dim('Available:'));
|
|
116
|
-
console.log(ui.label('auto', '
|
|
107
|
+
console.log(ui.label('auto', 'Claude (Anthropic) with tool use — default'));
|
|
108
|
+
console.log(ui.label('claude', 'Claude Sonnet 4 via Anthropic API'));
|
|
117
109
|
console.log(ui.label('gpt-4o', 'OpenAI GPT-4o (requires OPENAI_API_KEY)'));
|
|
118
|
-
console.log(ui.label('gpt-4o-mini', 'OpenAI GPT-4o-mini (requires OPENAI_API_KEY)'));
|
|
119
110
|
console.log(ui.label('qwen', 'Qwen Coder 32B (FREE via HuggingFace)'));
|
|
120
|
-
console.log(ui.label('flux', 'Cloudflare Flux (FREE image gen)'));
|
|
121
111
|
console.log('');
|
|
122
|
-
console.log(ui.dim('Set with: /model
|
|
112
|
+
console.log(ui.dim('Set with: /model claude'));
|
|
123
113
|
}
|
|
124
|
-
}, { category: 'AI', subs: ['auto', 'gpt-4o', 'gpt-4o-mini', 'qwen'
|
|
114
|
+
}, { category: 'AI', subs: ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen'] });
|
|
125
115
|
|
|
126
116
|
reg('research', 'RAG search via MCP', async (args) => {
|
|
127
117
|
const query = args.join(' ');
|
package/lib/commands/setup.js
CHANGED
|
@@ -9,128 +9,97 @@ function ask(rl, question) {
|
|
|
9
9
|
return new Promise(resolve => rl.question(question, resolve));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function runSetup() {
|
|
13
|
-
|
|
12
|
+
async function runSetup(fromRepl = false) {
|
|
13
|
+
// Create a fresh readline — don't conflict with the REPL
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
terminal: true,
|
|
18
|
+
});
|
|
14
19
|
|
|
15
|
-
console.log(ui.banner());
|
|
16
|
-
console.log(ui.box('WELCOME', ' First-time setup. Let\'s connect you to the NAVADA Edge Network.'));
|
|
17
20
|
console.log('');
|
|
18
|
-
|
|
19
|
-
// 1. API Key
|
|
20
|
-
const apiKey = await ask(rl, ' Enter your NAVADA Edge API key (or press Enter for FREE tier): ');
|
|
21
|
-
if (apiKey.trim()) {
|
|
22
|
-
config.setApiKey(apiKey.trim());
|
|
23
|
-
navada.init({ mcpApiKey: apiKey.trim(), dashboardApiKey: apiKey.trim() });
|
|
24
|
-
console.log(ui.success('API key saved'));
|
|
25
|
-
} else {
|
|
26
|
-
console.log(ui.dim('Continuing with FREE tier'));
|
|
27
|
-
}
|
|
21
|
+
console.log(ui.box('SETUP', ' Configure your NAVADA Edge CLI.'));
|
|
28
22
|
console.log('');
|
|
29
23
|
|
|
30
|
-
//
|
|
31
|
-
console.log(ui.dim('
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
24
|
+
// 1. API Key — the only required thing
|
|
25
|
+
console.log(ui.dim(' Your API key powers the AI agent. Accepts:'));
|
|
26
|
+
console.log(ui.dim(' - Anthropic key (sk-ant-...)'));
|
|
27
|
+
console.log(ui.dim(' - NAVADA Edge API key (nv_edge_...)'));
|
|
28
|
+
console.log(ui.dim(' - OpenAI key (sk-...)'));
|
|
29
|
+
console.log(ui.dim(' - Or press Enter to skip (limited mode)'));
|
|
30
|
+
console.log('');
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
const apiKey = await ask(rl, ' API key: ');
|
|
33
|
+
if (apiKey.trim()) {
|
|
34
|
+
const key = apiKey.trim();
|
|
35
|
+
config.setApiKey(key);
|
|
36
|
+
// Auto-detect key type and save to right field
|
|
37
|
+
if (key.startsWith('sk-ant')) {
|
|
38
|
+
config.set('anthropicKey', key);
|
|
39
|
+
console.log(ui.success('Anthropic key saved — full agent mode enabled'));
|
|
40
|
+
} else if (key.startsWith('nv_edge')) {
|
|
41
|
+
console.log(ui.success('NAVADA Edge key saved — MCP access enabled'));
|
|
42
|
+
} else if (key.startsWith('sk-')) {
|
|
43
|
+
config.set('openaiKey', key);
|
|
44
|
+
navada.init({ openaiKey: key });
|
|
45
|
+
console.log(ui.success('OpenAI key saved'));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(ui.success('Key saved'));
|
|
48
48
|
}
|
|
49
|
+
} else {
|
|
50
|
+
console.log(ui.dim('Skipped — you can set this later with /login <key>'));
|
|
49
51
|
}
|
|
50
52
|
console.log('');
|
|
51
53
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (detected.asus) {
|
|
61
|
-
config.set('mcp', `http://${detected.asus}:8811`);
|
|
62
|
-
config.set('dashboard', `http://${detected.asus}:7900`);
|
|
63
|
-
config.set('registry', `http://${detected.asus}:5000`);
|
|
64
|
-
navada.init({
|
|
65
|
-
asus: detected.asus, hp: detected.hp || '', ec2: detected.ec2 || '', oracle: detected.oracle || '',
|
|
66
|
-
mcp: `http://${detected.asus}:8811`, dashboard: `http://${detected.asus}:7900`, registry: `http://${detected.asus}:5000`,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (detected.ec2) {
|
|
70
|
-
config.set('lucas', `http://${detected.ec2}:8820`);
|
|
71
|
-
navada.init({ lucas: `http://${detected.ec2}:8820` });
|
|
72
|
-
}
|
|
73
|
-
console.log(ui.success('Nodes configured'));
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
console.log(ui.dim('No nodes auto-detected. Enter manually:'));
|
|
54
|
+
// 2. Node IPs — optional
|
|
55
|
+
const configureNodes = await ask(rl, ' Configure network nodes? (y/N): ');
|
|
56
|
+
if (configureNodes.trim().toLowerCase() === 'y') {
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(ui.dim(' Enter Tailscale IPs for your NAVADA Edge nodes.'));
|
|
59
|
+
console.log(ui.dim(' Press Enter to skip any node.'));
|
|
60
|
+
console.log('');
|
|
61
|
+
|
|
77
62
|
const asusIp = await ask(rl, ' ASUS IP (Production Engine): ');
|
|
78
63
|
if (asusIp.trim()) {
|
|
79
64
|
config.set('asus', asusIp.trim());
|
|
80
65
|
config.set('mcp', `http://${asusIp.trim()}:8811`);
|
|
81
66
|
config.set('dashboard', `http://${asusIp.trim()}:7900`);
|
|
82
67
|
config.set('registry', `http://${asusIp.trim()}:5000`);
|
|
83
|
-
navada.init({
|
|
68
|
+
navada.init({
|
|
69
|
+
asus: asusIp.trim(),
|
|
70
|
+
mcp: `http://${asusIp.trim()}:8811`,
|
|
71
|
+
dashboard: `http://${asusIp.trim()}:7900`,
|
|
72
|
+
registry: `http://${asusIp.trim()}:5000`,
|
|
73
|
+
});
|
|
84
74
|
}
|
|
85
|
-
const hpIp = await ask(rl, ' HP IP (Database
|
|
75
|
+
const hpIp = await ask(rl, ' HP IP (Database): ');
|
|
86
76
|
if (hpIp.trim()) config.set('hp', hpIp.trim());
|
|
87
|
-
const ec2Ip = await ask(rl, ' EC2 IP (Monitoring
|
|
77
|
+
const ec2Ip = await ask(rl, ' EC2 IP (Monitoring): ');
|
|
88
78
|
if (ec2Ip.trim()) {
|
|
89
79
|
config.set('ec2', ec2Ip.trim());
|
|
90
80
|
config.set('lucas', `http://${ec2Ip.trim()}:8820`);
|
|
91
81
|
}
|
|
92
|
-
const oracleIp = await ask(rl, ' Oracle IP (
|
|
82
|
+
const oracleIp = await ask(rl, ' Oracle IP (Infrastructure): ');
|
|
93
83
|
if (oracleIp.trim()) config.set('oracle', oracleIp.trim());
|
|
84
|
+
console.log(ui.success('Nodes configured'));
|
|
94
85
|
}
|
|
95
86
|
console.log('');
|
|
96
87
|
|
|
97
|
-
//
|
|
98
|
-
const theme = await ask(rl, '
|
|
88
|
+
// 3. Theme
|
|
89
|
+
const theme = await ask(rl, ' Theme (dark/crow/matrix/light) [dark]: ');
|
|
99
90
|
config.setTheme(theme.trim() || 'dark');
|
|
100
|
-
console.log(ui.success(`Theme: ${config.getTheme()}`));
|
|
101
|
-
console.log('');
|
|
102
|
-
|
|
103
|
-
// 5. Test connections
|
|
104
|
-
console.log(ui.dim('Testing connections...'));
|
|
105
|
-
const tests = [
|
|
106
|
-
{ name: 'MCP', url: navada.config.mcp ? navada.config.mcp + '/health' : '' },
|
|
107
|
-
{ name: 'Dashboard', url: navada.config.dashboard || '' },
|
|
108
|
-
{ name: 'Registry', url: navada.config.registry ? navada.config.registry + '/v2/_catalog' : '' },
|
|
109
|
-
];
|
|
110
|
-
let pass = 0;
|
|
111
|
-
for (const t of tests) {
|
|
112
|
-
if (!t.url) { console.log(ui.online(t.name, false, 'not configured')); continue; }
|
|
113
|
-
try {
|
|
114
|
-
const r = await navada.request(t.url, { timeout: 5000 });
|
|
115
|
-
console.log(ui.online(t.name, r.status < 500));
|
|
116
|
-
pass++;
|
|
117
|
-
} catch {
|
|
118
|
-
console.log(ui.online(t.name, false, 'unreachable'));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
91
|
console.log('');
|
|
122
92
|
|
|
123
|
-
//
|
|
124
|
-
console.log(ui.box('
|
|
93
|
+
// 4. Done
|
|
94
|
+
console.log(ui.box('READY', ` Config saved: ${config.CONFIG_FILE}\n Type naturally to chat, or /help for commands.`));
|
|
125
95
|
console.log('');
|
|
126
|
-
console.log(ui.dim('Run `navada` to start, or `navada doctor` to test all connections.'));
|
|
127
96
|
|
|
128
97
|
rl.close();
|
|
129
98
|
}
|
|
130
99
|
|
|
131
100
|
module.exports = function(reg) {
|
|
132
|
-
reg('setup', 'Run
|
|
133
|
-
await runSetup();
|
|
101
|
+
reg('setup', 'Run setup wizard', async () => {
|
|
102
|
+
await runSetup(true);
|
|
134
103
|
}, { category: 'SYSTEM' });
|
|
135
104
|
};
|
|
136
105
|
|
package/lib/commands/system.js
CHANGED
|
@@ -65,11 +65,32 @@ module.exports = function(reg) {
|
|
|
65
65
|
}, { category: 'SYSTEM' });
|
|
66
66
|
|
|
67
67
|
// --- /login ---
|
|
68
|
-
reg('login', 'Set API key', (args) => {
|
|
69
|
-
if (!args[0]) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
reg('login', 'Set API key (Anthropic, OpenAI, NAVADA Edge, or HuggingFace)', (args) => {
|
|
69
|
+
if (!args[0]) {
|
|
70
|
+
console.log(ui.dim('Usage: /login <api-key>'));
|
|
71
|
+
console.log(ui.dim('Accepts: sk-ant-... (Anthropic) | sk-... (OpenAI) | nv_edge_... (NAVADA) | hf_... (HuggingFace)'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const key = args[0].trim();
|
|
75
|
+
config.setApiKey(key);
|
|
76
|
+
|
|
77
|
+
if (key.startsWith('sk-ant')) {
|
|
78
|
+
config.set('anthropicKey', key);
|
|
79
|
+
console.log(ui.success('Anthropic key saved — full agent mode with tool use enabled'));
|
|
80
|
+
} else if (key.startsWith('hf_')) {
|
|
81
|
+
config.set('hfToken', key);
|
|
82
|
+
navada.init({ hfToken: key });
|
|
83
|
+
console.log(ui.success('HuggingFace key saved — Qwen Coder (FREE) enabled'));
|
|
84
|
+
} else if (key.startsWith('nv_edge')) {
|
|
85
|
+
navada.init({ mcpApiKey: key, dashboardApiKey: key });
|
|
86
|
+
console.log(ui.success('NAVADA Edge key saved — MCP + Dashboard access enabled'));
|
|
87
|
+
} else if (key.startsWith('sk-')) {
|
|
88
|
+
config.set('openaiKey', key);
|
|
89
|
+
navada.init({ openaiKey: key });
|
|
90
|
+
console.log(ui.success('OpenAI key saved — GPT-4o enabled'));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(ui.success('Key saved'));
|
|
93
|
+
}
|
|
73
94
|
}, { category: 'SYSTEM' });
|
|
74
95
|
|
|
75
96
|
// --- /init ---
|
|
@@ -217,13 +238,67 @@ module.exports = function(reg) {
|
|
|
217
238
|
}, { category: 'SYSTEM' });
|
|
218
239
|
|
|
219
240
|
// --- /email ---
|
|
220
|
-
reg('email', 'Send email
|
|
221
|
-
if (args
|
|
241
|
+
reg('email', 'Send email (SMTP or MCP)', async (args) => {
|
|
242
|
+
if (args[0] === 'setup') {
|
|
243
|
+
// Configure SMTP
|
|
244
|
+
const readline = require('readline');
|
|
245
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
246
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
247
|
+
|
|
248
|
+
console.log(ui.header('EMAIL SETUP'));
|
|
249
|
+
console.log(ui.dim(' Providers: Hotmail (smtp-mail.outlook.com), Gmail (smtp.gmail.com), Zoho (smtp.zoho.eu)'));
|
|
250
|
+
console.log('');
|
|
251
|
+
const host = await ask(' SMTP host: ');
|
|
252
|
+
const port = await ask(' SMTP port [587]: ');
|
|
253
|
+
const user = await ask(' Email/username: ');
|
|
254
|
+
const pass = await ask(' Password/app password: ');
|
|
255
|
+
const from = await ask(' From address: ');
|
|
256
|
+
rl.close();
|
|
257
|
+
|
|
258
|
+
config.setSmtp({ host: host.trim(), port: parseInt(port.trim() || '587'), user: user.trim(), pass: pass.trim(), from: from.trim() || user.trim() });
|
|
259
|
+
console.log(ui.success('SMTP configured'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (args.length < 3) {
|
|
264
|
+
console.log(ui.dim('Usage: /email <to> <subject> <body>'));
|
|
265
|
+
console.log(ui.dim('Setup: /email setup'));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
222
269
|
const [to, subject, ...bodyParts] = args;
|
|
223
270
|
const body = bodyParts.join(' ');
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
271
|
+
const smtp = config.getSmtp();
|
|
272
|
+
|
|
273
|
+
// Try local SMTP first
|
|
274
|
+
if (smtp.host && smtp.user && smtp.pass) {
|
|
275
|
+
try {
|
|
276
|
+
const nodemailer = require('nodemailer');
|
|
277
|
+
const transport = nodemailer.createTransport({
|
|
278
|
+
host: smtp.host, port: smtp.port, secure: smtp.port === 465,
|
|
279
|
+
auth: { user: smtp.user, pass: smtp.pass },
|
|
280
|
+
});
|
|
281
|
+
await transport.sendMail({ from: smtp.from || smtp.user, to, subject, text: body });
|
|
282
|
+
console.log(ui.success(`Email sent to ${to} via ${smtp.host}`));
|
|
283
|
+
return;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
console.log(ui.warn(`SMTP failed: ${e.message}. Trying MCP...`));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Fall back to MCP
|
|
290
|
+
if (navada.config.mcp) {
|
|
291
|
+
try {
|
|
292
|
+
await navada.mcp.call('send-email', { to, subject, body });
|
|
293
|
+
console.log(ui.success(`Email sent to ${to} via MCP`));
|
|
294
|
+
return;
|
|
295
|
+
} catch (e) {
|
|
296
|
+
console.log(ui.error(`MCP email failed: ${e.message}`));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(ui.error('No email provider configured. Run /email setup to configure SMTP.'));
|
|
301
|
+
}, { category: 'SYSTEM', subs: ['setup'] });
|
|
227
302
|
|
|
228
303
|
// --- /activity ---
|
|
229
304
|
reg('activity', 'Recent activity log', async () => {
|
package/lib/config.js
CHANGED
|
@@ -44,7 +44,19 @@ function getServePort() { return parseInt(load().servePort || process.env.NAVADA
|
|
|
44
44
|
function getTier() { return load().tier || 'FREE'; }
|
|
45
45
|
function setTier(tier) { const c = load(); c.tier = tier; save(c); }
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
// SMTP email config
|
|
48
|
+
function getSmtp() {
|
|
49
|
+
return load().smtp || {
|
|
50
|
+
host: process.env.SMTP_HOST || '',
|
|
51
|
+
port: parseInt(process.env.SMTP_PORT || '587'),
|
|
52
|
+
user: process.env.SMTP_USER || '',
|
|
53
|
+
pass: process.env.SMTP_PASS || '',
|
|
54
|
+
from: process.env.SMTP_FROM || '',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function setSmtp(smtp) { const c = load(); c.smtp = smtp; save(c); }
|
|
58
|
+
|
|
59
|
+
function get(key) { return process.env[`NAVADA_${key.toUpperCase()}`] || process.env[key.toUpperCase()] || process.env[key] || load()[key] || ''; }
|
|
48
60
|
function set(key, value) { const c = load(); c[key] = value; save(c); }
|
|
49
61
|
|
|
50
62
|
function getAll() { return { ...load(), apiKey: getApiKey() }; }
|
|
@@ -59,4 +71,5 @@ module.exports = {
|
|
|
59
71
|
getServePort,
|
|
60
72
|
getTier, setTier,
|
|
61
73
|
get, set, getAll,
|
|
74
|
+
getSmtp, setSmtp,
|
|
62
75
|
};
|
package/lib/registry.js
CHANGED
|
@@ -23,30 +23,45 @@ function register(name, description, handler, opts = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async function execute(input) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const args = parts.slice(1);
|
|
26
|
+
const trimmed = input.trim();
|
|
27
|
+
if (!trimmed) return;
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
// If input starts with /, treat as a command
|
|
30
|
+
if (trimmed.startsWith('/')) {
|
|
31
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
32
|
+
let name = parts[0]?.toLowerCase();
|
|
33
|
+
const args = parts.slice(1);
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
const aliases = config.getAliases();
|
|
34
|
-
if (aliases[name]) {
|
|
35
|
-
const expanded = aliases[name].split(/\s+/);
|
|
36
|
-
name = expanded[0];
|
|
37
|
-
args.unshift(...expanded.slice(1));
|
|
38
|
-
}
|
|
35
|
+
if (!name) return;
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
// Check user-defined aliases
|
|
38
|
+
const aliases = config.getAliases();
|
|
39
|
+
if (aliases[name]) {
|
|
40
|
+
const expanded = aliases[name].split(/\s+/);
|
|
41
|
+
name = expanded[0];
|
|
42
|
+
args.unshift(...expanded.slice(1));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const cmd = commands[name];
|
|
46
|
+
if (!cmd) {
|
|
47
|
+
const ui = require('./ui');
|
|
48
|
+
console.log(ui.error(`Unknown command: /${parts[0]}`));
|
|
49
|
+
console.log(ui.dim('Type /help for available commands'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await cmd.handler(args);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
const ui = require('./ui');
|
|
57
|
+
console.log(ui.error(e.message));
|
|
58
|
+
}
|
|
45
59
|
return;
|
|
46
60
|
}
|
|
47
61
|
|
|
62
|
+
// No slash — treat as natural language → route to AI agent
|
|
48
63
|
try {
|
|
49
|
-
await
|
|
64
|
+
await commands['chat'].handler(trimmed.split(/\s+/));
|
|
50
65
|
} catch (e) {
|
|
51
66
|
const ui = require('./ui');
|
|
52
67
|
console.log(ui.error(e.message));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "navada-edge-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Interactive CLI for the NAVADA Edge Network — explore nodes, agents, Cloudflare, AI, Docker, and MCP from your terminal",
|
|
5
5
|
"main": "lib/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"ora": "^5.4.1"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"qrcode-terminal": "^0.12.0"
|
|
39
|
+
"qrcode-terminal": "^0.12.0",
|
|
40
|
+
"nodemailer": "^6.9.0"
|
|
40
41
|
},
|
|
41
42
|
"publishConfig": {
|
|
42
43
|
"access": "public"
|