navada-edge-cli 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leslie Akpareva / NAVADA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # navada-edge-cli
2
+
3
+ Interactive CLI for the **NAVADA Edge Network**. Explore nodes, Cloudflare, AI services, Docker registry, MCP tools, and Postgres — all from your terminal.
4
+
5
+ ```
6
+ npm install -g navada-edge-cli
7
+ ```
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ # Launch interactive mode
13
+ navada
14
+
15
+ # Or run commands directly
16
+ navada status
17
+ navada mcp tools
18
+ navada registry
19
+ navada chat "What is ISA allowance for 2026?"
20
+ ```
21
+
22
+ ## Setup
23
+
24
+ ```bash
25
+ # Install globally
26
+ npm install -g navada-edge-cli
27
+
28
+ # Set your API key
29
+ navada login <your-api-key>
30
+
31
+ # Configure your nodes
32
+ navada init asus 10.0.0.1
33
+ navada init mcp http://10.0.0.1:8811
34
+ navada init dashboard http://10.0.0.1:7900
35
+ navada init registry http://10.0.0.1:5000
36
+ ```
37
+
38
+ Config is saved to `~/.navada/config.json`. You can also use environment variables (see `navada-edge-sdk` docs).
39
+
40
+ ## Interactive Mode
41
+
42
+ Run `navada` with no arguments to enter the interactive TUI:
43
+
44
+ ```
45
+ ███╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ █████╗
46
+ ████╗ ██║██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔══██╗
47
+ ██╔██╗ ██║███████║██║ ██║███████║██║ ██║███████║
48
+ ██║╚██╗██║██╔══██║╚██╗ ██╔╝██╔══██║██║ ██║██╔══██║
49
+ ██║ ╚████║██║ ██║ ╚████╔╝ ██║ ██║██████╔╝██║ ██║
50
+ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝
51
+ ─────────────────────────────────────────────────────
52
+ E D G E N E T W O R K v1.0.0
53
+ ─────────────────────────────────────────────────────
54
+
55
+ navada> /status
56
+ ● ASUS ONLINE (HTTP 200)
57
+ ● HP ONLINE (HTTP 401)
58
+ ● EC2 ONLINE (HTTP 401)
59
+ ● ORACLE ONLINE (HTTP 200)
60
+ ○ AZURE OFFLINE (unreachable)
61
+ ● CLOUDFLARE ONLINE (HTTP 200)
62
+ ```
63
+
64
+ ## Commands
65
+
66
+ ### Network
67
+ | Command | Description |
68
+ |---------|-------------|
69
+ | `/status` | Ping all nodes + cloud services |
70
+ | `/nodes` | Show node configuration |
71
+ | `/dashboard` | Get Command Dashboard status |
72
+
73
+ ### MCP
74
+ | Command | Description |
75
+ |---------|-------------|
76
+ | `/mcp tools` | List MCP tools |
77
+ | `/mcp call <tool> [json]` | Call an MCP tool |
78
+
79
+ ### Lucas CTO
80
+ | Command | Description |
81
+ |---------|-------------|
82
+ | `/lucas exec <cmd>` | Run bash on EC2 |
83
+ | `/lucas ssh <node> <cmd>` | SSH to node via Lucas |
84
+ | `/lucas docker <ctr> <cmd>` | Docker exec via Lucas |
85
+ | `/lucas status` | Lucas network status |
86
+
87
+ ### Docker
88
+ | Command | Description |
89
+ |---------|-------------|
90
+ | `/registry` | List images in private registry |
91
+ | `/registry tags <image>` | List tags for an image |
92
+
93
+ ### Database
94
+ | Command | Description |
95
+ |---------|-------------|
96
+ | `/db <sql>` | Run a Postgres query |
97
+
98
+ ### Cloudflare
99
+ | Command | Description |
100
+ |---------|-------------|
101
+ | `/r2 ls [prefix]` | List R2 objects |
102
+ | `/r2 buckets` | List R2 buckets |
103
+ | `/r2 upload <key> <file>` | Upload file to R2 |
104
+ | `/dns` | List DNS records |
105
+ | `/dns create <type> <name> <content>` | Create DNS record |
106
+ | `/tunnel` | List Cloudflare tunnels |
107
+ | `/stream` | List Stream videos |
108
+ | `/flux <prompt>` | Generate image (FREE) |
109
+ | `/trace <url>` | Trace through Cloudflare WAF |
110
+
111
+ ### AI
112
+ | Command | Description |
113
+ |---------|-------------|
114
+ | `/chat <message>` | Chat with GPT-4o |
115
+ | `/qwen <prompt>` | Qwen Coder (FREE) |
116
+ | `/yolo` | YOLO service status |
117
+ | `/yolo detect <image>` | Run object detection |
118
+
119
+ ### Azure
120
+ | Command | Description |
121
+ |---------|-------------|
122
+ | `/n8n` | Check n8n health |
123
+
124
+ ### System
125
+ | Command | Description |
126
+ |---------|-------------|
127
+ | `/config` | Show current configuration |
128
+ | `/login <api-key>` | Set API key |
129
+ | `/init <key> <value>` | Set a config value |
130
+ | `/clear` | Clear screen |
131
+ | `/exit` | Exit CLI |
132
+
133
+ ## NAVADA Edge Ecosystem
134
+
135
+ | Package | Install | Purpose |
136
+ |---------|---------|---------|
137
+ | `navada-edge-sdk` | `npm i navada-edge-sdk` | SDK — use in your Node.js apps |
138
+ | `navada-edge-cli` | `npm i -g navada-edge-cli` | CLI — interactive terminal tool |
139
+
140
+ ## License
141
+
142
+ MIT - Leslie Akpareva / NAVADA
package/bin/navada.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { run } = require('../lib/cli');
5
+ run(process.argv.slice(2));
package/lib/auth.js ADDED
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const CONFIG_DIR = path.join(os.homedir(), '.navada');
8
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
+
10
+ function ensureDir() {
11
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
12
+ }
13
+
14
+ function load() {
15
+ try {
16
+ if (!fs.existsSync(CONFIG_FILE)) return {};
17
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
18
+ } catch { return {}; }
19
+ }
20
+
21
+ function save(config) {
22
+ ensureDir();
23
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
24
+ }
25
+
26
+ function getApiKey() {
27
+ return process.env.NAVADA_API_KEY || load().apiKey || '';
28
+ }
29
+
30
+ function setApiKey(key) {
31
+ const config = load();
32
+ config.apiKey = key;
33
+ save(config);
34
+ }
35
+
36
+ function getConfig() {
37
+ return { ...load(), apiKey: getApiKey() };
38
+ }
39
+
40
+ function setConfig(key, value) {
41
+ const config = load();
42
+ config[key] = value;
43
+ save(config);
44
+ }
45
+
46
+ module.exports = { getApiKey, setApiKey, getConfig, setConfig, CONFIG_FILE };
package/lib/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const navada = require('navada-edge-sdk');
5
+ const ui = require('./ui');
6
+ const auth = require('./auth');
7
+ const { execute, commands } = require('./commands');
8
+
9
+ function applyConfig() {
10
+ const config = auth.getConfig();
11
+ 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;
21
+ if (Object.keys(overrides).length > 0) navada.init(overrides);
22
+ }
23
+
24
+ function showWelcome() {
25
+ console.clear();
26
+ console.log(ui.LOGO);
27
+ console.log(ui.dim('Type /help for commands. Tab to autocomplete. Ctrl+C to exit.'));
28
+ console.log('');
29
+ }
30
+
31
+ function startRepl() {
32
+ const rl = readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ 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
+ },
41
+ });
42
+
43
+ rl.prompt();
44
+
45
+ rl.on('line', async (line) => {
46
+ const input = line.trim();
47
+ if (input) {
48
+ await execute(input);
49
+ }
50
+ console.log('');
51
+ rl.prompt();
52
+ });
53
+
54
+ rl.on('close', () => {
55
+ console.log(ui.dim('\nGoodbye.'));
56
+ process.exit(0);
57
+ });
58
+ }
59
+
60
+ async function runDirect(args) {
61
+ const input = args.join(' ');
62
+ await execute(input);
63
+ }
64
+
65
+ function run(argv) {
66
+ applyConfig();
67
+
68
+ if (argv.length === 0) {
69
+ // Interactive mode
70
+ showWelcome();
71
+ startRepl();
72
+ } else if (argv[0] === '--version' || argv[0] === '-v') {
73
+ const pkg = require('../package.json');
74
+ console.log(`navada-edge-cli v${pkg.version}`);
75
+ } else if (argv[0] === '--help' || argv[0] === '-h') {
76
+ console.log(ui.LOGO);
77
+ console.log(' Usage:');
78
+ console.log(' navada Interactive mode');
79
+ console.log(' navada status Ping all nodes');
80
+ console.log(' navada mcp tools List MCP tools');
81
+ console.log(' navada registry List Docker images');
82
+ console.log(' navada chat "question" Chat with GPT-4o');
83
+ console.log(' navada login <key> Set API key');
84
+ console.log(' navada --version Show version');
85
+ console.log('');
86
+ console.log(' Run `navada` with no args for interactive mode.');
87
+ console.log('');
88
+ } else {
89
+ // Direct command mode
90
+ runDirect(argv).then(() => process.exit(0)).catch(e => {
91
+ console.log(ui.error(e.message));
92
+ process.exit(1);
93
+ });
94
+ }
95
+ }
96
+
97
+ module.exports = { run };
@@ -0,0 +1,468 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const Table = require('cli-table3');
5
+ const navada = require('navada-edge-sdk');
6
+ const ui = require('./ui');
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Command registry
10
+ // ---------------------------------------------------------------------------
11
+ const commands = {};
12
+
13
+ function register(name, description, handler) {
14
+ commands[name] = { description, handler };
15
+ }
16
+
17
+ async function execute(input) {
18
+ const parts = input.trim().replace(/^\//, '').split(/\s+/);
19
+ const name = parts[0]?.toLowerCase();
20
+ const args = parts.slice(1);
21
+
22
+ if (!name) return;
23
+
24
+ const cmd = commands[name];
25
+ if (!cmd) {
26
+ console.log(ui.error(`Unknown command: /${name}`));
27
+ console.log(ui.dim('Type /help for available commands'));
28
+ return;
29
+ }
30
+
31
+ try {
32
+ await cmd.handler(args);
33
+ } catch (e) {
34
+ console.log(ui.error(e.message));
35
+ }
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // /help
40
+ // ---------------------------------------------------------------------------
41
+ register('help', 'Show all commands', () => {
42
+ console.log(ui.header('COMMANDS'));
43
+ console.log('');
44
+ console.log(chalk.gray.dim(' NETWORK'));
45
+ console.log(ui.cmd('status', 'Ping all nodes + cloud services'));
46
+ console.log(ui.cmd('nodes', 'Show node configuration'));
47
+ console.log(ui.cmd('dashboard', 'Get Command Dashboard status'));
48
+ console.log('');
49
+ console.log(chalk.gray.dim(' MCP'));
50
+ console.log(ui.cmd('mcp tools', 'List MCP tools'));
51
+ console.log(ui.cmd('mcp call <tool> [json]', 'Call an MCP tool'));
52
+ console.log('');
53
+ console.log(chalk.gray.dim(' LUCAS CTO'));
54
+ console.log(ui.cmd('lucas exec <cmd>', 'Run bash on EC2'));
55
+ console.log(ui.cmd('lucas ssh <node> <cmd>', 'SSH to node via Lucas'));
56
+ console.log(ui.cmd('lucas docker <ctr> <cmd>', 'Docker exec via Lucas'));
57
+ console.log(ui.cmd('lucas status', 'Lucas network status'));
58
+ console.log('');
59
+ console.log(chalk.gray.dim(' DOCKER'));
60
+ console.log(ui.cmd('registry', 'List images in private registry'));
61
+ console.log(ui.cmd('registry tags <image>', 'List tags for an image'));
62
+ console.log('');
63
+ console.log(chalk.gray.dim(' DATABASE'));
64
+ console.log(ui.cmd('db <sql>', 'Run a Postgres query'));
65
+ console.log('');
66
+ console.log(chalk.gray.dim(' OPENCODE'));
67
+ console.log(ui.cmd('opencode', 'Check OpenCode on all nodes'));
68
+ console.log('');
69
+ console.log(chalk.gray.dim(' CLOUDFLARE'));
70
+ console.log(ui.cmd('r2 ls [prefix]', 'List R2 objects'));
71
+ console.log(ui.cmd('r2 buckets', 'List R2 buckets'));
72
+ console.log(ui.cmd('r2 upload <key> <file>', 'Upload file to R2'));
73
+ console.log(ui.cmd('r2 delete <key>', 'Delete R2 object'));
74
+ console.log(ui.cmd('dns', 'List DNS records'));
75
+ console.log(ui.cmd('dns create <type> <name> <content>', 'Create DNS record'));
76
+ console.log(ui.cmd('tunnel', 'List Cloudflare tunnels'));
77
+ console.log(ui.cmd('stream', 'List Stream videos'));
78
+ console.log(ui.cmd('flux <prompt>', 'Generate image (FREE)'));
79
+ console.log(ui.cmd('trace <url>', 'Trace request through Cloudflare'));
80
+ console.log('');
81
+ console.log(chalk.gray.dim(' AI'));
82
+ console.log(ui.cmd('chat <message>', 'Chat with GPT-4o'));
83
+ console.log(ui.cmd('qwen <prompt>', 'Qwen Coder (FREE)'));
84
+ console.log(ui.cmd('yolo health', 'YOLO service status'));
85
+ console.log(ui.cmd('yolo detect <image>', 'Run object detection'));
86
+ console.log('');
87
+ console.log(chalk.gray.dim(' AZURE'));
88
+ console.log(ui.cmd('n8n', 'Check n8n health'));
89
+ console.log('');
90
+ console.log(chalk.gray.dim(' SYSTEM'));
91
+ console.log(ui.cmd('config', 'Show current configuration'));
92
+ console.log(ui.cmd('login <api-key>', 'Set API key'));
93
+ console.log(ui.cmd('init <key> <value>', 'Set a config value'));
94
+ console.log(ui.cmd('clear', 'Clear screen'));
95
+ console.log(ui.cmd('exit', 'Exit CLI'));
96
+ console.log('');
97
+ });
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // /status
101
+ // ---------------------------------------------------------------------------
102
+ register('status', 'Ping all nodes', async () => {
103
+ const ora = require('ora');
104
+ const spinner = ora({ text: ' Pinging network...', color: 'white' }).start();
105
+ const results = await navada.network.ping();
106
+ spinner.stop();
107
+
108
+ console.log(ui.header('NETWORK STATUS'));
109
+ for (const [node, info] of Object.entries(results)) {
110
+ console.log(ui.online(node.toUpperCase(), info.online, info.status ? `HTTP ${info.status}` : info.error));
111
+ }
112
+ const total = Object.keys(results).length;
113
+ const up = Object.values(results).filter(r => r.online).length;
114
+ console.log('');
115
+ console.log(ui.dim(`${up}/${total} endpoints online`));
116
+ });
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // /nodes
120
+ // ---------------------------------------------------------------------------
121
+ register('nodes', 'Show node config', () => {
122
+ console.log(ui.header('NODES'));
123
+ const nodes = navada.network.nodes;
124
+ for (const [name, info] of Object.entries(nodes)) {
125
+ if (info.ip) {
126
+ console.log(ui.label(name.toUpperCase(), `${info.ip} ${chalk.gray(info.role)}`));
127
+ }
128
+ }
129
+ console.log('');
130
+ console.log(ui.label('MCP', navada.config.mcp || chalk.gray('not set')));
131
+ console.log(ui.label('DASHBOARD', navada.config.dashboard || chalk.gray('not set')));
132
+ console.log(ui.label('REGISTRY', navada.config.registry || chalk.gray('not set')));
133
+ console.log(ui.label('LUCAS', navada.config.lucas || chalk.gray('not set')));
134
+ console.log(ui.label('PORTAINER', navada.config.portainer || chalk.gray('not set')));
135
+ });
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // /dashboard
139
+ // ---------------------------------------------------------------------------
140
+ register('dashboard', 'Command Dashboard status', async () => {
141
+ const data = await navada.network.status();
142
+ console.log(ui.header('DASHBOARD'));
143
+ console.log(JSON.stringify(data, null, 2));
144
+ });
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // /mcp
148
+ // ---------------------------------------------------------------------------
149
+ register('mcp', 'MCP tools', async (args) => {
150
+ const sub = args[0];
151
+
152
+ if (sub === 'tools' || !sub) {
153
+ const ora = require('ora');
154
+ const spinner = ora({ text: ' Fetching MCP tools...', color: 'white' }).start();
155
+ const tools = await navada.mcp.tools();
156
+ spinner.stop();
157
+ console.log(ui.header(`MCP TOOLS (${tools.length})`));
158
+ const t = new Table({ head: ['Tool', 'Description'], style: { head: ['white'], border: ['gray'] }, chars: tableChars() });
159
+ tools.forEach(tool => t.push([chalk.white(tool.name), chalk.gray(tool.description || '')]));
160
+ console.log(t.toString());
161
+ } else if (sub === 'call' && args[1]) {
162
+ const toolName = args[1];
163
+ let toolArgs = {};
164
+ if (args[2]) { try { toolArgs = JSON.parse(args.slice(2).join(' ')); } catch { toolArgs = { input: args.slice(2).join(' ') }; } }
165
+ const ora = require('ora');
166
+ const spinner = ora({ text: ` Calling ${toolName}...`, color: 'white' }).start();
167
+ const result = await navada.mcp.call(toolName, toolArgs);
168
+ spinner.stop();
169
+ console.log(ui.header(`MCP: ${toolName}`));
170
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
171
+ } else {
172
+ console.log(ui.dim('Usage: /mcp tools | /mcp call <tool> [json]'));
173
+ }
174
+ });
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // /lucas
178
+ // ---------------------------------------------------------------------------
179
+ register('lucas', 'Lucas CTO commands', async (args) => {
180
+ const sub = args[0];
181
+ if (sub === 'exec' && args[1]) {
182
+ const cmd = args.slice(1).join(' ');
183
+ const result = await navada.lucas.exec(cmd);
184
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
185
+ } else if (sub === 'ssh' && args[1] && args[2]) {
186
+ const result = await navada.lucas.ssh(args[1], args.slice(2).join(' '));
187
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
188
+ } else if (sub === 'docker' && args[1] && args[2]) {
189
+ const result = await navada.lucas.docker(args[1], args.slice(2).join(' '));
190
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
191
+ } else if (sub === 'status') {
192
+ const result = await navada.lucas.networkStatus();
193
+ console.log(ui.header('LUCAS NETWORK STATUS'));
194
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
195
+ } else {
196
+ console.log(ui.dim('Usage: /lucas exec <cmd> | /lucas ssh <node> <cmd> | /lucas docker <ctr> <cmd> | /lucas status'));
197
+ }
198
+ });
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // /registry
202
+ // ---------------------------------------------------------------------------
203
+ register('registry', 'Docker registry', async (args) => {
204
+ if (args[0] === 'tags' && args[1]) {
205
+ const tags = await navada.registry.tags(args[1]);
206
+ console.log(ui.header(`TAGS: ${args[1]}`));
207
+ tags.forEach(t => console.log(` ${chalk.white(t)}`));
208
+ } else {
209
+ const ora = require('ora');
210
+ const spinner = ora({ text: ' Querying registry...', color: 'white' }).start();
211
+ const images = await navada.registry.catalog();
212
+ spinner.stop();
213
+ console.log(ui.header(`DOCKER REGISTRY (${images.length} images)`));
214
+ images.forEach(img => console.log(` ${chalk.white(img)}`));
215
+ }
216
+ });
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // /db
220
+ // ---------------------------------------------------------------------------
221
+ register('db', 'Query Postgres', async (args) => {
222
+ const sql = args.join(' ');
223
+ if (!sql) { console.log(ui.dim('Usage: /db SELECT NOW()')); return; }
224
+ const ora = require('ora');
225
+ const spinner = ora({ text: ' Querying...', color: 'white' }).start();
226
+ const rows = await navada.db.query(sql);
227
+ spinner.stop();
228
+ if (rows.length === 0) { console.log(ui.dim('No rows returned')); return; }
229
+ const t = new Table({ head: Object.keys(rows[0]), style: { head: ['white'], border: ['gray'] }, chars: tableChars() });
230
+ rows.slice(0, 50).forEach(row => t.push(Object.values(row).map(v => String(v ?? ''))));
231
+ console.log(t.toString());
232
+ if (rows.length > 50) console.log(ui.dim(`... ${rows.length - 50} more rows`));
233
+ });
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // /opencode
237
+ // ---------------------------------------------------------------------------
238
+ register('opencode', 'OpenCode status', async () => {
239
+ const results = await navada.opencode.statusAll();
240
+ console.log(ui.header('OPENCODE'));
241
+ for (const [node, info] of Object.entries(results)) {
242
+ console.log(ui.online(node.toUpperCase(), info.online, info.status ? `HTTP ${info.status}` : info.error));
243
+ }
244
+ });
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // /r2
248
+ // ---------------------------------------------------------------------------
249
+ register('r2', 'Cloudflare R2 storage', async (args) => {
250
+ const sub = args[0];
251
+ if (sub === 'buckets') {
252
+ const buckets = await navada.cloudflare.r2.buckets();
253
+ console.log(ui.header('R2 BUCKETS'));
254
+ if (Array.isArray(buckets)) buckets.forEach(b => console.log(` ${chalk.white(b.name || b)}`));
255
+ else console.log(JSON.stringify(buckets, null, 2));
256
+ } else if (sub === 'ls') {
257
+ const prefix = args[1] || '';
258
+ const objects = await navada.cloudflare.r2.list(prefix);
259
+ console.log(ui.header(`R2: ${prefix || '/'}`));
260
+ if (Array.isArray(objects)) objects.forEach(o => console.log(` ${chalk.white(o.key || o)}`));
261
+ else console.log(JSON.stringify(objects, null, 2));
262
+ } else if (sub === 'upload' && args[1] && args[2]) {
263
+ const result = await navada.cloudflare.r2.upload(args[1], args[2]);
264
+ console.log(ui.success(`Uploaded: ${result.key} (${result.size} bytes)`));
265
+ } else if (sub === 'delete' && args[1]) {
266
+ await navada.cloudflare.r2.delete(args[1]);
267
+ console.log(ui.success(`Deleted: ${args[1]}`));
268
+ } else {
269
+ console.log(ui.dim('Usage: /r2 ls [prefix] | /r2 buckets | /r2 upload <key> <file> | /r2 delete <key>'));
270
+ }
271
+ });
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // /dns
275
+ // ---------------------------------------------------------------------------
276
+ register('dns', 'Cloudflare DNS', async (args) => {
277
+ if (args[0] === 'create' && args[1] && args[2] && args[3]) {
278
+ const result = await navada.cloudflare.dns.create(args[1], args[2], args[3]);
279
+ console.log(ui.success(`Created: ${args[1]} ${args[2]} -> ${args[3]}`));
280
+ } else {
281
+ const records = await navada.cloudflare.dns.list(args[0]);
282
+ console.log(ui.header('DNS RECORDS'));
283
+ const t = new Table({ head: ['Type', 'Name', 'Content', 'Proxied'], style: { head: ['white'], border: ['gray'] }, chars: tableChars() });
284
+ records.forEach(r => t.push([r.type, r.name, r.content?.slice(0, 40), r.proxied ? 'yes' : 'no']));
285
+ console.log(t.toString());
286
+ }
287
+ });
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // /tunnel
291
+ // ---------------------------------------------------------------------------
292
+ register('tunnel', 'Cloudflare tunnels', async () => {
293
+ const tunnels = await navada.cloudflare.tunnel.list();
294
+ console.log(ui.header('CLOUDFLARE TUNNELS'));
295
+ tunnels.forEach(t => console.log(ui.label(t.name, `${t.id} ${chalk.gray(t.status)}`)));
296
+ });
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // /stream
300
+ // ---------------------------------------------------------------------------
301
+ register('stream', 'Cloudflare Stream videos', async () => {
302
+ const videos = await navada.cloudflare.stream.list();
303
+ console.log(ui.header(`STREAM VIDEOS (${videos.length})`));
304
+ videos.forEach(v => console.log(ui.label(v.meta?.name || 'Untitled', `${v.uid} ${chalk.gray(v.status?.state || '')}`)));
305
+ });
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // /flux
309
+ // ---------------------------------------------------------------------------
310
+ register('flux', 'Generate image (FREE)', async (args) => {
311
+ const prompt = args.join(' ');
312
+ if (!prompt) { console.log(ui.dim('Usage: /flux a futuristic server room')); return; }
313
+ const ora = require('ora');
314
+ const spinner = ora({ text: ` Generating: "${prompt}"...`, color: 'white' }).start();
315
+ const { size } = await navada.cloudflare.flux.generate(prompt, { savePath: `navada-flux-${Date.now()}.png` });
316
+ spinner.stop();
317
+ console.log(ui.success(`Generated: ${(size / 1024).toFixed(1)} KB`));
318
+ });
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // /trace
322
+ // ---------------------------------------------------------------------------
323
+ register('trace', 'Trace request through Cloudflare', async (args) => {
324
+ if (!args[0]) { console.log(ui.dim('Usage: /trace https://your-domain.com')); return; }
325
+ const result = await navada.cloudflare.trace(args[0]);
326
+ console.log(ui.header('CLOUDFLARE TRACE'));
327
+ console.log(JSON.stringify(result, null, 2));
328
+ });
329
+
330
+ // ---------------------------------------------------------------------------
331
+ // /chat
332
+ // ---------------------------------------------------------------------------
333
+ register('chat', 'Chat with GPT-4o', async (args) => {
334
+ const msg = args.join(' ');
335
+ if (!msg) { console.log(ui.dim('Usage: /chat What is ISA allowance for 2026?')); return; }
336
+ const ora = require('ora');
337
+ const spinner = ora({ text: ' Thinking...', color: 'white' }).start();
338
+ const response = await navada.ai.openai.chat(msg);
339
+ spinner.stop();
340
+ console.log(ui.header('AI RESPONSE'));
341
+ console.log(` ${response}`);
342
+ });
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // /qwen
346
+ // ---------------------------------------------------------------------------
347
+ register('qwen', 'Qwen Coder (FREE)', async (args) => {
348
+ const prompt = args.join(' ');
349
+ if (!prompt) { console.log(ui.dim('Usage: /qwen Write a function to validate UK postcodes')); return; }
350
+ const ora = require('ora');
351
+ const spinner = ora({ text: ' Qwen thinking...', color: 'white' }).start();
352
+ const result = await navada.ai.huggingface.qwen(prompt);
353
+ spinner.stop();
354
+ console.log(ui.header('QWEN CODER'));
355
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
356
+ });
357
+
358
+ // ---------------------------------------------------------------------------
359
+ // /yolo
360
+ // ---------------------------------------------------------------------------
361
+ register('yolo', 'YOLO object detection', async (args) => {
362
+ if (args[0] === 'detect' && args[1]) {
363
+ const ora = require('ora');
364
+ const spinner = ora({ text: ' Detecting...', color: 'white' }).start();
365
+ const result = await navada.ai.yolo.detect(args[1]);
366
+ spinner.stop();
367
+ console.log(ui.header(`DETECTIONS (${result.count || 0})`));
368
+ if (result.detections) {
369
+ const t = new Table({ head: ['Class', 'Confidence', 'BBox'], style: { head: ['white'], border: ['gray'] }, chars: tableChars() });
370
+ result.detections.forEach(d => t.push([d.class, `${(d.confidence * 100).toFixed(1)}%`, d.bbox?.join(', ') || '']));
371
+ console.log(t.toString());
372
+ }
373
+ console.log(ui.dim(`Inference: ${result.inference_ms}ms`));
374
+ } else {
375
+ const health = await navada.ai.yolo.health();
376
+ console.log(ui.header('YOLO'));
377
+ console.log(ui.online('YOLO Service', health.status === 'ok'));
378
+ if (health.model) console.log(ui.label('Model', health.model));
379
+ }
380
+ });
381
+
382
+ // ---------------------------------------------------------------------------
383
+ // /n8n
384
+ // ---------------------------------------------------------------------------
385
+ register('n8n', 'Azure n8n health', async () => {
386
+ const result = await navada.azure.n8n.health();
387
+ console.log(ui.header('AZURE N8N'));
388
+ console.log(ui.online('n8n', result.ok, result.status ? `HTTP ${result.status}` : result.error));
389
+ console.log(ui.label('URL', result.url || 'not set'));
390
+ });
391
+
392
+ // ---------------------------------------------------------------------------
393
+ // /config
394
+ // ---------------------------------------------------------------------------
395
+ register('config', 'Show config', () => {
396
+ console.log(ui.header('CONFIGURATION'));
397
+ const c = navada.config;
398
+ const show = (k, v) => console.log(ui.label(k, v ? chalk.white(v) : chalk.gray('not set')));
399
+ show('SERVICE', c.service);
400
+ show('NODE', c.node);
401
+ show('ASUS', c.asus);
402
+ show('HP', c.hp);
403
+ show('EC2', c.ec2);
404
+ show('ORACLE', c.oracle);
405
+ show('MCP', c.mcp);
406
+ show('DASHBOARD', c.dashboard);
407
+ show('REGISTRY', c.registry);
408
+ show('LUCAS', c.lucas);
409
+ show('PG_HOST', c.pgHost);
410
+ show('CF_ACCOUNT', c.cfAccountId ? c.cfAccountId.slice(0, 8) + '...' : '');
411
+ show('CF_DOMAIN', c.cfDomain);
412
+ show('YOLO', c.yoloUrl);
413
+ show('AZURE_N8N', c.azureN8nUrl);
414
+ show('HF_TOKEN', c.hfToken ? '****' + c.hfToken.slice(-4) : '');
415
+ show('OPENAI_KEY', c.openaiKey ? '****' + c.openaiKey.slice(-4) : '');
416
+ });
417
+
418
+ // ---------------------------------------------------------------------------
419
+ // /login
420
+ // ---------------------------------------------------------------------------
421
+ register('login', 'Set API key', (args) => {
422
+ const auth = require('./auth');
423
+ if (!args[0]) { console.log(ui.dim('Usage: /login <api-key>')); return; }
424
+ auth.setApiKey(args[0]);
425
+ navada.init({ mcpApiKey: args[0], dashboardApiKey: args[0] });
426
+ console.log(ui.success(`API key saved to ${auth.CONFIG_FILE}`));
427
+ });
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // /init
431
+ // ---------------------------------------------------------------------------
432
+ register('init', 'Set config value', (args) => {
433
+ if (!args[0] || !args[1]) { console.log(ui.dim('Usage: /init asus 10.0.0.1')); return; }
434
+ navada.init({ [args[0]]: args[1] });
435
+ const auth = require('./auth');
436
+ auth.setConfig(args[0], args[1]);
437
+ console.log(ui.success(`${args[0]} = ${args[1]}`));
438
+ });
439
+
440
+ // ---------------------------------------------------------------------------
441
+ // /clear
442
+ // ---------------------------------------------------------------------------
443
+ register('clear', 'Clear screen', () => {
444
+ console.clear();
445
+ console.log(ui.LOGO);
446
+ });
447
+
448
+ // ---------------------------------------------------------------------------
449
+ // /exit
450
+ // ---------------------------------------------------------------------------
451
+ register('exit', 'Exit CLI', () => {
452
+ console.log(ui.dim('Goodbye.'));
453
+ process.exit(0);
454
+ });
455
+
456
+ // ---------------------------------------------------------------------------
457
+ // Table style helper
458
+ // ---------------------------------------------------------------------------
459
+ function tableChars() {
460
+ return {
461
+ 'top': '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
462
+ 'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
463
+ 'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼',
464
+ 'right': '│', 'right-mid': '┤', 'middle': '│',
465
+ };
466
+ }
467
+
468
+ module.exports = { commands, execute, register };
package/lib/ui.js ADDED
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+
5
+ const LOGO = `
6
+ ${chalk.white.bold(' ███╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ █████╗ ')}
7
+ ${chalk.white.bold(' ████╗ ██║██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔══██╗')}
8
+ ${chalk.white.bold(' ██╔██╗ ██║███████║██║ ██║███████║██║ ██║███████║')}
9
+ ${chalk.gray(' ██║╚██╗██║██╔══██║╚██╗ ██╔╝██╔══██║██║ ██║██╔══██║')}
10
+ ${chalk.gray(' ██║ ╚████║██║ ██║ ╚████╔╝ ██║ ██║██████╔╝██║ ██║')}
11
+ ${chalk.gray.dim(' ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝')}
12
+ ${chalk.gray(' ─────────────────────────────────────────────────────')}
13
+ ${chalk.white(' E D G E N E T W O R K')} ${chalk.gray.dim('v1.0.0')}
14
+ ${chalk.gray(' ─────────────────────────────────────────────────────')}
15
+ `;
16
+
17
+ const DIVIDER = chalk.gray(' ─────────────────────────────────────────────────────');
18
+
19
+ function header(text) {
20
+ return `\n${chalk.white.bold(' ' + text)}\n${DIVIDER}`;
21
+ }
22
+
23
+ function label(key, val) {
24
+ return ` ${chalk.gray(key.padEnd(20))} ${chalk.white(val)}`;
25
+ }
26
+
27
+ function online(name, isOnline, detail = '') {
28
+ const dot = isOnline ? chalk.green('●') : chalk.red('○');
29
+ const status = isOnline ? chalk.green('ONLINE') : chalk.red('OFFLINE');
30
+ const d = detail ? chalk.gray(` (${detail})`) : '';
31
+ return ` ${dot} ${chalk.white(name.padEnd(14))} ${status}${d}`;
32
+ }
33
+
34
+ function cmd(name, desc) {
35
+ return ` ${chalk.white('/' + name.padEnd(16))} ${chalk.gray(desc)}`;
36
+ }
37
+
38
+ function error(msg) {
39
+ return chalk.red(` ✗ ${msg}`);
40
+ }
41
+
42
+ function success(msg) {
43
+ return chalk.green(` ✓ ${msg}`);
44
+ }
45
+
46
+ function warn(msg) {
47
+ return chalk.yellow(` ! ${msg}`);
48
+ }
49
+
50
+ function dim(msg) {
51
+ return chalk.gray(` ${msg}`);
52
+ }
53
+
54
+ function prompt() {
55
+ return chalk.gray(' navada') + chalk.white('> ');
56
+ }
57
+
58
+ module.exports = { LOGO, DIVIDER, header, label, online, cmd, error, success, warn, dim, prompt };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "navada-edge-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for the NAVADA Edge Network — interactive TUI to explore nodes, Cloudflare, AI, Docker, and MCP services",
5
+ "main": "lib/cli.js",
6
+ "bin": {
7
+ "navada": "./bin/navada.js"
8
+ },
9
+ "type": "commonjs",
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "scripts": {
14
+ "start": "node bin/navada.js",
15
+ "test": "node -e \"require('./lib/cli'); console.log('OK');\""
16
+ },
17
+ "keywords": [
18
+ "navada",
19
+ "edge",
20
+ "cli",
21
+ "tui",
22
+ "distributed",
23
+ "cloudflare",
24
+ "docker",
25
+ "ai",
26
+ "mcp",
27
+ "sdk"
28
+ ],
29
+ "author": {
30
+ "name": "Leslie Akpareva",
31
+ "email": "leeakpareva@hotmail.com",
32
+ "url": "https://www.navada-lab.space"
33
+ },
34
+ "license": "MIT",
35
+ "homepage": "https://github.com/Navada25/edge-sdk",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/Navada25/edge-sdk.git"
39
+ },
40
+ "dependencies": {
41
+ "navada-edge-sdk": "^1.0.0",
42
+ "chalk": "^4.1.2",
43
+ "cli-table3": "^0.6.5",
44
+ "ora": "^5.4.1"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "files": [
50
+ "bin/",
51
+ "lib/",
52
+ "README.md",
53
+ "LICENSE"
54
+ ]
55
+ }