navada-edge-cli 4.0.0 → 4.2.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.
@@ -13,12 +13,11 @@ module.exports = function(reg) {
13
13
 
14
14
  const hasKey = config.getApiKey() || config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY;
15
15
 
16
- // Reset streaming flag before each chat
17
- sessionState._lastStreamed = false;
18
-
19
- // Show thinking indicator
16
+ // Show a brief "thinking" indicator, then clear it when streaming starts
20
17
  let spinner;
21
- if (hasKey) {
18
+ if (!hasKey) {
19
+ process.stdout.write(ui.dim(' NAVADA > '));
20
+ } else {
22
21
  const ora = require('ora');
23
22
  spinner = ora({ text: ' NAVADA thinking...', color: 'white' }).start();
24
23
  }
@@ -32,9 +31,9 @@ module.exports = function(reg) {
32
31
  addToHistory('user', msg);
33
32
  addToHistory('assistant', response);
34
33
 
35
- // Only print if not already streamed to stdout
36
- if (response && !sessionState._lastStreamed) {
37
- console.log('');
34
+ // Only print if not already streamed
35
+ if (!response._streamed) {
36
+ console.log(ui.header('NAVADA'));
38
37
  console.log(` ${response}`);
39
38
  }
40
39
 
@@ -43,7 +42,7 @@ module.exports = function(reg) {
43
42
  } catch (e) {
44
43
  if (spinner) spinner.stop();
45
44
  console.log(ui.error(e.message));
46
- console.log(ui.dim('/config to check providers, or /setup to configure.'));
45
+ console.log(ui.dim('Check: /config to see which providers are set, or /setup to configure.'));
47
46
  }
48
47
  }, { category: 'AI', aliases: ['ask'] });
49
48
 
@@ -172,7 +172,7 @@ CURRENT STATE (March 2026):
172
172
  - Azure Key Vault: navada-edge-vault stores all secrets, user keys synced on create/revoke
173
173
 
174
174
  NOT YET BUILT:
175
- - Edge Compute MCP (/offload, /sessions, /attach)
175
+ - Automation queue API (/automate, /requests)
176
176
  - Agent customisation (agent.md, sub-agents)
177
177
  - Azure compute node
178
178
  - Billing/metering
@@ -5,31 +5,32 @@ const ui = require('../ui');
5
5
  const config = require('../config');
6
6
  const https = require('https');
7
7
  const http = require('http');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const crypto = require('crypto');
8
11
 
9
- const COMPUTE_ENDPOINT = 'https://edge-compute.navada-edge-server.uk';
12
+ const QUEUE_ENDPOINT = 'https://api.navada-edge-server.uk';
10
13
 
11
14
  function getEdgeKey() {
12
15
  return config.get('edgeKey') || '';
13
16
  }
14
17
 
15
- function request(method, path, body) {
18
+ async function submitRequest(body) {
16
19
  const key = getEdgeKey();
17
- if (!key) return Promise.reject(new Error('Not connected. Run /edge login <key> first.'));
18
-
19
- const url = new URL(COMPUTE_ENDPOINT + path);
20
- const transport = url.protocol === 'https:' ? https : http;
21
- const payload = body ? JSON.stringify(body) : '';
20
+ const headers = {
21
+ 'Content-Type': 'application/json',
22
+ 'X-Request-ID': `nv_${crypto.randomUUID()}`,
23
+ 'X-Client-Version': require('../package.json').version,
24
+ };
25
+ if (key) headers['Authorization'] = `Bearer ${key}`;
22
26
 
23
27
  return new Promise((resolve, reject) => {
24
- const req = transport.request(url, {
25
- method,
26
- headers: {
27
- 'Content-Type': 'application/json',
28
- 'x-api-key': key,
29
- ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
30
- },
31
- timeout: 30000,
32
- }, (res) => {
28
+ const url = new URL(QUEUE_ENDPOINT + '/api/v1/queue/automation');
29
+ const transport = url.protocol === 'https:' ? https : http;
30
+ const payload = JSON.stringify(body);
31
+ headers['Content-Length'] = Buffer.byteLength(payload);
32
+
33
+ const req = transport.request(url, { method: 'POST', headers, timeout: 15000 }, (res) => {
33
34
  let data = '';
34
35
  res.on('data', c => data += c);
35
36
  res.on('end', () => {
@@ -39,187 +40,165 @@ function request(method, path, body) {
39
40
  });
40
41
  req.on('error', reject);
41
42
  req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
42
- if (payload) req.write(payload);
43
+ req.write(payload);
43
44
  req.end();
44
45
  });
45
46
  }
46
47
 
47
48
  module.exports = function(reg) {
48
49
 
49
- // ── /offload ── Send a task to the cloud
50
- reg('offload', 'Run a task on the Edge Network (24/7 cloud)', async (args) => {
50
+ // ── /automate ── Submit automation request
51
+ reg('automate', 'Submit an automation request to NAVADA Edge', async (args) => {
51
52
  if (!args.length) {
52
- console.log(ui.header('EDGE COMPUTE OFFLOAD'));
53
- console.log(ui.cmd('offload <command>', 'Run a shell command on EC2'));
54
- console.log(ui.cmd('offload --python <code>', 'Run Python code on EC2'));
55
- console.log(ui.cmd('offload --js <code>', 'Run JavaScript on EC2'));
56
- console.log(ui.cmd('offload --name <name> <cmd>', 'Name the task'));
53
+ console.log(ui.header('NAVADA EDGE — AUTOMATION'));
54
+ console.log(ui.dim(' Submit a request and we\'ll set it up for you on 24/7 cloud.'));
55
+ console.log('');
56
+ console.log(ui.cmd('automate <description>', 'Quick automation request'));
57
+ console.log(ui.cmd('automate --type email', 'Specify type'));
58
+ console.log(ui.cmd('automate --schedule weekly', 'Specify schedule'));
59
+ console.log('');
60
+ console.log(ui.dim('Types: email, marketing, build, data, schedule, custom'));
61
+ console.log(ui.dim('Schedules: daily, weekly, monthly, cron, one-time'));
57
62
  console.log('');
58
63
  console.log(ui.dim('Examples:'));
59
- console.log(ui.dim(' /offload npm test'));
60
- console.log(ui.dim(' /offload --python "import time; time.sleep(60); print(\'done\')"'));
61
- console.log(ui.dim(' /offload --name "nightly-backup" tar czf backup.tar.gz /data'));
64
+ console.log(ui.dim(' /automate Send weekly newsletter to my leads'));
65
+ console.log(ui.dim(' /automate --type build Deploy my React app nightly'));
66
+ console.log(ui.dim(' /automate --type data --schedule daily Scrape competitor prices'));
62
67
  console.log('');
63
- console.log(ui.dim('Requires Edge Network connection: /edge login <key>'));
68
+ console.log(ui.dim('Or describe what you want to the AI agent — it will submit for you.'));
64
69
  return;
65
70
  }
66
71
 
67
- const isPython = args.includes('--python');
68
- const isJs = args.includes('--js');
69
- const nameIdx = args.indexOf('--name');
70
- let name = 'task';
71
- let filteredArgs = [...args];
72
+ // Parse flags
73
+ let type = 'custom';
74
+ let schedule = 'on-demand';
75
+ const filtered = [];
72
76
 
73
- if (nameIdx >= 0) {
74
- name = args[nameIdx + 1] || 'task';
75
- filteredArgs.splice(nameIdx, 2);
76
- }
77
- filteredArgs = filteredArgs.filter(a => a !== '--python' && a !== '--js');
78
-
79
- const body = {};
80
- if (isPython) {
81
- body.code = filteredArgs.join(' ');
82
- body.language = 'python';
83
- } else if (isJs) {
84
- body.code = filteredArgs.join(' ');
85
- body.language = 'javascript';
86
- } else {
87
- body.command = filteredArgs.join(' ');
77
+ for (let i = 0; i < args.length; i++) {
78
+ if (args[i] === '--type' && args[i + 1]) { type = args[++i]; }
79
+ else if (args[i] === '--schedule' && args[i + 1]) { schedule = args[++i]; }
80
+ else { filtered.push(args[i]); }
88
81
  }
89
- body.name = name;
82
+
83
+ const description = filtered.join(' ');
84
+ if (!description) { console.log(ui.error('Please describe what you want automated.')); return; }
85
+
86
+ const requestId = `req_${crypto.randomUUID().slice(0, 8)}`;
87
+ const userId = config.get('edgeUserId') || 'anonymous';
88
+ const email = config.get('edgeEmail') || '';
89
+ const name = config.get('edgeName') || '';
90
+
91
+ const request = {
92
+ id: requestId,
93
+ title: description.slice(0, 80),
94
+ description,
95
+ type,
96
+ schedule,
97
+ userId,
98
+ email,
99
+ name,
100
+ status: 'pending',
101
+ submittedAt: new Date().toISOString(),
102
+ };
90
103
 
91
104
  const ora = require('ora');
92
- const spinner = ora({ text: ' Offloading to Edge Network...', color: 'white' }).start();
105
+ const spinner = ora({ text: ' Submitting automation request...', color: 'white' }).start();
93
106
 
94
107
  try {
95
- const r = await request('POST', '/offload', body);
108
+ const r = await submitRequest(request);
96
109
  spinner.stop();
97
110
 
98
- if (r.status === 201) {
99
- console.log(ui.success(`Task offloaded: ${r.data.session.id}`));
100
- console.log(ui.label('Session', r.data.session.id));
101
- console.log(ui.label('Task', r.data.session.task));
102
- console.log(ui.label('Status', r.data.session.status));
111
+ if (r.status === 201 || r.status === 200) {
112
+ console.log(ui.success('Automation request submitted!'));
113
+ console.log('');
114
+ console.log(ui.label('Request ID', requestId));
115
+ console.log(ui.label('Title', request.title));
116
+ console.log(ui.label('Type', type));
117
+ console.log(ui.label('Schedule', schedule));
118
+ console.log(ui.label('Status', 'Pending review'));
103
119
  console.log('');
104
- console.log(ui.dim(`Track: /sessions`));
105
- console.log(ui.dim(`Output: /attach ${r.data.session.id}`));
106
- } else if (r.status === 401) {
107
- console.log(ui.error('Invalid API key. Run /edge login <key>'));
108
- } else if (r.status === 429) {
109
- console.log(ui.warn(r.data.error || 'Concurrent task limit reached'));
120
+ console.log(ui.dim('You\'ll receive an email once your automation is set up.'));
121
+ console.log(ui.dim('Track: /requests'));
110
122
  } else {
111
- console.log(ui.error(r.data.error || `HTTP ${r.status}`));
112
- }
113
- } catch (e) {
114
- spinner.stop();
115
- console.log(ui.error(`Edge Compute unreachable: ${e.message}`));
116
- console.log(ui.dim('The Edge Network may be offline. Try /doctor'));
117
- }
118
- }, { category: 'EDGE' });
119
-
120
- // ── /sessions ── List cloud sessions
121
- reg('sessions', 'View Edge Network task sessions', async () => {
122
- const ora = require('ora');
123
- const spinner = ora({ text: ' Loading sessions...', color: 'white' }).start();
124
-
125
- try {
126
- const r = await request('GET', '/sessions');
127
- spinner.stop();
128
-
129
- if (r.status === 401) { console.log(ui.error('Not connected. /edge login <key>')); return; }
130
- if (r.status !== 200) { console.log(ui.error(r.data.error || `HTTP ${r.status}`)); return; }
131
-
132
- console.log(ui.header('EDGE SESSIONS'));
133
- const sessions = r.data.sessions || [];
134
-
135
- if (sessions.length === 0) {
136
- console.log(ui.dim('No sessions yet. /offload <command> to start one.'));
137
- return;
138
- }
139
-
140
- for (const s of sessions) {
141
- const statusColor = s.status === 'completed' ? '\x1b[32m' : s.status === 'running' ? '\x1b[33m' : '\x1b[31m';
142
- const statusLabel = `${statusColor}${s.status.toUpperCase()}\x1b[0m`;
143
- const age = s.completedAt ? timeSince(s.completedAt) : timeSince(s.createdAt);
144
- console.log(` ${statusLabel} ${s.id.slice(0, 16)} ${(s.task || '').padEnd(20)} ${age}`);
123
+ spinner.stop();
124
+ console.log(ui.error(r.data?.error || `HTTP ${r.status}`));
145
125
  }
146
-
147
- console.log('');
148
- console.log(ui.label('Total', String(r.data.total)));
149
- console.log(ui.label('Limits', `${r.data.limits.concurrent} concurrent, ${Math.round(r.data.limits.maxRuntime / 60000)}min max`));
150
- console.log('');
151
- console.log(ui.dim('Details: /attach <session-id>'));
152
126
  } catch (e) {
153
127
  spinner.stop();
154
- console.log(ui.error(`Edge Compute unreachable: ${e.message}`));
155
- }
156
- }, { category: 'EDGE' });
157
-
158
- // ── /attach ── Stream session output
159
- reg('attach', 'Attach to a running Edge session (stream output)', async (args) => {
160
- const id = args[0];
161
- if (!id) {
162
- console.log(ui.dim('Usage: /attach <session-id>'));
163
- console.log(ui.dim('List sessions: /sessions'));
164
- return;
128
+ // Save locally if API unavailable
129
+ const reqDir = path.join(config.CONFIG_DIR, 'requests');
130
+ if (!fs.existsSync(reqDir)) fs.mkdirSync(reqDir, { recursive: true });
131
+ fs.writeFileSync(path.join(reqDir, `${requestId}.json`), JSON.stringify(request, null, 2));
132
+
133
+ console.log(ui.warn('Could not reach NAVADA Edge request saved locally.'));
134
+ console.log(ui.label('Request ID', requestId));
135
+ console.log(ui.dim('It will sync automatically when connected.'));
136
+ console.log(ui.dim('Track: /requests'));
165
137
  }
166
-
167
- // If short ID, try to match
168
- const sessionId = id.startsWith('ses_') ? id : `ses_${id}`;
169
-
170
- try {
171
- const r = await request('GET', `/session/${sessionId}`);
172
- if (r.status === 404) { console.log(ui.error('Session not found')); return; }
173
- if (r.status === 401) { console.log(ui.error('Not connected. /edge login <key>')); return; }
174
-
175
- const s = r.data;
176
- console.log(ui.header(`SESSION ${s.id}`));
177
- console.log(ui.label('Task', s.task?.name || s.task?.command?.slice(0, 60) || '?'));
178
- console.log(ui.label('Status', s.status));
179
- console.log(ui.label('Created', s.createdAt));
180
- if (s.completedAt) console.log(ui.label('Completed', s.completedAt));
181
- if (s.exitCode !== null) console.log(ui.label('Exit code', String(s.exitCode)));
182
- if (s.error) console.log(ui.label('Error', s.error));
183
-
184
- if (s.output) {
185
- console.log('');
186
- console.log(ui.dim('── OUTPUT ──'));
187
- console.log(s.output);
188
- }
189
-
190
- if (s.status === 'running') {
191
- console.log('');
192
- console.log(ui.dim('Session is running. Output will update on refresh.'));
193
- console.log(ui.dim('Kill: /kill ' + s.id));
194
- }
195
- } catch (e) {
196
- console.log(ui.error(`Edge Compute unreachable: ${e.message}`));
138
+ }, { category: 'AUTOMATION', aliases: ['auto'] });
139
+
140
+ // ── /requests ── View automation requests
141
+ reg('requests', 'View your automation requests', async (args) => {
142
+ const key = getEdgeKey();
143
+
144
+ // Try API first
145
+ if (key) {
146
+ try {
147
+ const headers = {
148
+ 'Content-Type': 'application/json',
149
+ 'Authorization': `Bearer ${key}`,
150
+ };
151
+ const url = new URL(QUEUE_ENDPOINT + '/api/v1/queue/requests');
152
+ const transport = url.protocol === 'https:' ? https : http;
153
+
154
+ const r = await new Promise((resolve, reject) => {
155
+ const req = transport.request(url, { method: 'GET', headers, timeout: 10000 }, (res) => {
156
+ let data = '';
157
+ res.on('data', c => data += c);
158
+ res.on('end', () => {
159
+ try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
160
+ catch { resolve({ status: res.statusCode, data }); }
161
+ });
162
+ });
163
+ req.on('error', reject);
164
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
165
+ req.end();
166
+ });
167
+
168
+ if (r.status === 200 && r.data?.requests) {
169
+ console.log(ui.header('AUTOMATION REQUESTS'));
170
+ const reqs = r.data.requests;
171
+ if (reqs.length === 0) {
172
+ console.log(ui.dim('No automation requests yet. Submit one: /automate'));
173
+ return;
174
+ }
175
+ for (const req of reqs) {
176
+ const statusColor = req.status === 'active' ? '\x1b[32m' : req.status === 'pending' ? '\x1b[33m' : '\x1b[37m';
177
+ console.log(` ${statusColor}${(req.status || '').toUpperCase().padEnd(10)}\x1b[0m ${req.id.padEnd(14)} ${(req.title || '').slice(0, 40)}`);
178
+ }
179
+ console.log('');
180
+ console.log(ui.dim(`${reqs.length} request(s)`));
181
+ return;
182
+ }
183
+ } catch {}
197
184
  }
198
- }, { category: 'EDGE' });
199
-
200
- // ── /kill ── Kill a running session
201
- reg('kill', 'Kill a running Edge session', async (args) => {
202
- const id = args[0];
203
- if (!id) { console.log(ui.dim('Usage: /kill <session-id>')); return; }
204
- const sessionId = id.startsWith('ses_') ? id : `ses_${id}`;
205
185
 
186
+ // Fallback: show local requests
187
+ const reqDir = path.join(config.CONFIG_DIR, 'requests');
188
+ console.log(ui.header('AUTOMATION REQUESTS (local)'));
206
189
  try {
207
- const r = await request('DELETE', `/session/${sessionId}`);
208
- if (r.status === 200) {
209
- console.log(ui.success(`Session ${sessionId} killed`));
210
- } else {
211
- console.log(ui.error(r.data.error || `HTTP ${r.status}`));
190
+ if (!fs.existsSync(reqDir)) { console.log(ui.dim('No requests yet. Submit one: /automate')); return; }
191
+ const files = fs.readdirSync(reqDir).filter(f => f.endsWith('.json'));
192
+ if (files.length === 0) { console.log(ui.dim('No requests yet. Submit one: /automate')); return; }
193
+ for (const f of files) {
194
+ const req = JSON.parse(fs.readFileSync(path.join(reqDir, f), 'utf-8'));
195
+ const statusColor = req.status === 'active' ? '\x1b[32m' : req.status === 'pending' ? '\x1b[33m' : '\x1b[37m';
196
+ console.log(` ${statusColor}${(req.status || '').toUpperCase().padEnd(10)}\x1b[0m ${req.id.padEnd(14)} ${(req.title || '').slice(0, 40)}`);
212
197
  }
198
+ console.log('');
199
+ console.log(ui.dim(`${files.length} request(s) — saved locally, will sync when connected`));
213
200
  } catch (e) {
214
201
  console.log(ui.error(e.message));
215
202
  }
216
- }, { category: 'EDGE' });
203
+ }, { category: 'AUTOMATION', aliases: ['reqs'] });
217
204
  };
218
-
219
- function timeSince(iso) {
220
- const ms = Date.now() - new Date(iso).getTime();
221
- if (ms < 60000) return `${Math.round(ms / 1000)}s ago`;
222
- if (ms < 3600000) return `${Math.round(ms / 60000)}m ago`;
223
- if (ms < 86400000) return `${Math.round(ms / 3600000)}h ago`;
224
- return `${Math.round(ms / 86400000)}d ago`;
225
- }
@@ -13,6 +13,78 @@ const { sessionState, listSubAgents } = require('../agent');
13
13
 
14
14
  module.exports = function(reg) {
15
15
 
16
+ // /register — self-service registration from CLI
17
+ reg('register', 'Create a NAVADA Edge Network account', async () => {
18
+ const readline = require('readline');
19
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
20
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
21
+
22
+ console.log(ui.header('NAVADA EDGE — REGISTRATION'));
23
+ console.log(ui.dim(' Create your account to access cloud compute, storage, and AI.'));
24
+ console.log('');
25
+
26
+ const name = await ask(' Name: ');
27
+ const email = await ask(' Email: ');
28
+
29
+ if (!name.trim() || !email.trim()) {
30
+ console.log(ui.error('Name and email are required.'));
31
+ rl.close();
32
+ return;
33
+ }
34
+
35
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
36
+ console.log(ui.error('Invalid email format.'));
37
+ rl.close();
38
+ return;
39
+ }
40
+
41
+ const ora = require('ora');
42
+ const spinner = ora({ text: ' Creating account...', color: 'white' }).start();
43
+
44
+ try {
45
+ const r = await navada.request('https://api.navada-edge-server.uk/api/v1/register', {
46
+ method: 'POST',
47
+ body: { name: name.trim(), email: email.trim() },
48
+ timeout: 15000,
49
+ });
50
+
51
+ spinner.stop();
52
+
53
+ if (r.status === 201 && r.data?.key) {
54
+ config.set('edgeKey', r.data.key);
55
+ config.set('edgeTier', r.data.tier || 'free');
56
+ config.set('edgeUserId', r.data.userId || '');
57
+ config.set('edgeName', name.trim());
58
+ config.set('edgeEmail', email.trim());
59
+ config.set('edgeConnected', true);
60
+
61
+ console.log(ui.success('Account created!'));
62
+ console.log('');
63
+ console.log(ui.label('API Key', r.data.key));
64
+ console.log(ui.label('Tier', (r.data.tier || 'FREE').toUpperCase()));
65
+ console.log(ui.label('User ID', r.data.userId || 'assigned'));
66
+ console.log(ui.label('Namespace', r.data.namespace || 'user_' + (r.data.userId || '').slice(0, 8)));
67
+ console.log('');
68
+ console.log(ui.dim('Your key has been saved. You\'re ready to go!'));
69
+ console.log(ui.dim('Try: /automate Send weekly newsletter to my leads'));
70
+ } else if (r.status === 409) {
71
+ spinner.stop();
72
+ console.log(ui.warn('An account with this email already exists.'));
73
+ console.log(ui.dim('Use /edge login <key> if you already have a key.'));
74
+ console.log(ui.dim('Or /onboard to reset your key via the portal.'));
75
+ } else {
76
+ spinner.stop();
77
+ console.log(ui.error(r.data?.error || `Registration failed (HTTP ${r.status})`));
78
+ }
79
+ } catch (e) {
80
+ spinner.stop();
81
+ console.log(ui.error(`Could not reach NAVADA Edge Network: ${e.message}`));
82
+ console.log(ui.dim('Try again later or register at /onboard'));
83
+ }
84
+
85
+ rl.close();
86
+ }, { category: 'EDGE' });
87
+
16
88
  // /onboard — open portal in browser
17
89
  reg('onboard', 'Open NAVADA Edge Portal to create account and get API key', async () => {
18
90
  console.log(ui.header('NAVADA EDGE NETWORK — ONBOARDING'));
@@ -39,7 +111,7 @@ module.exports = function(reg) {
39
111
  console.log('');
40
112
  console.log(ui.dim('After signing up and generating a key, run:'));
41
113
  console.log(ui.dim(' /edge login nv_edge_your_key_here'));
42
- }, { category: 'EDGE', aliases: ['signup', 'register'] });
114
+ }, { category: 'EDGE', aliases: ['signup'] });
43
115
 
44
116
  // /edge — edge network commands
45
117
  reg('edge', 'NAVADA Edge Network commands', async (args) => {
@@ -53,8 +125,10 @@ module.exports = function(reg) {
53
125
  console.log(ui.cmd('edge status', 'Check your Edge Network connection'));
54
126
  console.log(ui.cmd('edge logout', 'Disconnect from Edge Network'));
55
127
  console.log(ui.cmd('edge tier', 'Show your current tier and limits'));
128
+ console.log(ui.cmd('edge usage', 'View your usage and limits'));
56
129
  console.log(ui.cmd('edge setup', 'Create agent.md and sub-agents directory'));
57
- console.log(ui.cmd('onboard', 'Create account and get API key'));
130
+ console.log(ui.cmd('register', 'Create account from CLI'));
131
+ console.log(ui.cmd('onboard', 'Create account via portal'));
58
132
  console.log('');
59
133
  console.log(ui.dim('Get started: /onboard | Customise: /edge setup'));
60
134
  return;
@@ -100,10 +174,10 @@ module.exports = function(reg) {
100
174
  console.log(ui.label('User', r.data.name || r.data.userId || 'connected'));
101
175
  console.log('');
102
176
  console.log(ui.dim('You now have access to:'));
103
- console.log(ui.dim(' /offloadrun tasks 24/7 on cloud'));
104
- console.log(ui.dim(' /sessions view running tasks'));
105
- console.log(ui.dim(' /attach stream task output'));
106
- console.log(ui.dim(' /kill stop a task'));
177
+ console.log(ui.dim(' /automatesubmit automation requests'));
178
+ console.log(ui.dim(' /requests track your requests'));
179
+ console.log(ui.dim(' /tools available agent tools'));
180
+ console.log(ui.dim(' /skills what the agent can do'));
107
181
  validated = true;
108
182
  break;
109
183
  } else if (r.status === 401) {
@@ -170,14 +244,65 @@ module.exports = function(reg) {
170
244
  console.log(ui.header('EDGE NETWORK TIER'));
171
245
  console.log(ui.label('Current', tier));
172
246
  console.log('');
247
+
248
+ const tiers = {
249
+ FREE: {
250
+ limits: ['100 requests/day', '10MB cloud storage', '5 min max compute', '3 concurrent tasks', 'Read-only shared datasets'],
251
+ tools: ['navada_compute', 'navada_storage (read)', 'web_search', 'memory tools'],
252
+ },
253
+ PRO: {
254
+ limits: ['1,000 requests/day', '1GB cloud storage', '30 min max compute', '10 concurrent tasks', 'Read/write own namespace'],
255
+ tools: ['All FREE tools', 'navada_storage (write)', 'perception tools', 'priority compute'],
256
+ },
257
+ TEAM: {
258
+ limits: ['Unlimited requests', '10GB shared storage', '60 min max compute', 'Unlimited concurrent', 'Shared team namespace'],
259
+ tools: ['All PRO tools', 'shared namespaces', 'custom agents', 'dedicated compute'],
260
+ },
261
+ };
262
+
263
+ const tierInfo = tiers[tier] || tiers.FREE;
264
+ console.log(ui.dim('Limits:'));
265
+ tierInfo.limits.forEach(l => console.log(ui.dim(` - ${l}`)));
266
+ console.log('');
267
+ console.log(ui.dim('Available tools:'));
268
+ tierInfo.tools.forEach(t => console.log(ui.dim(` - ${t}`)));
269
+ console.log('');
270
+
173
271
  if (tier === 'FREE') {
174
- console.log(ui.dim('Free tier includes:'));
175
- console.log(ui.dim(' 100 requests/day'));
176
- console.log(ui.dim(' 50K tokens/day'));
177
- console.log(ui.dim(' 10 edge tasks'));
178
- console.log(ui.dim(' 5min max runtime per task'));
179
- console.log('');
180
- console.log(ui.dim('Upgrade: coming soon'));
272
+ console.log(ui.dim('Upgrade: https://portal.navada-edge-server.uk/pricing'));
273
+ }
274
+ return;
275
+ }
276
+
277
+ // /edge usage — show current usage stats
278
+ if (sub === 'usage') {
279
+ const ora = require('ora');
280
+ const spinner = ora({ text: ' Fetching usage...', color: 'white' }).start();
281
+ try {
282
+ const edgeKey = config.get('edgeKey');
283
+ if (!edgeKey) { spinner.stop(); console.log(ui.error('Not connected. /edge login or /register')); return; }
284
+
285
+ const r = await navada.request('https://api.navada-edge-server.uk/api/v1/usage', {
286
+ method: 'GET',
287
+ headers: { 'Authorization': `Bearer ${edgeKey}` },
288
+ timeout: 10000,
289
+ });
290
+ spinner.stop();
291
+
292
+ if (r.status === 200) {
293
+ const u = r.data;
294
+ console.log(ui.header('EDGE USAGE'));
295
+ console.log(ui.label('Requests today', `${u.requestsToday || 0} / ${u.requestLimit || 100}`));
296
+ console.log(ui.label('Storage used', `${u.storageMB || 0} MB / ${u.storageLimit || 10} MB`));
297
+ console.log(ui.label('Compute tasks', `${u.tasksToday || 0} / ${u.taskLimit || 3}`));
298
+ console.log(ui.label('Namespace', u.namespace || 'user_' + (config.get('edgeUserId') || 'unknown')));
299
+ console.log(ui.label('Tier', (config.get('edgeTier') || 'FREE').toUpperCase()));
300
+ } else {
301
+ console.log(ui.error(r.data?.error || `HTTP ${r.status}`));
302
+ }
303
+ } catch (e) {
304
+ spinner.stop();
305
+ console.log(ui.error(e.message));
181
306
  }
182
307
  return;
183
308
  }
@@ -249,7 +374,7 @@ Be methodical. Confirm each step before proceeding. Report any failures immediat
249
374
 
250
375
  console.log(ui.dim('Unknown subcommand. Try /edge help'));
251
376
 
252
- }, { category: 'EDGE', subs: ['login', 'status', 'logout', 'tier', 'setup', 'help'] });
377
+ }, { category: 'EDGE', subs: ['login', 'status', 'logout', 'tier', 'usage', 'setup', 'help'] });
253
378
 
254
379
  // ── /agent ── Manage sub-agents
255
380
  reg('agent', 'Manage NAVADA sub-agents', (args) => {
@@ -6,7 +6,7 @@ const { register } = require('../registry');
6
6
  const moduleNames = [
7
7
  'network', 'mcp', 'lucas', 'docker', 'database', 'cloudflare',
8
8
  'ai', 'azure', 'agents', 'tasks', 'keys', 'setup', 'system',
9
- 'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute', 'files',
9
+ 'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute', 'skills',
10
10
  ];
11
11
 
12
12
  function loadAll() {