| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- /**
- * CodeGraph Interactive Installer
- *
- * Provides a beautiful interactive CLI experience for setting up CodeGraph
- * with Claude Code.
- */
- import { execSync } from 'child_process';
- import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
- import { promptInstallLocation, promptAutoAllow, promptConfirm, InstallLocation } from './prompts';
- import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer';
- /**
- * 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: Install codegraph globally (with user consent).
- // The global install is needed because Claude Code hooks and the MCP server
- // invoke `codegraph` by name — the temporary npx binary vanishes when npx exits.
- console.log(chalk.bold(' Install codegraph globally?') + chalk.dim(' (Required for hooks & MCP server)'));
- console.log();
- const shouldInstallGlobally = await promptConfirm('Install globally via npm', true);
- if (shouldInstallGlobally) {
- console.log(chalk.dim(' Installing codegraph globally...'));
- try {
- execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' });
- success('Installed codegraph command globally');
- } catch {
- info('Could not install globally (permission denied)');
- info('Try: sudo npm install -g @colbymchenry/codegraph');
- }
- } else {
- info('Skipped global install — hooks and MCP server may not work without it');
- info('You can install later: npm install -g @colbymchenry/codegraph');
- }
- console.log();
- // Step 2: Ask for installation location
- const location = await promptInstallLocation();
- console.log();
- // Step 3: Write MCP configuration (always uses npx for reliability)
- 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 4: 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 5: Write auto-sync hooks
- const alreadyHasHooks = hasHooks(location);
- writeHooks(location);
- if (alreadyHasHooks) {
- success(`Updated auto-sync hooks in ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
- } else {
- success(`Added auto-sync hooks to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
- }
- // Step 6: Write CLAUDE.md instructions
- const claudeMdResult = writeClaudeMd(location);
- const claudeMdPath = location === 'global' ? '~/.claude/CLAUDE.md' : './.claude/CLAUDE.md';
- if (claudeMdResult.created) {
- success(`Created ${claudeMdPath} with CodeGraph instructions`);
- } else if (claudeMdResult.updated) {
- success(`Updated CodeGraph section in ${claudeMdPath}`);
- } else {
- success(`Added CodeGraph instructions to ${claudeMdPath}`);
- }
- // Step 7: 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();
- // Lazy-load CodeGraph (requires native modules)
- let CodeGraph: typeof import('../index').default;
- try {
- CodeGraph = (await import('../index')).default;
- } 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('If this persists, try a Node.js LTS version (20 or 22).');
- return;
- }
- // 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`);
- }
- cg.close();
- }
- // Export for use in CLI
- export { InstallLocation };
|