navada-edge-cli 3.5.2 → 3.5.3
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 +76 -55
- package/lib/commands/ai.js +3 -2
- 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
|
});
|
|
@@ -1078,89 +1082,107 @@ async function executeTool(name, input) {
|
|
|
1078
1082
|
// ---------------------------------------------------------------------------
|
|
1079
1083
|
function tryLocalAction(userMessage) {
|
|
1080
1084
|
const msg = userMessage.trim();
|
|
1081
|
-
const lower = msg.toLowerCase();
|
|
1082
1085
|
const home = os.homedir();
|
|
1083
1086
|
const desktop = path.join(home, 'Desktop');
|
|
1084
1087
|
|
|
1085
|
-
// Resolve
|
|
1086
|
-
function
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1088
|
+
// Resolve a location phrase to an absolute path (use ORIGINAL case, not lowered)
|
|
1089
|
+
function resolveLocation(phrase) {
|
|
1090
|
+
const p = phrase.trim().replace(/[""'.,!]/g, '');
|
|
1091
|
+
const low = p.toLowerCase();
|
|
1092
|
+
if (low === 'my desktop' || low === 'the desktop' || low === 'desktop') return desktop;
|
|
1093
|
+
if (low === 'home' || low === 'my home' || low === 'home directory') return home;
|
|
1094
|
+
if (p.startsWith('~')) return p.replace(/^~[/\\]?/, home + path.sep);
|
|
1095
|
+
if (path.isAbsolute(p)) return p;
|
|
1096
|
+
return path.join(process.cwd(), p);
|
|
1093
1097
|
}
|
|
1094
1098
|
|
|
1099
|
+
// Extract the ORIGINAL-CASE name from the original message using a case-insensitive match
|
|
1100
|
+
// We match on the original message to preserve casing
|
|
1101
|
+
let m;
|
|
1102
|
+
|
|
1095
1103
|
// ── 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}`;
|
|
1104
|
+
// Pattern: "create a folder called NAME on my desktop"
|
|
1105
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s+(?:on|at|in)\s+(.+?)$/i);
|
|
1106
|
+
if (m) {
|
|
1107
|
+
const name = m[1].trim();
|
|
1108
|
+
const loc = resolveLocation(m[2]);
|
|
1109
|
+
const resolved = path.join(loc, name);
|
|
1110
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1111
|
+
catch (e) { return null; }
|
|
1114
1112
|
}
|
|
1115
1113
|
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1114
|
+
// Pattern: "create a folder on my desktop called NAME"
|
|
1115
|
+
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);
|
|
1116
|
+
if (m) {
|
|
1117
|
+
const loc = resolveLocation(m[1]);
|
|
1118
|
+
const name = m[2].trim();
|
|
1119
|
+
const resolved = path.join(loc, name);
|
|
1120
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1121
|
+
catch (e) { return null; }
|
|
1122
|
+
}
|
|
1119
1123
|
|
|
1120
|
-
if
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
+
// Pattern: "create a folder called NAME" (no location — use cwd, or desktop if mentioned earlier)
|
|
1125
|
+
m = msg.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1126
|
+
if (m) {
|
|
1127
|
+
const name = m[1].trim();
|
|
1128
|
+
const loc = msg.toLowerCase().includes('desktop') ? desktop : process.cwd();
|
|
1129
|
+
const resolved = path.join(loc, name);
|
|
1130
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1131
|
+
catch (e) { return null; }
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Pattern: "create a new folder NAME on my desktop" (no "called/named")
|
|
1135
|
+
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);
|
|
1136
|
+
if (m) {
|
|
1137
|
+
const name = m[1].trim();
|
|
1138
|
+
const loc = resolveLocation(m[2]);
|
|
1139
|
+
const resolved = path.join(loc, name);
|
|
1140
|
+
try { fs.mkdirSync(resolved, { recursive: true }); return `Created folder: ${resolved}`; }
|
|
1141
|
+
catch (e) { return null; }
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// ── Create file ──
|
|
1145
|
+
m = msg.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s+(?:on|at|in)\s+(.+?)$/i);
|
|
1146
|
+
if (m) {
|
|
1147
|
+
const resolved = path.join(resolveLocation(m[2]), m[1].trim());
|
|
1124
1148
|
return localTools.writeFile.execute(resolved, '');
|
|
1125
1149
|
}
|
|
1150
|
+
m = msg.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1151
|
+
if (m) {
|
|
1152
|
+
const loc = msg.toLowerCase().includes('desktop') ? desktop : process.cwd();
|
|
1153
|
+
return localTools.writeFile.execute(path.join(loc, m[1].trim()), '');
|
|
1154
|
+
}
|
|
1126
1155
|
|
|
1127
1156
|
// ── Read file ──
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
const
|
|
1157
|
+
m = msg.match(/(?:read|show|display|cat|open)\s+(?:the\s+)?(?:file\s+)?[""']?([^""']+\.\w{1,5})[""']?/i);
|
|
1158
|
+
if (m) {
|
|
1159
|
+
const p = m[1].trim();
|
|
1160
|
+
const filePath = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
|
|
1131
1161
|
return localTools.readFile.execute(filePath);
|
|
1132
1162
|
}
|
|
1133
1163
|
|
|
1134
1164
|
// ── Delete file/folder ──
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
const
|
|
1165
|
+
m = msg.match(/(?:delete|remove|rm)\s+(?:the\s+)?(?:file|folder|directory)\s+[""']?([^""']+?)[""']?\s*$/i);
|
|
1166
|
+
if (m) {
|
|
1167
|
+
const p = m[1].trim();
|
|
1168
|
+
const filePath = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
|
|
1138
1169
|
return localTools.deleteFile.execute(filePath);
|
|
1139
1170
|
}
|
|
1140
1171
|
|
|
1141
1172
|
// ── List files ──
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
return localTools.listFiles.execute(dir);
|
|
1173
|
+
m = msg.match(/(?:list|show|ls|dir)\s+(?:the\s+)?(?:files|contents|items)\s+(?:in|on|at|of)\s+(.+)/i);
|
|
1174
|
+
if (m) {
|
|
1175
|
+
return localTools.listFiles.execute(resolveLocation(m[1]));
|
|
1146
1176
|
}
|
|
1147
1177
|
|
|
1148
|
-
return null;
|
|
1178
|
+
return null;
|
|
1149
1179
|
}
|
|
1150
1180
|
|
|
1151
1181
|
async function grokChat(userMessage, conversationHistory = []) {
|
|
1152
1182
|
// Try local action first — enables file ops on free tier
|
|
1153
1183
|
const localResult = tryLocalAction(userMessage);
|
|
1154
1184
|
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
|
-
}
|
|
1185
|
+
return `${localResult}\n\nWhat would you like to do next?`;
|
|
1164
1186
|
}
|
|
1165
1187
|
|
|
1166
1188
|
const messages = [
|
|
@@ -1235,7 +1257,6 @@ async function grokChat(userMessage, conversationHistory = []) {
|
|
|
1235
1257
|
|
|
1236
1258
|
// Extract final text
|
|
1237
1259
|
const content = response?.choices?.[0]?.message?.content || '';
|
|
1238
|
-
if (content) console.log(` ${content}`);
|
|
1239
1260
|
return content || 'No response.';
|
|
1240
1261
|
}
|
|
1241
1262
|
|
package/lib/commands/ai.js
CHANGED
|
@@ -31,11 +31,12 @@ module.exports = function(reg) {
|
|
|
31
31
|
addToHistory('user', msg);
|
|
32
32
|
addToHistory('assistant', response);
|
|
33
33
|
|
|
34
|
-
// Only print if not already streamed
|
|
35
|
-
if (!
|
|
34
|
+
// Only print if not already streamed to stdout
|
|
35
|
+
if (response && !sessionState._lastStreamed) {
|
|
36
36
|
console.log(ui.header('NAVADA'));
|
|
37
37
|
console.log(` ${response}`);
|
|
38
38
|
}
|
|
39
|
+
sessionState._lastStreamed = false;
|
|
39
40
|
|
|
40
41
|
// Track usage
|
|
41
42
|
reportTelemetry('chat', { messageLength: msg.length });
|
package/package.json
CHANGED