navada-edge-cli 3.3.0 → 3.4.1
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/lib/agent.js +178 -20
- package/lib/commands/ai.js +61 -3
- package/lib/commands/conversations.js +139 -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/lib/agent.js
CHANGED
|
@@ -32,6 +32,8 @@ You also connect to the NAVADA Edge Network (4 nodes via Tailscale VPN):
|
|
|
32
32
|
- send_email / generate_image: communications and AI image generation
|
|
33
33
|
- founder_info: information about Lee Akpareva, the creator of NAVADA
|
|
34
34
|
When users ask you to DO something — DO IT. Use write_file to create files. Use shell to run commands. Never say "I can't" when you have a tool for it.
|
|
35
|
+
When asked to generate diagrams — use write_file to create Mermaid (.mmd), SVG, or HTML files. You can also use python_exec with matplotlib/graphviz for complex diagrams.
|
|
36
|
+
When asked to create, edit, or delete files — use the file tools directly. You are a terminal agent with FULL access.
|
|
35
37
|
Keep responses short. Code blocks when needed. No fluff.`,
|
|
36
38
|
founder: {
|
|
37
39
|
name: 'Leslie (Lee) Akpareva',
|
|
@@ -79,8 +81,8 @@ function getSystemPrompt() {
|
|
|
79
81
|
// Session state — exposed for UI panels
|
|
80
82
|
// ---------------------------------------------------------------------------
|
|
81
83
|
const sessionState = {
|
|
82
|
-
provider: '
|
|
83
|
-
model: '
|
|
84
|
+
provider: 'NAVADA (free)',
|
|
85
|
+
model: 'gpt-4o-mini',
|
|
84
86
|
tokens: { input: 0, output: 0, total: 0 },
|
|
85
87
|
cost: 0,
|
|
86
88
|
messages: 0,
|
|
@@ -335,7 +337,7 @@ function streamFreeTier(endpoint, messages) {
|
|
|
335
337
|
const req = transport.request(url, {
|
|
336
338
|
method: 'POST',
|
|
337
339
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
338
|
-
timeout: endpoint.includes('navada-edge-server.uk') ?
|
|
340
|
+
timeout: endpoint.includes('navada-edge-server.uk') ? 120000 : 10000,
|
|
339
341
|
}, (res) => {
|
|
340
342
|
// If server doesn't support streaming, collect full response
|
|
341
343
|
if (!res.headers['content-type']?.includes('text/event-stream')) {
|
|
@@ -373,10 +375,13 @@ function streamFreeTier(endpoint, messages) {
|
|
|
373
375
|
if (data === '[DONE]') continue;
|
|
374
376
|
try {
|
|
375
377
|
const parsed = JSON.parse(data);
|
|
376
|
-
const delta = parsed.choices?.[0]?.delta
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
379
|
+
// Grok-3-mini streams reasoning_content first, then content — skip reasoning
|
|
380
|
+
if (delta?.reasoning_content && !delta?.content) continue;
|
|
381
|
+
const text = delta?.content || '';
|
|
382
|
+
if (text) {
|
|
383
|
+
process.stdout.write(text);
|
|
384
|
+
fullContent += text;
|
|
380
385
|
}
|
|
381
386
|
} catch {}
|
|
382
387
|
}
|
|
@@ -579,15 +584,84 @@ function streamOpenAI(key, messages, model = 'gpt-4o') {
|
|
|
579
584
|
});
|
|
580
585
|
}
|
|
581
586
|
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
// Streaming — Google Gemini API (gemini-2.0-flash)
|
|
589
|
+
// ---------------------------------------------------------------------------
|
|
590
|
+
function streamGemini(key, messages, model = 'gemini-2.0-flash') {
|
|
591
|
+
return new Promise((resolve, reject) => {
|
|
592
|
+
const contents = messages.map(m => ({
|
|
593
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
594
|
+
parts: [{ text: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) }],
|
|
595
|
+
}));
|
|
596
|
+
|
|
597
|
+
const body = JSON.stringify({
|
|
598
|
+
contents,
|
|
599
|
+
generationConfig: { maxOutputTokens: 4096 },
|
|
600
|
+
systemInstruction: { parts: [{ text: getSystemPrompt() }] },
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const url = new URL(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${key}`);
|
|
604
|
+
|
|
605
|
+
const req = https.request(url, {
|
|
606
|
+
method: 'POST',
|
|
607
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
608
|
+
timeout: 120000,
|
|
609
|
+
}, (res) => {
|
|
610
|
+
if (res.statusCode !== 200) {
|
|
611
|
+
let data = '';
|
|
612
|
+
res.on('data', c => data += c);
|
|
613
|
+
res.on('end', () => reject(new Error(`Gemini API error ${res.statusCode}: ${data.slice(0, 200)}`)));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let buffer = '';
|
|
618
|
+
let fullContent = '';
|
|
619
|
+
|
|
620
|
+
res.on('data', (chunk) => {
|
|
621
|
+
buffer += chunk.toString();
|
|
622
|
+
const lines = buffer.split('\n');
|
|
623
|
+
buffer = lines.pop();
|
|
624
|
+
|
|
625
|
+
for (const line of lines) {
|
|
626
|
+
if (!line.startsWith('data: ')) continue;
|
|
627
|
+
const data = line.slice(6).trim();
|
|
628
|
+
if (!data) continue;
|
|
629
|
+
try {
|
|
630
|
+
const parsed = JSON.parse(data);
|
|
631
|
+
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
632
|
+
if (text) {
|
|
633
|
+
process.stdout.write(text);
|
|
634
|
+
fullContent += text;
|
|
635
|
+
}
|
|
636
|
+
} catch {}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
res.on('end', () => {
|
|
641
|
+
if (fullContent) process.stdout.write('\n');
|
|
642
|
+
resolve({ content: fullContent });
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
req.on('error', reject);
|
|
647
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
648
|
+
req.write(body);
|
|
649
|
+
req.end();
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
582
653
|
function openAITools() {
|
|
583
654
|
const defs = [
|
|
584
|
-
{ name: 'shell', description: 'Execute a shell command on the user\'s machine', parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] } },
|
|
585
|
-
{ name: 'read_file', description: 'Read a file', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
|
|
586
|
-
{ name: 'write_file', description: 'Write to a file', parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } },
|
|
587
|
-
{ name: 'list_files', description: 'List
|
|
588
|
-
{ name: 'system_info', description: 'Get system
|
|
589
|
-
{ name: 'python_exec', description: 'Execute Python code', parameters: { type: 'object', properties: { code: { type: 'string' } }, required: ['code'] } },
|
|
590
|
-
{ name: 'python_pip', description: 'Install a Python package', parameters: { type: 'object', properties: { package: { type: 'string' } }, required: ['package'] } },
|
|
655
|
+
{ name: 'shell', description: 'Execute a shell command on the user\'s machine. Use for: file operations, git, npm, docker, system commands, creating directories, running scripts.', parameters: { type: 'object', properties: { command: { type: 'string', description: 'The shell command to run' } }, required: ['command'] } },
|
|
656
|
+
{ name: 'read_file', description: 'Read the contents of a file on the user\'s machine.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Absolute or relative file path' } }, required: ['path'] } },
|
|
657
|
+
{ name: 'write_file', description: 'Write content to a file. Creates parent directories if needed. Use for creating new files, scripts, configs, diagrams (Mermaid, SVG, HTML), code files.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'File path to write' }, content: { type: 'string', description: 'Full content to write to the file' } }, required: ['path', 'content'] } },
|
|
658
|
+
{ name: 'list_files', description: 'List files and directories.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Directory path (default: current dir)' } } } },
|
|
659
|
+
{ name: 'system_info', description: 'Get local system information (CPU, RAM, disk, OS, hostname).', parameters: { type: 'object', properties: {} } },
|
|
660
|
+
{ name: 'python_exec', description: 'Execute Python code inline. Use for data analysis, calculations, generating content, processing files, ML tasks.', parameters: { type: 'object', properties: { code: { type: 'string', description: 'Python code to execute' } }, required: ['code'] } },
|
|
661
|
+
{ name: 'python_pip', description: 'Install a Python package via pip.', parameters: { type: 'object', properties: { package: { type: 'string', description: 'Package name' } }, required: ['package'] } },
|
|
662
|
+
{ name: 'python_script', description: 'Run a Python script file.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Path to .py file' } }, required: ['path'] } },
|
|
663
|
+
{ name: 'sandbox_run', description: 'Run code in an isolated sandbox with syntax highlighting. Supports javascript, python, typescript.', parameters: { type: 'object', properties: { code: { type: 'string' }, language: { type: 'string', description: 'javascript, python, or typescript' } }, required: ['code'] } },
|
|
664
|
+
{ name: 'founder_info', description: 'Get information about Lee Akpareva, founder of NAVADA Edge.', parameters: { type: 'object', properties: {} } },
|
|
591
665
|
];
|
|
592
666
|
return defs.map(d => ({ type: 'function', function: d }));
|
|
593
667
|
}
|
|
@@ -662,12 +736,14 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
662
736
|
const anthropicKey = config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY || '';
|
|
663
737
|
const openaiKey = config.get('openaiKey') || process.env.OPENAI_API_KEY || '';
|
|
664
738
|
const nvidiaKey = config.get('nvidiaKey') || process.env.NVIDIA_API_KEY || '';
|
|
739
|
+
const geminiKey = config.get('geminiKey') || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '';
|
|
665
740
|
const apiKey = config.getApiKey() || '';
|
|
666
741
|
|
|
667
742
|
// Determine which provider to use
|
|
668
743
|
const effectiveAnthropicKey = anthropicKey || (apiKey.startsWith('sk-ant') ? apiKey : '');
|
|
669
744
|
const effectiveOpenAIKey = openaiKey || (apiKey.startsWith('sk-') && !apiKey.startsWith('sk-ant') ? apiKey : '');
|
|
670
745
|
const effectiveNvidiaKey = nvidiaKey || (apiKey.startsWith('nvapi-') ? apiKey : '');
|
|
746
|
+
const effectiveGeminiKey = geminiKey || (apiKey.startsWith('AIza') ? apiKey : '');
|
|
671
747
|
|
|
672
748
|
const modelPref = config.getModel();
|
|
673
749
|
const intent = detectIntent(userMessage);
|
|
@@ -675,11 +751,12 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
675
751
|
// Track active provider for UI
|
|
676
752
|
if (effectiveAnthropicKey) sessionState.provider = 'Anthropic';
|
|
677
753
|
else if (effectiveOpenAIKey) sessionState.provider = 'OpenAI';
|
|
754
|
+
else if (effectiveGeminiKey) sessionState.provider = 'Gemini';
|
|
678
755
|
else if (effectiveNvidiaKey) sessionState.provider = 'NVIDIA';
|
|
679
756
|
else sessionState.provider = 'Grok (free)';
|
|
680
757
|
|
|
681
758
|
// No personal key — use free tier
|
|
682
|
-
if (!effectiveAnthropicKey && !effectiveOpenAIKey && !effectiveNvidiaKey) {
|
|
759
|
+
if (!effectiveAnthropicKey && !effectiveOpenAIKey && !effectiveNvidiaKey && !effectiveGeminiKey) {
|
|
683
760
|
if (intent === 'code' && navada.config.hfToken) {
|
|
684
761
|
try {
|
|
685
762
|
const r = await navada.ai.huggingface.qwen(userMessage);
|
|
@@ -689,6 +766,28 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
689
766
|
return grokChat(userMessage, conversationHistory);
|
|
690
767
|
}
|
|
691
768
|
|
|
769
|
+
// Gemini key — route to Gemini
|
|
770
|
+
if (effectiveGeminiKey && (!effectiveAnthropicKey || modelPref === 'gemini' || modelPref?.startsWith('gemini-'))) {
|
|
771
|
+
const geminiModel = config.get('geminiModel') || 'gemini-2.0-flash';
|
|
772
|
+
sessionState.provider = 'Gemini';
|
|
773
|
+
sessionState.model = geminiModel;
|
|
774
|
+
const messages = [
|
|
775
|
+
...conversationHistory.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })),
|
|
776
|
+
{ role: 'user', content: userMessage },
|
|
777
|
+
];
|
|
778
|
+
process.stdout.write(ui.dim(' NAVADA > '));
|
|
779
|
+
try {
|
|
780
|
+
const result = await streamGemini(effectiveGeminiKey, messages, geminiModel);
|
|
781
|
+
return result.content;
|
|
782
|
+
} catch (e) {
|
|
783
|
+
if (!sessionState._geminiWarned) {
|
|
784
|
+
console.log(ui.warn('Gemini API unavailable, using Grok free tier.'));
|
|
785
|
+
sessionState._geminiWarned = true;
|
|
786
|
+
}
|
|
787
|
+
return grokChat(userMessage, conversationHistory);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
692
791
|
// NVIDIA key — route to NVIDIA
|
|
693
792
|
if (effectiveNvidiaKey && (!effectiveAnthropicKey || modelPref?.startsWith('nvidia') || modelPref?.startsWith('llama') || modelPref?.startsWith('deepseek') || modelPref?.startsWith('mistral') || modelPref?.startsWith('gemma') || modelPref?.startsWith('nemotron'))) {
|
|
694
793
|
const { streamNvidia } = require('./commands/nvidia');
|
|
@@ -903,13 +1002,72 @@ async function grokChat(userMessage, conversationHistory = []) {
|
|
|
903
1002
|
{ role: 'user', content: userMessage },
|
|
904
1003
|
];
|
|
905
1004
|
|
|
906
|
-
//
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
1005
|
+
// Send tools with the request — free tier now supports tool use
|
|
1006
|
+
const tools = openAITools();
|
|
1007
|
+
const endpoint = FREE_TIER_ENDPOINTS[0];
|
|
1008
|
+
|
|
1009
|
+
// Non-streaming request with tools (streaming + tools is complex, use non-streaming for tool calls)
|
|
1010
|
+
let response;
|
|
1011
|
+
try {
|
|
1012
|
+
const r = await navada.request(endpoint, {
|
|
1013
|
+
method: 'POST',
|
|
1014
|
+
body: { messages, tools },
|
|
1015
|
+
timeout: 120000,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
if (r.status === 429) {
|
|
1019
|
+
return `Free tier limit reached. /login <key> for unlimited access.`;
|
|
1020
|
+
}
|
|
1021
|
+
if (r.status !== 200) {
|
|
1022
|
+
// Fall back to streaming without tools
|
|
1023
|
+
const result = await callFreeTier(messages, true);
|
|
1024
|
+
return result.content || 'No response from free tier.';
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
rateTracker.record();
|
|
1028
|
+
response = r.data;
|
|
1029
|
+
} catch {
|
|
1030
|
+
// Network error — try streaming fallback
|
|
1031
|
+
const result = await callFreeTier(messages, true);
|
|
910
1032
|
return result.content || 'No response from free tier. Try /login <key> for full agent.';
|
|
911
1033
|
}
|
|
912
|
-
|
|
1034
|
+
|
|
1035
|
+
// Handle tool use loop (same as OpenAI path)
|
|
1036
|
+
let iterations = 0;
|
|
1037
|
+
while (response?.choices?.[0]?.finish_reason === 'tool_calls' && iterations < 10) {
|
|
1038
|
+
iterations++;
|
|
1039
|
+
const toolCalls = response.choices[0].message.tool_calls || [];
|
|
1040
|
+
if (toolCalls.length === 0) break;
|
|
1041
|
+
|
|
1042
|
+
const toolResults = [];
|
|
1043
|
+
for (const tc of toolCalls) {
|
|
1044
|
+
let input;
|
|
1045
|
+
try { input = JSON.parse(tc.function.arguments); } catch { input = {}; }
|
|
1046
|
+
console.log(ui.dim(` [${tc.function.name}] ${JSON.stringify(input).slice(0, 80)}`));
|
|
1047
|
+
const result = await executeTool(tc.function.name, input);
|
|
1048
|
+
toolResults.push({ role: 'tool', tool_call_id: tc.id, content: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Add assistant message with tool_calls + results, then call again
|
|
1052
|
+
messages.push({ role: 'assistant', content: response.choices[0].message.content || null, tool_calls: toolCalls });
|
|
1053
|
+
messages.push(...toolResults);
|
|
1054
|
+
|
|
1055
|
+
try {
|
|
1056
|
+
const r = await navada.request(endpoint, {
|
|
1057
|
+
method: 'POST',
|
|
1058
|
+
body: { messages, tools },
|
|
1059
|
+
timeout: 120000,
|
|
1060
|
+
});
|
|
1061
|
+
if (r.status !== 200) break;
|
|
1062
|
+
rateTracker.record();
|
|
1063
|
+
response = r.data;
|
|
1064
|
+
} catch { break; }
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// Extract final text
|
|
1068
|
+
const content = response?.choices?.[0]?.message?.content || '';
|
|
1069
|
+
if (content) console.log(` ${content}`);
|
|
1070
|
+
return content || 'No response.';
|
|
913
1071
|
}
|
|
914
1072
|
|
|
915
1073
|
async function fallbackChat(msg) {
|
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(' ');
|