@code-rag/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/dist/cli.test.d.ts +1 -0
  4. package/dist/cli.test.js +369 -0
  5. package/dist/cli.test.js.map +1 -0
  6. package/dist/commands/hooks-cmd.d.ts +53 -0
  7. package/dist/commands/hooks-cmd.js +279 -0
  8. package/dist/commands/index-cmd.d.ts +4 -0
  9. package/dist/commands/index-cmd.js +1037 -0
  10. package/dist/commands/index-cmd.js.map +1 -0
  11. package/dist/commands/index-cmd.test.d.ts +1 -0
  12. package/dist/commands/index-cmd.test.js +74 -0
  13. package/dist/commands/index-cmd.test.js.map +1 -0
  14. package/dist/commands/init-wizard.d.ts +95 -0
  15. package/dist/commands/init-wizard.js +526 -0
  16. package/dist/commands/init.d.ts +7 -0
  17. package/dist/commands/init.js +125 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/search.d.ts +7 -0
  20. package/dist/commands/search.js +124 -0
  21. package/dist/commands/search.js.map +1 -0
  22. package/dist/commands/serve.d.ts +2 -0
  23. package/dist/commands/serve.js +56 -0
  24. package/dist/commands/serve.js.map +1 -0
  25. package/dist/commands/status.d.ts +21 -0
  26. package/dist/commands/status.js +117 -0
  27. package/dist/commands/status.js.map +1 -0
  28. package/dist/commands/viewer.d.ts +20 -0
  29. package/dist/commands/viewer.js +197 -0
  30. package/dist/commands/viewer.js.map +1 -0
  31. package/dist/commands/viewer.test.d.ts +1 -0
  32. package/dist/commands/viewer.test.js +69 -0
  33. package/dist/commands/viewer.test.js.map +1 -0
  34. package/dist/commands/watch-cmd.d.ts +8 -0
  35. package/dist/commands/watch-cmd.js +152 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +24 -0
  38. package/dist/index.js.map +1 -0
  39. package/package.json +66 -0
