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 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 common path references
1086
- function resolvePath(p) {
1087
- return p
1088
- .replace(/^~\//, home + '/')
1089
- .replace(/^~\\/, home + '\\')
1090
- .replace(/\bmy desktop\b/i, desktop)
1091
- .replace(/\bthe desktop\b/i, desktop)
1092
- .replace(/\bdesktop\b/i, desktop);
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
- const mkdirMatch = lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named|on|at|in)?\s*[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
1097
- || lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:on|at|in)\s+(.+?)\s+(?:called|named)\s+[""']?(.+?)[""']?$/i)
1098
- || lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
1099
- || lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*$/i);
1100
-
1101
- if (mkdirMatch) {
1102
- let folderName, location;
1103
- if (mkdirMatch.length === 3) {
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
- // ── Create file ──
1117
- const touchMatch = lower.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file|empty file)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
1118
- || lower.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*$/i);
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 (touchMatch) {
1121
- let fileName = touchMatch[1].trim().replace(/[""']/g, '');
1122
- let location = touchMatch[2] ? touchMatch[2].trim().replace(/[""']/g, '') : process.cwd();
1123
- const resolved = path.resolve(resolvePath(location), fileName);
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
- const readMatch = lower.match(/(?:read|show|display|cat|open)\s+(?:the\s+)?(?:file\s+)?[""']?(.+\.\w+)[""']?/i);
1129
- if (readMatch) {
1130
- const filePath = resolvePath(readMatch[1].trim().replace(/[""']/g, ''));
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
- const deleteMatch = lower.match(/(?:delete|remove|rm)\s+(?:the\s+)?(?:file|folder|directory)?\s*[""']?(.+?)[""']?\s*$/i);
1136
- if (deleteMatch && (lower.includes('file') || lower.includes('folder') || lower.includes('directory'))) {
1137
- const filePath = path.resolve(resolvePath(deleteMatch[1].trim().replace(/[""']/g, '')));
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
- const lsMatch = lower.match(/(?:list|show|ls|dir)\s+(?:the\s+)?(?:files|contents|items)\s+(?:in|on|at|of)\s+(.+)/i);
1143
- if (lsMatch) {
1144
- const dir = resolvePath(lsMatch[1].trim().replace(/[""']/g, ''));
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; // No match — let LLM handle
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
- console.log(` ${localResult}`);
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
 
@@ -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 (!response._streamed) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navada-edge-cli",
3
- "version": "3.5.2",
3
+ "version": "3.5.3",
4
4
  "description": "Interactive CLI for the NAVADA Edge Network — explore nodes, agents, Cloudflare, AI, Docker, and MCP from your terminal",
5
5
  "main": "lib/cli.js",
6
6
  "bin": {