fix: Bring back global install attempt with loud failure messaging

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.
This commit is contained in:
Colby McHenry
2026-02-19 15:38:59 -06:00
parent 675aab386a
commit 88e1f2df7f
2 changed files with 47 additions and 12 deletions
+8 -5
View File
@@ -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!'));
}
+39 -7
View File
@@ -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<void> {
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 <command>');
}
} 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 <command>');
}
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<void> {
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<void> {
}
}
// 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<void> {
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<void> {
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')) {