@@ -0,0 +1,124 @@
1
+ import chalk from 'chalk';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join, resolve, sep } from 'node:path';
4
+ import { loadConfig, OllamaEmbeddingProvider, LanceDBStore, BM25Index, HybridSearch, } from '@code-rag/core';
5
+ /**
6
+ * Format a single search result for terminal display.
7
+ */
8
+ export function formatSearchResult(result, index) {
9
+ const lines = [];
10
+ const rank = chalk.dim(`[${index + 1}]`);
11
+ const score = chalk.green(result.score.toFixed(4));
12
+ const filePath = chalk.cyan(result.chunk?.filePath ?? result.metadata.name ?? 'unknown');
13
+ const chunkType = chalk.magenta(result.metadata.chunkType);
14
+ let lineRange = '';
15
+ if (result.chunk && result.chunk.startLine > 0) {
16
+ lineRange = chalk.dim(` L${result.chunk.startLine}-${result.chunk.endLine}`);
17
+ }
18
+ lines.push(`${rank} ${filePath}${lineRange} ${chunkType} score: ${score}`);
19
+ if (result.nlSummary) {
20
+ lines.push(` ${chalk.dim(result.nlSummary)}`);
21
+ }
22
+ return lines.join('\n');
23
+ }
24
+ export function registerSearchCommand(program) {
25
+ program
26
+ .command('search')
27
+ .description('Search the indexed codebase')
28
+ .argument('<query>', 'Search query')
29
+ .option('--language <lang>', 'Filter by programming language')
30
+ .option('--type <chunkType>', 'Filter by chunk type (function, class, method, etc.)')
31
+ .option('--file <path>', 'Filter by file path substring')
32
+ .option('--top-k <n>', 'Maximum number of results', '10')
33
+ .action(async (query, options) => {
34
+ try {
35
+ const rootDir = process.cwd();
36
+ const topK = parseInt(options.topK, 10);
37
+ if (isNaN(topK) || topK < 1) {
38
+ // eslint-disable-next-line no-console
39
+ console.error(chalk.red('Invalid --top-k value. Must be a positive integer.'));
40
+ process.exit(1);
41
+ }
42
+ // Load config
43
+ const configResult = await loadConfig(rootDir);
44
+ if (configResult.isErr()) {
45
+ // eslint-disable-next-line no-console
46
+ console.error(chalk.red('Config not found.'), 'Run "coderag init" first.');
47
+ process.exit(1);
48
+ }
49
+ const config = configResult.value;
50
+ const storagePath = resolve(rootDir, config.storage.path);
51
+ // Prevent path traversal outside project root
52
+ if (!storagePath.startsWith(resolve(rootDir) + sep) && storagePath !== resolve(rootDir)) {
53
+ // eslint-disable-next-line no-console
54
+ console.error(chalk.red('Storage path escapes project root'));
55
+ process.exit(1);
56
+ }
57
+ // Create services
58
+ const embeddingProvider = new OllamaEmbeddingProvider({
59
+ model: config.embedding.model,
60
+ dimensions: config.embedding.dimensions,
61
+ });
62
+ const store = new LanceDBStore(storagePath, config.embedding.dimensions);
63
+ await store.connect();
64
+ // Load BM25 index
65
+ let bm25 = new BM25Index();
66
+ const bm25Path = join(storagePath, 'bm25-index.json');
67
+ try {
68
+ const bm25Data = await readFile(bm25Path, 'utf-8');
69
+ bm25 = BM25Index.deserialize(bm25Data);
70
+ }
71
+ catch {
72
+ // No BM25 index yet
73
+ }
74
+ // Create hybrid search
75
+ const hybridSearch = new HybridSearch(store, bm25, embeddingProvider, config.search);
76
+ // Run search
77
+ const searchResult = await hybridSearch.search(query, { topK });
78
+ if (searchResult.isErr()) {
79
+ store.close();
80
+ // eslint-disable-next-line no-console
81
+ console.error(chalk.red('Search failed:'), searchResult.error.message);
82
+ process.exit(1);
83
+ }
84
+ let results = searchResult.value;
85
+ // Apply filters
86
+ if (options.language) {
87
+ const lang = options.language.toLowerCase();
88
+ results = results.filter((r) => r.chunk?.language?.toLowerCase() === lang);
89
+ }
90
+ if (options.type) {
91
+ const chunkType = options.type.toLowerCase();
92
+ results = results.filter((r) => r.metadata.chunkType.toLowerCase() === chunkType);
93
+ }
94
+ if (options.file) {
95
+ const fileFilter = options.file.toLowerCase();
96
+ results = results.filter((r) => (r.chunk?.filePath ?? '').toLowerCase().includes(fileFilter));
97
+ }
98
+ store.close();
99
+ // Display results
100
+ if (results.length === 0) {
101
+ // eslint-disable-next-line no-console
102
+ console.log(chalk.yellow('No results found.'));
103
+ return;
104
+ }
105
+ // eslint-disable-next-line no-console
106
+ console.log(chalk.bold(`Found ${results.length} result(s) for "${query}":\n`));
107
+ for (let i = 0; i < results.length; i++) {
108
+ const result = results[i];
109
+ // eslint-disable-next-line no-console
110
+ console.log(formatSearchResult(result, i));
111
+ if (i < results.length - 1) {
112
+ // eslint-disable-next-line no-console
113
+ console.log('');
114
+ }
115
+ }
116
+ }
117
+ catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ // eslint-disable-next-line no-console
120
+ console.error(chalk.red('Search failed:'), message);
121
+ process.exit(1);
122
+ }
123
+ });
124
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerServeCommand(program: Command): void;
@@ -0,0 +1,56 @@
1
+ import chalk from 'chalk';
2
+ import { CodeRAGServer, NO_INDEX_MESSAGE } from '@code-rag/mcp-server';
3
+ export function registerServeCommand(program) {
4
+ program
5
+ .command('serve')
6
+ .description('Start the CodeRAG MCP server')
7
+ .option('--port <port>', 'Port for SSE transport')
8
+ .action(async (options) => {
9
+ try {
10
+ const rootDir = process.cwd();
11
+ const server = new CodeRAGServer({ rootDir });
12
+ // Guard: check if index exists before starting the server
13
+ const indexCheck = await server.checkIndex();
14
+ if (indexCheck !== null && !indexCheck.exists) {
15
+ // eslint-disable-next-line no-console
16
+ console.error(chalk.yellow(NO_INDEX_MESSAGE));
17
+ process.exit(1);
18
+ }
19
+ await server.initialize();
20
+ // Graceful shutdown
21
+ const shutdown = () => {
22
+ // eslint-disable-next-line no-console
23
+ console.error(chalk.blue('[coderag]'), 'Shutting down...');
24
+ server.close().finally(() => process.exit(0));
25
+ };
26
+ process.on('SIGINT', shutdown);
27
+ process.on('SIGTERM', shutdown);
28
+ if (options.port) {
29
+ const port = parseInt(options.port, 10);
30
+ if (isNaN(port) || port < 1 || port > 65535) {
31
+ // eslint-disable-next-line no-console
32
+ console.error(chalk.red('[coderag] Invalid port number'));
33
+ process.exit(1);
34
+ }
35
+ // eslint-disable-next-line no-console
36
+ console.error(chalk.blue('[coderag]'), `Starting MCP server (SSE transport on port ${port})...`);
37
+ await server.connectSSE(port);
38
+ // eslint-disable-next-line no-console
39
+ console.error(chalk.green('[coderag]'), `MCP server running on http://localhost:${port}/sse`);
40
+ }
41
+ else {
42
+ // eslint-disable-next-line no-console
43
+ console.error(chalk.blue('[coderag]'), 'Starting MCP server (stdio transport)...');
44
+ await server.connectStdio();
45
+ // eslint-disable-next-line no-console
46
+ console.error(chalk.green('[coderag]'), 'MCP server running on stdio');
47
+ }
48
+ }
49
+ catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ // eslint-disable-next-line no-console
52
+ console.error(chalk.red('[coderag] Server failed:'), message);
53
+ process.exit(1);
54
+ }
55
+ });
56
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,8BAA8B,CAAC;SAC3C,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC;SACjD,MAAM,CAAC,KAAK,EAAE,OAA0B,EAAE,EAAE;QAC3C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAE9C,0DAA0D;YAC1D,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YAC7C,IAAI,UAAU,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;gBAC9C,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;gBAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YAE1B,oBAAoB;YACpB,MAAM,QAAQ,GAAG,GAAS,EAAE;gBAC1B,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC,CAAC;gBAC3D,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,CAAC,CAAC;YAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAEhC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACxC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;oBAC5C,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;oBAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,8CAA8C,IAAI,MAAM,CAAC,CAAC;gBACjG,MAAM,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC9B,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,0CAA0C,IAAI,MAAM,CAAC,CAAC;YAChG,CAAC;iBAAM,CAAC;gBACN,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,0CAA0C,CAAC,CAAC;gBACnF,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;gBAC5B,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,6BAA6B,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,OAAO,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Status information about the CodeRAG index.
4
+ */
5
+ export interface StatusInfo {
6
+ totalChunks: number;
7
+ model: string;
8
+ dimensions: number;
9
+ languages: string[] | 'auto';
10
+ storagePath: string;
11
+ health: 'ok' | 'degraded' | 'not_initialized';
12
+ }
13
+ /**
14
+ * Format status info for human-readable terminal output.
15
+ */
16
+ export declare function formatStatus(status: StatusInfo): string;
17
+ /**
18
+ * Format status info as JSON.
19
+ */
20
+ export declare function formatStatusJSON(status: StatusInfo): string;
21
+ export declare function registerStatusCommand(program: Command): void;
@@ -0,0 +1,117 @@
1
+ import chalk from 'chalk';
2
+ import { resolve, sep } from 'node:path';
3
+ import { loadConfig, LanceDBStore, } from '@code-rag/core';
4
+ /**
5
+ * Format status info for human-readable terminal output.
6
+ */
7
+ export function formatStatus(status) {
8
+ const lines = [];
9
+ lines.push(chalk.bold('CodeRAG Status'));
10
+ lines.push('');
11
+ const healthColor = status.health === 'ok'
12
+ ? chalk.green
13
+ : status.health === 'degraded'
14
+ ? chalk.yellow
15
+ : chalk.red;
16
+ lines.push(` Health: ${healthColor(status.health)}`);
17
+ lines.push(` Total chunks: ${chalk.cyan(String(status.totalChunks))}`);
18
+ lines.push(` Model: ${chalk.cyan(status.model)}`);
19
+ lines.push(` Dimensions: ${chalk.cyan(String(status.dimensions))}`);
20
+ const langDisplay = status.languages === 'auto'
21
+ ? 'auto'
22
+ : status.languages.join(', ');
23
+ lines.push(` Languages: ${chalk.cyan(langDisplay)}`);
24
+ lines.push(` Storage: ${chalk.dim(status.storagePath)}`);
25
+ return lines.join('\n');
26
+ }
27
+ /**
28
+ * Format status info as JSON.
29
+ */
30
+ export function formatStatusJSON(status) {
31
+ return JSON.stringify(status, null, 2);
32
+ }
33
+ export function registerStatusCommand(program) {
34
+ program
35
+ .command('status')
36
+ .description('Show the current CodeRAG index status')
37
+ .option('--json', 'Output in JSON format')
38
+ .action(async (options) => {
39
+ try {
40
+ const rootDir = process.cwd();
41
+ // Load config
42
+ const configResult = await loadConfig(rootDir);
43
+ if (configResult.isErr()) {
44
+ const status = {
45
+ totalChunks: 0,
46
+ model: 'unknown',
47
+ dimensions: 0,
48
+ languages: 'auto',
49
+ storagePath: '',
50
+ health: 'not_initialized',
51
+ };
52
+ if (options.json) {
53
+ // eslint-disable-next-line no-console
54
+ console.log(formatStatusJSON(status));
55
+ }
56
+ else {
57
+ // eslint-disable-next-line no-console
58
+ console.log(formatStatus(status));
59
+ // eslint-disable-next-line no-console
60
+ console.log('');
61
+ // eslint-disable-next-line no-console
62
+ console.log(chalk.yellow('Run "coderag init" to initialize the project.'));
63
+ }
64
+ return;
65
+ }
66
+ const config = configResult.value;
67
+ const storagePath = resolve(rootDir, config.storage.path);
68
+ // Prevent path traversal outside project root
69
+ if (!storagePath.startsWith(resolve(rootDir) + sep) && storagePath !== resolve(rootDir)) {
70
+ // eslint-disable-next-line no-console
71
+ console.error(chalk.red('Storage path escapes project root'));
72
+ process.exit(1);
73
+ }
74
+ // Connect to LanceDB to get chunk count
75
+ let totalChunks = 0;
76
+ let health = 'not_initialized';
77
+ try {
78
+ const store = new LanceDBStore(storagePath, config.embedding.dimensions);
79
+ await store.connect();
80
+ const countResult = await store.count();
81
+ if (countResult.isOk()) {
82
+ totalChunks = countResult.value;
83
+ health = totalChunks > 0 ? 'ok' : 'degraded';
84
+ }
85
+ else {
86
+ health = 'degraded';
87
+ }
88
+ store.close();
89
+ }
90
+ catch {
91
+ health = 'degraded';
92
+ }
93
+ const status = {
94
+ totalChunks,
95
+ model: config.embedding.model,
96
+ dimensions: config.embedding.dimensions,
97
+ languages: config.project.languages,
98
+ storagePath,
99
+ health,
100
+ };
101
+ if (options.json) {
102
+ // eslint-disable-next-line no-console
103
+ console.log(formatStatusJSON(status));
104
+ }
105
+ else {
106
+ // eslint-disable-next-line no-console
107
+ console.log(formatStatus(status));
108
+ }
109
+ }
110
+ catch (error) {
111
+ const message = error instanceof Error ? error.message : String(error);
112
+ // eslint-disable-next-line no-console
113
+ console.error(chalk.red('Status check failed:'), message);
114
+ process.exit(1);
115
+ }
116
+ });
117
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { Command } from 'commander';
2
+ export interface ViewerOptions {
3
+ readonly port: number;
4
+ readonly open: boolean;
5
+ }
6
+ /**
7
+ * Resolve the viewer dist directory, trying multiple known locations.
8
+ * Returns the absolute path to the dist directory if found, or null.
9
+ */
10
+ export declare function resolveViewerDist(): string | null;
11
+ /**
12
+ * Start the CodeRAG Viewer web interface.
13
+ *
14
+ * Creates an HTTP server that:
15
+ * - Serves static SPA files from the viewer dist directory
16
+ * - Proxies /api/* requests to the CodeRAG API server
17
+ * - Falls back to index.html for SPA client-side routing
18
+ */
19
+ export declare function viewerCommand(options: ViewerOptions): Promise<void>;
20
+ export declare function registerViewerCommand(program: Command): void;
@@ -0,0 +1,197 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import { resolve, dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createServer } from 'node:http';
6
+ import { readFile } from 'node:fs/promises';
7
+ const MIME_TYPES = {
8
+ '.html': 'text/html; charset=utf-8',
9
+ '.js': 'application/javascript; charset=utf-8',
10
+ '.css': 'text/css; charset=utf-8',
11
+ '.json': 'application/json; charset=utf-8',
12
+ '.svg': 'image/svg+xml',
13
+ '.png': 'image/png',
14
+ '.ico': 'image/x-icon',
15
+ '.woff': 'font/woff',
16
+ '.woff2': 'font/woff2',
17
+ '.map': 'application/json',
18
+ };
19
+ /**
20
+ * Resolve the viewer dist directory, trying multiple known locations.
21
+ * Returns the absolute path to the dist directory if found, or null.
22
+ */
23
+ export function resolveViewerDist() {
24
+ const currentDir = dirname(fileURLToPath(import.meta.url));
25
+ const candidates = [
26
+ // Relative from compiled CLI dist → viewer dist
27
+ resolve(currentDir, '..', '..', '..', 'viewer', 'dist'),
28
+ // Relative from source → viewer dist
29
+ resolve(currentDir, '..', '..', 'viewer', 'dist'),
30
+ // Monorepo root → viewer dist
31
+ resolve(currentDir, '..', '..', '..', '..', 'packages', 'viewer', 'dist'),
32
+ ];
33
+ for (const candidate of candidates) {
34
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
35
+ return candidate;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ /**
41
+ * Serve static files from the viewer dist directory.
42
+ */
43
+ async function serveStatic(distPath, req, res) {
44
+ const url = req.url ?? '/';
45
+ const pathname = new URL(url, 'http://localhost').pathname;
46
+ // Map URL path to file path
47
+ let filePath;
48
+ if (pathname === '/' || pathname === '') {
49
+ filePath = join(distPath, 'index.html');
50
+ }
51
+ else {
52
+ filePath = join(distPath, pathname);
53
+ }
54
+ // Security: prevent path traversal
55
+ const normalizedPath = resolve(filePath);
56
+ if (!normalizedPath.startsWith(resolve(distPath))) {
57
+ return false;
58
+ }
59
+ // Check file exists
60
+ if (!existsSync(normalizedPath) || !statSync(normalizedPath).isFile()) {
61
+ return false;
62
+ }
63
+ try {
64
+ const content = await readFile(normalizedPath);
65
+ const ext = normalizedPath.slice(normalizedPath.lastIndexOf('.'));
66
+ const mimeType = MIME_TYPES[ext] ?? 'application/octet-stream';
67
+ res.writeHead(200, {
68
+ 'Content-Type': mimeType,
69
+ 'Content-Length': content.length,
70
+ 'Cache-Control': ext === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable',
71
+ });
72
+ res.end(content);
73
+ return true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ /**
80
+ * Start the CodeRAG Viewer web interface.
81
+ *
82
+ * Creates an HTTP server that:
83
+ * - Serves static SPA files from the viewer dist directory
84
+ * - Proxies /api/* requests to the CodeRAG API server
85
+ * - Falls back to index.html for SPA client-side routing
86
+ */
87
+ export async function viewerCommand(options) {
88
+ const distPath = resolveViewerDist();
89
+ if (!distPath) {
90
+ // eslint-disable-next-line no-console
91
+ console.error(chalk.red('[coderag]'), 'Viewer not built. Run', chalk.cyan('pnpm --filter @code-rag/viewer build'), 'first.');
92
+ process.exit(1);
93
+ }
94
+ const { port } = options;
95
+ let apiHandler = null;
96
+ try {
97
+ const { ApiServer } = await import('@code-rag/api-server');
98
+ const rootDir = process.cwd();
99
+ const apiServer = new ApiServer({ rootDir, port: port + 1 });
100
+ await apiServer.initialize();
101
+ // Express app is a callable (req, res) => void handler by design
102
+ const app = apiServer.getApp();
103
+ if (typeof app === 'function') {
104
+ apiHandler = app;
105
+ }
106
+ // eslint-disable-next-line no-console
107
+ console.error(chalk.blue('[coderag]'), 'API server initialized');
108
+ }
109
+ catch {
110
+ // eslint-disable-next-line no-console
111
+ console.error(chalk.yellow('[coderag]'), 'API server not available. Viewer will serve static files only.');
112
+ }
113
+ const server = createServer(async (req, res) => {
114
+ const url = req.url ?? '/';
115
+ // Route /api/* to the API server if available
116
+ if (url.startsWith('/api/') || url === '/health') {
117
+ if (apiHandler) {
118
+ apiHandler(req, res);
119
+ return;
120
+ }
121
+ res.writeHead(503, { 'Content-Type': 'application/json' });
122
+ res.end(JSON.stringify({ error: 'API server not available' }));
123
+ return;
124
+ }
125
+ // Try to serve static file
126
+ const served = await serveStatic(distPath, req, res);
127
+ if (served)
128
+ return;
129
+ // SPA fallback: serve index.html for client-side routing
130
+ const indexPath = join(distPath, 'index.html');
131
+ if (existsSync(indexPath)) {
132
+ try {
133
+ const content = await readFile(indexPath);
134
+ res.writeHead(200, {
135
+ 'Content-Type': 'text/html; charset=utf-8',
136
+ 'Content-Length': content.length,
137
+ 'Cache-Control': 'no-cache',
138
+ });
139
+ res.end(content);
140
+ return;
141
+ }
142
+ catch {
143
+ // Fall through to 404
144
+ }
145
+ }
146
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
147
+ res.end('Not Found');
148
+ });
149
+ // Graceful shutdown
150
+ const shutdown = () => {
151
+ // eslint-disable-next-line no-console
152
+ console.error(chalk.blue('\n[coderag]'), 'Shutting down viewer...');
153
+ server.close(() => {
154
+ process.exit(0);
155
+ });
156
+ // Force exit after 5s if graceful close hangs
157
+ setTimeout(() => process.exit(0), 5000).unref();
158
+ };
159
+ process.on('SIGINT', shutdown);
160
+ process.on('SIGTERM', shutdown);
161
+ // Start listening
162
+ await new Promise((resolvePromise, reject) => {
163
+ server.on('error', reject);
164
+ server.listen(port, () => {
165
+ resolvePromise();
166
+ });
167
+ });
168
+ const url = `http://localhost:${port}`;
169
+ // eslint-disable-next-line no-console
170
+ console.error(chalk.green('[coderag]'), `Viewer running at ${chalk.cyan(url)}`);
171
+ // Open browser if requested
172
+ if (options.open) {
173
+ try {
174
+ const { exec } = await import('node:child_process');
175
+ const openCmd = process.platform === 'darwin' ? 'open' :
176
+ process.platform === 'win32' ? 'start' :
177
+ 'xdg-open';
178
+ exec(`${openCmd} ${url}`);
179
+ }
180
+ catch {
181
+ // Silently ignore if browser cannot be opened
182
+ }
183
+ }
184
+ }
185
+ export function registerViewerCommand(program) {
186
+ program
187
+ .command('viewer')
188
+ .description('Launch the CodeRAG Viewer web interface')
189
+ .option('-p, --port <port>', 'Port number', '3333')
190
+ .option('--no-open', 'Do not open browser automatically')
191
+ .action(async (opts) => {
192
+ await viewerCommand({
193
+ port: parseInt(opts.port, 10),
194
+ open: opts.open !== false,
195
+ });
196
+ });
197
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../../src/commands/viewer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerViewerCommand, resolveViewerDist } from './viewer.js';
4
+ describe('registerViewerCommand', () => {
5
+ let program;
6
+ beforeEach(() => {
7
+ program = new Command();
8
+ program.name('coderag').version('0.1.0');
9
+ registerViewerCommand(program);
10
+ });
11
+ it('should register the viewer command', () => {
12
+ const commandNames = program.commands.map((cmd) => cmd.name());
13
+ expect(commandNames).toContain('viewer');
14
+ });
15
+ it('should have --port option with default 3333', () => {
16
+ const viewerCmd = program.commands.find((c) => c.name() === 'viewer');
17
+ expect(viewerCmd).toBeDefined();
18
+ const portOpt = viewerCmd.options.find((o) => o.long === '--port');
19
+ expect(portOpt).toBeDefined();
20
+ expect(portOpt.defaultValue).toBe('3333');
21
+ });
22
+ it('should have --no-open option', () => {
23
+ const viewerCmd = program.commands.find((c) => c.name() === 'viewer');
24
+ expect(viewerCmd).toBeDefined();
25
+ const opts = viewerCmd.options.map((o) => o.long);
26
+ expect(opts).toContain('--no-open');
27
+ });
28
+ it('should have a description', () => {
29
+ const viewerCmd = program.commands.find((c) => c.name() === 'viewer');
30
+ expect(viewerCmd).toBeDefined();
31
+ expect(viewerCmd.description()).toBeTruthy();
32
+ expect(viewerCmd.description()).toContain('Viewer');
33
+ });
34
+ });
35
+ describe('resolveViewerDist', () => {
36
+ it('should return null when no dist directory exists', () => {
37
+ // resolveViewerDist looks relative to the compiled file location,
38
+ // which won't find a viewer/dist in test context
39
+ const result = resolveViewerDist();
40
+ // May or may not be null depending on if viewer is built,
41
+ // but the function should not throw
42
+ expect(result === null || typeof result === 'string').toBe(true);
43
+ });
44
+ });
45
+ describe('CLI integration with viewer command', () => {
46
+ let program;
47
+ beforeEach(() => {
48
+ program = new Command();
49
+ program.name('coderag').version('0.1.0');
50
+ program.exitOverride();
51
+ registerViewerCommand(program);
52
+ });
53
+ it('should parse custom port', () => {
54
+ const viewerCmd = program.commands.find((c) => c.name() === 'viewer');
55
+ expect(viewerCmd).toBeDefined();
56
+ // Verify the port option short flag is -p
57
+ const portOpt = viewerCmd.options.find((o) => o.long === '--port');
58
+ expect(portOpt?.short).toBe('-p');
59
+ });
60
+ it('should register all expected commands including viewer', () => {
61
+ // Simulate what index.ts does
62
+ const fullProgram = new Command();
63
+ fullProgram.name('coderag').version('0.1.0');
64
+ registerViewerCommand(fullProgram);
65
+ expect(fullProgram.commands).toHaveLength(1);
66
+ expect(fullProgram.commands[0].name()).toBe('viewer');
67
+ });
68
+ });
69
+ //# sourceMappingURL=viewer.test.js.map