codegraph.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. #!/usr/bin/env node
  2. /**
  3. * CodeGraph CLI
  4. *
  5. * Command-line interface for CodeGraph code intelligence.
  6. *
  7. * Usage:
  8. * codegraph init [path] Initialize CodeGraph in a project
  9. * codegraph index [path] Index all files in the project
  10. * codegraph sync [path] Sync changes since last index
  11. * codegraph status [path] Show index status
  12. * codegraph query <search> Search for symbols
  13. * codegraph context <task> Build context for a task
  14. * codegraph hooks install Install git hooks
  15. * codegraph hooks remove Remove git hooks
  16. */
  17. import { Command } from 'commander';
  18. import * as path from 'path';
  19. import * as fs from 'fs';
  20. import CodeGraph from '../index';
  21. import type { IndexProgress } from '../index';
  22. const program = new Command();
  23. // Version from package.json
  24. const packageJson = JSON.parse(
  25. fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')
  26. );
  27. // =============================================================================
  28. // ANSI Color Helpers (avoid chalk ESM issues)
  29. // =============================================================================
  30. const colors = {
  31. reset: '\x1b[0m',
  32. bold: '\x1b[1m',
  33. dim: '\x1b[2m',
  34. red: '\x1b[31m',
  35. green: '\x1b[32m',
  36. yellow: '\x1b[33m',
  37. blue: '\x1b[34m',
  38. cyan: '\x1b[36m',
  39. white: '\x1b[37m',
  40. gray: '\x1b[90m',
  41. };
  42. const chalk = {
  43. bold: (s: string) => `${colors.bold}${s}${colors.reset}`,
  44. dim: (s: string) => `${colors.dim}${s}${colors.reset}`,
  45. red: (s: string) => `${colors.red}${s}${colors.reset}`,
  46. green: (s: string) => `${colors.green}${s}${colors.reset}`,
  47. yellow: (s: string) => `${colors.yellow}${s}${colors.reset}`,
  48. blue: (s: string) => `${colors.blue}${s}${colors.reset}`,
  49. cyan: (s: string) => `${colors.cyan}${s}${colors.reset}`,
  50. white: (s: string) => `${colors.white}${s}${colors.reset}`,
  51. gray: (s: string) => `${colors.gray}${s}${colors.reset}`,
  52. };
  53. program
  54. .name('codegraph')
  55. .description('Code intelligence and knowledge graph for any codebase')
  56. .version(packageJson.version);
  57. // =============================================================================
  58. // Helper Functions
  59. // =============================================================================
  60. /**
  61. * Resolve project path from argument or current directory
  62. */
  63. function resolveProjectPath(pathArg?: string): string {
  64. return path.resolve(pathArg || process.cwd());
  65. }
  66. /**
  67. * Format a number with commas
  68. */
  69. function formatNumber(n: number): string {
  70. return n.toLocaleString();
  71. }
  72. /**
  73. * Format duration in milliseconds to human readable
  74. */
  75. function formatDuration(ms: number): string {
  76. if (ms < 1000) {
  77. return `${ms}ms`;
  78. }
  79. const seconds = ms / 1000;
  80. if (seconds < 60) {
  81. return `${seconds.toFixed(1)}s`;
  82. }
  83. const minutes = Math.floor(seconds / 60);
  84. const remainingSeconds = seconds % 60;
  85. return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
  86. }
  87. /**
  88. * Create a progress bar string
  89. */
  90. function progressBar(current: number, total: number, width: number = 30): string {
  91. const percent = total > 0 ? current / total : 0;
  92. const filled = Math.round(width * percent);
  93. const empty = width - filled;
  94. const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
  95. const percentStr = `${Math.round(percent * 100)}%`.padStart(4);
  96. return `${bar} ${percentStr}`;
  97. }
  98. /**
  99. * Print a progress update (overwrites current line)
  100. */
  101. function printProgress(progress: IndexProgress): void {
  102. const phaseNames: Record<string, string> = {
  103. scanning: 'Scanning files',
  104. parsing: 'Parsing code',
  105. storing: 'Storing data',
  106. resolving: 'Resolving refs',
  107. };
  108. const phaseName = phaseNames[progress.phase] || progress.phase;
  109. const bar = progressBar(progress.current, progress.total);
  110. const file = progress.currentFile ? chalk.dim(` ${progress.currentFile}`) : '';
  111. // Clear line and print progress
  112. process.stdout.write(`\r${chalk.cyan(phaseName)}: ${bar}${file}`.padEnd(100));
  113. }
  114. /**
  115. * Print success message
  116. */
  117. function success(message: string): void {
  118. console.log(chalk.green('✓') + ' ' + message);
  119. }
  120. /**
  121. * Print error message
  122. */
  123. function error(message: string): void {
  124. console.error(chalk.red('✗') + ' ' + message);
  125. }
  126. /**
  127. * Print info message
  128. */
  129. function info(message: string): void {
  130. console.log(chalk.blue('ℹ') + ' ' + message);
  131. }
  132. /**
  133. * Print warning message
  134. */
  135. function warn(message: string): void {
  136. console.log(chalk.yellow('⚠') + ' ' + message);
  137. }
  138. // =============================================================================
  139. // Commands
  140. // =============================================================================
  141. /**
  142. * codegraph init [path]
  143. */
  144. program
  145. .command('init [path]')
  146. .description('Initialize CodeGraph in a project directory')
  147. .option('-i, --index', 'Run initial indexing after initialization')
  148. .option('--no-hooks', 'Skip git hooks installation')
  149. .action(async (pathArg: string | undefined, options: { index?: boolean; hooks?: boolean }) => {
  150. const projectPath = resolveProjectPath(pathArg);
  151. console.log(chalk.bold('\nInitializing CodeGraph...\n'));
  152. try {
  153. // Check if already initialized
  154. if (CodeGraph.isInitialized(projectPath)) {
  155. warn(`CodeGraph already initialized in ${projectPath}`);
  156. info('Use "codegraph index" to re-index or "codegraph sync" to update');
  157. return;
  158. }
  159. // Initialize
  160. const cg = await CodeGraph.init(projectPath, {
  161. index: false, // We'll handle indexing ourselves for progress
  162. });
  163. success(`Initialized CodeGraph in ${projectPath}`);
  164. info(`Created .codegraph/ directory`);
  165. // Install git hooks if requested (default: true)
  166. if (options.hooks !== false && cg.isGitRepository()) {
  167. const hookResult = cg.installGitHooks();
  168. if (hookResult.success) {
  169. success('Installed git post-commit hook for auto-sync');
  170. } else {
  171. warn(`Could not install git hooks: ${hookResult.message}`);
  172. }
  173. }
  174. // Run initial index if requested
  175. if (options.index) {
  176. console.log('\nIndexing project...\n');
  177. const result = await cg.indexAll({
  178. onProgress: printProgress,
  179. });
  180. // Clear progress line
  181. process.stdout.write('\r' + ' '.repeat(100) + '\r');
  182. if (result.success) {
  183. success(`Indexed ${formatNumber(result.filesIndexed)} files`);
  184. info(`Created ${formatNumber(result.nodesCreated)} nodes and ${formatNumber(result.edgesCreated)} edges`);
  185. info(`Completed in ${formatDuration(result.durationMs)}`);
  186. } else {
  187. warn(`Indexing completed with ${result.errors.length} errors`);
  188. }
  189. } else {
  190. info('Run "codegraph index" to index the project');
  191. }
  192. cg.destroy();
  193. } catch (err) {
  194. error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
  195. process.exit(1);
  196. }
  197. });
  198. /**
  199. * codegraph index [path]
  200. */
  201. program
  202. .command('index [path]')
  203. .description('Index all files in the project')
  204. .option('-f, --force', 'Force full re-index even if already indexed')
  205. .option('-q, --quiet', 'Suppress progress output')
  206. .action(async (pathArg: string | undefined, options: { force?: boolean; quiet?: boolean }) => {
  207. const projectPath = resolveProjectPath(pathArg);
  208. try {
  209. if (!CodeGraph.isInitialized(projectPath)) {
  210. error(`CodeGraph not initialized in ${projectPath}`);
  211. info('Run "codegraph init" first');
  212. process.exit(1);
  213. }
  214. const cg = await CodeGraph.open(projectPath);
  215. if (!options.quiet) {
  216. console.log(chalk.bold('\nIndexing project...\n'));
  217. }
  218. // Clear existing data if force
  219. if (options.force) {
  220. cg.clear();
  221. if (!options.quiet) {
  222. info('Cleared existing index');
  223. }
  224. }
  225. const result = await cg.indexAll({
  226. onProgress: options.quiet ? undefined : printProgress,
  227. });
  228. // Clear progress line
  229. if (!options.quiet) {
  230. process.stdout.write('\r' + ' '.repeat(100) + '\r');
  231. }
  232. if (result.success) {
  233. if (!options.quiet) {
  234. success(`Indexed ${formatNumber(result.filesIndexed)} files`);
  235. info(`Created ${formatNumber(result.nodesCreated)} nodes and ${formatNumber(result.edgesCreated)} edges`);
  236. info(`Completed in ${formatDuration(result.durationMs)}`);
  237. }
  238. } else {
  239. if (!options.quiet) {
  240. warn(`Indexing completed with ${result.errors.length} errors`);
  241. for (const err of result.errors.slice(0, 5)) {
  242. console.log(chalk.dim(` - ${err.message}`));
  243. }
  244. if (result.errors.length > 5) {
  245. console.log(chalk.dim(` ... and ${result.errors.length - 5} more`));
  246. }
  247. }
  248. process.exit(1);
  249. }
  250. cg.destroy();
  251. } catch (err) {
  252. error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
  253. process.exit(1);
  254. }
  255. });
  256. /**
  257. * codegraph sync [path]
  258. */
  259. program
  260. .command('sync [path]')
  261. .description('Sync changes since last index')
  262. .option('-q, --quiet', 'Suppress output (for git hooks)')
  263. .action(async (pathArg: string | undefined, options: { quiet?: boolean }) => {
  264. const projectPath = resolveProjectPath(pathArg);
  265. try {
  266. if (!CodeGraph.isInitialized(projectPath)) {
  267. if (!options.quiet) {
  268. error(`CodeGraph not initialized in ${projectPath}`);
  269. }
  270. process.exit(1);
  271. }
  272. const cg = await CodeGraph.open(projectPath);
  273. const result = await cg.sync({
  274. onProgress: options.quiet ? undefined : printProgress,
  275. });
  276. // Clear progress line
  277. if (!options.quiet) {
  278. process.stdout.write('\r' + ' '.repeat(100) + '\r');
  279. }
  280. const totalChanges = result.filesAdded + result.filesModified + result.filesRemoved;
  281. if (!options.quiet) {
  282. if (totalChanges === 0) {
  283. success('Already up to date');
  284. } else {
  285. success(`Synced ${formatNumber(totalChanges)} changed files`);
  286. if (result.filesAdded > 0) {
  287. info(` Added: ${result.filesAdded}`);
  288. }
  289. if (result.filesModified > 0) {
  290. info(` Modified: ${result.filesModified}`);
  291. }
  292. if (result.filesRemoved > 0) {
  293. info(` Removed: ${result.filesRemoved}`);
  294. }
  295. info(`Updated ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
  296. }
  297. }
  298. cg.destroy();
  299. } catch (err) {
  300. if (!options.quiet) {
  301. error(`Failed to sync: ${err instanceof Error ? err.message : String(err)}`);
  302. }
  303. process.exit(1);
  304. }
  305. });
  306. /**
  307. * codegraph status [path]
  308. */
  309. program
  310. .command('status [path]')
  311. .description('Show index status and statistics')
  312. .action(async (pathArg: string | undefined) => {
  313. const projectPath = resolveProjectPath(pathArg);
  314. try {
  315. if (!CodeGraph.isInitialized(projectPath)) {
  316. console.log(chalk.bold('\nCodeGraph Status\n'));
  317. info(`Project: ${projectPath}`);
  318. warn('Not initialized');
  319. info('Run "codegraph init" to initialize');
  320. return;
  321. }
  322. const cg = await CodeGraph.open(projectPath);
  323. const stats = cg.getStats();
  324. const changes = cg.getChangedFiles();
  325. console.log(chalk.bold('\nCodeGraph Status\n'));
  326. // Project info
  327. console.log(chalk.cyan('Project:'), projectPath);
  328. console.log();
  329. // Index stats
  330. console.log(chalk.bold('Index Statistics:'));
  331. console.log(` Files: ${formatNumber(stats.fileCount)}`);
  332. console.log(` Nodes: ${formatNumber(stats.nodeCount)}`);
  333. console.log(` Edges: ${formatNumber(stats.edgeCount)}`);
  334. console.log(` DB Size: ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`);
  335. console.log();
  336. // Node breakdown
  337. console.log(chalk.bold('Nodes by Kind:'));
  338. const nodesByKind = Object.entries(stats.nodesByKind)
  339. .filter(([, count]) => count > 0)
  340. .sort((a, b) => b[1] - a[1]);
  341. for (const [kind, count] of nodesByKind) {
  342. console.log(` ${kind.padEnd(15)} ${formatNumber(count)}`);
  343. }
  344. console.log();
  345. // Language breakdown
  346. console.log(chalk.bold('Files by Language:'));
  347. const filesByLang = Object.entries(stats.filesByLanguage)
  348. .filter(([, count]) => count > 0)
  349. .sort((a, b) => b[1] - a[1]);
  350. for (const [lang, count] of filesByLang) {
  351. console.log(` ${lang.padEnd(15)} ${formatNumber(count)}`);
  352. }
  353. console.log();
  354. // Pending changes
  355. const totalChanges = changes.added.length + changes.modified.length + changes.removed.length;
  356. if (totalChanges > 0) {
  357. console.log(chalk.bold('Pending Changes:'));
  358. if (changes.added.length > 0) {
  359. console.log(` Added: ${changes.added.length} files`);
  360. }
  361. if (changes.modified.length > 0) {
  362. console.log(` Modified: ${changes.modified.length} files`);
  363. }
  364. if (changes.removed.length > 0) {
  365. console.log(` Removed: ${changes.removed.length} files`);
  366. }
  367. info('Run "codegraph sync" to update the index');
  368. } else {
  369. success('Index is up to date');
  370. }
  371. console.log();
  372. // Git hooks status
  373. if (cg.isGitRepository()) {
  374. const hookInstalled = cg.isGitHookInstalled();
  375. if (hookInstalled) {
  376. success('Git hooks: installed');
  377. } else {
  378. warn('Git hooks: not installed');
  379. info('Run "codegraph hooks install" to enable auto-sync');
  380. }
  381. }
  382. cg.destroy();
  383. } catch (err) {
  384. error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
  385. process.exit(1);
  386. }
  387. });
  388. /**
  389. * codegraph query <search>
  390. */
  391. program
  392. .command('query <search>')
  393. .description('Search for symbols in the codebase')
  394. .option('-p, --path <path>', 'Project path')
  395. .option('-l, --limit <number>', 'Maximum results', '10')
  396. .option('-k, --kind <kind>', 'Filter by node kind (function, class, etc.)')
  397. .option('-j, --json', 'Output as JSON')
  398. .action(async (search: string, options: { path?: string; limit?: string; kind?: string; json?: boolean }) => {
  399. const projectPath = resolveProjectPath(options.path);
  400. try {
  401. if (!CodeGraph.isInitialized(projectPath)) {
  402. error(`CodeGraph not initialized in ${projectPath}`);
  403. process.exit(1);
  404. }
  405. const cg = await CodeGraph.open(projectPath);
  406. const limit = parseInt(options.limit || '10', 10);
  407. const results = cg.searchNodes(search, {
  408. limit,
  409. kinds: options.kind ? [options.kind as any] : undefined,
  410. });
  411. if (options.json) {
  412. console.log(JSON.stringify(results, null, 2));
  413. } else {
  414. if (results.length === 0) {
  415. info(`No results found for "${search}"`);
  416. } else {
  417. console.log(chalk.bold(`\nSearch Results for "${search}":\n`));
  418. for (const result of results) {
  419. const node = result.node;
  420. const location = `${node.filePath}:${node.startLine}`;
  421. const score = chalk.dim(`(${(result.score * 100).toFixed(0)}%)`);
  422. console.log(
  423. chalk.cyan(node.kind.padEnd(12)) +
  424. chalk.white(node.name) +
  425. ' ' + score
  426. );
  427. console.log(chalk.dim(` ${location}`));
  428. if (node.signature) {
  429. console.log(chalk.dim(` ${node.signature}`));
  430. }
  431. console.log();
  432. }
  433. }
  434. }
  435. cg.destroy();
  436. } catch (err) {
  437. error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
  438. process.exit(1);
  439. }
  440. });
  441. /**
  442. * codegraph context <task>
  443. */
  444. program
  445. .command('context <task>')
  446. .description('Build context for a task (outputs markdown)')
  447. .option('-p, --path <path>', 'Project path')
  448. .option('-n, --max-nodes <number>', 'Maximum nodes to include', '50')
  449. .option('-c, --max-code <number>', 'Maximum code blocks', '10')
  450. .option('--no-code', 'Exclude code blocks')
  451. .option('-f, --format <format>', 'Output format (markdown, json)', 'markdown')
  452. .action(async (task: string, options: {
  453. path?: string;
  454. maxNodes?: string;
  455. maxCode?: string;
  456. code?: boolean;
  457. format?: string;
  458. }) => {
  459. const projectPath = resolveProjectPath(options.path);
  460. try {
  461. if (!CodeGraph.isInitialized(projectPath)) {
  462. error(`CodeGraph not initialized in ${projectPath}`);
  463. process.exit(1);
  464. }
  465. const cg = await CodeGraph.open(projectPath);
  466. const context = await cg.buildContext(task, {
  467. maxNodes: parseInt(options.maxNodes || '50', 10),
  468. maxCodeBlocks: parseInt(options.maxCode || '10', 10),
  469. includeCode: options.code !== false,
  470. format: options.format as 'markdown' | 'json',
  471. });
  472. // Output the context
  473. console.log(context);
  474. cg.destroy();
  475. } catch (err) {
  476. error(`Failed to build context: ${err instanceof Error ? err.message : String(err)}`);
  477. process.exit(1);
  478. }
  479. });
  480. /**
  481. * codegraph hooks <action>
  482. */
  483. const hooksCommand = program
  484. .command('hooks')
  485. .description('Manage git hooks');
  486. hooksCommand
  487. .command('install')
  488. .description('Install git post-commit hook for auto-sync')
  489. .option('-p, --path <path>', 'Project path')
  490. .action(async (options: { path?: string }) => {
  491. const projectPath = resolveProjectPath(options.path);
  492. try {
  493. if (!CodeGraph.isInitialized(projectPath)) {
  494. error(`CodeGraph not initialized in ${projectPath}`);
  495. process.exit(1);
  496. }
  497. const cg = await CodeGraph.open(projectPath);
  498. if (!cg.isGitRepository()) {
  499. error('Not a git repository');
  500. cg.destroy();
  501. process.exit(1);
  502. }
  503. const result = cg.installGitHooks();
  504. if (result.success) {
  505. success(result.message);
  506. if (result.previousHookBackedUp) {
  507. info('Previous hook backed up to post-commit.codegraph-backup');
  508. }
  509. } else {
  510. error(result.message);
  511. process.exit(1);
  512. }
  513. cg.destroy();
  514. } catch (err) {
  515. error(`Failed to install hooks: ${err instanceof Error ? err.message : String(err)}`);
  516. process.exit(1);
  517. }
  518. });
  519. hooksCommand
  520. .command('remove')
  521. .description('Remove git post-commit hook')
  522. .option('-p, --path <path>', 'Project path')
  523. .action(async (options: { path?: string }) => {
  524. const projectPath = resolveProjectPath(options.path);
  525. try {
  526. if (!CodeGraph.isInitialized(projectPath)) {
  527. error(`CodeGraph not initialized in ${projectPath}`);
  528. process.exit(1);
  529. }
  530. const cg = await CodeGraph.open(projectPath);
  531. if (!cg.isGitRepository()) {
  532. error('Not a git repository');
  533. cg.destroy();
  534. process.exit(1);
  535. }
  536. const result = cg.removeGitHooks();
  537. if (result.success) {
  538. success(result.message);
  539. if (result.restoredFromBackup) {
  540. info('Restored previous hook from backup');
  541. }
  542. } else {
  543. error(result.message);
  544. process.exit(1);
  545. }
  546. cg.destroy();
  547. } catch (err) {
  548. error(`Failed to remove hooks: ${err instanceof Error ? err.message : String(err)}`);
  549. process.exit(1);
  550. }
  551. });
  552. hooksCommand
  553. .command('status')
  554. .description('Check git hooks status')
  555. .option('-p, --path <path>', 'Project path')
  556. .action(async (options: { path?: string }) => {
  557. const projectPath = resolveProjectPath(options.path);
  558. try {
  559. if (!CodeGraph.isInitialized(projectPath)) {
  560. error(`CodeGraph not initialized in ${projectPath}`);
  561. process.exit(1);
  562. }
  563. const cg = await CodeGraph.open(projectPath);
  564. if (!cg.isGitRepository()) {
  565. info('Not a git repository');
  566. cg.destroy();
  567. return;
  568. }
  569. if (cg.isGitHookInstalled()) {
  570. success('Git hook is installed');
  571. } else {
  572. warn('Git hook is not installed');
  573. info('Run "codegraph hooks install" to enable auto-sync');
  574. }
  575. cg.destroy();
  576. } catch (err) {
  577. error(`Failed to check hooks: ${err instanceof Error ? err.message : String(err)}`);
  578. process.exit(1);
  579. }
  580. });
  581. /**
  582. * codegraph serve
  583. */
  584. program
  585. .command('serve')
  586. .description('Start CodeGraph as an MCP server for AI assistants')
  587. .option('-p, --path <path>', 'Project path (optional for MCP mode, uses rootUri from client)')
  588. .option('--mcp', 'Run as MCP server (stdio transport)')
  589. .action(async (options: { path?: string; mcp?: boolean }) => {
  590. const projectPath = options.path ? resolveProjectPath(options.path) : undefined;
  591. try {
  592. if (options.mcp) {
  593. // Start MCP server - it handles initialization lazily based on rootUri from client
  594. const { MCPServer } = await import('../mcp/index');
  595. const server = new MCPServer(projectPath);
  596. await server.start();
  597. // Server will run until terminated
  598. } else {
  599. // Default: show info about MCP mode
  600. console.log(chalk.bold('\nCodeGraph MCP Server\n'));
  601. info('Use --mcp flag to start the MCP server');
  602. console.log('\nTo use with Claude Code, add to your MCP configuration:');
  603. console.log(chalk.dim(`
  604. {
  605. "mcpServers": {
  606. "codegraph": {
  607. "command": "codegraph",
  608. "args": ["serve", "--mcp"]
  609. }
  610. }
  611. }
  612. `));
  613. console.log('Available tools:');
  614. console.log(chalk.cyan(' codegraph_search') + ' - Search for code symbols');
  615. console.log(chalk.cyan(' codegraph_context') + ' - Build context for a task');
  616. console.log(chalk.cyan(' codegraph_callers') + ' - Find callers of a symbol');
  617. console.log(chalk.cyan(' codegraph_callees') + ' - Find what a symbol calls');
  618. console.log(chalk.cyan(' codegraph_impact') + ' - Analyze impact of changes');
  619. console.log(chalk.cyan(' codegraph_node') + ' - Get symbol details');
  620. console.log(chalk.cyan(' codegraph_status') + ' - Get index status');
  621. }
  622. } catch (err) {
  623. error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
  624. process.exit(1);
  625. }
  626. });
  627. // Parse and run
  628. program.parse();