Add interactive CLI installer for Claude Code integration

- New `codegraph install` command and auto-run when invoked with no args
- Beautiful ASCII banner using figlet
- Interactive prompts for global (~/.claude) or local (./.claude) installation
- Writes MCP server config to claude.json
- Writes auto-allow permissions to settings.json
- For local installs: auto-initializes project, indexes, and installs git hooks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-01-19 14:31:29 -06:00
co-authored by Claude Opus 4.5
parent 0a1b78854a
commit 9baf59e5f0
7 changed files with 584 additions and 5 deletions
+132
View File
@@ -0,0 +1,132 @@
/**
* CodeGraph Interactive Installer
*
* Provides a beautiful interactive CLI experience for setting up CodeGraph
* with Claude Code.
*/
import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts';
import { writeMcpConfig, writePermissions, hasMcpConfig, hasPermissions } from './config-writer';
import CodeGraph from '../index';
/**
* Format a number with commas
*/
function formatNumber(n: number): string {
return n.toLocaleString();
}
/**
* Run the interactive installer
*/
export async function runInstaller(): Promise<void> {
// Show the banner
showBanner();
try {
// Step 1: Ask for installation location
const location = await promptInstallLocation();
console.log();
// Step 2: Write MCP configuration
const alreadyHasMcp = hasMcpConfig(location);
writeMcpConfig(location);
if (alreadyHasMcp) {
success(`Updated MCP server in ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
} else {
success(`Added MCP server to ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
}
// Step 3: Ask about auto-allow permissions
const autoAllow = await promptAutoAllow();
console.log();
if (autoAllow) {
const alreadyHasPerms = hasPermissions(location);
writePermissions(location);
if (alreadyHasPerms) {
success(`Updated permissions in ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
} else {
success(`Added permissions to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
}
}
// Step 4: For local install, initialize the project
if (location === 'local') {
await initializeLocalProject();
}
// Show next steps
showNextSteps(location);
} catch (err) {
console.log();
if (err instanceof Error && err.message.includes('readline was closed')) {
// User cancelled with Ctrl+C
console.log(chalk.dim(' Installation cancelled.'));
} else {
error(`Installation failed: ${err instanceof Error ? err.message : String(err)}`);
}
process.exit(1);
}
}
/**
* Initialize CodeGraph in the current project (for local installs)
*/
async function initializeLocalProject(): Promise<void> {
const projectPath = process.cwd();
// Check if already initialized
if (CodeGraph.isInitialized(projectPath)) {
info('CodeGraph already initialized in this project');
return;
}
console.log();
console.log(chalk.dim(' Initializing CodeGraph in current project...'));
// Initialize CodeGraph
const cg = await CodeGraph.init(projectPath);
success('Created .codegraph/ directory');
// Index the project
const result = await cg.indexAll({
onProgress: (progress) => {
// Simple progress indicator
const phaseNames: Record<string, string> = {
scanning: 'Scanning files',
parsing: 'Parsing code',
storing: 'Storing data',
resolving: 'Resolving refs',
};
const phaseName = phaseNames[progress.phase] || progress.phase;
const percent = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;
process.stdout.write(`\r ${chalk.dim(phaseName)}... ${percent}% `);
},
});
// Clear progress line
process.stdout.write('\r' + ' '.repeat(50) + '\r');
if (result.success) {
success(`Indexed ${formatNumber(result.filesIndexed)} files (${formatNumber(result.nodesCreated)} symbols)`);
} else {
success(`Indexed ${formatNumber(result.filesIndexed)} files with ${result.errors.length} warnings`);
}
// Install git hooks if this is a git repository
if (cg.isGitRepository()) {
const hookResult = cg.installGitHooks();
if (hookResult.success) {
success('Installed git post-commit hook');
}
}
cg.close();
}
// Export for use in CLI
export { InstallLocation };