navada-edge-cli 3.5.2 → 3.5.4
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/lib/agent.js +81 -69
- package/lib/commands/ai.js +9 -8
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -454,6 +454,7 @@ function streamFreeTier(endpoint, messages) {
|
|
|
454
454
|
|
|
455
455
|
res.on('end', () => {
|
|
456
456
|
if (fullContent) process.stdout.write('\n');
|
|
457
|
+
sessionState._lastStreamed = true;
|
|
457
458
|
resolve({ content: fullContent, isRateLimit: false, streamed: true });
|
|
458
459
|
});
|
|
459
460
|
});
|
|
@@ -552,6 +553,7 @@ function streamAnthropic(key, messages, tools, system) {
|
|
|
552
553
|
|
|
553
554
|
res.on('end', () => {
|
|
554
555
|
if (contentBlocks.some(b => b.type === 'text')) process.stdout.write('\n');
|
|
556
|
+
sessionState._lastStreamed = true;
|
|
555
557
|
resolve({ content: contentBlocks, stop_reason: stopReason });
|
|
556
558
|
});
|
|
557
559
|
});
|
|
@@ -637,6 +639,7 @@ function streamOpenAI(key, messages, model = 'gpt-4o') {
|
|
|
637
639
|
|
|
638
640
|
res.on('end', () => {
|
|
639
641
|
if (fullContent) process.stdout.write('\n');
|
|
642
|
+
sessionState._lastStreamed = true;
|
|
640
643
|
toolCalls = toolCalls.filter(Boolean);
|
|
641
644
|
resolve({ content: fullContent, tool_calls: toolCalls, finish_reason: finishReason });
|
|
642
645
|
});
|
|
@@ -704,6 +707,7 @@ function streamGemini(key, messages, model = 'gemini-2.0-flash') {
|
|
|
704
707
|
|
|
705
708
|
res.on('end', () => {
|
|
706
709
|
if (fullContent) process.stdout.write('\n');
|
|
710
|
+
sessionState._lastStreamed = true;
|
|
707
711
|
resolve({ content: fullContent });
|
|
708
712
|
});
|
|
709
713
|
});
|
|
@@ -746,10 +750,7 @@ async function openAIChat(key, userMessage, conversationHistory = []) {
|
|
|
746
750
|
response = await streamOpenAI(key, messages, model);
|
|
747
751
|
} catch (e) {
|
|
748
752
|
if (e.message.includes('401') || e.message.includes('429') || e.message.includes('billing')) {
|
|
749
|
-
|
|
750
|
-
console.log(ui.warn('OpenAI API unavailable, using Grok free tier. /login with a valid key to switch.'));
|
|
751
|
-
sessionState._openaiWarned = true;
|
|
752
|
-
}
|
|
753
|
+
sessionState._openaiWarned = true;
|
|
753
754
|
return grokChat(userMessage, conversationHistory);
|
|
754
755
|
}
|
|
755
756
|
throw e;
|
|
@@ -842,15 +843,12 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
842
843
|
...conversationHistory.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })),
|
|
843
844
|
{ role: 'user', content: userMessage },
|
|
844
845
|
];
|
|
845
|
-
process.stdout.write(ui.dim(' NAVADA > '));
|
|
846
846
|
try {
|
|
847
|
+
process.stdout.write(ui.dim(' '));
|
|
847
848
|
const result = await streamGemini(effectiveGeminiKey, messages, geminiModel);
|
|
848
849
|
return result.content;
|
|
849
850
|
} catch (e) {
|
|
850
|
-
|
|
851
|
-
console.log(ui.warn('Gemini API unavailable, using Grok free tier.'));
|
|
852
|
-
sessionState._geminiWarned = true;
|
|
853
|
-
}
|
|
851
|
+
sessionState._geminiWarned = true;
|
|
854
852
|
return grokChat(userMessage, conversationHistory);
|
|
855
853
|
}
|
|
856
854
|
}
|
|
@@ -865,7 +863,7 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
865
863
|
...conversationHistory.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })),
|
|
866
864
|
{ role: 'user', content: userMessage },
|
|
867
865
|
];
|
|
868
|
-
process.stdout.write(ui.dim('
|
|
866
|
+
process.stdout.write(ui.dim(' '));
|
|
869
867
|
const result = await streamNvidia(effectiveNvidiaKey, messages, nvidiaModel);
|
|
870
868
|
return result.content;
|
|
871
869
|
}
|
|
@@ -999,10 +997,7 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
999
997
|
const errMsg = e.message || '';
|
|
1000
998
|
// If billing/rate limit/auth error, fall back to free tier
|
|
1001
999
|
if (errMsg.includes('400') || errMsg.includes('401') || errMsg.includes('429') || errMsg.includes('usage limits')) {
|
|
1002
|
-
|
|
1003
|
-
console.log(ui.warn('Anthropic API unavailable, using Grok free tier. /login with a valid key to switch.'));
|
|
1004
|
-
sessionState._anthropicWarned = true;
|
|
1005
|
-
}
|
|
1000
|
+
sessionState._anthropicWarned = true;
|
|
1006
1001
|
return grokChat(userMessage, conversationHistory);
|
|
1007
1002
|
}
|
|
1008
1003
|
throw e;
|
|
@@ -1078,89 +1073,107 @@ async function executeTool(name, input) {
|
|
|
1078
1073
|
// ---------------------------------------------------------------------------
|
|
1079
1074
|
function tryLocalAction(userMessage) {
|
|
1080
1075
|
const msg = userMessage.trim();
|
|
1081
|
-
const lower = msg.toLowerCase();
|
|
1082
1076
|
const home = os.homedir();
|
|
1083
1077
|
const desktop = path.join(home, 'Desktop');
|
|
1084
1078
|
|
|
1085
|
-
// Resolve
|
|
1086
|
-
function
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1079
|
+
// Resolve a location phrase to an absolute path (use ORIGINAL case, not lowered)
|
|
1080
|
+
function resolveLocation(phrase) {
|
|
1081
|
+
const p = phrase.trim().replace(/[""'.,!]/g, '');
|
|
1082
|
+
const low = p.toLowerCase();
|
|
1083
|
+
if (low === 'my desktop' || low === 'the desktop' || low === 'desktop') return desktop;
|
|
1084
|
+
if (low === 'home' || low === 'my home' || low === 'home directory') return home;
|
|
1085
|
+
if (p.startsWith('~')) return p.replace(/^~[/\\]?/, home + path.sep);
|
|
1086
|
+
if (path.isAbsolute(p)) return p;
|
|
1087
|
+
return path.join(process.cwd(), p);
|
|
1093
1088
|
}
|
|
1094
1089
|
|
|
1090
|
+
// Extract the ORIGINAL-CASE name from the original message using a case-insensitive match
|
|
1091
|
+
// We match on the original message to preserve casing
|
|
1092
|
+
let m;
|
|
1093
|
+
|
|
1095
1094
|
// ── Create folder/directory ──
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
folderName = mkdirMatch[1].trim().replace(/[""']/g, '');
|
|
1105
|
-
location = mkdirMatch[2].trim().replace(/[""']/g, '');
|
|
1106
|
-
} else {
|
|
1107
|
-
folderName = mkdirMatch[1].trim().replace(/[""']/g, '');
|
|
1108
|
-
location = lower.includes('desktop') ? desktop : process.cwd();
|
|
1109
|
-
}
|
|
1110
|
-
const resolved = path.resolve(resolvePath(location), folderName);
|
|
1111
|
-
const result = localTools.shell.execute(process.platform === 'win32' ? `mkdir "${resolved}"` : `mkdir -p "${resolved}"`);
|
|
1112
|
-
if (result.includes('Error')) return null; // let LLM handle
|
|
1113
|
-
return `Created folder: ${resolved}`;
|
|
1095
|
+
// Pattern: "create a folder called NAME on my desktop"
|
|
1096
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s+(?:on|at|in)\s+(.+?)$/i);
|
|
1097
|
+
if (m) {
|
|
1098
|
+
const name = m[1].trim();
|
|
1099
|
+
const loc = resolveLocation(m[2]);
|
|
1100
|
+
const resolved = path.join(loc, name);
|
|
1101
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1102
|
+
catch (e) { return null; }
|
|
1114
1103
|
}
|
|
1115
1104
|
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1105
|
+
// Pattern: "create a folder on my desktop called NAME"
|
|
1106
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:on|at|in)\s+(.+?)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1107
|
+
if (m) {
|
|
1108
|
+
const loc = resolveLocation(m[1]);
|
|
1109
|
+
const name = m[2].trim();
|
|
1110
|
+
const resolved = path.join(loc, name);
|
|
1111
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1112
|
+
catch (e) { return null; }
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Pattern: "create a folder called NAME" (no location — use cwd, or desktop if mentioned earlier)
|
|
1116
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1117
|
+
if (m) {
|
|
1118
|
+
const name = m[1].trim();
|
|
1119
|
+
const loc = msg.toLowerCase().includes('desktop') ? desktop : process.cwd();
|
|
1120
|
+
const resolved = path.join(loc, name);
|
|
1121
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1122
|
+
catch (e) { return null; }
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Pattern: "create a new folder NAME on my desktop" (no "called/named")
|
|
1126
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+([A-Za-z0-9_\-. ]+?)\s+(?:on|at|in)\s+(.+?)$/i);
|
|
1127
|
+
if (m) {
|
|
1128
|
+
const name = m[1].trim();
|
|
1129
|
+
const loc = resolveLocation(m[2]);
|
|
1130
|
+
const resolved = path.join(loc, name);
|
|
1131
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1132
|
+
catch (e) { return null; }
|
|
1133
|
+
}
|
|
1119
1134
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const resolved = path.
|
|
1135
|
+
// ── Create file ──
|
|
1136
|
+
m = msg.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s+(?:on|at|in)\s+(.+?)$/i);
|
|
1137
|
+
if (m) {
|
|
1138
|
+
const resolved = path.join(resolveLocation(m[2]), m[1].trim());
|
|
1124
1139
|
return localTools.writeFile.execute(resolved, '');
|
|
1125
1140
|
}
|
|
1141
|
+
m = msg.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1142
|
+
if (m) {
|
|
1143
|
+
const loc = msg.toLowerCase().includes('desktop') ? desktop : process.cwd();
|
|
1144
|
+
return localTools.writeFile.execute(path.join(loc, m[1].trim()), '');
|
|
1145
|
+
}
|
|
1126
1146
|
|
|
1127
1147
|
// ── Read file ──
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
const
|
|
1148
|
+
m = msg.match(/(?:read|show|display|cat|open)\s+(?:the\s+)?(?:file\s+)?[""']?([^""']+\.\w{1,5})[""']?/i);
|
|
1149
|
+
if (m) {
|
|
1150
|
+
const p = m[1].trim();
|
|
1151
|
+
const filePath = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
|
|
1131
1152
|
return localTools.readFile.execute(filePath);
|
|
1132
1153
|
}
|
|
1133
1154
|
|
|
1134
1155
|
// ── Delete file/folder ──
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
const
|
|
1156
|
+
m = msg.match(/(?:delete|remove|rm)\s+(?:the\s+)?(?:file|folder|directory)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1157
|
+
if (m) {
|
|
1158
|
+
const p = m[1].trim();
|
|
1159
|
+
const filePath = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
|
|
1138
1160
|
return localTools.deleteFile.execute(filePath);
|
|
1139
1161
|
}
|
|
1140
1162
|
|
|
1141
1163
|
// ── List files ──
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
return localTools.listFiles.execute(dir);
|
|
1164
|
+
m = msg.match(/(?:list|show|ls|dir)\s+(?:the\s+)?(?:files|contents|items)\s+(?:in|on|at|of)\s+(.+)/i);
|
|
1165
|
+
if (m) {
|
|
1166
|
+
return localTools.listFiles.execute(resolveLocation(m[1]));
|
|
1146
1167
|
}
|
|
1147
1168
|
|
|
1148
|
-
return null;
|
|
1169
|
+
return null;
|
|
1149
1170
|
}
|
|
1150
1171
|
|
|
1151
1172
|
async function grokChat(userMessage, conversationHistory = []) {
|
|
1152
1173
|
// Try local action first — enables file ops on free tier
|
|
1153
1174
|
const localResult = tryLocalAction(userMessage);
|
|
1154
1175
|
if (localResult) {
|
|
1155
|
-
|
|
1156
|
-
// Send result to LLM for a natural confirmation
|
|
1157
|
-
const confirmMsg = `The user asked: "${userMessage}"\nI executed the action locally. Result: ${localResult}\nGive a brief, friendly confirmation and ask if they need anything else.`;
|
|
1158
|
-
try {
|
|
1159
|
-
const result = await callFreeTier([{ role: 'user', content: confirmMsg }], true);
|
|
1160
|
-
return result.content || localResult;
|
|
1161
|
-
} catch {
|
|
1162
|
-
return localResult;
|
|
1163
|
-
}
|
|
1176
|
+
return `${localResult}\n\nWhat would you like to do next?`;
|
|
1164
1177
|
}
|
|
1165
1178
|
|
|
1166
1179
|
const messages = [
|
|
@@ -1235,7 +1248,6 @@ async function grokChat(userMessage, conversationHistory = []) {
|
|
|
1235
1248
|
|
|
1236
1249
|
// Extract final text
|
|
1237
1250
|
const content = response?.choices?.[0]?.message?.content || '';
|
|
1238
|
-
if (content) console.log(` ${content}`);
|
|
1239
1251
|
return content || 'No response.';
|
|
1240
1252
|
}
|
|
1241
1253
|
|
package/lib/commands/ai.js
CHANGED
|
@@ -13,11 +13,12 @@ module.exports = function(reg) {
|
|
|
13
13
|
|
|
14
14
|
const hasKey = config.getApiKey() || config.get('anthropicKey') || process.env.ANTHROPIC_API_KEY;
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Reset streaming flag before each chat
|
|
17
|
+
sessionState._lastStreamed = false;
|
|
18
|
+
|
|
19
|
+
// Show thinking indicator
|
|
17
20
|
let spinner;
|
|
18
|
-
if (
|
|
19
|
-
process.stdout.write(ui.dim(' NAVADA > '));
|
|
20
|
-
} else {
|
|
21
|
+
if (hasKey) {
|
|
21
22
|
const ora = require('ora');
|
|
22
23
|
spinner = ora({ text: ' NAVADA thinking...', color: 'white' }).start();
|
|
23
24
|
}
|
|
@@ -31,9 +32,9 @@ module.exports = function(reg) {
|
|
|
31
32
|
addToHistory('user', msg);
|
|
32
33
|
addToHistory('assistant', response);
|
|
33
34
|
|
|
34
|
-
// Only print if not already streamed
|
|
35
|
-
if (!
|
|
36
|
-
console.log(
|
|
35
|
+
// Only print if not already streamed to stdout
|
|
36
|
+
if (response && !sessionState._lastStreamed) {
|
|
37
|
+
console.log('');
|
|
37
38
|
console.log(` ${response}`);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -42,7 +43,7 @@ module.exports = function(reg) {
|
|
|
42
43
|
} catch (e) {
|
|
43
44
|
if (spinner) spinner.stop();
|
|
44
45
|
console.log(ui.error(e.message));
|
|
45
|
-
console.log(ui.dim('
|
|
46
|
+
console.log(ui.dim('/config to check providers, or /setup to configure.'));
|
|
46
47
|
}
|
|
47
48
|
}, { category: 'AI', aliases: ['ask'] });
|
|
48
49
|
|
package/package.json
CHANGED