navada-edge-cli 3.2.0 → 3.4.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/README.md +499 -191
- package/architecture.svg +171 -0
- package/docker-compose.yml +1 -1
- package/lib/agent.js +102 -8
- package/lib/commands/ai.js +61 -3
- package/lib/commands/conversations.js +139 -0
- package/lib/commands/edge.js +186 -0
- package/lib/commands/index.js +1 -1
- package/lib/commands/system.js +5 -0
- package/package.json +2 -2
- package/network.svg +0 -207
package/architecture.svg
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500" width="800" height="500">
|
|
2
|
+
<style>
|
|
3
|
+
text { font-family: 'SF Mono', 'Fira Code', monospace; fill: #e0e0e0; }
|
|
4
|
+
.label { font-size: 9px; fill: #666; }
|
|
5
|
+
.title { font-size: 11px; font-weight: bold; }
|
|
6
|
+
.small { font-size: 8px; fill: #888; }
|
|
7
|
+
.accent { fill: #a855f7; }
|
|
8
|
+
.box { fill: #0a0a0a; stroke: #1a1a1a; stroke-width: 1; }
|
|
9
|
+
.box-accent { fill: #0a0a0a; stroke: #a855f7; stroke-width: 1; }
|
|
10
|
+
.line { stroke: #333; stroke-width: 1; fill: none; }
|
|
11
|
+
.line-accent { stroke: #a855f7; stroke-width: 1; fill: none; }
|
|
12
|
+
.arrow { fill: #a855f7; }
|
|
13
|
+
</style>
|
|
14
|
+
|
|
15
|
+
<rect width="800" height="500" fill="#0a0a0a"/>
|
|
16
|
+
|
|
17
|
+
<!-- Title -->
|
|
18
|
+
<text x="400" y="24" text-anchor="middle" font-size="13" font-weight="bold" fill="#e0e0e0">NAVADA Edge CLI — Architecture</text>
|
|
19
|
+
<text x="400" y="38" text-anchor="middle" class="label">v3.3.0</text>
|
|
20
|
+
|
|
21
|
+
<!-- USER -->
|
|
22
|
+
<rect x="20" y="60" width="110" height="40" rx="2" class="box-accent"/>
|
|
23
|
+
<text x="75" y="78" text-anchor="middle" class="title">USER</text>
|
|
24
|
+
<text x="75" y="90" text-anchor="middle" class="label">Terminal</text>
|
|
25
|
+
|
|
26
|
+
<!-- Arrow: User -> CLI -->
|
|
27
|
+
<line x1="130" y1="80" x2="178" y2="80" class="line-accent"/>
|
|
28
|
+
<polygon points="178,76 186,80 178,84" class="arrow"/>
|
|
29
|
+
|
|
30
|
+
<!-- CLI Core -->
|
|
31
|
+
<rect x="188" y="55" width="140" height="50" rx="2" class="box-accent"/>
|
|
32
|
+
<text x="258" y="76" text-anchor="middle" class="title" fill="#a855f7">NAVADA Edge CLI</text>
|
|
33
|
+
<text x="258" y="90" text-anchor="middle" class="label">agent.md + config</text>
|
|
34
|
+
|
|
35
|
+
<!-- Three output arrows from CLI -->
|
|
36
|
+
<!-- Arrow: CLI -> AI Router -->
|
|
37
|
+
<line x1="328" y1="70" x2="410" y2="70" class="line-accent"/>
|
|
38
|
+
<line x1="410" y1="70" x2="410" y2="140" class="line-accent"/>
|
|
39
|
+
<polygon points="406,140 410,148 414,140" class="arrow"/>
|
|
40
|
+
|
|
41
|
+
<!-- Arrow: CLI -> Tool Engine -->
|
|
42
|
+
<line x1="328" y1="80" x2="540" y2="80" class="line-accent"/>
|
|
43
|
+
<line x1="540" y1="80" x2="540" y2="140" class="line-accent"/>
|
|
44
|
+
<polygon points="536,140 540,148 544,140" class="arrow"/>
|
|
45
|
+
|
|
46
|
+
<!-- Arrow: CLI -> Edge Network -->
|
|
47
|
+
<line x1="328" y1="90" x2="680" y2="90" class="line-accent"/>
|
|
48
|
+
<line x1="680" y1="90" x2="680" y2="140" class="line-accent"/>
|
|
49
|
+
<polygon points="676,140 680,148 684,140" class="arrow"/>
|
|
50
|
+
|
|
51
|
+
<!-- === COLUMN 1: AI Router === -->
|
|
52
|
+
<rect x="340" y="150" width="140" height="36" rx="2" class="box-accent"/>
|
|
53
|
+
<text x="410" y="170" text-anchor="middle" class="title">AI Router</text>
|
|
54
|
+
<text x="410" y="180" text-anchor="middle" class="label">model selection</text>
|
|
55
|
+
|
|
56
|
+
<!-- Provider grid: 3x2 -->
|
|
57
|
+
<rect x="310" y="200" width="90" height="28" rx="2" class="box"/>
|
|
58
|
+
<text x="355" y="215" text-anchor="middle" font-size="9" fill="#a855f7">NAVADA Free</text>
|
|
59
|
+
|
|
60
|
+
<rect x="410" y="200" width="90" height="28" rx="2" class="box"/>
|
|
61
|
+
<text x="455" y="215" text-anchor="middle" font-size="9">Anthropic</text>
|
|
62
|
+
|
|
63
|
+
<rect x="310" y="236" width="90" height="28" rx="2" class="box"/>
|
|
64
|
+
<text x="355" y="251" text-anchor="middle" font-size="9">OpenAI</text>
|
|
65
|
+
|
|
66
|
+
<rect x="410" y="236" width="90" height="28" rx="2" class="box"/>
|
|
67
|
+
<text x="455" y="251" text-anchor="middle" font-size="9">Gemini</text>
|
|
68
|
+
|
|
69
|
+
<rect x="310" y="272" width="90" height="28" rx="2" class="box"/>
|
|
70
|
+
<text x="355" y="287" text-anchor="middle" font-size="9">NVIDIA</text>
|
|
71
|
+
|
|
72
|
+
<rect x="410" y="272" width="90" height="28" rx="2" class="box"/>
|
|
73
|
+
<text x="455" y="287" text-anchor="middle" font-size="9">HuggingFace</text>
|
|
74
|
+
|
|
75
|
+
<!-- Connector lines from router to providers -->
|
|
76
|
+
<line x1="410" y1="186" x2="355" y2="200" class="line"/>
|
|
77
|
+
<line x1="410" y1="186" x2="455" y2="200" class="line"/>
|
|
78
|
+
<line x1="355" y1="228" x2="355" y2="236" class="line"/>
|
|
79
|
+
<line x1="455" y1="228" x2="455" y2="236" class="line"/>
|
|
80
|
+
<line x1="355" y1="264" x2="355" y2="272" class="line"/>
|
|
81
|
+
<line x1="455" y1="264" x2="455" y2="272" class="line"/>
|
|
82
|
+
|
|
83
|
+
<!-- === COLUMN 2: Tool Engine === -->
|
|
84
|
+
<rect x="510" y="150" width="120" height="36" rx="2" class="box-accent"/>
|
|
85
|
+
<text x="570" y="170" text-anchor="middle" class="title">Tool Engine</text>
|
|
86
|
+
<text x="570" y="180" text-anchor="middle" class="label">execution layer</text>
|
|
87
|
+
|
|
88
|
+
<!-- Tools: Local (left col) + Network (right col) -->
|
|
89
|
+
<text x="540" y="207" text-anchor="middle" class="label">LOCAL</text>
|
|
90
|
+
<text x="620" y="207" text-anchor="middle" class="label">NETWORK</text>
|
|
91
|
+
|
|
92
|
+
<rect x="510" y="214" width="56" height="24" rx="2" class="box"/>
|
|
93
|
+
<text x="538" y="229" text-anchor="middle" font-size="8">Shell</text>
|
|
94
|
+
|
|
95
|
+
<rect x="510" y="244" width="56" height="24" rx="2" class="box"/>
|
|
96
|
+
<text x="538" y="259" text-anchor="middle" font-size="8">Files</text>
|
|
97
|
+
|
|
98
|
+
<rect x="510" y="274" width="56" height="24" rx="2" class="box"/>
|
|
99
|
+
<text x="538" y="289" text-anchor="middle" font-size="8">Python</text>
|
|
100
|
+
|
|
101
|
+
<rect x="510" y="304" width="56" height="24" rx="2" class="box"/>
|
|
102
|
+
<text x="538" y="319" text-anchor="middle" font-size="8">Docker</text>
|
|
103
|
+
|
|
104
|
+
<rect x="576" y="214" width="56" height="24" rx="2" class="box"/>
|
|
105
|
+
<text x="604" y="229" text-anchor="middle" font-size="8">MCP</text>
|
|
106
|
+
|
|
107
|
+
<rect x="576" y="244" width="56" height="24" rx="2" class="box"/>
|
|
108
|
+
<text x="604" y="259" text-anchor="middle" font-size="8">SSH</text>
|
|
109
|
+
|
|
110
|
+
<rect x="576" y="274" width="56" height="24" rx="2" class="box"/>
|
|
111
|
+
<text x="604" y="289" text-anchor="middle" font-size="8">Email</text>
|
|
112
|
+
|
|
113
|
+
<rect x="576" y="304" width="56" height="24" rx="2" class="box"/>
|
|
114
|
+
<text x="604" y="319" text-anchor="middle" font-size="8">Registry</text>
|
|
115
|
+
|
|
116
|
+
<!-- Connector lines from engine to tools -->
|
|
117
|
+
<line x1="550" y1="186" x2="538" y2="214" class="line"/>
|
|
118
|
+
<line x1="590" y1="186" x2="604" y2="214" class="line"/>
|
|
119
|
+
|
|
120
|
+
<!-- === COLUMN 3: Edge Network === -->
|
|
121
|
+
<rect x="645" y="150" width="130" height="36" rx="2" class="box-accent"/>
|
|
122
|
+
<text x="710" y="170" text-anchor="middle" class="title">Edge Network</text>
|
|
123
|
+
<text x="710" y="180" text-anchor="middle" class="label">Tailscale mesh</text>
|
|
124
|
+
|
|
125
|
+
<!-- 4 Node boxes -->
|
|
126
|
+
<rect x="650" y="200" width="120" height="34" rx="2" class="box"/>
|
|
127
|
+
<text x="710" y="215" text-anchor="middle" font-size="9" fill="#a855f7">ASUS</text>
|
|
128
|
+
<text x="710" y="226" text-anchor="middle" class="small">Production Engine</text>
|
|
129
|
+
|
|
130
|
+
<rect x="650" y="242" width="120" height="34" rx="2" class="box"/>
|
|
131
|
+
<text x="710" y="257" text-anchor="middle" font-size="9">HP</text>
|
|
132
|
+
<text x="710" y="268" text-anchor="middle" class="small">Database (PG17)</text>
|
|
133
|
+
|
|
134
|
+
<rect x="650" y="284" width="120" height="34" rx="2" class="box"/>
|
|
135
|
+
<text x="710" y="299" text-anchor="middle" font-size="9">EC2</text>
|
|
136
|
+
<text x="710" y="310" text-anchor="middle" class="small">24/7 Monitoring</text>
|
|
137
|
+
|
|
138
|
+
<rect x="650" y="326" width="120" height="34" rx="2" class="box"/>
|
|
139
|
+
<text x="710" y="341" text-anchor="middle" font-size="9">Oracle</text>
|
|
140
|
+
<text x="710" y="352" text-anchor="middle" class="small">Infra + Tunnel</text>
|
|
141
|
+
|
|
142
|
+
<!-- Connector lines from network to nodes -->
|
|
143
|
+
<line x1="710" y1="186" x2="710" y2="200" class="line"/>
|
|
144
|
+
<line x1="710" y1="234" x2="710" y2="242" class="line"/>
|
|
145
|
+
<line x1="710" y1="276" x2="710" y2="284" class="line"/>
|
|
146
|
+
<line x1="710" y1="318" x2="710" y2="326" class="line"/>
|
|
147
|
+
|
|
148
|
+
<!-- Mesh lines between nodes (subtle) -->
|
|
149
|
+
<line x1="650" y1="217" x2="645" y2="259" stroke="#1a1a1a" stroke-width="1" stroke-dasharray="2,3"/>
|
|
150
|
+
<line x1="650" y1="259" x2="645" y2="301" stroke="#1a1a1a" stroke-width="1" stroke-dasharray="2,3"/>
|
|
151
|
+
<line x1="650" y1="301" x2="645" y2="343" stroke="#1a1a1a" stroke-width="1" stroke-dasharray="2,3"/>
|
|
152
|
+
|
|
153
|
+
<!-- Bottom bar -->
|
|
154
|
+
<line x1="40" y1="400" x2="760" y2="400" stroke="#1a1a1a" stroke-width="1"/>
|
|
155
|
+
|
|
156
|
+
<!-- Legend -->
|
|
157
|
+
<text x="40" y="425" class="label">FLOW</text>
|
|
158
|
+
<line x1="70" y1="422" x2="100" y2="422" class="line-accent"/>
|
|
159
|
+
<polygon points="100,419 106,422 100,425" class="arrow"/>
|
|
160
|
+
<text x="112" y="425" class="small">data path</text>
|
|
161
|
+
|
|
162
|
+
<rect x="170" y="414" width="14" height="14" rx="2" class="box-accent"/>
|
|
163
|
+
<text x="190" y="425" class="small">core module</text>
|
|
164
|
+
|
|
165
|
+
<rect x="260" y="414" width="14" height="14" rx="2" class="box"/>
|
|
166
|
+
<text x="280" y="425" class="small">component</text>
|
|
167
|
+
|
|
168
|
+
<!-- Footer -->
|
|
169
|
+
<text x="400" y="470" text-anchor="middle" class="label">navada-edge-cli@3.3.0 | npm i -g navada-edge-cli | github.com/navada25/edge-cli</text>
|
|
170
|
+
<text x="400" y="484" text-anchor="middle" font-size="8" fill="#333">NAVADA Edge Network 2026</text>
|
|
171
|
+
</svg>
|
package/docker-compose.yml
CHANGED
package/lib/agent.js
CHANGED
|
@@ -79,8 +79,8 @@ function getSystemPrompt() {
|
|
|
79
79
|
// Session state — exposed for UI panels
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
81
|
const sessionState = {
|
|
82
|
-
provider: '
|
|
83
|
-
model: '
|
|
82
|
+
provider: 'NAVADA (free)',
|
|
83
|
+
model: 'gpt-4o-mini',
|
|
84
84
|
tokens: { input: 0, output: 0, total: 0 },
|
|
85
85
|
cost: 0,
|
|
86
86
|
messages: 0,
|
|
@@ -335,7 +335,7 @@ function streamFreeTier(endpoint, messages) {
|
|
|
335
335
|
const req = transport.request(url, {
|
|
336
336
|
method: 'POST',
|
|
337
337
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
338
|
-
timeout: endpoint.includes('navada-edge-server.uk') ?
|
|
338
|
+
timeout: endpoint.includes('navada-edge-server.uk') ? 120000 : 10000,
|
|
339
339
|
}, (res) => {
|
|
340
340
|
// If server doesn't support streaming, collect full response
|
|
341
341
|
if (!res.headers['content-type']?.includes('text/event-stream')) {
|
|
@@ -373,10 +373,13 @@ function streamFreeTier(endpoint, messages) {
|
|
|
373
373
|
if (data === '[DONE]') continue;
|
|
374
374
|
try {
|
|
375
375
|
const parsed = JSON.parse(data);
|
|
376
|
-
const delta = parsed.choices?.[0]?.delta
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
376
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
377
|
+
// Grok-3-mini streams reasoning_content first, then content — skip reasoning
|
|
378
|
+
if (delta?.reasoning_content && !delta?.content) continue;
|
|
379
|
+
const text = delta?.content || '';
|
|
380
|
+
if (text) {
|
|
381
|
+
process.stdout.write(text);
|
|
382
|
+
fullContent += text;
|
|
380
383
|
}
|
|
381
384
|
} catch {}
|
|
382
385
|
}
|
|
@@ -579,6 +582,72 @@ function streamOpenAI(key, messages, model = 'gpt-4o') {
|
|
|
579
582
|
});
|
|
580
583
|
}
|
|
581
584
|
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
// Streaming — Google Gemini API (gemini-2.0-flash)
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
function streamGemini(key, messages, model = 'gemini-2.0-flash') {
|
|
589
|
+
return new Promise((resolve, reject) => {
|
|
590
|
+
const contents = messages.map(m => ({
|
|
591
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
592
|
+
parts: [{ text: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) }],
|
|
593
|
+
}));
|
|
594
|
+
|
|
595
|
+
const body = JSON.stringify({
|
|
596
|
+
contents,
|
|
597
|
+
generationConfig: { maxOutputTokens: 4096 },
|
|
598
|
+
systemInstruction: { parts: [{ text: getSystemPrompt() }] },
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const url = new URL(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${key}`);
|
|
602
|
+
|
|
603
|
+
const req = https.request(url, {
|
|
604
|
+
method: 'POST',
|
|
605
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
606
|
+
timeout: 120000,
|
|
607
|
+
}, (res) => {
|
|
608
|
+
if (res.statusCode !== 200) {
|
|
609
|
+
let data = '';
|
|
610
|
+
res.on('data', c => data += c);
|
|
611
|
+
res.on('end', () => reject(new Error(`Gemini API error ${res.statusCode}: ${data.slice(0, 200)}`)));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
let buffer = '';
|
|
616
|
+
let fullContent = '';
|
|
617
|
+
|
|
618
|
+
res.on('data', (chunk) => {
|
|
619
|
+
buffer += chunk.toString();
|
|
620
|
+
const lines = buffer.split('\n');
|
|
621
|
+
buffer = lines.pop();
|
|
622
|
+
|
|
623
|
+
for (const line of lines) {
|
|
624
|
+
if (!line.startsWith('data: ')) continue;
|
|
625
|
+
const data = line.slice(6).trim();
|
|
626
|
+
if (!data) continue;
|
|
627
|
+
try {
|
|
628
|
+
const parsed = JSON.parse(data);
|
|
629
|
+
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
630
|
+
if (text) {
|
|
631
|
+
process.stdout.write(text);
|
|
632
|
+
fullContent += text;
|
|
633
|
+
}
|
|
634
|
+
} catch {}
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
res.on('end', () => {
|
|
639
|
+
if (fullContent) process.stdout.write('\n');
|
|
640
|
+
resolve({ content: fullContent });
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
req.on('error', reject);
|
|
645
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
646
|
+
req.write(body);
|
|
647
|
+
req.end();
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
582
651
|
function openAITools() {
|
|
583
652
|
const defs = [
|
|
584
653
|
{ name: 'shell', description: 'Execute a shell command on the user\'s machine', parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] } },
|
|
@@ -662,12 +731,14 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
662
731
|
const anthropicKey = config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY || '';
|
|
663
732
|
const openaiKey = config.get('openaiKey') || process.env.OPENAI_API_KEY || '';
|
|
664
733
|
const nvidiaKey = config.get('nvidiaKey') || process.env.NVIDIA_API_KEY || '';
|
|
734
|
+
const geminiKey = config.get('geminiKey') || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '';
|
|
665
735
|
const apiKey = config.getApiKey() || '';
|
|
666
736
|
|
|
667
737
|
// Determine which provider to use
|
|
668
738
|
const effectiveAnthropicKey = anthropicKey || (apiKey.startsWith('sk-ant') ? apiKey : '');
|
|
669
739
|
const effectiveOpenAIKey = openaiKey || (apiKey.startsWith('sk-') && !apiKey.startsWith('sk-ant') ? apiKey : '');
|
|
670
740
|
const effectiveNvidiaKey = nvidiaKey || (apiKey.startsWith('nvapi-') ? apiKey : '');
|
|
741
|
+
const effectiveGeminiKey = geminiKey || (apiKey.startsWith('AIza') ? apiKey : '');
|
|
671
742
|
|
|
672
743
|
const modelPref = config.getModel();
|
|
673
744
|
const intent = detectIntent(userMessage);
|
|
@@ -675,11 +746,12 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
675
746
|
// Track active provider for UI
|
|
676
747
|
if (effectiveAnthropicKey) sessionState.provider = 'Anthropic';
|
|
677
748
|
else if (effectiveOpenAIKey) sessionState.provider = 'OpenAI';
|
|
749
|
+
else if (effectiveGeminiKey) sessionState.provider = 'Gemini';
|
|
678
750
|
else if (effectiveNvidiaKey) sessionState.provider = 'NVIDIA';
|
|
679
751
|
else sessionState.provider = 'Grok (free)';
|
|
680
752
|
|
|
681
753
|
// No personal key — use free tier
|
|
682
|
-
if (!effectiveAnthropicKey && !effectiveOpenAIKey && !effectiveNvidiaKey) {
|
|
754
|
+
if (!effectiveAnthropicKey && !effectiveOpenAIKey && !effectiveNvidiaKey && !effectiveGeminiKey) {
|
|
683
755
|
if (intent === 'code' && navada.config.hfToken) {
|
|
684
756
|
try {
|
|
685
757
|
const r = await navada.ai.huggingface.qwen(userMessage);
|
|
@@ -689,6 +761,28 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
689
761
|
return grokChat(userMessage, conversationHistory);
|
|
690
762
|
}
|
|
691
763
|
|
|
764
|
+
// Gemini key — route to Gemini
|
|
765
|
+
if (effectiveGeminiKey && (!effectiveAnthropicKey || modelPref === 'gemini' || modelPref?.startsWith('gemini-'))) {
|
|
766
|
+
const geminiModel = config.get('geminiModel') || 'gemini-2.0-flash';
|
|
767
|
+
sessionState.provider = 'Gemini';
|
|
768
|
+
sessionState.model = geminiModel;
|
|
769
|
+
const messages = [
|
|
770
|
+
...conversationHistory.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })),
|
|
771
|
+
{ role: 'user', content: userMessage },
|
|
772
|
+
];
|
|
773
|
+
process.stdout.write(ui.dim(' NAVADA > '));
|
|
774
|
+
try {
|
|
775
|
+
const result = await streamGemini(effectiveGeminiKey, messages, geminiModel);
|
|
776
|
+
return result.content;
|
|
777
|
+
} catch (e) {
|
|
778
|
+
if (!sessionState._geminiWarned) {
|
|
779
|
+
console.log(ui.warn('Gemini API unavailable, using Grok free tier.'));
|
|
780
|
+
sessionState._geminiWarned = true;
|
|
781
|
+
}
|
|
782
|
+
return grokChat(userMessage, conversationHistory);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
692
786
|
// NVIDIA key — route to NVIDIA
|
|
693
787
|
if (effectiveNvidiaKey && (!effectiveAnthropicKey || modelPref?.startsWith('nvidia') || modelPref?.startsWith('llama') || modelPref?.startsWith('deepseek') || modelPref?.startsWith('mistral') || modelPref?.startsWith('gemma') || modelPref?.startsWith('nemotron'))) {
|
|
694
788
|
const { streamNvidia } = require('./commands/nvidia');
|
package/lib/commands/ai.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const navada = require('navada-edge-sdk');
|
|
4
4
|
const ui = require('../ui');
|
|
5
5
|
const config = require('../config');
|
|
6
|
-
const { chat: agentChat, reportTelemetry, rateTracker, addToHistory, getConversationHistory, clearHistory } = require('../agent');
|
|
6
|
+
const { chat: agentChat, reportTelemetry, rateTracker, addToHistory, getConversationHistory, clearHistory, sessionState } = require('../agent');
|
|
7
7
|
|
|
8
8
|
module.exports = function(reg) {
|
|
9
9
|
|
|
@@ -109,12 +109,14 @@ module.exports = function(reg) {
|
|
|
109
109
|
|
|
110
110
|
reg('model', 'Show/set default AI model', (args) => {
|
|
111
111
|
if (args[0]) {
|
|
112
|
-
const valid = ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen', 'nvidia', 'llama-3.3-70b', 'llama-3.1-8b', 'mistral-large', 'gemma-2-27b', 'codellama-70b', 'deepseek-r1', 'phi-3-medium', 'nemotron-70b'];
|
|
112
|
+
const valid = ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'gemini', 'gemini-2.0-flash', 'gemini-2.5-pro', 'qwen', 'nvidia', 'llama-3.3-70b', 'llama-3.1-8b', 'mistral-large', 'gemma-2-27b', 'codellama-70b', 'deepseek-r1', 'phi-3-medium', 'nemotron-70b'];
|
|
113
113
|
if (!valid.includes(args[0])) { console.log(ui.error(`Invalid model. Options: ${valid.join(', ')}`)); return; }
|
|
114
114
|
config.setModel(args[0]);
|
|
115
115
|
// If it's an NVIDIA model name, also set it as the nvidia model
|
|
116
116
|
const nvidiaModels = ['llama-3.3-70b', 'llama-3.1-8b', 'mistral-large', 'gemma-2-27b', 'codellama-70b', 'deepseek-r1', 'phi-3-medium', 'nemotron-70b'];
|
|
117
117
|
if (nvidiaModels.includes(args[0])) config.set('nvidiaModel', args[0]);
|
|
118
|
+
// If it's a Gemini model name, set it
|
|
119
|
+
if (args[0].startsWith('gemini')) config.set('geminiModel', args[0]);
|
|
118
120
|
console.log(ui.success(`Model set to: ${args[0]}`));
|
|
119
121
|
} else {
|
|
120
122
|
console.log(ui.header('AI MODELS'));
|
|
@@ -125,6 +127,8 @@ module.exports = function(reg) {
|
|
|
125
127
|
console.log(ui.label('auto', 'Smart routing — picks best provider per query'));
|
|
126
128
|
console.log(ui.label('claude', 'Claude Sonnet 4 (Anthropic) — full agent + tools'));
|
|
127
129
|
console.log(ui.label('gpt-4o', 'GPT-4o (OpenAI) — tool use + streaming'));
|
|
130
|
+
console.log(ui.label('gemini', 'Gemini 2.0 Flash (Google — FREE)'));
|
|
131
|
+
console.log(ui.label('gemini-2.5-pro', 'Gemini 2.5 Pro (Google)'));
|
|
128
132
|
console.log(ui.label('qwen', 'Qwen Coder 32B (HuggingFace — FREE)'));
|
|
129
133
|
console.log('');
|
|
130
134
|
console.log(ui.dim('NVIDIA models (FREE via build.nvidia.com):'));
|
|
@@ -138,7 +142,61 @@ module.exports = function(reg) {
|
|
|
138
142
|
console.log(ui.dim('Set: /model deepseek-r1'));
|
|
139
143
|
console.log(ui.dim('NVIDIA key: /login nvapi-your-key (free at build.nvidia.com)'));
|
|
140
144
|
}
|
|
141
|
-
}, { category: 'AI', subs: ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'qwen', 'nvidia', 'llama-3.3-70b', 'deepseek-r1', 'mistral-large', 'codellama-70b', 'gemma-2-27b', 'nemotron-70b'] });
|
|
145
|
+
}, { category: 'AI', subs: ['auto', 'claude', 'gpt-4o', 'gpt-4o-mini', 'gemini', 'gemini-2.0-flash', 'gemini-2.5-pro', 'qwen', 'nvidia', 'llama-3.3-70b', 'deepseek-r1', 'mistral-large', 'codellama-70b', 'gemma-2-27b', 'nemotron-70b'] });
|
|
146
|
+
|
|
147
|
+
// --- /retry ---
|
|
148
|
+
reg('retry', 'Resend the last message to the AI', async () => {
|
|
149
|
+
const history = getConversationHistory();
|
|
150
|
+
const lastUserMsg = [...history].reverse().find(m => m.role === 'user');
|
|
151
|
+
if (!lastUserMsg) {
|
|
152
|
+
console.log(ui.warn('No previous message to retry.'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const msg = typeof lastUserMsg.content === 'string' ? lastUserMsg.content : JSON.stringify(lastUserMsg.content);
|
|
157
|
+
console.log(ui.dim(` Retrying: ${msg.slice(0, 80)}${msg.length > 80 ? '...' : ''}`));
|
|
158
|
+
|
|
159
|
+
const hasKey = config.getApiKey() || config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY;
|
|
160
|
+
let spinner;
|
|
161
|
+
if (!hasKey) {
|
|
162
|
+
process.stdout.write(ui.dim(' NAVADA > '));
|
|
163
|
+
} else {
|
|
164
|
+
const ora = require('ora');
|
|
165
|
+
spinner = ora({ text: ' NAVADA thinking...', color: 'white' }).start();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Remove the last assistant response + user message, then resend
|
|
170
|
+
const trimmedHistory = history.slice(0, -2);
|
|
171
|
+
const response = await agentChat(msg, trimmedHistory);
|
|
172
|
+
if (spinner) spinner.stop();
|
|
173
|
+
// Replace the last exchange in history
|
|
174
|
+
addToHistory('user', msg);
|
|
175
|
+
addToHistory('assistant', response);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
if (spinner) spinner.stop();
|
|
178
|
+
console.log(ui.error(e.message));
|
|
179
|
+
}
|
|
180
|
+
}, { category: 'AI', aliases: ['r'] });
|
|
181
|
+
|
|
182
|
+
// --- /tokens ---
|
|
183
|
+
reg('tokens', 'Show session token usage and cost', () => {
|
|
184
|
+
console.log(ui.header('SESSION USAGE'));
|
|
185
|
+
const s = sessionState;
|
|
186
|
+
const uptime = ((Date.now() - s.startTime) / 60000).toFixed(1);
|
|
187
|
+
console.log(ui.label('Provider', s.provider));
|
|
188
|
+
console.log(ui.label('Model', s.model || config.getModel()));
|
|
189
|
+
console.log(ui.label('Messages', String(s.messages)));
|
|
190
|
+
console.log(ui.label('Input tokens', String(s.tokens.input)));
|
|
191
|
+
console.log(ui.label('Output tokens', String(s.tokens.output)));
|
|
192
|
+
console.log(ui.label('Total tokens', String(s.tokens.total)));
|
|
193
|
+
console.log(ui.label('Est. cost', `$${s.cost.toFixed(4)}`));
|
|
194
|
+
console.log(ui.label('Session time', `${uptime} min`));
|
|
195
|
+
console.log(ui.label('Rate limit', `${rateTracker.used()}/${rateTracker.limit} RPM`));
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(ui.dim('Free tier: Grok 3 via NAVADA Edge server'));
|
|
198
|
+
console.log(ui.dim('Upgrade: /login <key> for full agent + tools'));
|
|
199
|
+
}, { category: 'AI', aliases: ['usage'] });
|
|
142
200
|
|
|
143
201
|
reg('research', 'RAG search via MCP', async (args) => {
|
|
144
202
|
const query = args.join(' ');
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const ui = require('../ui');
|
|
6
|
+
const config = require('../config');
|
|
7
|
+
const { getConversationHistory, clearHistory, addToHistory, sessionState } = require('../agent');
|
|
8
|
+
|
|
9
|
+
const CONV_DIR = path.join(config.CONFIG_DIR, 'conversations');
|
|
10
|
+
|
|
11
|
+
function ensureDir() {
|
|
12
|
+
if (!fs.existsSync(CONV_DIR)) fs.mkdirSync(CONV_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function slugify(name) {
|
|
16
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function listSaved() {
|
|
20
|
+
ensureDir();
|
|
21
|
+
const files = fs.readdirSync(CONV_DIR).filter(f => f.endsWith('.json'));
|
|
22
|
+
return files.map(f => {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(fs.readFileSync(path.join(CONV_DIR, f), 'utf-8'));
|
|
25
|
+
return {
|
|
26
|
+
file: f,
|
|
27
|
+
name: data.name || f.replace('.json', ''),
|
|
28
|
+
messages: data.messages?.length || 0,
|
|
29
|
+
provider: data.provider || 'unknown',
|
|
30
|
+
saved: data.savedAt || '',
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return { file: f, name: f.replace('.json', ''), messages: 0, provider: '?', saved: '' };
|
|
34
|
+
}
|
|
35
|
+
}).sort((a, b) => b.saved.localeCompare(a.saved));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = function(reg) {
|
|
39
|
+
|
|
40
|
+
reg('save', 'Save current conversation', (args) => {
|
|
41
|
+
const history = getConversationHistory();
|
|
42
|
+
if (history.length === 0) {
|
|
43
|
+
console.log(ui.warn('No conversation to save.'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const name = args.join(' ') || `session-${new Date().toISOString().slice(0, 16).replace(/[T:]/g, '-')}`;
|
|
48
|
+
const slug = slugify(name);
|
|
49
|
+
ensureDir();
|
|
50
|
+
|
|
51
|
+
const data = {
|
|
52
|
+
name,
|
|
53
|
+
provider: sessionState.provider,
|
|
54
|
+
model: sessionState.model,
|
|
55
|
+
messages: history,
|
|
56
|
+
messageCount: history.length,
|
|
57
|
+
tokens: sessionState.tokens,
|
|
58
|
+
savedAt: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const filePath = path.join(CONV_DIR, `${slug}.json`);
|
|
62
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
63
|
+
console.log(ui.success(`Conversation saved: ${name}`));
|
|
64
|
+
console.log(ui.label('Messages', String(history.length)));
|
|
65
|
+
console.log(ui.label('File', filePath));
|
|
66
|
+
console.log(ui.dim('Load it later: /load ' + slug));
|
|
67
|
+
}, { category: 'AI' });
|
|
68
|
+
|
|
69
|
+
reg('load', 'Load a saved conversation', (args) => {
|
|
70
|
+
const name = args.join(' ');
|
|
71
|
+
if (!name) {
|
|
72
|
+
console.log(ui.dim('Usage: /load <name>'));
|
|
73
|
+
console.log(ui.dim('List saved: /conversations'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ensureDir();
|
|
78
|
+
const slug = slugify(name);
|
|
79
|
+
const filePath = path.join(CONV_DIR, `${slug}.json`);
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(filePath)) {
|
|
82
|
+
// Try partial match
|
|
83
|
+
const files = fs.readdirSync(CONV_DIR).filter(f => f.includes(slug) && f.endsWith('.json'));
|
|
84
|
+
if (files.length === 0) {
|
|
85
|
+
console.log(ui.error(`No saved conversation matching: ${name}`));
|
|
86
|
+
console.log(ui.dim('List saved: /conversations'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Use first match
|
|
90
|
+
const matchPath = path.join(CONV_DIR, files[0]);
|
|
91
|
+
return loadConversation(matchPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
loadConversation(filePath);
|
|
95
|
+
}, { category: 'AI' });
|
|
96
|
+
|
|
97
|
+
reg('conversations', 'List saved conversations', () => {
|
|
98
|
+
const saved = listSaved();
|
|
99
|
+
console.log(ui.header('SAVED CONVERSATIONS'));
|
|
100
|
+
|
|
101
|
+
if (saved.length === 0) {
|
|
102
|
+
console.log(ui.dim('No saved conversations yet.'));
|
|
103
|
+
console.log(ui.dim('Save one: /save my-project-chat'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const s of saved) {
|
|
108
|
+
const date = s.saved ? s.saved.slice(0, 10) : '?';
|
|
109
|
+
console.log(ui.label(
|
|
110
|
+
s.name.slice(0, 28).padEnd(28),
|
|
111
|
+
`${s.messages} msgs ${s.provider} ${date}`
|
|
112
|
+
));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(ui.dim(`${saved.length} conversations saved`));
|
|
117
|
+
console.log(ui.dim('Load: /load <name> | Delete: /conversations rm <name>'));
|
|
118
|
+
}, { category: 'AI', aliases: ['convos'], subs: ['rm'] });
|
|
119
|
+
|
|
120
|
+
function loadConversation(filePath) {
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
123
|
+
clearHistory();
|
|
124
|
+
|
|
125
|
+
for (const msg of data.messages || []) {
|
|
126
|
+
addToHistory(msg.role, msg.content);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(ui.success(`Loaded: ${data.name}`));
|
|
130
|
+
console.log(ui.label('Messages', String(data.messages?.length || 0)));
|
|
131
|
+
console.log(ui.label('Provider', data.provider || 'unknown'));
|
|
132
|
+
console.log(ui.label('Saved', data.savedAt?.slice(0, 16) || '?'));
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(ui.dim('Conversation context restored. Continue chatting.'));
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.log(ui.error(`Failed to load: ${e.message}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|