From 675aab386aed0e76afdff25838426cb54dd4fe6d Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Thu, 19 Feb 2026 15:28:56 -0600 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20Always=20use=20npx=20=E2=80=94=20sto?= =?UTF-8?q?p=20silent=20global=20install=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the npm install -g attempt from the installer that silently fails on many systems (permissions, PATH, node version managers). All configs (MCP server, hooks, next-steps) now always use npx @colbymchenry/codegraph. Global install offered as an optional tip. Fixes #37, #38 --- src/bin/codegraph.ts | 12 ++++----- src/installer/banner.ts | 8 +++--- src/installer/config-writer.ts | 31 +++++----------------- src/installer/index.ts | 48 +++++++--------------------------- 4 files changed, 26 insertions(+), 73 deletions(-) diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index 75a78dd..810144d 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -1015,17 +1015,17 @@ program process.exit(0); } - // Spawn `codegraph sync` as a detached background process - // so this hook exits immediately and doesn't block Claude Code - const isWindows = process.platform === 'win32'; + // Spawn sync as a detached background process + // so this hook exits immediately and doesn't block Claude Code. + // Uses process.argv[0]/[1] (e.g. node /path/to/codegraph.js) so it + // works whether invoked via global install, npx, or directly. const child = spawn( - isWindows ? 'codegraph' : process.argv[0]!, - isWindows ? ['sync', '--quiet', projectRoot!] : [process.argv[1]!, 'sync', '--quiet', projectRoot!], + process.argv[0]!, + [process.argv[1]!, 'sync', '--quiet', projectRoot!], { detached: true, stdio: 'ignore', windowsHide: true, - shell: isWindows, } ); child.unref(); diff --git a/src/installer/banner.ts b/src/installer/banner.ts index 1f3f811..b0d6d8f 100644 --- a/src/installer/banner.ts +++ b/src/installer/banner.ts @@ -113,16 +113,18 @@ export function warn(message: string): void { /** * Show the "next steps" section after installation */ -export function showNextSteps(location: 'global' | 'local', useNpx?: boolean): void { +export function showNextSteps(location: 'global' | 'local'): void { console.log(); console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.'); console.log(); if (location === 'global') { - const cmd = useNpx ? 'npx @colbymchenry/codegraph' : 'codegraph'; console.log(chalk.dim(' Quick start:')); console.log(chalk.dim(' cd your-project')); - console.log(chalk.cyan(` ${cmd} init -i`)); + console.log(chalk.cyan(' npx @colbymchenry/codegraph init -i')); + console.log(); + console.log(chalk.dim(' Tip: For a shorter command, install globally:')); + console.log(chalk.dim(' npm install -g @colbymchenry/codegraph')); } else { console.log(chalk.dim(' CodeGraph is ready to use in this project!')); } diff --git a/src/installer/config-writer.ts b/src/installer/config-writer.ts index d37edd1..edb2465 100644 --- a/src/installer/config-writer.ts +++ b/src/installer/config-writer.ts @@ -98,28 +98,9 @@ function writeJsonFile(filePath: string, data: Record): void { } /** - * When true, all configs use `npx @colbymchenry/codegraph` instead of the - * bare `codegraph` command. Set by the installer when global install fails. + * Get the MCP server configuration — always uses npx for reliability */ -let useNpxFallback = false; - -export function setUseNpxFallback(value: boolean): void { - useNpxFallback = value; -} - -/** - * Get the MCP server configuration for the given location - */ -function getMcpServerConfig(location: InstallLocation): Record { - if (location === 'global' && !useNpxFallback) { - // Global: use 'codegraph' command directly (globally installed and in PATH) - return { - type: 'stdio', - command: 'codegraph', - args: ['serve', '--mcp'], - }; - } - // Local or npx fallback: use npx to run the package +function getMcpServerConfig(): Record { return { type: 'stdio', command: 'npx', @@ -140,7 +121,7 @@ export function writeMcpConfig(location: InstallLocation): void { } // Add or update codegraph server - config.mcpServers.codegraph = getMcpServerConfig(location); + config.mcpServers.codegraph = getMcpServerConfig(); writeJsonFile(claudeJsonPath, config); } @@ -221,8 +202,8 @@ export function hasPermissions(location: InstallLocation): boolean { * PostToolUse(Edit|Write) → mark-dirty (async, non-blocking) * Stop → sync-if-dirty (sync, ensures fresh index before next user turn) */ -function getHooksConfig(location: InstallLocation): Record { - const command = (location === 'global' && !useNpxFallback) ? 'codegraph' : 'npx @colbymchenry/codegraph'; +function getHooksConfig(): Record { + const command = 'npx @colbymchenry/codegraph'; return { PostToolUse: [ @@ -277,7 +258,7 @@ export function writeHooks(location: InstallLocation): void { settings.hooks = {}; } - const newHooks = getHooksConfig(location); + const newHooks = getHooksConfig(); // For each hook event (PostToolUse, Stop), merge with existing entries for (const [event, newEntries] of Object.entries(newHooks)) { diff --git a/src/installer/index.ts b/src/installer/index.ts index 1cc6f9d..cb8781d 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -5,10 +5,9 @@ * with Claude Code. */ -import { execSync } from 'child_process'; import { showBanner, showNextSteps, success, error, info, chalk } from './banner'; import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts'; -import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks, setUseNpxFallback } from './config-writer'; +import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer'; /** * Format a number with commas @@ -25,40 +24,11 @@ export async function runInstaller(): Promise { showBanner(); try { - // Step 1: Check if codegraph is available (skip install if already there) - let codegraphAvailable = false; - try { - const checkCmd = process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph'; - execSync(checkCmd, { stdio: 'pipe' }); - codegraphAvailable = true; - } catch { - // Not installed globally yet - } - - if (!codegraphAvailable) { - console.log(chalk.dim(' Installing codegraph globally...')); - try { - execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' }); - success('Installed codegraph command globally'); - codegraphAvailable = true; - } catch { - // May fail if no permissions, but that's ok - npx still works - info('Could not install globally — will use npx instead'); - info('(MCP server and hooks will use npx @colbymchenry/codegraph)'); - } - console.log(); - } - - // If codegraph binary isn't in PATH, tell config-writer to use npx for everything - if (!codegraphAvailable) { - setUseNpxFallback(true); - } - - // Step 2: Ask for installation location + // Step 1: Ask for installation location const location = await promptInstallLocation(); console.log(); - // Step 3: Write MCP configuration + // Step 2: Write MCP configuration const alreadyHasMcp = hasMcpConfig(location); writeMcpConfig(location); @@ -68,7 +38,7 @@ export async function runInstaller(): Promise { success(`Added MCP server to ${location === 'global' ? '~/.claude.json' : './.claude.json'}`); } - // Step 4: Ask about auto-allow permissions + // Step 3: Ask about auto-allow permissions const autoAllow = await promptAutoAllow(); console.log(); @@ -83,7 +53,7 @@ export async function runInstaller(): Promise { } } - // Step 5: Write auto-sync hooks + // Step 4: Write auto-sync hooks const alreadyHasHooks = hasHooks(location); writeHooks(location); @@ -93,7 +63,7 @@ export async function runInstaller(): Promise { success(`Added auto-sync hooks to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`); } - // Step 6: Write CLAUDE.md instructions + // Step 5: Write CLAUDE.md instructions const claudeMdResult = writeClaudeMd(location); const claudeMdPath = location === 'global' ? '~/.claude/CLAUDE.md' : './.claude/CLAUDE.md'; @@ -105,13 +75,13 @@ export async function runInstaller(): Promise { success(`Added CodeGraph instructions to ${claudeMdPath}`); } - // Step 7: For local install, initialize the project + // Step 6: For local install, initialize the project if (location === 'local') { await initializeLocalProject(); } // Show next steps - showNextSteps(location, !codegraphAvailable); + showNextSteps(location); } catch (err) { console.log(); if (err instanceof Error && err.message.includes('readline was closed')) { @@ -137,7 +107,7 @@ async function initializeLocalProject(): Promise { } catch (err) { const msg = err instanceof Error ? err.message : String(err); error(`Could not load native modules: ${msg}`); - info('Skipping project initialization. You can run "codegraph init -i" later.'); + info('Skipping project initialization. You can run "npx @colbymchenry/codegraph init -i" later.'); info('If this persists, try a Node.js LTS version (20 or 22).'); return; } From 88e1f2df7fbb2c7a0488bfe70de5cdb3d836f9c3 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Thu, 19 Feb 2026 15:38:59 -0600 Subject: [PATCH 2/5] fix: Bring back global install attempt with loud failure messaging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Global install is still attempted for bare `codegraph` convenience, but now verifies the command is actually in PATH after install. If it fails, users get clear actionable messages instead of silent swallowing. Configs (MCP server, hooks) always use npx regardless — those never break even if global install fails. --- src/installer/banner.ts | 13 +++++++----- src/installer/index.ts | 46 ++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/installer/banner.ts b/src/installer/banner.ts index b0d6d8f..1d004fc 100644 --- a/src/installer/banner.ts +++ b/src/installer/banner.ts @@ -113,18 +113,21 @@ export function warn(message: string): void { /** * Show the "next steps" section after installation */ -export function showNextSteps(location: 'global' | 'local'): void { +export function showNextSteps(location: 'global' | 'local', codegraphAvailable?: boolean): void { console.log(); console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.'); console.log(); if (location === 'global') { + const cmd = codegraphAvailable ? 'codegraph' : 'npx @colbymchenry/codegraph'; console.log(chalk.dim(' Quick start:')); console.log(chalk.dim(' cd your-project')); - console.log(chalk.cyan(' npx @colbymchenry/codegraph init -i')); - console.log(); - console.log(chalk.dim(' Tip: For a shorter command, install globally:')); - console.log(chalk.dim(' npm install -g @colbymchenry/codegraph')); + console.log(chalk.cyan(` ${cmd} init -i`)); + if (!codegraphAvailable) { + console.log(); + console.log(chalk.dim(' Tip: For a shorter command, install globally:')); + console.log(chalk.dim(' npm install -g @colbymchenry/codegraph')); + } } else { console.log(chalk.dim(' CodeGraph is ready to use in this project!')); } diff --git a/src/installer/index.ts b/src/installer/index.ts index cb8781d..ffab815 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -5,6 +5,7 @@ * with Claude Code. */ +import { execSync } from 'child_process'; import { showBanner, showNextSteps, success, error, info, chalk } from './banner'; import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts'; import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer'; @@ -24,11 +25,42 @@ export async function runInstaller(): Promise { showBanner(); try { - // Step 1: Ask for installation location + // Step 1: Try global install for bare `codegraph` command convenience. + // This is best-effort — configs always use npx regardless. + let codegraphAvailable = false; + try { + const checkCmd = process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph'; + execSync(checkCmd, { stdio: 'pipe' }); + codegraphAvailable = true; + } catch { + // Not installed globally yet — try to install + console.log(chalk.dim(' Installing codegraph globally...')); + try { + execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' }); + // Verify it actually worked (PATH may not include npm global bin) + try { + execSync(process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph', { stdio: 'pipe' }); + codegraphAvailable = true; + success('Installed codegraph command globally'); + } catch { + // Install "succeeded" but command not in PATH — common with nvm/fnm + info('Global install succeeded but codegraph is not in your PATH'); + info('You may need to add npm\'s global bin to your PATH, or use:'); + info(' npx @colbymchenry/codegraph '); + } + } catch { + info('Could not install globally (permission denied)'); + info('You can install manually with: sudo npm install -g @colbymchenry/codegraph'); + info('Or use: npx @colbymchenry/codegraph '); + } + console.log(); + } + + // Step 2: Ask for installation location const location = await promptInstallLocation(); console.log(); - // Step 2: Write MCP configuration + // Step 3: Write MCP configuration (always uses npx for reliability) const alreadyHasMcp = hasMcpConfig(location); writeMcpConfig(location); @@ -38,7 +70,7 @@ export async function runInstaller(): Promise { success(`Added MCP server to ${location === 'global' ? '~/.claude.json' : './.claude.json'}`); } - // Step 3: Ask about auto-allow permissions + // Step 4: Ask about auto-allow permissions const autoAllow = await promptAutoAllow(); console.log(); @@ -53,7 +85,7 @@ export async function runInstaller(): Promise { } } - // Step 4: Write auto-sync hooks + // Step 5: Write auto-sync hooks const alreadyHasHooks = hasHooks(location); writeHooks(location); @@ -63,7 +95,7 @@ export async function runInstaller(): Promise { success(`Added auto-sync hooks to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`); } - // Step 5: Write CLAUDE.md instructions + // Step 6: Write CLAUDE.md instructions const claudeMdResult = writeClaudeMd(location); const claudeMdPath = location === 'global' ? '~/.claude/CLAUDE.md' : './.claude/CLAUDE.md'; @@ -75,13 +107,13 @@ export async function runInstaller(): Promise { success(`Added CodeGraph instructions to ${claudeMdPath}`); } - // Step 6: For local install, initialize the project + // Step 7: For local install, initialize the project if (location === 'local') { await initializeLocalProject(); } // Show next steps - showNextSteps(location); + showNextSteps(location, codegraphAvailable); } catch (err) { console.log(); if (err instanceof Error && err.message.includes('readline was closed')) { From 67f60b9b2f603244edd8c5232072c72483b5310b Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Thu, 19 Feb 2026 15:46:34 -0600 Subject: [PATCH 3/5] fix: Use bare codegraph everywhere, add preuninstall cleanup - Revert configs (MCP, hooks) to use bare `codegraph` command - Remove all npx references from configs and messaging - Add preuninstall script that runs on `npm uninstall -g` to clean up MCP server, permissions, hooks, and CLAUDE.md section - Show uninstall instructions in post-install next steps --- package.json | 3 +- src/bin/uninstall.ts | 151 +++++++++++++++++++++++++++++++++ src/installer/banner.ts | 13 ++- src/installer/config-writer.ts | 8 +- src/installer/index.ts | 17 ++-- 5 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 src/bin/uninstall.ts diff --git a/package.json b/package.json index 0e50f78..1577279 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@colbymchenry/codegraph", - "version": "0.5.5", + "version": "0.6.0", "description": "Supercharge Claude Code with semantic code intelligence. 30% fewer tokens, 25% fewer tool calls, 100% local.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -15,6 +15,7 @@ "scripts": { "build": "tsc && npm run copy-assets", "postinstall": "node scripts/postinstall.js", + "preuninstall": "node dist/bin/uninstall.js", "copy-assets": "node -e \"const fs=require('fs');fs.mkdirSync('dist/db',{recursive:true});fs.copyFileSync('src/db/schema.sql','dist/db/schema.sql');fs.mkdirSync('dist/extraction/wasm',{recursive:true});fs.readdirSync('src/extraction/wasm').filter(f=>f.endsWith('.wasm')).forEach(f=>fs.copyFileSync('src/extraction/wasm/'+f,'dist/extraction/wasm/'+f))\"", "dev": "tsc --watch", "cli": "npm run build && node dist/bin/codegraph.js", diff --git a/src/bin/uninstall.ts b/src/bin/uninstall.ts new file mode 100644 index 0000000..8ae3d13 --- /dev/null +++ b/src/bin/uninstall.ts @@ -0,0 +1,151 @@ +#!/usr/bin/env node +/** + * CodeGraph preuninstall cleanup script + * + * Runs automatically when `npm uninstall -g @colbymchenry/codegraph` is called. + * Removes all CodeGraph configuration from Claude Code: + * - MCP server entry from ~/.claude.json + * - Permissions from ~/.claude/settings.json + * - Hooks from ~/.claude/settings.json + * - CodeGraph section from ~/.claude/CLAUDE.md + * + * This script must never throw — a failed cleanup must not block uninstall. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +const CODEGRAPH_SECTION_START = ''; +const CODEGRAPH_SECTION_END = ''; + +function readJson(filePath: string): Record | null { + try { + if (!fs.existsSync(filePath)) return null; + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } catch { + return null; + } +} + +function writeJson(filePath: string, data: Record): void { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n'); +} + +/** + * Remove CodeGraph MCP server from ~/.claude.json + */ +function removeMcpConfig(): void { + const filePath = path.join(os.homedir(), '.claude.json'); + const config = readJson(filePath); + if (!config?.mcpServers?.codegraph) return; + + delete config.mcpServers.codegraph; + + // Clean up empty mcpServers object + if (Object.keys(config.mcpServers).length === 0) { + delete config.mcpServers; + } + + writeJson(filePath, config); +} + +/** + * Remove CodeGraph permissions and hooks from ~/.claude/settings.json + */ +function removeSettings(): void { + const filePath = path.join(os.homedir(), '.claude', 'settings.json'); + const settings = readJson(filePath); + if (!settings) return; + + let changed = false; + + // Remove codegraph permissions + if (Array.isArray(settings.permissions?.allow)) { + const before = settings.permissions.allow.length; + settings.permissions.allow = settings.permissions.allow.filter( + (p: string) => !p.startsWith('mcp__codegraph__') + ); + if (settings.permissions.allow.length !== before) changed = true; + + // Clean up empty allow array + if (settings.permissions.allow.length === 0) { + delete settings.permissions.allow; + } + // Clean up empty permissions object + if (Object.keys(settings.permissions).length === 0) { + delete settings.permissions; + } + } + + // Remove codegraph hooks + if (settings.hooks) { + for (const event of Object.keys(settings.hooks)) { + if (!Array.isArray(settings.hooks[event])) continue; + + const before = settings.hooks[event].length; + settings.hooks[event] = settings.hooks[event].filter((entry: any) => { + const json = JSON.stringify(entry); + return !json.includes('codegraph mark-dirty') && !json.includes('codegraph sync-if-dirty'); + }); + if (settings.hooks[event].length !== before) changed = true; + + // Clean up empty event arrays + if (settings.hooks[event].length === 0) { + delete settings.hooks[event]; + } + } + + // Clean up empty hooks object + if (Object.keys(settings.hooks).length === 0) { + delete settings.hooks; + } + } + + if (changed) { + writeJson(filePath, settings); + } +} + +/** + * Remove CodeGraph section from ~/.claude/CLAUDE.md + */ +function removeClaudeMd(): void { + const filePath = path.join(os.homedir(), '.claude', 'CLAUDE.md'); + try { + if (!fs.existsSync(filePath)) return; + let content = fs.readFileSync(filePath, 'utf-8'); + + // Remove marked section + const startIdx = content.indexOf(CODEGRAPH_SECTION_START); + const endIdx = content.indexOf(CODEGRAPH_SECTION_END); + + if (startIdx !== -1 && endIdx > startIdx) { + const before = content.substring(0, startIdx).trimEnd(); + const after = content.substring(endIdx + CODEGRAPH_SECTION_END.length).trimStart(); + content = before + (before && after ? '\n\n' : '') + after; + + if (content.trim() === '') { + // File is empty after removing section — delete it + fs.unlinkSync(filePath); + } else { + fs.writeFileSync(filePath, content.trim() + '\n'); + } + } + } catch { + // Never fail + } +} + +// Run cleanup — never throw +try { + removeMcpConfig(); +} catch { /* ignore */ } + +try { + removeSettings(); +} catch { /* ignore */ } + +try { + removeClaudeMd(); +} catch { /* ignore */ } diff --git a/src/installer/banner.ts b/src/installer/banner.ts index 1d004fc..fb70bf3 100644 --- a/src/installer/banner.ts +++ b/src/installer/banner.ts @@ -113,21 +113,18 @@ export function warn(message: string): void { /** * Show the "next steps" section after installation */ -export function showNextSteps(location: 'global' | 'local', codegraphAvailable?: boolean): void { +export function showNextSteps(location: 'global' | 'local'): void { console.log(); console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.'); console.log(); if (location === 'global') { - const cmd = codegraphAvailable ? 'codegraph' : 'npx @colbymchenry/codegraph'; console.log(chalk.dim(' Quick start:')); console.log(chalk.dim(' cd your-project')); - console.log(chalk.cyan(` ${cmd} init -i`)); - if (!codegraphAvailable) { - console.log(); - console.log(chalk.dim(' Tip: For a shorter command, install globally:')); - console.log(chalk.dim(' npm install -g @colbymchenry/codegraph')); - } + console.log(chalk.cyan(' codegraph init -i')); + console.log(); + console.log(chalk.dim(' To uninstall:')); + console.log(chalk.dim(' npm uninstall -g @colbymchenry/codegraph')); } else { console.log(chalk.dim(' CodeGraph is ready to use in this project!')); } diff --git a/src/installer/config-writer.ts b/src/installer/config-writer.ts index edb2465..2d9e8ab 100644 --- a/src/installer/config-writer.ts +++ b/src/installer/config-writer.ts @@ -98,13 +98,13 @@ function writeJsonFile(filePath: string, data: Record): void { } /** - * Get the MCP server configuration — always uses npx for reliability + * Get the MCP server configuration */ function getMcpServerConfig(): Record { return { type: 'stdio', - command: 'npx', - args: ['@colbymchenry/codegraph', 'serve', '--mcp'], + command: 'codegraph', + args: ['serve', '--mcp'], }; } @@ -203,7 +203,7 @@ export function hasPermissions(location: InstallLocation): boolean { * Stop → sync-if-dirty (sync, ensures fresh index before next user turn) */ function getHooksConfig(): Record { - const command = 'npx @colbymchenry/codegraph'; + const command = 'codegraph'; return { PostToolUse: [ diff --git a/src/installer/index.ts b/src/installer/index.ts index ffab815..9d773a5 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -25,13 +25,10 @@ export async function runInstaller(): Promise { showBanner(); try { - // Step 1: Try global install for bare `codegraph` command convenience. - // This is best-effort — configs always use npx regardless. - let codegraphAvailable = false; + // Step 1: Ensure codegraph is installed globally try { const checkCmd = process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph'; execSync(checkCmd, { stdio: 'pipe' }); - codegraphAvailable = true; } catch { // Not installed globally yet — try to install console.log(chalk.dim(' Installing codegraph globally...')); @@ -40,18 +37,16 @@ export async function runInstaller(): Promise { // Verify it actually worked (PATH may not include npm global bin) try { execSync(process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph', { stdio: 'pipe' }); - codegraphAvailable = true; success('Installed codegraph command globally'); } catch { // Install "succeeded" but command not in PATH — common with nvm/fnm info('Global install succeeded but codegraph is not in your PATH'); - info('You may need to add npm\'s global bin to your PATH, or use:'); - info(' npx @colbymchenry/codegraph '); + info('You may need to add npm\'s global bin directory to your PATH'); + info('Then restart your terminal and run: codegraph init -i'); } } catch { info('Could not install globally (permission denied)'); - info('You can install manually with: sudo npm install -g @colbymchenry/codegraph'); - info('Or use: npx @colbymchenry/codegraph '); + info('Try: sudo npm install -g @colbymchenry/codegraph'); } console.log(); } @@ -113,7 +108,7 @@ export async function runInstaller(): Promise { } // Show next steps - showNextSteps(location, codegraphAvailable); + showNextSteps(location); } catch (err) { console.log(); if (err instanceof Error && err.message.includes('readline was closed')) { @@ -139,7 +134,7 @@ async function initializeLocalProject(): Promise { } catch (err) { const msg = err instanceof Error ? err.message : String(err); error(`Could not load native modules: ${msg}`); - info('Skipping project initialization. You can run "npx @colbymchenry/codegraph init -i" later.'); + info('Skipping project initialization. You can run "codegraph init -i" later.'); info('If this persists, try a Node.js LTS version (20 or 22).'); return; } From a46d6b7487819db270d5bc2716c274a0e21a9db4 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Thu, 19 Feb 2026 15:51:03 -0600 Subject: [PATCH 4/5] fix: Always run npm install -g, skip command -v check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit command -v codegraph is unreliable inside npx because npx puts a temporary binary in PATH. The check always passes, so the global install is always skipped — which is the root cause of #37 and #38. Fix: remove the check entirely, always run npm install -g. --- src/installer/index.ts | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/installer/index.ts b/src/installer/index.ts index 9d773a5..c381489 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -25,31 +25,18 @@ export async function runInstaller(): Promise { showBanner(); try { - // Step 1: Ensure codegraph is installed globally + // Step 1: Install codegraph globally. + // Always run npm install -g — we can't use `command -v codegraph` to check + // because npx puts a temporary binary in PATH that vanishes when npx exits. + console.log(chalk.dim(' Installing codegraph globally...')); try { - const checkCmd = process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph'; - execSync(checkCmd, { stdio: 'pipe' }); + execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' }); + success('Installed codegraph command globally'); } catch { - // Not installed globally yet — try to install - console.log(chalk.dim(' Installing codegraph globally...')); - try { - execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' }); - // Verify it actually worked (PATH may not include npm global bin) - try { - execSync(process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph', { stdio: 'pipe' }); - success('Installed codegraph command globally'); - } catch { - // Install "succeeded" but command not in PATH — common with nvm/fnm - info('Global install succeeded but codegraph is not in your PATH'); - info('You may need to add npm\'s global bin directory to your PATH'); - info('Then restart your terminal and run: codegraph init -i'); - } - } catch { - info('Could not install globally (permission denied)'); - info('Try: sudo npm install -g @colbymchenry/codegraph'); - } - console.log(); + info('Could not install globally (permission denied)'); + info('Try: sudo npm install -g @colbymchenry/codegraph'); } + console.log(); // Step 2: Ask for installation location const location = await promptInstallLocation(); From dff98a638cbf44cb81f7575ccecc99d570fe24e3 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Thu, 19 Feb 2026 15:57:01 -0600 Subject: [PATCH 5/5] Bumps version to 0.6.2 Bumps version to 0.6.2 in both package.json and lockfile to align release metadata and prevent silent install failures on fresh installs. Relates to silent install fix --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4693c4f..f0702e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@colbymchenry/codegraph", - "version": "0.5.5", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@colbymchenry/codegraph", - "version": "0.5.5", + "version": "0.6.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1577279..a28a284 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@colbymchenry/codegraph", - "version": "0.6.0", + "version": "0.6.2", "description": "Supercharge Claude Code with semantic code intelligence. 30% fewer tokens, 25% fewer tool calls, 100% local.", "main": "dist/index.js", "types": "dist/index.d.ts",