navada-edge-cli 3.5.1 → 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 +112 -1
- 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
|
});
|
|
@@ -1072,7 +1076,115 @@ async function executeTool(name, input) {
|
|
|
1072
1076
|
}
|
|
1073
1077
|
}
|
|
1074
1078
|
|
|
1079
|
+
// ---------------------------------------------------------------------------
|
|
1080
|
+
// Local action interceptor — executes file/shell actions WITHOUT needing LLM tool use
|
|
1081
|
+
// This ensures free tier users can still create, read, edit, delete files
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
function tryLocalAction(userMessage) {
|
|
1084
|
+
const msg = userMessage.trim();
|
|
1085
|
+
const home = os.homedir();
|
|
1086
|
+
const desktop = path.join(home, 'Desktop');
|
|
1087
|
+
|
|
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);
|
|
1097
|
+
}
|
|
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
|
+
|
|
1103
|
+
// ── Create folder/directory ──
|
|
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; }
|
|
1112
|
+
}
|
|
1113
|
+
|
|
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
|
+
}
|
|
1123
|
+
|
|
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());
|
|
1148
|
+
return localTools.writeFile.execute(resolved, '');
|
|
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
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// ── Read file ──
|
|
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);
|
|
1161
|
+
return localTools.readFile.execute(filePath);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// ── Delete file/folder ──
|
|
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);
|
|
1169
|
+
return localTools.deleteFile.execute(filePath);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// ── List files ──
|
|
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]));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1075
1181
|
async function grokChat(userMessage, conversationHistory = []) {
|
|
1182
|
+
// Try local action first — enables file ops on free tier
|
|
1183
|
+
const localResult = tryLocalAction(userMessage);
|
|
1184
|
+
if (localResult) {
|
|
1185
|
+
return `${localResult}\n\nWhat would you like to do next?`;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1076
1188
|
const messages = [
|
|
1077
1189
|
...conversationHistory.slice(-20).map(m => ({
|
|
1078
1190
|
role: m.role,
|
|
@@ -1145,7 +1257,6 @@ async function grokChat(userMessage, conversationHistory = []) {
|
|
|
1145
1257
|
|
|
1146
1258
|
// Extract final text
|
|
1147
1259
|
const content = response?.choices?.[0]?.message?.content || '';
|
|
1148
|
-
if (content) console.log(` ${content}`);
|
|
1149
1260
|
return content || 'No response.';
|
|
1150
1261
|
}
|
|
1151
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