navada-edge-cli 1.1.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 +3 -0
- package/docker-compose.yml +10 -0
- package/lib/cli.js +60 -26
- package/lib/commands/agents.js +70 -0
- package/lib/commands/ai.js +136 -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 +262 -0
- package/lib/commands/tasks.js +34 -0
- package/lib/completer.js +27 -0
- package/lib/config.js +62 -0
- package/lib/history.js +32 -0
- package/lib/registry.js +74 -0
- package/lib/serve.js +148 -0
- package/lib/theme.js +44 -0
- package/lib/ui.js +98 -31
- package/package.json +12 -15
- package/lib/auth.js +0 -46
- package/lib/commands.js +0 -468
package/Dockerfile
ADDED
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
|
|
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');
|
|
8
11
|
|
|
9
12
|
function applyConfig() {
|
|
10
|
-
const
|
|
13
|
+
const cfg = config.getAll();
|
|
11
14
|
const overrides = {};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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.
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
+
};
|