@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,279 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFile, writeFile, chmod, unlink, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
/**
|
|
6
|
+
* Marker comment used to identify CodeRAG hook sections.
|
|
7
|
+
* Used for both install and uninstall operations.
|
|
8
|
+
*/
|
|
9
|
+
const HOOK_MARKER_START = '# --- CodeRAG auto-index (start) ---';
|
|
10
|
+
const HOOK_MARKER_END = '# --- CodeRAG auto-index (end) ---';
|
|
11
|
+
/**
|
|
12
|
+
* The hook script fragment that triggers background incremental indexing.
|
|
13
|
+
* Spawns `coderag index --quiet` in the background so git operations
|
|
14
|
+
* are not blocked (<100ms hook execution time).
|
|
15
|
+
*/
|
|
16
|
+
const HOOK_SCRIPT = `${HOOK_MARKER_START}
|
|
17
|
+
# Trigger background incremental re-indexing (non-blocking).
|
|
18
|
+
if command -v coderag >/dev/null 2>&1; then
|
|
19
|
+
nohup coderag index --quiet >/dev/null 2>&1 &
|
|
20
|
+
fi
|
|
21
|
+
${HOOK_MARKER_END}`;
|
|
22
|
+
/** Hook types that CodeRAG installs. */
|
|
23
|
+
const HOOK_NAMES = ['post-commit', 'post-merge', 'post-checkout'];
|
|
24
|
+
/**
|
|
25
|
+
* Determine the .git/hooks directory path for the current project.
|
|
26
|
+
*/
|
|
27
|
+
function getHooksDir(rootDir) {
|
|
28
|
+
return join(rootDir, '.git', 'hooks');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check whether a hook file already contains the CodeRAG marker.
|
|
32
|
+
*/
|
|
33
|
+
function hookHasCoderagSection(content) {
|
|
34
|
+
return content.includes(HOOK_MARKER_START) && content.includes(HOOK_MARKER_END);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Remove the CodeRAG section from a hook file's content.
|
|
38
|
+
* Returns the cleaned content, or null if the file becomes empty/shebang-only.
|
|
39
|
+
*/
|
|
40
|
+
function removeCoderagSection(content) {
|
|
41
|
+
const startIdx = content.indexOf(HOOK_MARKER_START);
|
|
42
|
+
const endIdx = content.indexOf(HOOK_MARKER_END);
|
|
43
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
const before = content.slice(0, startIdx);
|
|
47
|
+
const after = content.slice(endIdx + HOOK_MARKER_END.length);
|
|
48
|
+
const cleaned = (before + after).trim();
|
|
49
|
+
// If only a shebang remains (or nothing), the file is effectively empty
|
|
50
|
+
if (cleaned === '' || cleaned === '#!/bin/sh' || cleaned === '#!/bin/bash') {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return cleaned + '\n';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Install the CodeRAG hook fragment into a single hook file.
|
|
57
|
+
* If the hook file already exists, append the fragment (don't overwrite).
|
|
58
|
+
* If it already contains the CodeRAG section, skip it.
|
|
59
|
+
*/
|
|
60
|
+
async function installHook(hooksDir, hookName) {
|
|
61
|
+
const hookPath = join(hooksDir, hookName);
|
|
62
|
+
let existingContent = '';
|
|
63
|
+
try {
|
|
64
|
+
existingContent = await readFile(hookPath, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// File doesn't exist — we'll create it
|
|
68
|
+
}
|
|
69
|
+
if (existingContent && hookHasCoderagSection(existingContent)) {
|
|
70
|
+
return 'already';
|
|
71
|
+
}
|
|
72
|
+
let newContent;
|
|
73
|
+
if (existingContent) {
|
|
74
|
+
// Append to existing hook
|
|
75
|
+
const trimmed = existingContent.trimEnd();
|
|
76
|
+
newContent = `${trimmed}\n\n${HOOK_SCRIPT}\n`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Create new hook file with shebang
|
|
80
|
+
newContent = `#!/bin/sh\n\n${HOOK_SCRIPT}\n`;
|
|
81
|
+
}
|
|
82
|
+
await writeFile(hookPath, newContent, 'utf-8');
|
|
83
|
+
await chmod(hookPath, 0o755);
|
|
84
|
+
return existingContent ? 'appended' : 'installed';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Uninstall the CodeRAG hook fragment from a single hook file.
|
|
88
|
+
* If the hook file only contains the CodeRAG section, delete the file.
|
|
89
|
+
* If it contains other content, remove only the CodeRAG section.
|
|
90
|
+
*/
|
|
91
|
+
async function uninstallHook(hooksDir, hookName) {
|
|
92
|
+
const hookPath = join(hooksDir, hookName);
|
|
93
|
+
let existingContent;
|
|
94
|
+
try {
|
|
95
|
+
existingContent = await readFile(hookPath, 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return 'not_found';
|
|
99
|
+
}
|
|
100
|
+
if (!hookHasCoderagSection(existingContent)) {
|
|
101
|
+
return 'not_found';
|
|
102
|
+
}
|
|
103
|
+
const cleaned = removeCoderagSection(existingContent);
|
|
104
|
+
if (cleaned === null) {
|
|
105
|
+
// File is now empty — delete it
|
|
106
|
+
await unlink(hookPath);
|
|
107
|
+
return 'deleted';
|
|
108
|
+
}
|
|
109
|
+
await writeFile(hookPath, cleaned, 'utf-8');
|
|
110
|
+
return 'removed';
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Register the `coderag hooks` CLI command group.
|
|
114
|
+
*
|
|
115
|
+
* Subcommands:
|
|
116
|
+
* - `coderag hooks install` — install post-commit/post-merge/post-checkout hooks
|
|
117
|
+
* - `coderag hooks uninstall` — remove CodeRAG hooks
|
|
118
|
+
* - `coderag hooks status` — check which hooks are installed
|
|
119
|
+
*/
|
|
120
|
+
export function registerHooksCommand(program) {
|
|
121
|
+
const hooks = program
|
|
122
|
+
.command('hooks')
|
|
123
|
+
.description('Manage git hooks for automatic re-indexing');
|
|
124
|
+
hooks
|
|
125
|
+
.command('install')
|
|
126
|
+
.description('Install git hooks for automatic re-indexing on commit, merge, and checkout')
|
|
127
|
+
.action(async () => {
|
|
128
|
+
const rootDir = process.cwd();
|
|
129
|
+
const hooksDir = getHooksDir(rootDir);
|
|
130
|
+
// Verify .git directory exists
|
|
131
|
+
if (!existsSync(join(rootDir, '.git'))) {
|
|
132
|
+
// eslint-disable-next-line no-console
|
|
133
|
+
console.error(chalk.red('Not a git repository. Run this command from a git repo root.'));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Ensure hooks directory exists
|
|
137
|
+
if (!existsSync(hooksDir)) {
|
|
138
|
+
await mkdir(hooksDir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.log(chalk.bold('Installing CodeRAG git hooks...'));
|
|
142
|
+
// eslint-disable-next-line no-console
|
|
143
|
+
console.log('');
|
|
144
|
+
for (const hookName of HOOK_NAMES) {
|
|
145
|
+
const result = await installHook(hooksDir, hookName);
|
|
146
|
+
switch (result) {
|
|
147
|
+
case 'installed':
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.log(` ${chalk.green('+')} ${hookName} — ${chalk.green('installed')}`);
|
|
150
|
+
break;
|
|
151
|
+
case 'appended':
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.log(` ${chalk.green('+')} ${hookName} — ${chalk.cyan('appended to existing hook')}`);
|
|
154
|
+
break;
|
|
155
|
+
case 'already':
|
|
156
|
+
// eslint-disable-next-line no-console
|
|
157
|
+
console.log(` ${chalk.gray('=')} ${hookName} — ${chalk.gray('already installed')}`);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// eslint-disable-next-line no-console
|
|
162
|
+
console.log('');
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.log(chalk.green('Git hooks installed. Incremental indexing will run automatically on:'));
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.log(' - post-commit (after each commit)');
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.log(' - post-merge (after merge/pull)');
|
|
169
|
+
// eslint-disable-next-line no-console
|
|
170
|
+
console.log(' - post-checkout (after branch switch)');
|
|
171
|
+
});
|
|
172
|
+
hooks
|
|
173
|
+
.command('uninstall')
|
|
174
|
+
.description('Remove CodeRAG git hooks')
|
|
175
|
+
.action(async () => {
|
|
176
|
+
const rootDir = process.cwd();
|
|
177
|
+
const hooksDir = getHooksDir(rootDir);
|
|
178
|
+
if (!existsSync(join(rootDir, '.git'))) {
|
|
179
|
+
// eslint-disable-next-line no-console
|
|
180
|
+
console.error(chalk.red('Not a git repository. Run this command from a git repo root.'));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
// eslint-disable-next-line no-console
|
|
184
|
+
console.log(chalk.bold('Uninstalling CodeRAG git hooks...'));
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log('');
|
|
187
|
+
for (const hookName of HOOK_NAMES) {
|
|
188
|
+
const result = await uninstallHook(hooksDir, hookName);
|
|
189
|
+
switch (result) {
|
|
190
|
+
case 'removed':
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.log(` ${chalk.red('-')} ${hookName} — ${chalk.yellow('CodeRAG section removed')}`);
|
|
193
|
+
break;
|
|
194
|
+
case 'deleted':
|
|
195
|
+
// eslint-disable-next-line no-console
|
|
196
|
+
console.log(` ${chalk.red('-')} ${hookName} — ${chalk.red('hook file deleted (was CodeRAG-only)')}`);
|
|
197
|
+
break;
|
|
198
|
+
case 'not_found':
|
|
199
|
+
// eslint-disable-next-line no-console
|
|
200
|
+
console.log(` ${chalk.gray('=')} ${hookName} — ${chalk.gray('not installed')}`);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
205
|
+
console.log('');
|
|
206
|
+
// eslint-disable-next-line no-console
|
|
207
|
+
console.log(chalk.green('CodeRAG git hooks removed.'));
|
|
208
|
+
});
|
|
209
|
+
hooks
|
|
210
|
+
.command('status')
|
|
211
|
+
.description('Check which CodeRAG git hooks are installed')
|
|
212
|
+
.action(async () => {
|
|
213
|
+
const rootDir = process.cwd();
|
|
214
|
+
const hooksDir = getHooksDir(rootDir);
|
|
215
|
+
if (!existsSync(join(rootDir, '.git'))) {
|
|
216
|
+
// eslint-disable-next-line no-console
|
|
217
|
+
console.error(chalk.red('Not a git repository. Run this command from a git repo root.'));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
// eslint-disable-next-line no-console
|
|
221
|
+
console.log(chalk.bold('CodeRAG Git Hooks Status'));
|
|
222
|
+
// eslint-disable-next-line no-console
|
|
223
|
+
console.log('');
|
|
224
|
+
let installed = 0;
|
|
225
|
+
for (const hookName of HOOK_NAMES) {
|
|
226
|
+
const hookPath = join(hooksDir, hookName);
|
|
227
|
+
let status = 'not installed';
|
|
228
|
+
try {
|
|
229
|
+
const content = await readFile(hookPath, 'utf-8');
|
|
230
|
+
if (hookHasCoderagSection(content)) {
|
|
231
|
+
status = 'installed';
|
|
232
|
+
installed++;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
status = 'hook exists (no CodeRAG section)';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// File doesn't exist
|
|
240
|
+
}
|
|
241
|
+
const icon = status === 'installed' ? chalk.green('*') : chalk.gray('-');
|
|
242
|
+
const label = status === 'installed'
|
|
243
|
+
? chalk.green(status)
|
|
244
|
+
: status === 'not installed'
|
|
245
|
+
? chalk.gray(status)
|
|
246
|
+
: chalk.yellow(status);
|
|
247
|
+
// eslint-disable-next-line no-console
|
|
248
|
+
console.log(` ${icon} ${hookName}: ${label}`);
|
|
249
|
+
}
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
251
|
+
console.log('');
|
|
252
|
+
if (installed === HOOK_NAMES.length) {
|
|
253
|
+
// eslint-disable-next-line no-console
|
|
254
|
+
console.log(chalk.green('All hooks installed.'));
|
|
255
|
+
}
|
|
256
|
+
else if (installed > 0) {
|
|
257
|
+
// eslint-disable-next-line no-console
|
|
258
|
+
console.log(chalk.yellow(`${installed}/${HOOK_NAMES.length} hooks installed.`));
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
262
|
+
console.log(chalk.gray('No CodeRAG hooks installed. Run "coderag hooks install" to install.'));
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Exported for testing.
|
|
268
|
+
*/
|
|
269
|
+
export const _testing = {
|
|
270
|
+
HOOK_MARKER_START,
|
|
271
|
+
HOOK_MARKER_END,
|
|
272
|
+
HOOK_SCRIPT,
|
|
273
|
+
HOOK_NAMES,
|
|
274
|
+
hookHasCoderagSection,
|
|
275
|
+
removeCoderagSection,
|
|
276
|
+
installHook,
|
|
277
|
+
uninstallHook,
|
|
278
|
+
getHooksDir,
|
|
279
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { type EmbeddingConfig, type EmbeddingProvider } from '@code-rag/core';
|
|
3
|
+
export declare function createSimpleEmbeddingProvider(embeddingConfig: EmbeddingConfig): EmbeddingProvider;
|
|
4
|
+
export declare function registerIndexCommand(program: Command): void;
|