navada-edge-cli 1.0.0 → 2.0.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 ADDED
@@ -0,0 +1,3 @@
1
+ FROM node:22-slim
2
+ RUN npm install -g navada-edge-cli@2.0.0
3
+ ENTRYPOINT ["navada"]
package/README.md CHANGED
@@ -6,6 +6,10 @@ Interactive CLI for the **NAVADA Edge Network**. Explore nodes, Cloudflare, AI s
6
6
  npm install -g navada-edge-cli
7
7
  ```
8
8
 
9
+ ## Network Architecture
10
+
11
+ ![NAVADA Edge Network](https://cdn.jsdelivr.net/npm/navada-edge-sdk@1.1.0/network.svg)
12
+
9
13
  ## Quick Start
10
14
 
11
15
  ```bash
@@ -0,0 +1,10 @@
1
+ services:
2
+ cli:
3
+ build: .
4
+ image: navada-edge-cli:2.0.0
5
+ container_name: navada-edge-cli
6
+ env_file: .env
7
+ stdin_open: true
8
+ tty: true
9
+ ports:
10
+ - "${SERVE_PORT:-7800}:7800"
package/lib/cli.js CHANGED
@@ -3,41 +3,55 @@
3
3
  const readline = require('readline');
4
4
  const navada = require('navada-edge-sdk');
5
5
  const ui = require('./ui');
6
- const auth = require('./auth');
7
- const { execute, commands } = require('./commands');
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');
8
11
 
9
12
  function applyConfig() {
10
- const config = auth.getConfig();
13
+ const cfg = config.getAll();
11
14
  const overrides = {};
12
- if (config.apiKey) { overrides.mcpApiKey = config.apiKey; overrides.dashboardApiKey = config.apiKey; }
13
- if (config.asus) overrides.asus = config.asus;
14
- if (config.hp) overrides.hp = config.hp;
15
- if (config.ec2) overrides.ec2 = config.ec2;
16
- if (config.oracle) overrides.oracle = config.oracle;
17
- if (config.mcp) overrides.mcp = config.mcp;
18
- if (config.dashboard) overrides.dashboard = config.dashboard;
19
- if (config.registry) overrides.registry = config.registry;
20
- if (config.lucas) overrides.lucas = config.lucas;
15
+ const map = {
16
+ apiKey: ['mcpApiKey', 'dashboardApiKey'],
17
+ asus: ['asus'], hp: ['hp'], ec2: ['ec2'], oracle: ['oracle'],
18
+ mcp: ['mcp'], dashboard: ['dashboard'], registry: ['registry'],
19
+ lucas: ['lucas'], portainer: ['portainer'],
20
+ cfAccountId: ['cfAccountId'], cfApiToken: ['cfApiToken'],
21
+ cfR2Bucket: ['cfR2Bucket'], cfDomain: ['cfDomain'],
22
+ cfZoneId: ['cfZoneId'], cfStreamSubdomain: ['cfStreamSubdomain'],
23
+ azureN8nUrl: ['azureN8nUrl'], azureN8nRg: ['azureN8nRg'], azureN8nApp: ['azureN8nApp'],
24
+ yoloUrl: ['yoloUrl'], hfToken: ['hfToken'], openaiKey: ['openaiKey'],
25
+ pgHost: ['pgHost'], pgPort: ['pgPort'], pgDb: ['pgDb'], pgUser: ['pgUser'], pgPass: ['pgPass'],
26
+ opencodePassword: ['opencodePassword'],
27
+ };
28
+
29
+ for (const [cfgKey, sdkKeys] of Object.entries(map)) {
30
+ if (cfg[cfgKey]) {
31
+ for (const sk of sdkKeys) overrides[sk] = cfg[cfgKey];
32
+ }
33
+ }
34
+
21
35
  if (Object.keys(overrides).length > 0) navada.init(overrides);
22
36
  }
23
37
 
