fix: Always use npx — stop silent global install failures

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
This commit is contained in:
Colby McHenry
2026-02-19 15:28:56 -06:00
parent 3be779f6f6
commit 675aab386a
4 changed files with 26 additions and 73 deletions
+6 -6
View File
@@ -1015,17 +1015,17 @@ program
process.exit(0); process.exit(0);
} }
// Spawn `codegraph sync` as a detached background process // Spawn sync as a detached background process
// so this hook exits immediately and doesn't block Claude Code // so this hook exits immediately and doesn't block Claude Code.
const isWindows = process.platform === 'win32'; // 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( const child = spawn(
isWindows ? 'codegraph' : process.argv[0]!, process.argv[0]!,
isWindows ? ['sync', '--quiet', projectRoot!] : [process.argv[1]!, 'sync', '--quiet', projectRoot!], [process.argv[1]!, 'sync', '--quiet', projectRoot!],
{ {
detached: true, detached: true,
stdio: 'ignore', stdio: 'ignore',
windowsHide: true, windowsHide: true,
shell: isWindows,
} }
); );
child.unref(); child.unref();
+5 -3
View File
@@ -113,16 +113,18 @@ export function warn(message: string): void {
/** /**
* Show the "next steps" section after installation * 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();
console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.'); console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.');
console.log(); console.log();
if (location === 'global') { if (location === 'global') {
const cmd = useNpx ? 'npx @colbymchenry/codegraph' : 'codegraph';
console.log(chalk.dim(' Quick start:')); console.log(chalk.dim(' Quick start:'));
console.log(chalk.dim(' cd your-project')); 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 { } else {
console.log(chalk.dim(' CodeGraph is ready to use in this project!')); console.log(chalk.dim(' CodeGraph is ready to use in this project!'));
} }
+6 -25
View File
@@ -98,28 +98,9 @@ function writeJsonFile(filePath: string, data: Record<string, any>): void {
} }
/** /**
* When true, all configs use `npx @colbymchenry/codegraph` instead of the * Get the MCP server configuration — always uses npx for reliability
* bare `codegraph` command. Set by the installer when global install fails.
*/ */
let useNpxFallback = false; function getMcpServerConfig(): Record<string, any> {
export function setUseNpxFallback(value: boolean): void {
useNpxFallback = value;
}
/**
* Get the MCP server configuration for the given location
*/
function getMcpServerConfig(location: InstallLocation): Record<string, any> {
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
return { return {
type: 'stdio', type: 'stdio',
command: 'npx', command: 'npx',
@@ -140,7 +121,7 @@ export function writeMcpConfig(location: InstallLocation): void {
} }
// Add or update codegraph server // Add or update codegraph server
config.mcpServers.codegraph = getMcpServerConfig(location); config.mcpServers.codegraph = getMcpServerConfig();
writeJsonFile(claudeJsonPath, config); writeJsonFile(claudeJsonPath, config);
} }
@@ -221,8 +202,8 @@ export function hasPermissions(location: InstallLocation): boolean {
* PostToolUse(Edit|Write) → mark-dirty (async, non-blocking) * PostToolUse(Edit|Write) → mark-dirty (async, non-blocking)
* Stop → sync-if-dirty (sync, ensures fresh index before next user turn) * Stop → sync-if-dirty (sync, ensures fresh index before next user turn)
*/ */
function getHooksConfig(location: InstallLocation): Record<string, any> { function getHooksConfig(): Record<string, any> {
const command = (location === 'global' && !useNpxFallback) ? 'codegraph' : 'npx @colbymchenry/codegraph'; const command = 'npx @colbymchenry/codegraph';
return { return {
PostToolUse: [ PostToolUse: [
@@ -277,7 +258,7 @@ export function writeHooks(location: InstallLocation): void {
settings.hooks = {}; settings.hooks = {};
} }
const newHooks = getHooksConfig(location); const newHooks = getHooksConfig();
// For each hook event (PostToolUse, Stop), merge with existing entries // For each hook event (PostToolUse, Stop), merge with existing entries
for (const [event, newEntries] of Object.entries(newHooks)) { for (const [event, newEntries] of Object.entries(newHooks)) {
+9 -39
View File
@@ -5,10 +5,9 @@
* with Claude Code. * with Claude Code.
*/ */
import { execSync } from 'child_process';
import { showBanner, showNextSteps, success, error, info, chalk } from './banner'; import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts'; 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 * Format a number with commas
@@ -25,40 +24,11 @@ export async function runInstaller(): Promise<void> {
showBanner(); showBanner();
try { try {
// Step 1: Check if codegraph is available (skip install if already there) // Step 1: Ask for installation location
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
const location = await promptInstallLocation(); const location = await promptInstallLocation();
console.log(); console.log();
// Step 3: Write MCP configuration // Step 2: Write MCP configuration
const alreadyHasMcp = hasMcpConfig(location); const alreadyHasMcp = hasMcpConfig(location);
writeMcpConfig(location); writeMcpConfig(location);
@@ -68,7 +38,7 @@ export async function runInstaller(): Promise<void> {
success(`Added MCP server to ${location === 'global' ? '~/.claude.json' : './.claude.json'}`); 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(); const autoAllow = await promptAutoAllow();
console.log(); console.log();
@@ -83,7 +53,7 @@ export async function runInstaller(): Promise<void> {
} }
} }
// Step 5: Write auto-sync hooks // Step 4: Write auto-sync hooks
const alreadyHasHooks = hasHooks(location); const alreadyHasHooks = hasHooks(location);
writeHooks(location); writeHooks(location);
@@ -93,7 +63,7 @@ export async function runInstaller(): Promise<void> {
success(`Added auto-sync hooks to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`); 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 claudeMdResult = writeClaudeMd(location);
const claudeMdPath = location === 'global' ? '~/.claude/CLAUDE.md' : './.claude/CLAUDE.md'; const claudeMdPath = location === 'global' ? '~/.claude/CLAUDE.md' : './.claude/CLAUDE.md';
@@ -105,13 +75,13 @@ export async function runInstaller(): Promise<void> {
success(`Added CodeGraph instructions to ${claudeMdPath}`); 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') { if (location === 'local') {
await initializeLocalProject(); await initializeLocalProject();
} }
// Show next steps // Show next steps
showNextSteps(location, !codegraphAvailable); showNextSteps(location);
} catch (err) { } catch (err) {
console.log(); console.log();
if (err instanceof Error && err.message.includes('readline was closed')) { if (err instanceof Error && err.message.includes('readline was closed')) {
@@ -137,7 +107,7 @@ async function initializeLocalProject(): Promise<void> {
} catch (err) { } catch (err) {
const msg = err instanceof Error ? err.message : String(err); const msg = err instanceof Error ? err.message : String(err);
error(`Could not load native modules: ${msg}`); 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).'); info('If this persists, try a Node.js LTS version (20 or 22).');
return; return;
} }