@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.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +369 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/commands/hooks-cmd.d.ts +53 -0
- package/dist/commands/hooks-cmd.js +279 -0
- package/dist/commands/index-cmd.d.ts +4 -0
- package/dist/commands/index-cmd.js +1037 -0
- package/dist/commands/index-cmd.js.map +1 -0
- package/dist/commands/index-cmd.test.d.ts +1 -0
- package/dist/commands/index-cmd.test.js +74 -0
- package/dist/commands/index-cmd.test.js.map +1 -0
- package/dist/commands/init-wizard.d.ts +95 -0
- package/dist/commands/init-wizard.js +526 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +124 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +56 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.d.ts +21 -0
- package/dist/commands/status.js +117 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/viewer.d.ts +20 -0
- package/dist/commands/viewer.js +197 -0
- package/dist/commands/viewer.js.map +1 -0
- package/dist/commands/viewer.test.d.ts +1 -0
- package/dist/commands/viewer.test.js +69 -0
- package/dist/commands/viewer.test.js.map +1 -0
- package/dist/commands/watch-cmd.d.ts +8 -0
- package/dist/commands/watch-cmd.js +152 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- 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;
|