@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,526 @@
1
+ import chalk from 'chalk';
2
+ import { select, input, confirm } from '@inquirer/prompts';
3
+ import { stringify } from 'yaml';
4
+ import { writeFile, mkdir, access, readdir } from 'node:fs/promises';
5
+ import { join, basename } from 'node:path';
6
+ import { detectLanguages } from './init.js';
7
+ // --- Constants ---
8
+ const EMBEDDING_PROVIDERS = new Map([
9
+ [
10
+ 'ollama',
11
+ {
12
+ model: 'nomic-embed-text',
13
+ dimensions: 768,
14
+ description: 'Local, free, private. Requires Ollama running on your machine.',
15
+ },
16
+ ],
17
+ [
18
+ 'voyage',
19
+ {
20
+ model: 'voyage-code-3',
21
+ dimensions: 1024,
22
+ description: 'Best for code. Cloud API, requires API key. ~$0.06/1M tokens.',
23
+ },
24
+ ],
25
+ [
26
+ 'openai',
27
+ {
28
+ model: 'text-embedding-3-small',
29
+ dimensions: 1536,
30
+ description: 'General purpose. Cloud API, requires API key. ~$0.02/1M tokens.',
31
+ },
32
+ ],
33
+ ]);
34
+ const MONOREPO_INDICATORS = [
35
+ { file: 'pnpm-workspace.yaml', tool: 'pnpm workspaces' },
36
+ { file: 'lerna.json', tool: 'Lerna' },
37
+ { file: 'nx.json', tool: 'Nx' },
38
+ ];
39
+ // --- Auto-detection ---
40
+ /**
41
+ * Detect monorepo structure by checking for common config files
42
+ * and a packages/ directory.
43
+ */
44
+ export async function detectMonorepo(rootDir) {
45
+ for (const indicator of MONOREPO_INDICATORS) {
46
+ try {
47
+ await access(join(rootDir, indicator.file));
48
+ let packagesDir = false;
49
+ try {
50
+ await access(join(rootDir, 'packages'));
51
+ packagesDir = true;
52
+ }
53
+ catch {
54
+ // packages dir not found
55
+ }
56
+ return { detected: true, tool: indicator.tool, packagesDir };
57
+ }
58
+ catch {
59
+ // File not found, try next
60
+ }
61
+ }
62
+ // Check for packages/ directory even without a monorepo config
63
+ try {
64
+ await access(join(rootDir, 'packages'));
65
+ return { detected: true, tool: 'unknown', packagesDir: true };
66
+ }
67
+ catch {
68
+ return { detected: false, tool: '', packagesDir: false };
69
+ }
70
+ }
71
+ /**
72
+ * Check if Ollama is running and what models are available.
73
+ */
74
+ export async function checkOllamaStatus(host) {
75
+ const baseUrl = host ?? process.env['OLLAMA_HOST'] ?? 'http://localhost:11434';
76
+ try {
77
+ const response = await globalThis.fetch(`${baseUrl}/api/tags`, {
78
+ signal: AbortSignal.timeout(3000),
79
+ });
80
+ if (!response.ok) {
81
+ return { running: false, models: [], hasNomicEmbed: false };
82
+ }
83
+ const data = (await response.json());
84
+ const models = (data.models ?? []).map((m) => m.name);
85
+ const hasNomicEmbed = models.some((m) => m.startsWith('nomic-embed-text'));
86
+ return { running: true, models, hasNomicEmbed };
87
+ }
88
+ catch {
89
+ return { running: false, models: [], hasNomicEmbed: false };
90
+ }
91
+ }
92
+ /**
93
+ * Validate an API key by making a test embedding call.
94
+ * Returns true if the call succeeds, false otherwise.
95
+ */
96
+ export async function validateApiKey(provider, apiKey) {
97
+ const testText = 'Hello, world!';
98
+ try {
99
+ if (provider === 'openai') {
100
+ const response = await globalThis.fetch('https://api.openai.com/v1/embeddings', {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ Authorization: `Bearer ${apiKey}`,
105
+ },
106
+ body: JSON.stringify({
107
+ model: 'text-embedding-3-small',
108
+ input: testText,
109
+ }),
110
+ signal: AbortSignal.timeout(10000),
111
+ });
112
+ if (!response.ok) {
113
+ const text = await response.text();
114
+ return { valid: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}` };
115
+ }
116
+ return { valid: true };
117
+ }
118
+ // Voyage AI
119
+ const response = await globalThis.fetch('https://api.voyageai.com/v1/embeddings', {
120
+ method: 'POST',
121
+ headers: {
122
+ 'Content-Type': 'application/json',
123
+ Authorization: `Bearer ${apiKey}`,
124
+ },
125
+ body: JSON.stringify({
126
+ model: 'voyage-code-3',
127
+ input: [testText],
128
+ }),
129
+ signal: AbortSignal.timeout(10000),
130
+ });
131
+ if (!response.ok) {
132
+ const text = await response.text();
133
+ return { valid: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}` };
134
+ }
135
+ return { valid: true };
136
+ }
137
+ catch (error) {
138
+ const message = error instanceof Error ? error.message : 'Unknown error';
139
+ return { valid: false, error: message };
140
+ }
141
+ }
142
+ /**
143
+ * Count files by extension in the root directory (non-recursive, fast scan).
144
+ * Used for the language detection summary display.
145
+ */
146
+ export async function countFilesByLanguage(rootDir) {
147
+ const counts = new Map();
148
+ const EXTENSION_TO_LANGUAGE = new Map([
149
+ ['.ts', 'typescript'],
150
+ ['.tsx', 'typescript'],
151
+ ['.js', 'javascript'],
152
+ ['.jsx', 'javascript'],
153
+ ['.py', 'python'],
154
+ ['.go', 'go'],
155
+ ['.rs', 'rust'],
156
+ ['.java', 'java'],
157
+ ['.cs', 'c_sharp'],
158
+ ['.c', 'c'],
159
+ ['.cpp', 'cpp'],
160
+ ['.rb', 'ruby'],
161
+ ['.php', 'php'],
162
+ ]);
163
+ const SKIP_DIRS = new Set([
164
+ 'node_modules', '.git', '.coderag', 'dist', 'build',
165
+ 'coverage', '.next', '__pycache__', '.venv', 'venv',
166
+ 'target', 'vendor',
167
+ ]);
168
+ async function walk(dir, depth) {
169
+ if (depth > 5)
170
+ return; // Limit depth for speed
171
+ let entries;
172
+ try {
173
+ entries = await readdir(dir, { withFileTypes: true });
174
+ }
175
+ catch {
176
+ return;
177
+ }
178
+ for (const entry of entries) {
179
+ if (entry.isDirectory()) {
180
+ if (!SKIP_DIRS.has(entry.name)) {
181
+ await walk(join(dir, entry.name), depth + 1);
182
+ }
183
+ }
184
+ else if (entry.isFile()) {
185
+ const ext = entry.name.slice(entry.name.lastIndexOf('.'));
186
+ const lang = EXTENSION_TO_LANGUAGE.get(ext);
187
+ if (lang !== undefined) {
188
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ await walk(rootDir, 0);
194
+ return counts;
195
+ }
196
+ // --- Config Builder ---
197
+ /**
198
+ * Build a .coderag.yaml config object from wizard answers.
199
+ */
200
+ export function buildWizardConfig(answers) {
201
+ const providerInfo = EMBEDDING_PROVIDERS.get(answers.embeddingProvider);
202
+ const embeddingModel = providerInfo?.model ?? 'nomic-embed-text';
203
+ const dimensions = providerInfo?.dimensions ?? 768;
204
+ const config = {
205
+ version: '1',
206
+ project: {
207
+ name: answers.projectName,
208
+ languages: answers.languages.length > 0 ? answers.languages : 'auto',
209
+ },
210
+ ingestion: {
211
+ maxTokensPerChunk: 512,
212
+ exclude: ['node_modules', 'dist', '.git', 'coverage'],
213
+ },
214
+ embedding: {
215
+ provider: answers.embeddingProvider,
216
+ model: embeddingModel,
217
+ dimensions,
218
+ },
219
+ llm: {
220
+ provider: 'ollama',
221
+ model: 'qwen2.5-coder:7b',
222
+ },
223
+ search: {
224
+ topK: 10,
225
+ vectorWeight: 0.7,
226
+ bm25Weight: 0.3,
227
+ },
228
+ storage: {
229
+ path: '.coderag',
230
+ },
231
+ };
232
+ if (answers.isMonorepo) {
233
+ config.repos = [];
234
+ }
235
+ return config;
236
+ }
237
+ /**
238
+ * Generate the YAML content from a config, with optional multi-repo comments.
239
+ */
240
+ export function generateYamlContent(config) {
241
+ let yaml = stringify(config);
242
+ if (config.repos !== undefined) {
243
+ yaml += [
244
+ '# repos:',
245
+ '# - path: /absolute/path/to/repo-a',
246
+ '# name: repo-a',
247
+ '# languages:',
248
+ '# - typescript',
249
+ '# exclude:',
250
+ '# - dist',
251
+ '# - path: /absolute/path/to/repo-b',
252
+ '',
253
+ ].join('\n');
254
+ }
255
+ return yaml;
256
+ }
257
+ // --- Interactive Wizard ---
258
+ /**
259
+ * Run the interactive configuration wizard.
260
+ * This is the main entry point called by the init command.
261
+ */
262
+ export async function runWizard(rootDir) {
263
+ // eslint-disable-next-line no-console
264
+ console.log(chalk.bold.blue('\n CodeRAG Configuration Wizard\n'));
265
+ // Step 1: Project name
266
+ const dirName = basename(rootDir);
267
+ const projectName = await input({
268
+ message: 'Project name:',
269
+ default: dirName,
270
+ });
271
+ // Step 2: Detect languages
272
+ // eslint-disable-next-line no-console
273
+ console.log(chalk.dim('\nScanning project for languages...'));
274
+ const languages = await detectLanguages(rootDir);
275
+ const fileCounts = await countFilesByLanguage(rootDir);
276
+ if (languages.length > 0) {
277
+ // eslint-disable-next-line no-console
278
+ console.log(chalk.green(' Detected languages:'));
279
+ for (const lang of languages) {
280
+ const count = fileCounts.get(lang) ?? 0;
281
+ // eslint-disable-next-line no-console
282
+ console.log(chalk.dim(` - ${lang} (${count} files)`));
283
+ }
284
+ }
285
+ else {
286
+ // eslint-disable-next-line no-console
287
+ console.log(chalk.yellow(' No programming languages detected. Using "auto" detection.'));
288
+ }
289
+ // Step 3: Detect monorepo
290
+ const monorepo = await detectMonorepo(rootDir);
291
+ let isMonorepo = false;
292
+ if (monorepo.detected) {
293
+ // eslint-disable-next-line no-console
294
+ console.log(chalk.green(`\n Monorepo detected: ${monorepo.tool}`));
295
+ isMonorepo = await confirm({
296
+ message: 'Enable multi-repo configuration?',
297
+ default: true,
298
+ });
299
+ }
300
+ // Step 4: Choose embedding provider
301
+ // eslint-disable-next-line no-console
302
+ console.log('');
303
+ const embeddingProvider = await select({
304
+ message: 'Embedding provider:',
305
+ choices: [
306
+ {
307
+ name: `Ollama (local) - ${chalk.dim('Free, private, requires Ollama')}`,
308
+ value: 'ollama',
309
+ },
310
+ {
311
+ name: `Voyage AI - ${chalk.dim('Best for code, ~$0.06/1M tokens')}`,
312
+ value: 'voyage',
313
+ },
314
+ {
315
+ name: `OpenAI - ${chalk.dim('General purpose, ~$0.02/1M tokens')}`,
316
+ value: 'openai',
317
+ },
318
+ ],
319
+ default: 'ollama',
320
+ });
321
+ let apiKey;
322
+ // Step 5a: If Ollama, check availability
323
+ if (embeddingProvider === 'ollama') {
324
+ // eslint-disable-next-line no-console
325
+ console.log(chalk.dim('\nChecking Ollama status...'));
326
+ const ollamaStatus = await checkOllamaStatus();
327
+ if (ollamaStatus.running) {
328
+ // eslint-disable-next-line no-console
329
+ console.log(chalk.green(' Ollama is running'));
330
+ if (ollamaStatus.hasNomicEmbed) {
331
+ // eslint-disable-next-line no-console
332
+ console.log(chalk.green(' nomic-embed-text model is available'));
333
+ }
334
+ else {
335
+ // eslint-disable-next-line no-console
336
+ console.log(chalk.yellow(' nomic-embed-text model not found'));
337
+ const shouldPull = await confirm({
338
+ message: 'Pull nomic-embed-text model now?',
339
+ default: true,
340
+ });
341
+ if (shouldPull) {
342
+ // eslint-disable-next-line no-console
343
+ console.log(chalk.dim(' Pulling model (this may take a few minutes)...'));
344
+ try {
345
+ await pullOllamaModel('nomic-embed-text');
346
+ // eslint-disable-next-line no-console
347
+ console.log(chalk.green(' Model pulled successfully'));
348
+ }
349
+ catch {
350
+ // eslint-disable-next-line no-console
351
+ console.log(chalk.yellow(' Failed to pull model. You can pull it later with: ollama pull nomic-embed-text'));
352
+ }
353
+ }
354
+ }
355
+ }
356
+ else {
357
+ // eslint-disable-next-line no-console
358
+ console.log(chalk.yellow(' Ollama is not running'));
359
+ // eslint-disable-next-line no-console
360
+ console.log(chalk.dim(' Start Ollama and run "ollama pull nomic-embed-text" before indexing.'));
361
+ }
362
+ }
363
+ // Step 5b: If API provider, get API key
364
+ if (embeddingProvider === 'voyage' || embeddingProvider === 'openai') {
365
+ const envVarName = embeddingProvider === 'voyage' ? 'VOYAGE_API_KEY' : 'OPENAI_API_KEY';
366
+ const existingKey = process.env[envVarName];
367
+ if (existingKey) {
368
+ // eslint-disable-next-line no-console
369
+ console.log(chalk.green(`\n Found ${envVarName} in environment`));
370
+ // eslint-disable-next-line no-console
371
+ console.log(chalk.dim(' Validating key...'));
372
+ const validation = await validateApiKey(embeddingProvider, existingKey);
373
+ if (validation.valid) {
374
+ // eslint-disable-next-line no-console
375
+ console.log(chalk.green(' API key is valid'));
376
+ apiKey = existingKey;
377
+ }
378
+ else {
379
+ // eslint-disable-next-line no-console
380
+ console.log(chalk.yellow(` API key validation failed: ${validation.error ?? 'unknown error'}`));
381
+ }
382
+ }
383
+ if (!apiKey) {
384
+ apiKey = await input({
385
+ message: `${envVarName}:`,
386
+ validate: (val) => (val.length > 0 ? true : 'API key is required'),
387
+ });
388
+ // eslint-disable-next-line no-console
389
+ console.log(chalk.dim(' Validating key...'));
390
+ const validation = await validateApiKey(embeddingProvider, apiKey);
391
+ if (validation.valid) {
392
+ // eslint-disable-next-line no-console
393
+ console.log(chalk.green(' API key is valid'));
394
+ }
395
+ else {
396
+ // eslint-disable-next-line no-console
397
+ console.log(chalk.yellow(` API key validation failed: ${validation.error ?? 'unknown error'}`));
398
+ // eslint-disable-next-line no-console
399
+ console.log(chalk.dim(' Continuing anyway. You can update the key later.'));
400
+ }
401
+ }
402
+ }
403
+ // Step 6: Build and write config
404
+ const answers = {
405
+ embeddingProvider,
406
+ apiKey,
407
+ projectName,
408
+ languages,
409
+ isMonorepo,
410
+ };
411
+ const config = buildWizardConfig(answers);
412
+ const yamlContent = generateYamlContent(config);
413
+ const configPath = join(rootDir, '.coderag.yaml');
414
+ await writeFile(configPath, yamlContent, 'utf-8');
415
+ // eslint-disable-next-line no-console
416
+ console.log(chalk.green(`\n Created ${configPath}`));
417
+ // Step 7: Create storage directory
418
+ const storageDir = join(rootDir, '.coderag');
419
+ await mkdir(storageDir, { recursive: true });
420
+ // eslint-disable-next-line no-console
421
+ console.log(chalk.green(` Created ${storageDir}`));
422
+ // Step 8: Summary
423
+ // eslint-disable-next-line no-console
424
+ console.log(chalk.bold.green('\n CodeRAG initialized successfully!\n'));
425
+ // eslint-disable-next-line no-console
426
+ console.log(chalk.dim(' Next steps:'));
427
+ // eslint-disable-next-line no-console
428
+ console.log(chalk.dim(' 1. Review .coderag.yaml and adjust settings'));
429
+ // eslint-disable-next-line no-console
430
+ console.log(chalk.dim(' 2. Run "coderag index" to index your codebase'));
431
+ // eslint-disable-next-line no-console
432
+ console.log(chalk.dim(' 3. Run "coderag search <query>" to search\n'));
433
+ }
434
+ /**
435
+ * Run init with sensible defaults (non-interactive mode).
436
+ * Used when --yes or --default flag is passed.
437
+ */
438
+ export async function runNonInteractive(rootDir, options) {
439
+ const dirName = basename(rootDir);
440
+ // Detect languages
441
+ let languages;
442
+ if (options.languages) {
443
+ languages = options.languages.split(',').map((l) => l.trim()).filter((l) => l.length > 0);
444
+ // eslint-disable-next-line no-console
445
+ console.log(chalk.blue('Using specified languages:'), languages.join(', '));
446
+ }
447
+ else {
448
+ // eslint-disable-next-line no-console
449
+ console.log(chalk.blue('Scanning for project languages...'));
450
+ languages = await detectLanguages(rootDir);
451
+ if (languages.length > 0) {
452
+ // eslint-disable-next-line no-console
453
+ console.log(chalk.green('Detected languages:'), languages.join(', '));
454
+ }
455
+ else {
456
+ // eslint-disable-next-line no-console
457
+ console.log(chalk.yellow('No languages detected, using "auto"'));
458
+ }
459
+ }
460
+ // Detect monorepo
461
+ const monorepo = await detectMonorepo(rootDir);
462
+ const isMonorepo = options.multi ?? monorepo.detected;
463
+ if (monorepo.detected) {
464
+ // eslint-disable-next-line no-console
465
+ console.log(chalk.green('Monorepo detected:'), monorepo.tool);
466
+ }
467
+ // Check Ollama
468
+ const ollamaStatus = await checkOllamaStatus();
469
+ if (ollamaStatus.running) {
470
+ // eslint-disable-next-line no-console
471
+ console.log(chalk.green('\u2714'), 'Ollama is running');
472
+ if (ollamaStatus.hasNomicEmbed) {
473
+ // eslint-disable-next-line no-console
474
+ console.log(chalk.green('\u2714'), 'nomic-embed-text available');
475
+ }
476
+ else {
477
+ // eslint-disable-next-line no-console
478
+ console.log(chalk.yellow('\u26A0'), 'nomic-embed-text not found. Run: ollama pull nomic-embed-text');
479
+ }
480
+ }
481
+ else {
482
+ // eslint-disable-next-line no-console
483
+ console.log(chalk.yellow('\u26A0'), 'Ollama is not running');
484
+ }
485
+ // Build config with defaults (Ollama)
486
+ const answers = {
487
+ embeddingProvider: 'ollama',
488
+ projectName: dirName,
489
+ languages,
490
+ isMonorepo,
491
+ };
492
+ const config = buildWizardConfig(answers);
493
+ const yamlContent = generateYamlContent(config);
494
+ // Write config
495
+ const configPath = join(rootDir, '.coderag.yaml');
496
+ await writeFile(configPath, yamlContent, 'utf-8');
497
+ // eslint-disable-next-line no-console
498
+ console.log(chalk.green('Created'), configPath);
499
+ // Create storage directory
500
+ const storageDir = join(rootDir, '.coderag');
501
+ await mkdir(storageDir, { recursive: true });
502
+ // eslint-disable-next-line no-console
503
+ console.log(chalk.green('Created'), storageDir);
504
+ // Done
505
+ // eslint-disable-next-line no-console
506
+ console.log(chalk.green('\nCodeRAG initialized successfully!'));
507
+ // eslint-disable-next-line no-console
508
+ console.log(chalk.dim('Run "coderag index" to index your codebase.'));
509
+ }
510
+ /**
511
+ * Pull an Ollama model via the API.
512
+ */
513
+ async function pullOllamaModel(modelName) {
514
+ const host = process.env['OLLAMA_HOST'] ?? 'http://localhost:11434';
515
+ const response = await globalThis.fetch(`${host}/api/pull`, {
516
+ method: 'POST',
517
+ headers: { 'Content-Type': 'application/json' },
518
+ body: JSON.stringify({ name: modelName, stream: false }),
519
+ signal: AbortSignal.timeout(300_000), // 5 minutes for model pull
520
+ });
521
+ if (!response.ok) {
522
+ throw new Error(`Ollama pull failed: HTTP ${response.status}`);
523
+ }
524
+ // Consume the response body
525
+ await response.text();
526
+ }
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Recursively scan a directory to detect programming languages
4
+ * based on file extensions.
5
+ */
6
+ export declare function detectLanguages(rootDir: string): Promise<string[]>;
7
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,125 @@
1
+ import chalk from 'chalk';
2
+ import { readdir, access } from 'node:fs/promises';
3
+ import { join, extname } from 'node:path';
4
+ import { runWizard, runNonInteractive } from './init-wizard.js';
5
+ /**
6
+ * Maps file extensions to language names for auto-detection.
7
+ */
8
+ const EXTENSION_TO_LANGUAGE = new Map([
9
+ ['.ts', 'typescript'],
10
+ ['.tsx', 'typescript'],
11
+ ['.mts', 'typescript'],
12
+ ['.cts', 'typescript'],
13
+ ['.js', 'javascript'],
14
+ ['.jsx', 'javascript'],
15
+ ['.mjs', 'javascript'],
16
+ ['.cjs', 'javascript'],
17
+ ['.py', 'python'],
18
+ ['.pyw', 'python'],
19
+ ['.go', 'go'],
20
+ ['.rs', 'rust'],
21
+ ['.java', 'java'],
22
+ ['.cs', 'c_sharp'],
23
+ ['.c', 'c'],
24
+ ['.h', 'c'],
25
+ ['.cpp', 'cpp'],
26
+ ['.cc', 'cpp'],
27
+ ['.cxx', 'cpp'],
28
+ ['.hpp', 'cpp'],
29
+ ['.hxx', 'cpp'],
30
+ ['.rb', 'ruby'],
31
+ ['.php', 'php'],
32
+ ]);
33
+ /**
34
+ * Directories to skip during language detection scan.
35
+ */
36
+ const SKIP_DIRS = new Set([
37
+ 'node_modules',
38
+ '.git',
39
+ '.coderag',
40
+ 'dist',
41
+ 'build',
42
+ 'coverage',
43
+ '.next',
44
+ '__pycache__',
45
+ '.venv',
46
+ 'venv',
47
+ 'target',
48
+ 'vendor',
49
+ ]);
50
+ /**
51
+ * Recursively scan a directory to detect programming languages
52
+ * based on file extensions.
53
+ */
54
+ export async function detectLanguages(rootDir) {
55
+ const found = new Set();
56
+ async function walk(dir) {
57
+ let entries;
58
+ try {
59
+ entries = await readdir(dir, { withFileTypes: true });
60
+ }
61
+ catch {
62
+ return;
63
+ }
64
+ for (const entry of entries) {
65
+ if (entry.isDirectory()) {
66
+ if (!SKIP_DIRS.has(entry.name)) {
67
+ await walk(join(dir, entry.name));
68
+ }
69
+ }
70
+ else if (entry.isFile()) {
71
+ const ext = extname(entry.name);
72
+ const lang = EXTENSION_TO_LANGUAGE.get(ext);
73
+ if (lang !== undefined) {
74
+ found.add(lang);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ await walk(rootDir);
80
+ return [...found].sort();
81
+ }
82
+ export function registerInitCommand(program) {
83
+ program
84
+ .command('init')
85
+ .description('Initialize a new CodeRAG project in the current directory')
86
+ .option('--languages <langs>', 'Comma-separated list of languages (overrides auto-detection)')
87
+ .option('--force', 'Overwrite existing configuration file')
88
+ .option('--multi', 'Generate multi-repo configuration with repos array')
89
+ .option('--yes', 'Non-interactive mode with sensible defaults')
90
+ .option('--default', 'Non-interactive mode with sensible defaults (alias for --yes)')
91
+ .action(async (options) => {
92
+ try {
93
+ const rootDir = process.cwd();
94
+ // Check if config already exists
95
+ const configPath = join(rootDir, '.coderag.yaml');
96
+ if (!options.force) {
97
+ try {
98
+ await access(configPath);
99
+ // eslint-disable-next-line no-console
100
+ console.error(chalk.red('.coderag.yaml already exists.'), 'Use --force to overwrite.');
101
+ process.exit(1);
102
+ }
103
+ catch {
104
+ // File doesn't exist, proceed
105
+ }
106
+ }
107
+ const nonInteractive = options.yes === true || options.default === true;
108
+ if (nonInteractive) {
109
+ await runNonInteractive(rootDir, {
110
+ languages: options.languages,
111
+ multi: options.multi,
112
+ });
113
+ }
114
+ else {
115
+ await runWizard(rootDir);
116
+ }
117
+ }
118
+ catch (error) {
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ // eslint-disable-next-line no-console
121
+ console.error(chalk.red('Init failed:'), message);
122
+ process.exit(1);
123
+ }
124
+ });
125
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ import { type SearchResult } from '@code-rag/core';
3
+ /**
4
+ * Format a single search result for terminal display.
5
+ */
6
+ export declare function formatSearchResult(result: SearchResult, index: number): string;
7
+ export declare function registerSearchCommand(program: Command): void;