24
38
  function showWelcome() {
25
39
  console.clear();
26
- console.log(ui.LOGO);
40
+ console.log(ui.banner());
27
41
  console.log(ui.dim('Type /help for commands. Tab to autocomplete. Ctrl+C to exit.'));
28
42
  console.log('');
29
43
  }
30
44
 
31
45
  function startRepl() {
46
+ const historyItems = history.load();
47
+
32
48
  const rl = readline.createInterface({
33
49
  input: process.stdin,
34
50
  output: process.stdout,
35
51
  prompt: ui.prompt(),
36
- completer: (line) => {
37
- const cmds = Object.keys(commands).map(c => '/' + c);
38
- const hits = cmds.filter(c => c.startsWith(line));
39
- return [hits.length ? hits : cmds, line];
40
- },
52
+ completer,
53
+ history: historyItems,
54
+ historySize: 1000,
41
55
  });
42
56
 
43
57
  rl.prompt();
@@ -45,6 +59,7 @@ function startRepl() {
45
59
  rl.on('line', async (line) => {
46
60
  const input = line.trim();
47
61
  if (input) {
62
+ history.append(input);
48
63
  await execute(input);
49
64
  }
50
65
  console.log('');
@@ -58,14 +73,23 @@ function startRepl() {
58
73
  }
59
74
 
60
75
  async function runDirect(args) {
61
- const input = args.join(' ');
62
- await execute(input);
76
+ await execute(args.join(' '));
63
77
  }
64
78
 
65
- function run(argv) {
79
+ async function run(argv) {
80
+ // Load all command modules
81
+ loadAll();
82
+
83
+ // Apply saved config
66
84
  applyConfig();
67
85
 
68
86
  if (argv.length === 0) {
87
+ // Check first run
88
+ if (config.isFirstRun()) {
89
+ const { runSetup } = require('./commands/setup');
90
+ await runSetup();
91
+ return;
92
+ }
69
93
  // Interactive mode
70
94
  showWelcome();
71
95
  startRepl();
@@ -73,24 +97,34 @@ function run(argv) {
73
97
  const pkg = require('../package.json');
74
98
  console.log(`navada-edge-cli v${pkg.version}`);
75
99
  } else if (argv[0] === '--help' || argv[0] === '-h') {
76
- console.log(ui.LOGO);
100
+ console.log(ui.banner());
77
101
  console.log(' Usage:');
78
- console.log(' navada Interactive mode');
102
+ console.log(' navada Interactive mode (first run triggers setup)');
103
+ console.log(' navada setup Run onboarding wizard');
79
104
  console.log(' navada status Ping all nodes');
105
+ console.log(' navada doctor Test all connections');
80
106
  console.log(' navada mcp tools List MCP tools');
81
107
  console.log(' navada registry List Docker images');
82
- console.log(' navada chat "question" Chat with GPT-4o');
108
+ console.log(' navada agents Show Lucas + Claude status');
109
+ console.log(' navada chat "question" Chat with AI');
83
110
  console.log(' navada login <key> Set API key');
111
+ console.log(' navada theme crow Switch theme');
84
112
  console.log(' navada --version Show version');
85
113
  console.log('');
86
114
  console.log(' Run `navada` with no args for interactive mode.');
87
115
  console.log('');
116
+ } else if (argv[0] === '--serve') {
117
+ const serve = require('./serve');
118
+ const port = parseInt(argv[1]) || config.getServePort();
119
+ serve.start(port);
88
120
  } else {
89
121
  // Direct command mode
90
- runDirect(argv).then(() => process.exit(0)).catch(e => {
122
+ try {
123
+ await runDirect(argv);
124
+ } catch (e) {
91
125
  console.log(ui.error(e.message));
92
126
  process.exit(1);
93
- });
127
+ }
94
128
  }
95
129
  }
96
130
 
@@ -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,136 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+ const config = require('../config');
6
+
7
+ module.exports = function(reg) {
8
+
9
+ reg('chat', 'Chat with AI (uses configured model)', async (args) => {
10
+ const msg = args.join(' ');
11
+ if (!msg) { console.log(ui.dim('Usage: /chat What is the ISA allowance for 2026?')); return; }
12
+ const ora = require('ora');
13
+ const model = config.getModel();
14
+
15
+ if (model === 'qwen' || (!navada.config.openaiKey && navada.config.hfToken)) {
16
+ // Use Qwen (FREE)
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);
26
+ spinner.stop();
27
+ console.log(ui.header('AI RESPONSE'));
28
+ console.log(` ${response}`);
29
+ } else {
30
+ // Route through MCP if available
31
+ if (navada.config.mcp) {
32
+ const spinner = ora({ text: ' Asking NAVADA...', color: 'white' }).start();
33
+ try {
34
+ const result = await navada.mcp.call('chat', { message: msg });
35
+ spinner.stop();
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
+ }
48
+ }
49
+ }, { category: 'AI', aliases: ['ask'] });
50
+
51
+ reg('qwen', 'Qwen Coder 32B (FREE via HuggingFace)', async (args) => {
52
+ const prompt = args.join(' ');
53
+ if (!prompt) { console.log(ui.dim('Usage: /qwen Write a function to validate UK postcodes')); return; }
54
+ const ora = require('ora');
55
+ const spinner = ora({ text: ' Qwen thinking...', color: 'white' }).start();
56
+ const result = await navada.ai.huggingface.qwen(prompt);
57
+ spinner.stop();
58
+ console.log(ui.header('QWEN CODER'));
59
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
60
+ }, { category: 'AI' });
61
+
62
+ reg('yolo', 'YOLO object detection', async (args) => {
63
+ if (args[0] === 'detect' && args[1]) {
64
+ const ora = require('ora');
65
+ const spinner = ora({ text: ' Detecting...', color: 'white' }).start();
66
+ const result = await navada.ai.yolo.detect(args[1]);
67
+ spinner.stop();
68
+ console.log(ui.header(`DETECTIONS (${result.count || 0})`));
69
+ if (result.detections) {
70
+ result.detections.forEach(d => console.log(ui.label(d.class, `${(d.confidence * 100).toFixed(1)}%`)));
71
+ }
72
+ console.log(ui.dim(`Inference: ${result.inference_ms}ms`));
73
+ } else if (args[0] === 'model') {
74
+ const model = await navada.ai.yolo.model();
75
+ console.log(ui.header('YOLO MODEL'));
76
+ console.log(ui.jsonColorize(model));
77
+ } else {
78
+ const health = await navada.ai.yolo.health();
79
+ console.log(ui.header('YOLO'));
80
+ console.log(ui.online('YOLO Service', health.status === 'ok'));
81
+ }
82
+ }, { category: 'AI', subs: ['detect', 'model'] });
83
+
84
+ reg('image', 'Generate an image', async (args) => {
85
+ const prompt = args.filter(a => !a.startsWith('--')).join(' ');
86
+ if (!prompt) { console.log(ui.dim('Usage: /image a futuristic city | /image --dalle NAVADA logo')); return; }
87
+ const useDalle = args.includes('--dalle');
88
+ const ora = require('ora');
89
+
90
+ if (useDalle && navada.config.openaiKey) {
91
+ const spinner = ora({ text: ` DALL-E generating...`, color: 'white' }).start();
92
+ const result = await navada.ai.openai.image(prompt);
93
+ spinner.stop();
94
+ console.log(ui.success(`Generated: ${result.url || result.revised_prompt || 'done'}`));
95
+ } else {
96
+ const spinner = ora({ text: ` Flux generating (FREE)...`, color: 'white' }).start();
97
+ const savePath = `navada-image-${Date.now()}.png`;
98
+ const { size } = await navada.cloudflare.flux.generate(prompt, { savePath });
99
+ spinner.stop();
100
+ console.log(ui.success(`Generated: ${savePath} (${(size / 1024).toFixed(1)} KB)`));
101
+ }
102
+ }, { category: 'AI' });
103
+
104
+ reg('model', 'Show/set default AI model', (args) => {
105
+ if (args[0]) {
106
+ const valid = ['auto', 'gpt-4o', 'gpt-4o-mini', 'qwen', 'flux'];
107
+ if (!valid.includes(args[0])) { console.log(ui.error(`Invalid model. Options: ${valid.join(', ')}`)); return; }
108
+ config.setModel(args[0]);
109
+ console.log(ui.success(`Model set to: ${args[0]}`));
110
+ } else {
111
+ console.log(ui.header('AI MODELS'));
112
+ const current = config.getModel();
113
+ console.log(ui.label('Current', current));
114
+ console.log('');
115
+ console.log(ui.dim('Available:'));
116
+ console.log(ui.label('auto', 'Route to best available provider'));
117
+ 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
+ console.log(ui.label('qwen', 'Qwen Coder 32B (FREE via HuggingFace)'));
120
+ console.log(ui.label('flux', 'Cloudflare Flux (FREE image gen)'));
121
+ console.log('');
122
+ console.log(ui.dim('Set with: /model qwen'));
123
+ }
124
+ }, { category: 'AI', subs: ['auto', 'gpt-4o', 'gpt-4o-mini', 'qwen', 'flux'] });
125
+
126
+ reg('research', 'RAG search via MCP', async (args) => {
127
+ const query = args.join(' ');
128
+ if (!query) { console.log(ui.dim('Usage: /research Docker networking best practices')); return; }
129
+ const ora = require('ora');
130
+ const spinner = ora({ text: ' Searching...', color: 'white' }).start();
131
+ const result = await navada.mcp.call('rag-search', { query });
132
+ spinner.stop();
133
+ console.log(ui.header('RESEARCH'));
134
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : ` ${result}`);
135
+ }, { category: 'AI' });
136
+ };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+
6
+ module.exports = function(reg) {
7
+ reg('n8n', 'Azure n8n health + restart', async (args) => {
8
+ if (args[0] === 'restart') {
9
+ const result = navada.azure.n8n.restart();
10
+ console.log(result.ok ? ui.success(result.message) : ui.error(result.message));
11
+ } else {
12
+ const result = await navada.azure.n8n.health();
13
+ console.log(ui.header('AZURE N8N'));
14
+ console.log(ui.online('n8n', result.ok, result.status ? `HTTP ${result.status}` : result.error));
15
+ console.log(ui.label('URL', result.url || 'not set'));
16
+ }
17
+ }, { category: 'AZURE', subs: ['restart'] });
18
+ };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+
6
+ module.exports = function(reg) {
7
+ reg('r2', 'Cloudflare R2 storage', async (args) => {
8
+ const sub = args[0];
9
+ if (sub === 'buckets') {
10
+ const buckets = await navada.cloudflare.r2.buckets();
11
+ console.log(ui.header('R2 BUCKETS'));
12
+ if (Array.isArray(buckets)) buckets.forEach(b => console.log(` ${b.name || b}`));
13
+ else console.log(ui.jsonColorize(buckets));
14
+ } else if (sub === 'ls') {
15
+ const prefix = args[1] || '';
16
+ const objects = await navada.cloudflare.r2.list(prefix);
17
+ console.log(ui.header(`R2: ${prefix || '/'}`));
18
+ if (Array.isArray(objects)) objects.forEach(o => console.log(` ${o.key || o}`));
19
+ else console.log(ui.jsonColorize(objects));
20
+ } else if (sub === 'upload' && args[1] && args[2]) {
21
+ const ora = require('ora');
22
+ const spinner = ora({ text: ` Uploading ${args[1]}...`, color: 'white' }).start();
23
+ const result = await navada.cloudflare.r2.upload(args[1], args[2]);
24
+ spinner.stop();
25
+ console.log(ui.success(`Uploaded: ${result.key} (${result.size} bytes)`));
26
+ } else if (sub === 'delete' && args[1]) {
27
+ await navada.cloudflare.r2.delete(args[1]);
28
+ console.log(ui.success(`Deleted: ${args[1]}`));
29
+ } else if (sub === 'url' && args[1]) {
30
+ console.log(navada.cloudflare.r2.publicUrl(args[1]));
31
+ } else {
32
+ console.log(ui.dim('Usage: /r2 ls [prefix] | buckets | upload <key> <file> | delete <key> | url <key>'));
33
+ }
34
+ }, { category: 'CLOUDFLARE', subs: ['ls', 'buckets', 'upload', 'delete', 'url'] });
35
+
36
+ reg('dns', 'Cloudflare DNS records', async (args) => {
37
+ if (args[0] === 'create' && args[1] && args[2] && args[3]) {
38
+ await navada.cloudflare.dns.create(args[1], args[2], args[3]);
39
+ console.log(ui.success(`Created: ${args[1]} ${args[2]} -> ${args[3]}`));
40
+ } else {
41
+ const records = await navada.cloudflare.dns.list(args[0]);
42
+ console.log(ui.header('DNS RECORDS'));
43
+ records.forEach(r => console.log(ui.label(`${r.type} ${r.name}`, r.content?.slice(0, 50) || '')));
44
+ }
45
+ }, { category: 'CLOUDFLARE', subs: ['create'] });
46
+
47
+ reg('tunnel', 'Cloudflare tunnels', async () => {
48
+ const tunnels = await navada.cloudflare.tunnel.list();
49
+ console.log(ui.header(`TUNNELS (${tunnels.length})`));
50
+ tunnels.forEach(t => console.log(ui.label(t.name || t.id, t.status || '')));
51
+ }, { category: 'CLOUDFLARE' });
52
+
53
+ reg('stream', 'Cloudflare Stream videos', async () => {
54
+ const videos = await navada.cloudflare.stream.list();
55
+ console.log(ui.header(`VIDEOS (${videos.length})`));
56
+ videos.forEach(v => console.log(ui.label(v.meta?.name || 'Untitled', `${v.uid} ${v.status?.state || ''}`)));
57
+ }, { category: 'CLOUDFLARE' });
58
+
59
+ reg('flux', 'Generate image (FREE Cloudflare AI)', async (args) => {
60
+ const prompt = args.join(' ');
61
+ if (!prompt) { console.log(ui.dim('Usage: /flux a futuristic server room')); return; }
62
+ const ora = require('ora');
63
+ const spinner = ora({ text: ` Generating: "${prompt}"...`, color: 'white' }).start();
64
+ const savePath = `navada-flux-${Date.now()}.png`;
65
+ const { size } = await navada.cloudflare.flux.generate(prompt, { savePath });
66
+ spinner.stop();
67
+ console.log(ui.success(`Generated: ${savePath} (${(size / 1024).toFixed(1)} KB)`));
68
+ }, { category: 'CLOUDFLARE' });
69
+
70
+ reg('trace', 'Trace request through Cloudflare WAF', async (args) => {
71
+ if (!args[0]) { console.log(ui.dim('Usage: /trace https://your-domain.com')); return; }
72
+ const result = await navada.cloudflare.trace(args[0]);
73
+ console.log(ui.header('CLOUDFLARE TRACE'));
74
+ console.log(ui.jsonColorize(result));
75
+ }, { category: 'CLOUDFLARE' });
76
+ };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const Table = require('cli-table3');
5
+ const ui = require('../ui');
6
+ const { tableChars } = require('./helpers');
7
+
8
+ module.exports = function(reg) {
9
+ reg('db', 'Query Postgres', async (args) => {
10
+ const sql = args.join(' ');
11
+ if (!sql) { console.log(ui.dim('Usage: /db SELECT NOW()')); return; }
12
+ const ora = require('ora');
13
+ const spinner = ora({ text: ' Querying...', color: 'white' }).start();
14
+ const rows = await navada.db.query(sql);
15
+ spinner.stop();
16
+ if (rows.length === 0) { console.log(ui.dim('No rows returned')); return; }
17
+ const t = new Table({ head: Object.keys(rows[0]), style: { head: ['white'], border: ['gray'] }, chars: tableChars() });
18
+ rows.slice(0, 50).forEach(row => t.push(Object.values(row).map(v => String(v ?? ''))));
19
+ console.log(t.toString());
20
+ if (rows.length > 50) console.log(ui.dim(`... ${rows.length - 50} more rows`));
21
+ }, { category: 'DATABASE' });
22
+ };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+
6
+ module.exports = function(reg) {
7
+ reg('registry', 'Docker registry images', async (args) => {
8
+ if (args[0] === 'tags' && args[1]) {
9
+ const tags = await navada.registry.tags(args[1]);
10
+ console.log(ui.header(`TAGS: ${args[1]}`));
11
+ tags.forEach(t => console.log(` ${t}`));
12
+ } else {
13
+ const ora = require('ora');
14
+ const spinner = ora({ text: ' Querying registry...', color: 'white' }).start();
15
+ const images = await navada.registry.catalog();
16
+ spinner.stop();
17
+ console.log(ui.header(`DOCKER REGISTRY (${images.length} images)`));
18
+ images.forEach(img => console.log(` ${img}`));
19
+ }
20
+ }, { category: 'DOCKER', subs: ['tags'] });
21
+
22
+ reg('logs', 'View container logs', async (args) => {
23
+ if (!args[0]) { console.log(ui.dim('Usage: /logs <container-name>')); return; }
24
+ if (!navada.config.dashboard) throw new Error('NAVADA_DASHBOARD not set');
25
+ const node = args[1] || 'asus';
26
+ const r = await navada.request(`${navada.config.dashboard}/api/docker/${node}/${args[0]}`);
27
+ console.log(ui.header(`LOGS: ${args[0]} (${node})`));
28
+ console.log(typeof r.data === 'string' ? r.data : ui.jsonColorize(r.data));
29
+ }, { category: 'DOCKER', subs: [] });
30
+
31
+ reg('deploy', 'Deploy container to a node', async (args) => {
32
+ if (!args[0] || !args[1]) { console.log(ui.dim('Usage: /deploy <name> <node>')); return; }
33
+ const ora = require('ora');
34
+ const spinner = ora({ text: ` Deploying ${args[0]} to ${args[1]}...`, color: 'white' }).start();
35
+ const result = await navada.lucas.deploy(args[0], args[1]);
36
+ spinner.stop();
37
+ console.log(ui.success(`Deployed ${args[0]} to ${args[1]}`));
38
+ if (result) console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
39
+ }, { category: 'DOCKER' });
40
+ };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ function tableChars() {
4
+ return {
5
+ 'top': '─', 'top-mid': '┬', 'top-left': '╭', 'top-right': '╮',
6
+ 'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '╰', 'bottom-right': '╯',
7
+ 'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼',
8
+ 'right': '│', 'right-mid': '┤', 'middle': '│',
9
+ };
10
+ }
11
+
12
+ module.exports = { tableChars };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const { register } = require('../registry');
4
+
5
+ // Load all command modules — each exports a function(register)
6
+ const modules = [
7
+ require('./network'),
8
+ require('./mcp'),
9
+ require('./lucas'),
10
+ require('./docker'),
11
+ require('./database'),
12
+ require('./cloudflare'),
13
+ require('./ai'),
14
+ require('./azure'),
15
+ require('./agents'),
16
+ require('./tasks'),
17
+ require('./keys'),
18
+ require('./setup'),
19
+ require('./system'),
20
+ ];
21
+
22
+ function loadAll() {
23
+ for (const mod of modules) {
24
+ mod(register);
25
+ }
26
+ }
27
+
28
+ module.exports = { loadAll };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+
6
+ module.exports = function(reg) {
7
+ reg('keys', 'API key management', async (args) => {
8
+ if (!navada.config.dashboard) throw new Error('NAVADA_DASHBOARD not set');
9
+ const base = navada.config.dashboard + '/api/keys';
10
+
11
+ if (!args[0] || args[0] === 'list') {
12
+ const r = await navada.request(base);
13
+ console.log(ui.header('API KEYS'));
14
+ const keys = Array.isArray(r.data) ? r.data : [];
15
+ if (keys.length === 0) { console.log(ui.dim('No API keys')); return; }
16
+ keys.forEach(k => {
17
+ const masked = k.key ? k.key.slice(0, 8) + '...' + k.key.slice(-4) : k.id || '';
18
+ console.log(ui.label(masked, `${k.tier || 'unknown'} ${k.name || ''}`));
19
+ });
20
+ } else if (args[0] === 'create') {
21
+ const name = args.slice(1).join(' ') || 'CLI Key';
22
+ const r = await navada.request(base, { method: 'POST', body: { name } });
23
+ console.log(ui.success('Key created'));
24
+ console.log(ui.jsonColorize(r.data));
25
+ } else if (args[0] === 'delete' && args[1]) {
26
+ await navada.request(`${base}/${args[1]}`, { method: 'DELETE' });
27
+ console.log(ui.success(`Deleted: ${args[1]}`));
28
+ } else {
29
+ console.log(ui.dim('Usage: /keys | /keys create [name] | /keys delete <key>'));
30
+ }
31
+ }, { category: 'KEYS', subs: ['list', 'create', 'delete'] });
32
+ };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const navada = require('navada-edge-sdk');
4
+ const ui = require('../ui');
5
+
6
+ module.exports = function(reg) {
7
+ reg('lucas', 'Lucas CTO agent commands', async (args) => {
8
+ const sub = args[0];
9
+ if (sub === 'exec' && args[1]) {
10
+ const cmd = args.slice(1).join(' ');
11
+ const result = await navada.lucas.exec(cmd);
12
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
13
+ } else if (sub === 'ssh' && args[1] && args[2]) {
14
+ const result = await navada.lucas.ssh(args[1], args.slice(2).join(' '));
15
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
16
+ } else if (sub === 'docker' && args[1] && args[2]) {
17
+ const result = await navada.lucas.docker(args[1], args.slice(2).join(' '));
18
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
19
+ } else if (sub === 'deploy' && args[1] && args[2]) {
20
+ const ora = require('ora');
21
+ const spinner = ora({ text: ` Deploying ${args[1]} to ${args[2]}...`, color: 'white' }).start();
22
+ const result = await navada.lucas.deploy(args[1], args[2]);
23
+ spinner.stop();
24
+ console.log(ui.success(`Deployed ${args[1]} to ${args[2]}`));
25
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
26
+ } else if (sub === 'status') {
27
+ const result = await navada.lucas.networkStatus();
28
+ console.log(ui.header('LUCAS NETWORK STATUS'));
29
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
30
+ } else if (sub === 'files' && args[1]) {
31
+ const result = await navada.lucas.listFiles(args[1]);
32
+ console.log(typeof result === 'object' ? ui.jsonColorize(result) : result);
33
+ } else if (sub === 'read' && args[1]) {
34
+ const result = await navada.lucas.readFile(args[1]);
35
+ console.log(typeof result === 'string' ? result : ui.jsonColorize(result));
36
+ } else {
37
+ console.log(ui.dim('Usage: /lucas exec <cmd> | ssh <node> <cmd> | docker <ctr> <cmd> | deploy <name> <node> | status | files <dir> | read <file>'));
38
+ }
39
+ }, { category: 'AGENTS', subs: ['exec', 'ssh', 'docker', 'deploy', 'status', 'files', 'read'] });
40
+ };