diff --git a/src/updater/auto-update.ts b/src/updater/auto-update.ts index 8a4555b..30dd378 100644 --- a/src/updater/auto-update.ts +++ b/src/updater/auto-update.ts @@ -1,9 +1,18 @@ import { exec } from "node:child_process"; +import { realpathSync } from "node:fs"; import { promisify } from "node:util"; import { getVersion } from "../version"; const execAsync = promisify(exec); +// Debug logging - set LETTA_DEBUG_AUTOUPDATE=1 to enable +const DEBUG = process.env.LETTA_DEBUG_AUTOUPDATE === "1"; +function debugLog(...args: unknown[]) { + if (DEBUG) { + console.error("[auto-update]", ...args); + } +} + interface UpdateCheckResult { updateAvailable: boolean; latestVersion?: string; @@ -17,30 +26,48 @@ function isAutoUpdateEnabled(): boolean { function isRunningLocally(): boolean { const argv = process.argv[1] || ""; + // Resolve symlinks to get the real path + // npm creates symlinks in /bin/ that point to /lib/node_modules/ + // Without resolving, argv would be like ~/.nvm/.../bin/letta (no node_modules) + let resolvedPath = argv; + try { + resolvedPath = realpathSync(argv); + } catch { + // If realpath fails (file doesn't exist), use original path + } + + debugLog("argv[1]:", argv); + debugLog("resolved path:", resolvedPath); + // If running from node_modules, it's npm installed (should auto-update) // Otherwise it's local dev (source or built locally) - return !argv.includes("node_modules"); + return !resolvedPath.includes("node_modules"); } async function checkForUpdate(): Promise { const currentVersion = getVersion(); + debugLog("Current version:", currentVersion); try { + debugLog("Checking npm for latest version..."); const { stdout } = await execAsync( "npm view @letta-ai/letta-code version", { timeout: 5000 }, ); const latestVersion = stdout.trim(); + debugLog("Latest version from npm:", latestVersion); if (latestVersion !== currentVersion) { + debugLog("Update available!"); return { updateAvailable: true, latestVersion, currentVersion, }; } - } catch (_error) { - // Silently fail + debugLog("Already on latest version"); + } catch (error) { + debugLog("Failed to check for updates:", error); } return { @@ -51,11 +78,14 @@ async function checkForUpdate(): Promise { async function performUpdate(): Promise<{ success: boolean; error?: string }> { try { + debugLog("Running npm install -g @letta-ai/letta-code@latest..."); await execAsync("npm install -g @letta-ai/letta-code@latest", { timeout: 60000, }); + debugLog("Update completed successfully"); return { success: true }; } catch (error) { + debugLog("Update failed:", error); return { success: false, error: error instanceof Error ? error.message : String(error), @@ -64,7 +94,18 @@ async function performUpdate(): Promise<{ success: boolean; error?: string }> { } export async function checkAndAutoUpdate() { - if (!isAutoUpdateEnabled() || isRunningLocally()) { + debugLog("Auto-update check starting..."); + debugLog("isAutoUpdateEnabled:", isAutoUpdateEnabled()); + const runningLocally = isRunningLocally(); + debugLog("isRunningLocally:", runningLocally); + + if (!isAutoUpdateEnabled()) { + debugLog("Auto-update disabled via DISABLE_AUTOUPDATER=1"); + return; + } + + if (runningLocally) { + debugLog("Running locally, skipping auto-update"); return; }