navada-edge-cli 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +3 -0
- package/docker-compose.yml +10 -0
- package/lib/agent.js +306 -0
- package/lib/cli.js +64 -26
- package/lib/commands/agents.js +70 -0
- package/lib/commands/ai.js +126 -0
- package/lib/commands/azure.js +18 -0
- package/lib/commands/cloudflare.js +76 -0
- package/lib/commands/database.js +22 -0
- package/lib/commands/docker.js +40 -0
- package/lib/commands/helpers.js +12 -0
- package/lib/commands/index.js +28 -0
- package/lib/commands/keys.js +32 -0
- package/lib/commands/lucas.js +40 -0
- package/lib/commands/mcp.js +35 -0
- package/lib/commands/network.js +130 -0
- package/lib/commands/setup.js +137 -0
- package/lib/commands/system.js +316 -0
- package/lib/commands/tasks.js +34 -0
- package/lib/completer.js +27 -0
- package/lib/config.js +75 -0
- package/lib/history.js +32 -0
- package/lib/registry.js +89 -0
- package/lib/serve.js +148 -0
- package/lib/theme.js +44 -0
- package/lib/ui.js +98 -31
- package/package.json +13 -15
- package/lib/auth.js +0 -46
- package/lib/commands.js +0 -468
package/Dockerfile
ADDED
package/lib/agent.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
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
|
+
const anthropicKey = config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY || '';
|
|
102
|
+
|
|
103
|
+
if (!anthropicKey) {
|
|
104
|
+
// Fall back to other providers
|
|
105
|
+
return fallbackChat(userMessage);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const tools = [
|
|
109
|
+
{
|
|
110
|
+
name: 'shell',
|
|
111
|
+
description: 'Execute a shell command on the user\'s local machine. Use for: file operations, git, npm, docker, system commands.',
|
|
112
|
+
input_schema: { type: 'object', properties: { command: { type: 'string', description: 'The shell command to run' } }, required: ['command'] },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'read_file',
|
|
116
|
+
description: 'Read the contents of a file on the user\'s local machine.',
|
|
117
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File path to read' } }, required: ['path'] },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'write_file',
|
|
121
|
+
description: 'Write content to a file on the user\'s local machine.',
|
|
122
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, content: { type: 'string', description: 'Content to write' } }, required: ['path', 'content'] },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'list_files',
|
|
126
|
+
description: 'List files and directories.',
|
|
127
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Directory path (default: current dir)' } } },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'system_info',
|
|
131
|
+
description: 'Get local system information (CPU, RAM, disk, OS, hostname).',
|
|
132
|
+
input_schema: { type: 'object', properties: {} },
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'network_status',
|
|
136
|
+
description: 'Ping all NAVADA Edge Network nodes and cloud services.',
|
|
137
|
+
input_schema: { type: 'object', properties: {} },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'lucas_exec',
|
|
141
|
+
description: 'Run a bash command on EC2 via Lucas CTO agent.',
|
|
142
|
+
input_schema: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'lucas_ssh',
|
|
146
|
+
description: 'SSH to a NAVADA Edge node (hp, ec2, oracle) and run a command via Lucas CTO.',
|
|
147
|
+
input_schema: { type: 'object', properties: { node: { type: 'string' }, command: { type: 'string' } }, required: ['node', 'command'] },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'lucas_docker',
|
|
151
|
+
description: 'Run a command inside a Docker container on EC2 via Lucas CTO.',
|
|
152
|
+
input_schema: { type: 'object', properties: { container: { type: 'string' }, command: { type: 'string' } }, required: ['container', 'command'] },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'mcp_call',
|
|
156
|
+
description: 'Call a tool on the NAVADA Edge MCP server (18 tools: docker, ssh, files, database, monitoring).',
|
|
157
|
+
input_schema: { type: 'object', properties: { tool: { type: 'string' }, args: { type: 'object' } }, required: ['tool'] },
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'docker_registry',
|
|
161
|
+
description: 'List images or tags in the NAVADA private Docker registry.',
|
|
162
|
+
input_schema: { type: 'object', properties: { image: { type: 'string', description: 'Image name for tags (optional — omit to list all)' } } },
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'send_email',
|
|
166
|
+
description: 'Send an email via the NAVADA Edge MCP email tool.',
|
|
167
|
+
input_schema: { type: 'object', properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } }, required: ['to', 'subject', 'body'] },
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'generate_image',
|
|
171
|
+
description: 'Generate an image using Cloudflare Flux (FREE) or DALL-E.',
|
|
172
|
+
input_schema: { type: 'object', properties: { prompt: { type: 'string' }, provider: { type: 'string', description: 'flux (default, free) or dalle' } }, required: ['prompt'] },
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const messages = [
|
|
177
|
+
...conversationHistory,
|
|
178
|
+
{ role: 'user', content: userMessage },
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
// Call Anthropic API
|
|
182
|
+
let response = await callAnthropic(anthropicKey, messages, tools);
|
|
183
|
+
|
|
184
|
+
// Handle tool use loop
|
|
185
|
+
let iterations = 0;
|
|
186
|
+
while (response.stop_reason === 'tool_use' && iterations < 10) {
|
|
187
|
+
iterations++;
|
|
188
|
+
const toolBlocks = response.content.filter(b => b.type === 'tool_use');
|
|
189
|
+
const results = [];
|
|
190
|
+
|
|
191
|
+
for (const block of toolBlocks) {
|
|
192
|
+
// Print what the agent is doing
|
|
193
|
+
console.log(ui.dim(` [${block.name}] ${JSON.stringify(block.input).slice(0, 80)}`));
|
|
194
|
+
const result = await executeTool(block.name, block.input);
|
|
195
|
+
results.push({ type: 'tool_result', tool_use_id: block.id, content: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Print any text blocks
|
|
199
|
+
for (const block of response.content) {
|
|
200
|
+
if (block.type === 'text' && block.text) console.log(` ${block.text}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Continue conversation with tool results
|
|
204
|
+
messages.push({ role: 'assistant', content: response.content });
|
|
205
|
+
messages.push({ role: 'user', content: results });
|
|
206
|
+
response = await callAnthropic(anthropicKey, messages, tools);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Extract final text
|
|
210
|
+
const textBlocks = response.content?.filter(b => b.type === 'text') || [];
|
|
211
|
+
return textBlocks.map(b => b.text).join('\n');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function callAnthropic(key, messages, tools) {
|
|
215
|
+
const r = await navada.request('https://api.anthropic.com/v1/messages', {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
body: {
|
|
218
|
+
model: 'claude-sonnet-4-20250514',
|
|
219
|
+
max_tokens: 4096,
|
|
220
|
+
system: IDENTITY.personality,
|
|
221
|
+
messages,
|
|
222
|
+
tools,
|
|
223
|
+
},
|
|
224
|
+
headers: {
|
|
225
|
+
'x-api-key': key,
|
|
226
|
+
'anthropic-version': '2023-06-01',
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
},
|
|
229
|
+
timeout: 60000,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (r.status !== 200) {
|
|
233
|
+
throw new Error(`Anthropic API error ${r.status}: ${JSON.stringify(r.data).slice(0, 200)}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return r.data;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function executeTool(name, input) {
|
|
240
|
+
try {
|
|
241
|
+
switch (name) {
|
|
242
|
+
case 'shell': return localTools.shell.execute(input.command);
|
|
243
|
+
case 'read_file': return localTools.readFile.execute(input.path);
|
|
244
|
+
case 'write_file': return localTools.writeFile.execute(input.path, input.content);
|
|
245
|
+
case 'list_files': return localTools.listFiles.execute(input.path);
|
|
246
|
+
case 'system_info': return localTools.systemInfo.execute();
|
|
247
|
+
case 'network_status': return JSON.stringify(await navada.network.ping());
|
|
248
|
+
case 'lucas_exec': return JSON.stringify(await navada.lucas.exec(input.command));
|
|
249
|
+
case 'lucas_ssh': return JSON.stringify(await navada.lucas.ssh(input.node, input.command));
|
|
250
|
+
case 'lucas_docker': return JSON.stringify(await navada.lucas.docker(input.container, input.command));
|
|
251
|
+
case 'mcp_call': return JSON.stringify(await navada.mcp.call(input.tool, input.args || {}));
|
|
252
|
+
case 'docker_registry':
|
|
253
|
+
if (input.image) return JSON.stringify(await navada.registry.tags(input.image));
|
|
254
|
+
return JSON.stringify(await navada.registry.catalog());
|
|
255
|
+
case 'send_email': return JSON.stringify(await navada.mcp.call('send-email', input));
|
|
256
|
+
case 'generate_image':
|
|
257
|
+
if (input.provider === 'dalle') return JSON.stringify(await navada.ai.openai.image(input.prompt));
|
|
258
|
+
const { size } = await navada.cloudflare.flux.generate(input.prompt, { savePath: `navada-${Date.now()}.png` });
|
|
259
|
+
return `Image generated: ${size} bytes`;
|
|
260
|
+
default: return `Unknown tool: ${name}`;
|
|
261
|
+
}
|
|
262
|
+
} catch (e) {
|
|
263
|
+
return `Tool error: ${e.message}`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function fallbackChat(msg) {
|
|
268
|
+
// Try MCP → Qwen → OpenAI → error
|
|
269
|
+
if (navada.config.mcp) {
|
|
270
|
+
try { return JSON.stringify(await navada.mcp.call('chat', { message: msg })); } catch {}
|
|
271
|
+
}
|
|
272
|
+
if (navada.config.hfToken) {
|
|
273
|
+
try { return await navada.ai.huggingface.qwen(msg); } catch {}
|
|
274
|
+
}
|
|
275
|
+
if (navada.config.openaiKey) {
|
|
276
|
+
try { return await navada.ai.openai.chat(msg); } catch {}
|
|
277
|
+
}
|
|
278
|
+
return 'No AI provider configured. Run /setup or set ANTHROPIC_API_KEY.';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Telemetry — track installs + usage
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
async function reportTelemetry(event, data = {}) {
|
|
285
|
+
if (!navada.config.dashboard) return;
|
|
286
|
+
try {
|
|
287
|
+
await navada.request(navada.config.dashboard + '/api/agent-heartbeat', {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
body: {
|
|
290
|
+
agent: 'navada-edge-cli',
|
|
291
|
+
event,
|
|
292
|
+
version: require('../package.json').version,
|
|
293
|
+
hostname: os.hostname(),
|
|
294
|
+
platform: os.platform(),
|
|
295
|
+
arch: os.arch(),
|
|
296
|
+
nodeVersion: process.version,
|
|
297
|
+
apiKey: config.getApiKey()?.slice(0, 8) || 'none',
|
|
298
|
+
ts: new Date().toISOString(),
|
|
299
|
+
...data,
|
|
300
|
+
},
|
|
301
|
+
timeout: 5000,
|
|
302
|
+
});
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = { IDENTITY, chat, localTools, reportTelemetry, fallbackChat };
|
package/lib/cli.js
CHANGED
|
@@ -3,41 +3,56 @@
|
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const navada = require('navada-edge-sdk');
|
|
5
5
|
const ui = require('./ui');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const config = require('./config');
|
|
7
|
+
const history = require('./history');
|
|
8
|
+
const { completer } = require('./completer');
|
|
9
|
+
const { execute, getCompletions } = require('./registry');
|
|
10
|
+
const { loadAll } = require('./commands/index');
|
|
11
|
+
const { reportTelemetry } = require('./agent');
|
|
8
12
|
|
|
9
13
|
function applyConfig() {
|
|
10
|
-
const
|
|
14
|
+
const cfg = config.getAll();
|
|
11
15
|
const overrides = {};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const map = {
|
|
17
|
+
apiKey: ['mcpApiKey', 'dashboardApiKey'],
|
|
18
|
+
asus: ['asus'], hp: ['hp'], ec2: ['ec2'], oracle: ['oracle'],
|
|
19
|
+
mcp: ['mcp'], dashboard: ['dashboard'], registry: ['registry'],
|
|
20
|
+
lucas: ['lucas'], portainer: ['portainer'],
|
|
21
|
+
cfAccountId: ['cfAccountId'], cfApiToken: ['cfApiToken'],
|
|
22
|
+
cfR2Bucket: ['cfR2Bucket'], cfDomain: ['cfDomain'],
|
|
23
|
+
cfZoneId: ['cfZoneId'], cfStreamSubdomain: ['cfStreamSubdomain'],
|
|
24
|
+
azureN8nUrl: ['azureN8nUrl'], azureN8nRg: ['azureN8nRg'], azureN8nApp: ['azureN8nApp'],
|
|
25
|
+
yoloUrl: ['yoloUrl'], hfToken: ['hfToken'], openaiKey: ['openaiKey'],
|
|
26
|
+
pgHost: ['pgHost'], pgPort: ['pgPort'], pgDb: ['pgDb'], pgUser: ['pgUser'], pgPass: ['pgPass'],
|
|
27
|
+
opencodePassword: ['opencodePassword'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const [cfgKey, sdkKeys] of Object.entries(map)) {
|
|
31
|
+
if (cfg[cfgKey]) {
|
|
32
|
+
for (const sk of sdkKeys) overrides[sk] = cfg[cfgKey];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
if (Object.keys(overrides).length > 0) navada.init(overrides);
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
function showWelcome() {
|
|
25
40
|
console.clear();
|
|
26
|
-
console.log(ui.
|
|
41
|
+
console.log(ui.banner());
|
|
27
42
|
console.log(ui.dim('Type /help for commands. Tab to autocomplete. Ctrl+C to exit.'));
|
|
28
43
|
console.log('');
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
function startRepl() {
|
|
47
|
+
const historyItems = history.load();
|
|
48
|
+
|
|
32
49
|
const rl = readline.createInterface({
|
|
33
50
|
input: process.stdin,
|
|
34
51
|
output: process.stdout,
|
|
35
52
|
prompt: ui.prompt(),
|
|
36
|
-
completer
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return [hits.length ? hits : cmds, line];
|
|
40
|
-
},
|
|
53
|
+
completer,
|
|
54
|
+
history: historyItems,
|
|
55
|
+
historySize: 1000,
|
|
41
56
|
});
|
|
42
57
|
|
|
43
58
|
rl.prompt();
|
|
@@ -45,6 +60,7 @@ function startRepl() {
|
|
|
45
60
|
rl.on('line', async (line) => {
|
|
46
61
|
const input = line.trim();
|
|
47
62
|
if (input) {
|
|
63
|
+
history.append(input);
|
|
48
64
|
await execute(input);
|
|
49
65
|
}
|
|
50
66
|
console.log('');
|
|
@@ -58,14 +74,26 @@ function startRepl() {
|
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
async function runDirect(args) {
|
|
61
|
-
|
|
62
|
-
await execute(input);
|
|
77
|
+
await execute(args.join(' '));
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
function run(argv) {
|
|
80
|
+
async function run(argv) {
|
|
81
|
+
// Load all command modules
|
|
82
|
+
loadAll();
|
|
83
|
+
|
|
84
|
+
// Apply saved config
|
|
66
85
|
applyConfig();
|
|
67
86
|
|
|
68
87
|
if (argv.length === 0) {
|
|
88
|
+
// Check first run
|
|
89
|
+
if (config.isFirstRun()) {
|
|
90
|
+
reportTelemetry('install');
|
|
91
|
+
const { runSetup } = require('./commands/setup');
|
|
92
|
+
await runSetup();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Report session start
|
|
96
|
+
reportTelemetry('session_start');
|
|
69
97
|
// Interactive mode
|
|
70
98
|
showWelcome();
|
|
71
99
|
startRepl();
|
|
@@ -73,24 +101,34 @@ function run(argv) {
|
|
|
73
101
|
const pkg = require('../package.json');
|
|
74
102
|
console.log(`navada-edge-cli v${pkg.version}`);
|
|
75
103
|
} else if (argv[0] === '--help' || argv[0] === '-h') {
|
|
76
|
-
console.log(ui.
|
|
104
|
+
console.log(ui.banner());
|
|
77
105
|
console.log(' Usage:');
|
|
78
|
-
console.log(' navada Interactive mode');
|
|
106
|
+
console.log(' navada Interactive mode (first run triggers setup)');
|
|
107
|
+
console.log(' navada setup Run onboarding wizard');
|
|
79
108
|
console.log(' navada status Ping all nodes');
|
|
109
|
+
console.log(' navada doctor Test all connections');
|
|
80
110
|
console.log(' navada mcp tools List MCP tools');
|
|
81
111
|
console.log(' navada registry List Docker images');
|
|
82
|
-
console.log(' navada
|
|
112
|
+
console.log(' navada agents Show Lucas + Claude status');
|
|
113
|
+
console.log(' navada chat "question" Chat with AI');
|
|
83
114
|
console.log(' navada login <key> Set API key');
|
|
115
|
+
console.log(' navada theme crow Switch theme');
|
|
84
116
|
console.log(' navada --version Show version');
|
|
85
117
|
console.log('');
|
|
86
118
|
console.log(' Run `navada` with no args for interactive mode.');
|
|
87
119
|
console.log('');
|
|
120
|
+
} else if (argv[0] === '--serve') {
|
|
121
|
+
const serve = require('./serve');
|
|
122
|
+
const port = parseInt(argv[1]) || config.getServePort();
|
|
123
|
+
serve.start(port);
|
|
88
124
|
} else {
|
|
89
125
|
// Direct command mode
|
|
90
|
-
|
|
126
|
+
try {
|
|
127
|
+
await runDirect(argv);
|
|
128
|
+
} catch (e) {
|
|
91
129
|
console.log(ui.error(e.message));
|
|
92
130
|
process.exit(1);
|
|
93
|
-
}
|
|
131
|
+
}
|
|
94
132
|
}
|
|
95
133
|
}
|
|
96
134
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const navada = require('navada-edge-sdk');
|
|
4
|
+
const ui = require('../ui');
|
|
5
|
+
|
|
6
|
+
module.exports = function(reg) {
|
|
7
|
+
|
|
8
|
+
reg('agents', 'Show NAVADA Edge agents status', async () => {
|
|
9
|
+
console.log(ui.header('NAVADA EDGE AGENTS'));
|
|
10
|
+
|
|
11
|
+
// Lucas CTO
|
|
12
|
+
if (navada.config.lucas) {
|
|
13
|
+
try {
|
|
14
|
+
const r = await navada.request(navada.config.lucas + '/health', { timeout: 5000 });
|
|
15
|
+
console.log(ui.online('Lucas CTO', r.status < 500, `MCP ${navada.config.lucas}`));
|
|
16
|
+
} catch {
|
|
17
|
+
console.log(ui.online('Lucas CTO', false, 'unreachable'));
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
console.log(ui.online('Lucas CTO', false, 'not configured'));
|
|
21
|
+
}
|
|
22
|
+
console.log(ui.dim(' Tools: bash, ssh, docker_exec, deploy, read_file, write_file, list_files, network_status'));
|
|
23
|
+
|
|
24
|
+
console.log('');
|
|
25
|
+
|
|
26
|
+
// Claude CoS
|
|
27
|
+
if (navada.config.dashboard) {
|
|
28
|
+
try {
|
|
29
|
+
const r = await navada.request(navada.config.dashboard + '/api/agent-heartbeat', { timeout: 5000 });
|
|
30
|
+
console.log(ui.online('Claude CoS', r.status < 500, `Dashboard ${navada.config.dashboard}`));
|
|
31
|
+
} catch {
|
|
32
|
+
console.log(ui.online('Claude CoS', false, 'unreachable'));
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
console.log(ui.online('Claude CoS', false, 'not configured'));
|
|
36
|
+
}
|
|
37
|
+
console.log(ui.dim(' Controls: Telegram, email, SMS, image gen, R2, cost tracking'));
|
|
38
|
+
|
|
39
|
+
console.log('');
|
|
40
|
+
// MCP Server
|
|
41
|
+
if (navada.config.mcp) {
|
|
42
|
+
try {
|
|
43
|
+
const r = await navada.request(navada.config.mcp + '/health', { timeout: 5000 });
|
|
44
|
+
console.log(ui.online('MCP Server', r.status < 500, `${navada.config.mcp}`));
|
|
45
|
+
if (r.data?.version) console.log(ui.dim(` Version: ${r.data.version}`));
|
|
46
|
+
} catch {
|
|
47
|
+
console.log(ui.online('MCP Server', false, 'unreachable'));
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
console.log(ui.online('MCP Server', false, 'not configured'));
|
|
51
|
+
}
|
|
52
|
+
}, { category: 'AGENTS' });
|
|
53
|
+
|
|
54
|
+
reg('claude', 'Send message to Claude CoS', async (args) => {
|
|
55
|
+
const msg = args.join(' ');
|
|
56
|
+
if (!msg) { console.log(ui.dim('Usage: /claude Check HP disk space')); return; }
|
|
57
|
+
const ora = require('ora');
|
|
58
|
+
const spinner = ora({ text: ' Asking Claude...', color: 'white' }).start();
|
|
59
|
+
try {
|
|
60
|
+
// Try via MCP first
|
|
61
|
+
const result = await navada.mcp.call('chat', { message: msg, agent: 'claude' });
|
|
62
|
+
spinner.stop();
|
|
63
|
+
console.log(ui.header('CLAUDE'));
|
|
64
|
+
console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
|
|
65
|
+
} catch {
|
|
66
|
+
spinner.stop();
|
|
67
|
+
console.log(ui.warn('Claude agent not available via MCP. Try /lucas exec for direct commands.'));
|
|
68
|
+
}
|
|
69
|
+
}, { category: 'AGENTS' });
|
|
70
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const navada = require('navada-edge-sdk');
|
|
4
|
+
const ui = require('../ui');
|
|
5
|
+
const config = require('../config');
|
|
6
|
+
const { chat: agentChat, reportTelemetry } = require('../agent');
|
|
7
|
+
|
|
8
|
+
module.exports = function(reg) {
|
|
9
|
+
|
|
10
|
+
// Conversation history for multi-turn
|
|
11
|
+
const conversationHistory = [];
|
|
12
|
+
|
|
13
|
+
reg('chat', 'Chat with NAVADA Edge AI agent', async (args) => {
|
|
14
|
+
const msg = args.join(' ');
|
|
15
|
+
if (!msg) { console.log(ui.dim('Just type naturally — no /command needed.')); return; }
|
|
16
|
+
const ora = require('ora');
|
|
17
|
+
const spinner = ora({ text: ' NAVADA thinking...', color: 'white' }).start();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await agentChat(msg, conversationHistory);
|
|
21
|
+
spinner.stop();
|
|
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'));
|
|
31
|
+
console.log(` ${response}`);
|
|
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.'));
|
|
39
|
+
}
|
|
40
|
+
}, { category: 'AI', aliases: ['ask'] });
|
|
41
|
+
|
|
42
|
+
reg('qwen', 'Qwen Coder 32B (FREE via HuggingFace)', async (args) => {
|
|
43
|
+
const prompt = args.join(' ');
|
|
44
|
+
if (!prompt) { console.log(ui.dim('Usage: /qwen Write a function to validate UK postcodes')); return; }
|
|
45
|
+
const ora = require('ora');
|
|
46
|
+
const spinner = ora({ text: ' Qwen thinking...', color: 'white' }).start();
|
|
47
|
+
const result = await navada.ai.huggingface.qwen(prompt);
|
|
48
|
+
spinner.stop();
|
|
49
|
+
console.log(ui.header('QWEN CODER'));
|
|
50
|
+
console.log(` ${typeof result === 'object' ? ui.jsonColorize(result) : result}`);
|
|
51
|
+
}, { category: 'AI' });
|
|
52
|
+
|
|
53
|
+
reg('yolo', 'YOLO object detection', async (args) => {
|
|
54
|
+
if (args[0] === 'detect' && args[1]) {
|
|
55
|
+
const ora = require('ora');
|
|
56
|
+
const spinner = ora({ text: ' Detecting...', color: 'white' }).start();
|
|
57
|
+
const result = await navada.ai.yolo.detect(args[1]);
|
|
58
|
+
spinner.stop();
|
|
59
|
+
console.log(ui.header(`DETECTIONS (${result.count || 0})`));
|
|
60
|
+
if (result.detections) {
|
|
61
|
+
result.detections.forEach(d => console.log(ui.label(d.class, `${(d.confidence * 100).toFixed(1)}%`)));
|
|
62
|
+
}
|
|
63
|
+
console.log(ui.dim(`Inference: ${result.inference_ms}ms`));
|
|
64
|
+
} else if (args[0] === 'model') {
|
|
65
|
+
const model = await navada.ai.yolo.model();
|
|
66
|
+
console.log(ui.header('YOLO MODEL'));
|
|
67
|
+
console.log(ui.jsonColorize(model));
|
|
68
|
+
} else {
|
|
69
|
+
const health = await navada.ai.yolo.health();
|
|
70
|
+
console.log(ui.header('YOLO'));
|
|
71
|
+
console.log(ui.online('YOLO Service', health.status === 'ok'));
|
|
72
|
+
}
|
|
73
|
+
}, { category: 'AI', subs: ['detect', 'model'] });
|
|
74
|
+
|
|
75
|
+
reg('image', 'Generate an image', async (args) => {
|
|
76
|
+
const prompt = args.filter(a => !a.startsWith('--')).join(' ');
|
|
77
|
+
if (!prompt) { console.log(ui.dim('Usage: /image a futuristic city | /image --dalle NAVADA logo')); return; }
|
|
78
|
+
const useDalle = args.includes('--dalle');
|
|
79
|
+
const ora = require('ora');
|
|
80
|
+
|
|
81
|
+
if (useDalle && navada.config.openaiKey) {
|
|
82
|
+
const spinner = ora({ text: ' DALL-E generating...', color: 'white' }).start();
|
|
83
|
+
const result = await navada.ai.openai.image(prompt);
|
|
84
|
+
spinner.stop();
|
|
85
|
+
console.log(ui.success(`Generated: ${result.url || result.revised_prompt || 'done'}`));
|
|
86
|
+
} else {
|
|
87
|
+
const spinner = ora({ text: ' Flux generating (FREE)...', color: 'white' }).start();
|
|
88
|
+
const savePath = `navada-image-${Date.now()}.png`;
|
|
89
|
+
const { size } = await navada.cloudflare.flux.generate(prompt, { savePath });
|
|
90
|
+
spinner.stop();
|
|
91
|
+
console.log(ui.success(`Generated: ${savePath} (${(size / 1024).toFixed(1)} KB)`));
|
|
92
|
+
}
|
|
93
|
+
}, { category: 'AI' });
|
|
94
|
+
|
|
95
|
+
reg('model', 'Show/set default AI model', (args) => {
|
|
96
|
+
if (args[0]) {
|
|
97
|
+
const valid = ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen'];
|
|
98
|
+
if (!valid.includes(args[0])) { console.log(ui.error(`Invalid model. Options: ${valid.join(', ')}`)); return; }
|
|
99
|
+
config.setModel(args[0]);
|
|
100
|
+
console.log(ui.success(`Model set to: ${args[0]}`));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(ui.header('AI MODELS'));
|
|
103
|
+
const current = config.getModel();
|
|
104
|
+
console.log(ui.label('Current', current));
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(ui.dim('Available:'));
|
|
107
|
+
console.log(ui.label('auto', 'Claude (Anthropic) with tool use — default'));
|
|
108
|
+
console.log(ui.label('claude', 'Claude Sonnet 4 via Anthropic API'));
|
|
109
|
+
console.log(ui.label('gpt-4o', 'OpenAI GPT-4o (requires OPENAI_API_KEY)'));
|
|
110
|
+
console.log(ui.label('qwen', 'Qwen Coder 32B (FREE via HuggingFace)'));
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(ui.dim('Set with: /model claude'));
|
|
113
|
+
}
|
|
114
|
+
}, { category: 'AI', subs: ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen'] });
|
|
115
|
+
|
|
116
|
+
reg('research', 'RAG search via MCP', async (args) => {
|
|
117
|
+
const query = args.join(' ');
|
|
118
|
+
if (!query) { console.log(ui.dim('Usage: /research Docker networking best practices')); return; }
|
|
119
|
+
const ora = require('ora');
|
|
120
|
+
const spinner = ora({ text: ' Searching...', color: 'white' }).start();
|
|
121
|
+
const result = await navada.mcp.call('rag-search', { query });
|
|
122
|
+
spinner.stop();
|
|
123
|
+
console.log(ui.header('RESEARCH'));
|
|
124
|
+
console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
|
|
125
|
+
}, { category: 'AI' });
|
|
126
|
+
};
|