#!/usr/bin/env node /** * Smart Install Script for claude-mem * * Ensures Bun runtime and uv (Python package manager) are installed * (auto-installs if missing) and handles dependency installation when needed. */ import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs'; import { execSync, spawnSync } from 'child_process'; import { join } from 'path'; import { homedir } from 'os'; const IS_WINDOWS = process.platform === 'win32'; /** * Resolve the marketplace root directory. * * Claude Code may store plugins under either `~/.claude/plugins/` (legacy) or * `~/.config/claude/plugins/` (XDG-compliant, e.g. Nix-managed installs). * When `CLAUDE_PLUGIN_ROOT` is set we derive the base from it; otherwise we * probe both candidate paths and fall back to the legacy location. */ function resolveRoot() { const marketplaceRel = join('plugins', 'marketplaces', 'thedotmack'); // Derive from CLAUDE_PLUGIN_ROOT (e.g. .../plugins/cache/thedotmack/claude-mem/) if (process.env.CLAUDE_PLUGIN_ROOT) { let dir = process.env.CLAUDE_PLUGIN_ROOT; const cacheIndex = dir.indexOf(join('plugins', 'cache')); if (cacheIndex !== -1) { const base = dir.substring(0, cacheIndex); const candidate = join(base, marketplaceRel); if (existsSync(join(candidate, 'package.json'))) return candidate; } } // Probe XDG path first, then legacy const xdg = join(homedir(), '.config', 'claude', marketplaceRel); if (existsSync(join(xdg, 'package.json'))) return xdg; return join(homedir(), '.claude', marketplaceRel); } const ROOT = resolveRoot(); const MARKER = join(ROOT, '.install-version'); // Common installation paths (handles fresh installs before PATH reload) const BUN_COMMON_PATHS = IS_WINDOWS ? [join(homedir(), '.bun', 'bin', 'bun.exe')] : [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun', '/opt/homebrew/bin/bun']; const UV_COMMON_PATHS = IS_WINDOWS ? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')] : [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv', '/opt/homebrew/bin/uv']; /** * Get the Bun executable path (from PATH or common install locations) */ function getBunPath() { // Try PATH first try { const result = spawnSync('bun', ['--version'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], shell: IS_WINDOWS }); if (result.status === 0) return 'bun'; } catch { // Not in PATH } // Check common installation paths return BUN_COMMON_PATHS.find(existsSync) || null; } /** * Check if Bun is installed and accessible */ function isBunInstalled() { return getBunPath() !== null; } /** * Get Bun version if installed */ function getBunVersion() { const bunPath = getBunPath(); if (!bunPath) return null; try { const result = spawnSync(bunPath, ['--version'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], shell: IS_WINDOWS }); return result.status === 0 ? result.stdout.trim() : null; } catch { return null; } } /** * Get the uv executable path (from PATH or common install locations) */ function getUvPath() { // Try PATH first try { const result = spawnSync('uv', ['--version'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], shell: IS_WINDOWS }); if (result.status === 0) return 'uv'; } catch { // Not in PATH } // Check common installation paths return UV_COMMON_PATHS.find(existsSync) || null; } /** * Check if uv is installed and accessible */ function isUvInstalled() { return getUvPath() !== null; } /** * Get uv version if installed */ function getUvVersion() { const uvPath = getUvPath(); if (!uvPath) return null; try { const result = spawnSync(uvPath, ['--version'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], shell: IS_WINDOWS }); return result.status === 0 ? result.stdout.trim() : null; } catch { return null; } } /** * Install Bun automatically based on platform */ function installBun() { console.error('🔧 Bun not found. Installing Bun runtime...'); try { if (IS_WINDOWS) { console.error(' Installing via PowerShell...'); execSync('powershell -c "irm bun.sh/install.ps1 | iex"', { stdio: 'inherit', shell: true }); } else { console.error(' Installing via curl...'); execSync('curl -fsSL https://bun.sh/install | bash', { stdio: 'inherit', shell: true }); } if (!isBunInstalled()) { throw new Error( 'Bun installation completed but binary not found. ' + 'Please restart your terminal and try again.' ); } const version = getBunVersion(); console.error(`✅ Bun ${version} installed successfully`); } catch (error) { console.error('❌ Failed to install Bun'); console.error(' Please install manually:'); if (IS_WINDOWS) { console.error(' - winget install Oven-sh.Bun'); console.error(' - Or: powershell -c "irm bun.sh/install.ps1 | iex"'); } else { console.error(' - curl -fsSL https://bun.sh/install | bash'); console.error(' - Or: brew install oven-sh/bun/bun'); } console.error(' Then restart your terminal and try again.'); throw error; } } /** * Install uv automatically based on platform */ function installUv() { console.error('🐍 Installing uv for Python/Chroma support...'); try { if (IS_WINDOWS) { console.error(' Installing via PowerShell...'); execSync('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', { stdio: 'inherit', shell: true }); } else { console.error(' Installing via curl...'); execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', { stdio: 'inherit', shell: true }); } if (!isUvInstalled()) { throw new Error( 'uv installation completed but binary not found. ' + 'Please restart your terminal and try again.' ); } const version = getUvVersion(); console.error(`✅ uv ${version} installed successfully`); } catch (error) { console.error('❌ Failed to install uv'); console.error(' Please install manually:'); if (IS_WINDOWS) { console.error(' - winget install astral-sh.uv'); console.error(' - Or: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"'); } else { console.error(' - curl -LsSf https://astral.sh/uv/install.sh | sh'); console.error(' - Or: brew install uv (macOS)'); } console.error(' Then restart your terminal and try again.'); throw error; } } /** * Check if dependencies need to be installed */ function needsInstall() { if (!existsSync(join(ROOT, 'node_modules'))) return true; try { const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')); const marker = JSON.parse(readFileSync(MARKER, 'utf-8')); return pkg.version !== marker.version || getBunVersion() !== marker.bun; } catch { return true; } } /** * Install dependencies using Bun */ function installDeps() { const bunPath = getBunPath(); if (!bunPath) { throw new Error('Bun executable not found'); } console.error('📦 Installing dependencies with Bun...'); // Clear stale native module cache (sharp/libvips) to prevent broken dylib references. // Bun's cache can retain native binaries that reference companion libraries at // broken relative paths after version upgrades. const bunCacheImgDir = join(homedir(), '.bun', 'install', 'cache', '@img'); if (existsSync(bunCacheImgDir)) { console.error(' Clearing stale native module cache (@img/sharp)...'); rmSync(bunCacheImgDir, { recursive: true, force: true }); } // Quote path for Windows paths with spaces const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath; execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS }); // Write version marker const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')); writeFileSync(MARKER, JSON.stringify({ version: pkg.version, bun: getBunVersion(), uv: getUvVersion(), installedAt: new Date().toISOString() })); } // Main execution try { if (!isBunInstalled()) installBun(); if (!isUvInstalled()) installUv(); if (needsInstall()) { installDeps(); console.error('✅ Dependencies installed'); } } catch (e) { console.error('❌ Installation failed:', e.message); process.exit(1); }