Sentry error reporting can now be disabled by: 1. Declining during the interactive installer (sets CODEGRAPH_TELEMETRY=off in the MCP server config env) 2. Setting CODEGRAPH_TELEMETRY=off in your shell environment README updated with a Telemetry section documenting what is collected and how to opt out. Closes #68 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
193 lines
6.6 KiB
TypeScript
193 lines
6.6 KiB
TypeScript
/**
|
|
* 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: Ask about auto-allow permissions
|
|
const autoAllow = await promptAutoAllow();
|
|
console.log();
|
|
|
|
// Step 4: Ask about anonymous error reporting
|
|
console.log(chalk.bold(' Send anonymous error reports?') + chalk.dim(' (Helps fix bugs — no source code collected)'));
|
|
console.log();
|
|
const enableTelemetry = await promptConfirm('Enable anonymous error reporting', true);
|
|
|
|
if (!enableTelemetry) {
|
|
info('Telemetry disabled');
|
|
} else {
|
|
success('Anonymous error reporting enabled');
|
|
}
|
|
console.log();
|
|
|
|
// Step 5: Write MCP configuration (includes telemetry env if opted out)
|
|
const alreadyHasMcp = hasMcpConfig(location);
|
|
writeMcpConfig(location, { telemetry: enableTelemetry });
|
|
|
|
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'}`);
|
|
}
|
|
|
|
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 6: 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 7: 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 };
|