UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

134 lines 6.07 kB
import React from 'react'; import { ErrorMessage, InfoMessage, SuccessMessage, } from '../components/message-box.js'; import { toolRegistry } from '../tools/index.js'; import { logError, logInfo } from '../utils/message-queue.js'; import { checkForUpdates } from '../utils/update-checker.js'; /** * Determines if a command execution failed based on multiple signals. * Checks exit code first (most reliable), then looks for specific error patterns. * Exported for testing purposes. */ export function hasCommandFailed(output) { const outputStr = String(output || ''); // Strategy 1: Check exit code (most reliable) const exitCodeMatch = outputStr.match(/^EXIT_CODE:\s*(\d+)/m); if (exitCodeMatch) { const exitCode = parseInt(exitCodeMatch[1], 10); // Non-zero exit code indicates failure if (exitCode !== 0) { return true; } } // Strategy 2: Check for critical error patterns // Use word boundaries and case-sensitive matching to avoid false positives const normalized = outputStr.toLowerCase(); // Critical errors that definitively indicate failure const criticalErrors = [ /\bcommand not found\b/i, /\bno such file or directory\b/i, /\bpermission denied\b/i, /^error:/im, // Error at start of line /\berror:\s*(?!0\b)/i, // "error:" not followed by 0 /\bfatal\b/i, /\bfailed\b/i, /\bcannot\b/i, ]; for (const pattern of criticalErrors) { if (pattern.test(normalized)) { // Additional check: avoid false positives for success messages // like "0 errors", "error-free", "no errors found" if (/0\s*errors?|error-?free|no\s*errors?\s*found/i.test(normalized)) { continue; } return true; } } // Strategy 3: Check if STDERR has content (warning: not always an error) // Some tools write progress to stderr, so this is a weak signal // Only use this if no other signals present const hasStderr = /^STDERR:\s*\S/m.test(outputStr); if (hasStderr) { // Check if stderr contains actual error indicators, not just warnings/info const stderrMatch = outputStr.match(/^STDERR:\s*([\s\S]*?)(?:^STDOUT:|$)/m); if (stderrMatch) { const stderrContent = stderrMatch[1].toLowerCase(); // Only treat as error if stderr contains error-like content if (/\berror\b|\bfatal\b|\bfailed\b|\bcannot\b/i.test(stderrContent)) { return true; } } } return false; } export const updateCommand = { name: 'update', description: 'Update Nanocoder to the latest version', handler: async (_args) => { // Show initial checking message logInfo('Checking for available updates...', true); try { const updateInfo = await checkForUpdates(); if (updateInfo.hasUpdate) { // Show updating message logInfo('Downloading and installing the latest Nanocoder update...', true); // Run update command if provided; otherwise show informative message if (updateInfo.updateCommand) { try { const result = await toolRegistry.execute_bash({ command: updateInfo.updateCommand, }); // Check for command failure using multiple strategies if (hasCommandFailed(result)) { logError('Update command executed but returned an error', true); return React.createElement(ErrorMessage, { message: `Update command failed. Output: ${String(result)}`, hideBox: true, }); } // Show success message return React.createElement(SuccessMessage, { message: 'Nanocoder has been updated to the latest version. Please restart your session to apply the update.', hideBox: true, }); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); logError(`Failed to execute update command: ${errorMessage}`, true); return React.createElement(ErrorMessage, { message: `Failed to execute update command: ${errorMessage}`, hideBox: true, }); } } if (updateInfo.updateMessage) { // We cannot run an automated update; show instructions to user return React.createElement(InfoMessage, { message: updateInfo.updateMessage, hideBox: true, }); } // Fallback for unknown installation method return React.createElement(InfoMessage, { message: 'A new version is available. Please update using your package manager.', hideBox: true, }); } else { // Already up to date return React.createElement(InfoMessage, { message: 'You are already on the latest version.', hideBox: true, }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Failed to update Nanocoder: ${errorMessage}`, true); return React.createElement(ErrorMessage, { message: `Failed to check for updates: ${errorMessage}`, hideBox: true, }); } }, }; //# sourceMappingURL=update.js.map