@unlimiting/qsc 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/dist/chunker/ast.d.ts +7 -0
- package/dist/chunker/ast.d.ts.map +1 -0
- package/dist/chunker/ast.js +302 -0
- package/dist/chunker/ast.js.map +1 -0
- package/dist/chunker/index.d.ts +15 -0
- package/dist/chunker/index.d.ts.map +1 -0
- package/dist/chunker/index.js +26 -0
- package/dist/chunker/index.js.map +1 -0
- package/dist/chunker/languages/dart.d.ts +3 -0
- package/dist/chunker/languages/dart.d.ts.map +1 -0
- package/dist/chunker/languages/dart.js +22 -0
- package/dist/chunker/languages/dart.js.map +1 -0
- package/dist/chunker/languages/go.d.ts +3 -0
- package/dist/chunker/languages/go.d.ts.map +1 -0
- package/dist/chunker/languages/go.js +20 -0
- package/dist/chunker/languages/go.js.map +1 -0
- package/dist/chunker/languages/index.d.ts +12 -0
- package/dist/chunker/languages/index.d.ts.map +1 -0
- package/dist/chunker/languages/index.js +35 -0
- package/dist/chunker/languages/index.js.map +1 -0
- package/dist/chunker/languages/kotlin.d.ts +3 -0
- package/dist/chunker/languages/kotlin.d.ts.map +1 -0
- package/dist/chunker/languages/kotlin.js +23 -0
- package/dist/chunker/languages/kotlin.js.map +1 -0
- package/dist/chunker/languages/python.d.ts +3 -0
- package/dist/chunker/languages/python.d.ts.map +1 -0
- package/dist/chunker/languages/python.js +21 -0
- package/dist/chunker/languages/python.js.map +1 -0
- package/dist/chunker/languages/swift.d.ts +3 -0
- package/dist/chunker/languages/swift.d.ts.map +1 -0
- package/dist/chunker/languages/swift.js +24 -0
- package/dist/chunker/languages/swift.js.map +1 -0
- package/dist/chunker/languages/typescript.d.ts +4 -0
- package/dist/chunker/languages/typescript.d.ts.map +1 -0
- package/dist/chunker/languages/typescript.js +34 -0
- package/dist/chunker/languages/typescript.js.map +1 -0
- package/dist/chunker/token.d.ts +6 -0
- package/dist/chunker/token.d.ts.map +1 -0
- package/dist/chunker/token.js +107 -0
- package/dist/chunker/token.js.map +1 -0
- package/dist/collection.d.ts +22 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +154 -0
- package/dist/collection.js.map +1 -0
- package/dist/config/index.d.ts +95 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +103 -0
- package/dist/config/index.js.map +1 -0
- package/dist/embedder/index.d.ts +14 -0
- package/dist/embedder/index.d.ts.map +1 -0
- package/dist/embedder/index.js +18 -0
- package/dist/embedder/index.js.map +1 -0
- package/dist/embedder/local.d.ts +11 -0
- package/dist/embedder/local.d.ts.map +1 -0
- package/dist/embedder/local.js +60 -0
- package/dist/embedder/local.js.map +1 -0
- package/dist/embedder/openai.d.ts +10 -0
- package/dist/embedder/openai.d.ts.map +1 -0
- package/dist/embedder/openai.js +69 -0
- package/dist/embedder/openai.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +824 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/index.d.ts +17 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +18 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/local.d.ts +10 -0
- package/dist/llm/local.d.ts.map +1 -0
- package/dist/llm/local.js +76 -0
- package/dist/llm/local.js.map +1 -0
- package/dist/llm/openai.d.ts +10 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +76 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +393 -0
- package/dist/mcp.js.map +1 -0
- package/dist/scanner/git.d.ts +26 -0
- package/dist/scanner/git.d.ts.map +1 -0
- package/dist/scanner/git.js +134 -0
- package/dist/scanner/git.js.map +1 -0
- package/dist/scanner/index.d.ts +17 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +174 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/search/bm25.d.ts +17 -0
- package/dist/search/bm25.d.ts.map +1 -0
- package/dist/search/bm25.js +27 -0
- package/dist/search/bm25.js.map +1 -0
- package/dist/search/expander.d.ts +12 -0
- package/dist/search/expander.d.ts.map +1 -0
- package/dist/search/expander.js +60 -0
- package/dist/search/expander.js.map +1 -0
- package/dist/search/fusion.d.ts +32 -0
- package/dist/search/fusion.d.ts.map +1 -0
- package/dist/search/fusion.js +80 -0
- package/dist/search/fusion.js.map +1 -0
- package/dist/search/index.d.ts +61 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +137 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/reranker.d.ts +18 -0
- package/dist/search/reranker.d.ts.map +1 -0
- package/dist/search/reranker.js +56 -0
- package/dist/search/reranker.js.map +1 -0
- package/dist/search/vector.d.ts +23 -0
- package/dist/search/vector.d.ts.map +1 -0
- package/dist/search/vector.js +47 -0
- package/dist/search/vector.js.map +1 -0
- package/dist/store.d.ts +119 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +500 -0
- package/dist/store.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve, basename } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { createStore, formatChunkForEmbedding } from "./store.js";
|
|
5
|
+
import { createChunker } from "./chunker/index.js";
|
|
6
|
+
import { createEmbedder } from "./embedder/index.js";
|
|
7
|
+
import { createLLMProvider } from "./llm/index.js";
|
|
8
|
+
import { scanRepository, detectLanguage } from "./scanner/index.js";
|
|
9
|
+
import { detectChanges, getCurrentCommit, isGitRepository } from "./scanner/git.js";
|
|
10
|
+
import { createSearchPipeline } from "./search/index.js";
|
|
11
|
+
import { loadConfig } from "./config/index.js";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { registerCollection, resolveCollectionDb, resolveCollectionSourcePath, getCollection, listCollections, ensureQscHome, getCollectionDbPath, copyCollection, importCollection, exportCollection, updateCollectionMeta, } from "./collection.js";
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const args = argv.slice(2);
|
|
17
|
+
if (args.length === 0 || (args.length === 1 && args[0] === "--help")) {
|
|
18
|
+
return { command: "help", positional: [], flags: {} };
|
|
19
|
+
}
|
|
20
|
+
const command = args[0].startsWith("--") ? "help" : args[0];
|
|
21
|
+
const positional = [];
|
|
22
|
+
const flags = {};
|
|
23
|
+
const startIdx = args[0].startsWith("--") ? 0 : 1;
|
|
24
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
25
|
+
const arg = args[i];
|
|
26
|
+
if (arg.startsWith("--")) {
|
|
27
|
+
const key = arg.slice(2);
|
|
28
|
+
const eqIdx = key.indexOf("=");
|
|
29
|
+
if (eqIdx !== -1) {
|
|
30
|
+
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
31
|
+
}
|
|
32
|
+
else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
33
|
+
if (key.startsWith("no-")) {
|
|
34
|
+
flags[key] = true;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
flags[key] = args[++i];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
flags[key] = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
positional.push(arg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { command, positional, flags };
|
|
49
|
+
}
|
|
50
|
+
function getFlag(flags, key, defaultVal) {
|
|
51
|
+
const v = flags[key];
|
|
52
|
+
if (v === undefined || v === true)
|
|
53
|
+
return defaultVal;
|
|
54
|
+
return String(v);
|
|
55
|
+
}
|
|
56
|
+
function hasFlag(flags, key) {
|
|
57
|
+
return flags[key] !== undefined;
|
|
58
|
+
}
|
|
59
|
+
// --- Helpers ---
|
|
60
|
+
function openStore(dbPath, config) {
|
|
61
|
+
if (!existsSync(dbPath)) {
|
|
62
|
+
throw new Error(`Database not found: ${dbPath}\nRun 'qsc init <name> <path>' first.`);
|
|
63
|
+
}
|
|
64
|
+
const store = createStore(dbPath);
|
|
65
|
+
store.initDb(config.embedder.dimensions);
|
|
66
|
+
return store;
|
|
67
|
+
}
|
|
68
|
+
function getRepoId(repoPath) {
|
|
69
|
+
return basename(resolve(repoPath));
|
|
70
|
+
}
|
|
71
|
+
function formatScore(score) {
|
|
72
|
+
return score.toFixed(4);
|
|
73
|
+
}
|
|
74
|
+
function truncate(s, maxLen) {
|
|
75
|
+
if (s.length <= maxLen)
|
|
76
|
+
return s;
|
|
77
|
+
return s.slice(0, maxLen - 3) + "...";
|
|
78
|
+
}
|
|
79
|
+
function printResults(results, mode) {
|
|
80
|
+
if (results.length === 0) {
|
|
81
|
+
console.log("No results found.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(`\n${results.length} result(s) [${mode}]:\n`);
|
|
85
|
+
for (let i = 0; i < results.length; i++) {
|
|
86
|
+
const r = results[i];
|
|
87
|
+
const lineInfo = r.startLine != null ? `:${r.startLine}` : "";
|
|
88
|
+
const nameInfo = r.name ? ` (${r.name})` : "";
|
|
89
|
+
const typeInfo = r.chunkType ? ` [${r.chunkType}]` : "";
|
|
90
|
+
console.log(`--- #${i + 1} | ${r.filePath}${lineInfo}${nameInfo}${typeInfo} | score: ${formatScore(r.score)} ---`);
|
|
91
|
+
const parts = [];
|
|
92
|
+
if (r.scores.bm25 != null)
|
|
93
|
+
parts.push(`bm25=${formatScore(r.scores.bm25)}`);
|
|
94
|
+
if (r.scores.vector != null)
|
|
95
|
+
parts.push(`vector=${formatScore(r.scores.vector)}`);
|
|
96
|
+
if (r.scores.rrf != null)
|
|
97
|
+
parts.push(`rrf=${formatScore(r.scores.rrf)}`);
|
|
98
|
+
if (r.scores.rerank != null)
|
|
99
|
+
parts.push(`rerank=${formatScore(r.scores.rerank)}`);
|
|
100
|
+
if (parts.length > 0)
|
|
101
|
+
console.log(` scores: ${parts.join(", ")}`);
|
|
102
|
+
const lines = r.content.split("\n");
|
|
103
|
+
const preview = lines.slice(0, 8).join("\n");
|
|
104
|
+
console.log(preview);
|
|
105
|
+
if (lines.length > 8)
|
|
106
|
+
console.log(` ... (${lines.length - 8} more lines)`);
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function printBenchmark(response) {
|
|
111
|
+
if (!response.timing)
|
|
112
|
+
return;
|
|
113
|
+
const t = response.timing;
|
|
114
|
+
console.log("--- Benchmark ---");
|
|
115
|
+
if (t.expand != null) {
|
|
116
|
+
console.log(`Query Expansion: ${Math.round(t.expand)}ms`);
|
|
117
|
+
}
|
|
118
|
+
if (t.bm25 != null) {
|
|
119
|
+
const countInfo = response.counts?.bm25 != null ? ` (${response.counts.bm25} results)` : "";
|
|
120
|
+
console.log(`BM25: ${Math.round(t.bm25)}ms${countInfo}`);
|
|
121
|
+
}
|
|
122
|
+
if (t.vector != null) {
|
|
123
|
+
const countInfo = response.counts?.vector != null ? ` (${response.counts.vector} results)` : "";
|
|
124
|
+
console.log(`Vector: ${Math.round(t.vector)}ms${countInfo}`);
|
|
125
|
+
}
|
|
126
|
+
if (t.fusion != null) {
|
|
127
|
+
console.log(`RRF Fusion: ${Math.round(t.fusion)}ms`);
|
|
128
|
+
}
|
|
129
|
+
if (t.rerank != null) {
|
|
130
|
+
console.log(`LLM Reranking: ${Math.round(t.rerank)}ms`);
|
|
131
|
+
}
|
|
132
|
+
console.log(`Total: ${Math.round(t.total)}ms`);
|
|
133
|
+
}
|
|
134
|
+
// --- Commands ---
|
|
135
|
+
async function cmdInit(positional, flags) {
|
|
136
|
+
const name = positional[0];
|
|
137
|
+
const sourcePath = positional[1];
|
|
138
|
+
if (!name || !sourcePath) {
|
|
139
|
+
console.error("Usage: qsc init <name> <path> [--update-cmd <command>]");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const absSourcePath = resolve(sourcePath);
|
|
143
|
+
if (!existsSync(absSourcePath)) {
|
|
144
|
+
console.error(`Source path does not exist: ${absSourcePath}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
ensureQscHome();
|
|
148
|
+
const dbPath = getCollectionDbPath(name);
|
|
149
|
+
const config = loadConfig(absSourcePath);
|
|
150
|
+
// Create DB and initialize schema
|
|
151
|
+
const store = createStore(dbPath);
|
|
152
|
+
store.initDb(config.embedder.dimensions);
|
|
153
|
+
// Register repository in DB
|
|
154
|
+
const repoId = getRepoId(absSourcePath);
|
|
155
|
+
store.upsertRepository({
|
|
156
|
+
id: repoId,
|
|
157
|
+
path: absSourcePath,
|
|
158
|
+
});
|
|
159
|
+
store.close();
|
|
160
|
+
// Register collection with optional updateCommand
|
|
161
|
+
const updateCmd = hasFlag(flags, "update-cmd")
|
|
162
|
+
? getFlag(flags, "update-cmd", "")
|
|
163
|
+
: undefined;
|
|
164
|
+
registerCollection(name, absSourcePath, dbPath, updateCmd || undefined);
|
|
165
|
+
console.log(`Collection '${name}' initialized.`);
|
|
166
|
+
console.log(` Database: ${dbPath}`);
|
|
167
|
+
console.log(` Source: ${absSourcePath}`);
|
|
168
|
+
console.log(` Embedding dimensions: ${config.embedder.dimensions}`);
|
|
169
|
+
if (updateCmd) {
|
|
170
|
+
console.log(` Update command: ${updateCmd}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function cmdIndex(positional, _flags) {
|
|
174
|
+
const name = positional[0];
|
|
175
|
+
if (!name) {
|
|
176
|
+
console.error("Usage: qsc index <name>");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const dbPath = resolveCollectionDb(name);
|
|
180
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
181
|
+
const config = loadConfig(sourcePath);
|
|
182
|
+
const store = openStore(dbPath, config);
|
|
183
|
+
const repoId = getRepoId(sourcePath);
|
|
184
|
+
try {
|
|
185
|
+
console.log(`Scanning ${sourcePath}...`);
|
|
186
|
+
const scanResult = await scanRepository(sourcePath, config.scanner);
|
|
187
|
+
console.log(`Found ${scanResult.files.length} files (${(scanResult.totalSize / 1024).toFixed(1)} KB)`);
|
|
188
|
+
const chunker = createChunker(config.chunker);
|
|
189
|
+
let indexed = 0;
|
|
190
|
+
let skipped = 0;
|
|
191
|
+
for (let i = 0; i < scanResult.files.length; i++) {
|
|
192
|
+
const file = scanResult.files[i];
|
|
193
|
+
const progress = `[${i + 1}/${scanResult.files.length}]`;
|
|
194
|
+
const { id: fileId, changed } = store.upsertFile({
|
|
195
|
+
repo_id: repoId,
|
|
196
|
+
path: file.path,
|
|
197
|
+
hash: file.hash,
|
|
198
|
+
language: file.language ?? null,
|
|
199
|
+
active: 1,
|
|
200
|
+
indexed_at: new Date().toISOString(),
|
|
201
|
+
});
|
|
202
|
+
if (!changed) {
|
|
203
|
+
skipped++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const content = readFileSync(file.absolutePath, "utf-8");
|
|
207
|
+
const chunks = await chunker.chunk(content, file.path);
|
|
208
|
+
store.insertChunks(fileId, chunks.map((c, seq) => ({
|
|
209
|
+
hash: createHash("sha256").update(c.content).digest("hex"),
|
|
210
|
+
seq,
|
|
211
|
+
start_line: c.startLine,
|
|
212
|
+
end_line: c.endLine,
|
|
213
|
+
chunk_type: c.type,
|
|
214
|
+
name: c.name ?? null,
|
|
215
|
+
content: c.content,
|
|
216
|
+
metadata: c.metadata ? JSON.stringify(c.metadata) : null,
|
|
217
|
+
})));
|
|
218
|
+
indexed++;
|
|
219
|
+
process.stdout.write(`\r${progress} Indexed: ${indexed}, Skipped: ${skipped}`);
|
|
220
|
+
}
|
|
221
|
+
console.log(`\nIndexing complete. Indexed: ${indexed}, Skipped (unchanged): ${skipped}`);
|
|
222
|
+
store.upsertRepository({
|
|
223
|
+
id: repoId,
|
|
224
|
+
path: sourcePath,
|
|
225
|
+
indexed_at: new Date().toISOString(),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
store.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function cmdEmbed(positional, flags) {
|
|
233
|
+
const name = positional[0];
|
|
234
|
+
if (!name) {
|
|
235
|
+
console.error("Usage: qsc embed <name>");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const dbPath = resolveCollectionDb(name);
|
|
239
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
240
|
+
const config = loadConfig(sourcePath);
|
|
241
|
+
const store = openStore(dbPath, config);
|
|
242
|
+
try {
|
|
243
|
+
const batchSize = parseInt(getFlag(flags, "batch", "100"), 10);
|
|
244
|
+
const embedder = await createEmbedder(config.embedder);
|
|
245
|
+
console.log(`Embedder: ${embedder.modelName} (${embedder.dimensions}d)`);
|
|
246
|
+
let totalEmbedded = 0;
|
|
247
|
+
while (true) {
|
|
248
|
+
const chunks = store.getUnembeddedChunks(batchSize);
|
|
249
|
+
if (chunks.length === 0)
|
|
250
|
+
break;
|
|
251
|
+
const texts = chunks.map((c) => formatChunkForEmbedding(c));
|
|
252
|
+
const vectors = await embedder.embed(texts);
|
|
253
|
+
store.insertEmbeddings(chunks.map((c, i) => ({
|
|
254
|
+
chunk_id: c.chunk_id,
|
|
255
|
+
embedding: new Float32Array(vectors[i]),
|
|
256
|
+
model: embedder.modelName,
|
|
257
|
+
})));
|
|
258
|
+
totalEmbedded += chunks.length;
|
|
259
|
+
process.stdout.write(`\rEmbedded: ${totalEmbedded} chunks`);
|
|
260
|
+
}
|
|
261
|
+
console.log(`\nEmbedding complete. Total: ${totalEmbedded} chunks`);
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
store.close();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function cmdUpdate(positional, flags) {
|
|
268
|
+
const name = positional[0];
|
|
269
|
+
if (!name) {
|
|
270
|
+
console.error("Usage: qsc update <name>");
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
// Run pre-update command if configured
|
|
274
|
+
const collectionMeta = getCollection(name);
|
|
275
|
+
if (!collectionMeta) {
|
|
276
|
+
console.error(`Collection '${name}' not found. Run 'qsc init ${name} <path>' first.`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
if (collectionMeta.updateCommand) {
|
|
280
|
+
const cmd = collectionMeta.updateCommand;
|
|
281
|
+
console.log(`Running pre-update command: ${cmd}`);
|
|
282
|
+
console.log(` Working directory: ${collectionMeta.sourcePath}`);
|
|
283
|
+
try {
|
|
284
|
+
const output = execSync(cmd, {
|
|
285
|
+
cwd: collectionMeta.sourcePath,
|
|
286
|
+
timeout: 60_000,
|
|
287
|
+
stdio: "pipe",
|
|
288
|
+
encoding: "utf-8",
|
|
289
|
+
});
|
|
290
|
+
if (output.trim()) {
|
|
291
|
+
console.log(output.trimEnd());
|
|
292
|
+
}
|
|
293
|
+
console.log("Pre-update command completed successfully.\n");
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const execErr = err;
|
|
297
|
+
console.error(`Warning: Pre-update command failed (exit code: ${execErr.status ?? "unknown"})`);
|
|
298
|
+
if (execErr.stderr) {
|
|
299
|
+
console.error(execErr.stderr.trimEnd());
|
|
300
|
+
}
|
|
301
|
+
if (execErr.stdout) {
|
|
302
|
+
console.log(execErr.stdout.trimEnd());
|
|
303
|
+
}
|
|
304
|
+
console.error("Continuing with update...\n");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const dbPath = resolveCollectionDb(name);
|
|
308
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
309
|
+
const config = loadConfig(sourcePath);
|
|
310
|
+
const store = openStore(dbPath, config);
|
|
311
|
+
const repoId = getRepoId(sourcePath);
|
|
312
|
+
let closed = false;
|
|
313
|
+
try {
|
|
314
|
+
const isGit = isGitRepository(sourcePath);
|
|
315
|
+
// Try git-optimized path first
|
|
316
|
+
if (isGit) {
|
|
317
|
+
const repo = store.getRepository(repoId);
|
|
318
|
+
const lastCommit = repo?.last_commit ?? undefined;
|
|
319
|
+
if (lastCommit) {
|
|
320
|
+
const gitInfo = detectChanges(sourcePath, lastCommit);
|
|
321
|
+
if (!gitInfo.isFullScan) {
|
|
322
|
+
console.log(`Git incremental update (${lastCommit.slice(0, 8)} -> ${gitInfo.currentCommit.slice(0, 8)})`);
|
|
323
|
+
const added = gitInfo.changes.filter((c) => c.status === "added" || c.status === "renamed");
|
|
324
|
+
const modified = gitInfo.changes.filter((c) => c.status === "modified");
|
|
325
|
+
const deleted = gitInfo.changes.filter((c) => c.status === "deleted");
|
|
326
|
+
console.log(` Added: ${added.length}, Modified: ${modified.length}, Deleted: ${deleted.length}`);
|
|
327
|
+
if (deleted.length > 0) {
|
|
328
|
+
store.deactivateFiles(repoId, deleted.map((c) => c.path));
|
|
329
|
+
console.log(`Deactivated ${deleted.length} deleted files`);
|
|
330
|
+
}
|
|
331
|
+
const changedPaths = [...added, ...modified].map((c) => c.path);
|
|
332
|
+
if (changedPaths.length > 0) {
|
|
333
|
+
const chunker = createChunker(config.chunker);
|
|
334
|
+
let indexed = 0;
|
|
335
|
+
for (const relPath of changedPaths) {
|
|
336
|
+
const absPath = resolve(sourcePath, relPath);
|
|
337
|
+
if (!existsSync(absPath))
|
|
338
|
+
continue;
|
|
339
|
+
const content = readFileSync(absPath, "utf-8");
|
|
340
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
341
|
+
const language = detectLanguage(relPath);
|
|
342
|
+
const { id: fileId, changed } = store.upsertFile({
|
|
343
|
+
repo_id: repoId,
|
|
344
|
+
path: relPath,
|
|
345
|
+
hash,
|
|
346
|
+
language: language ?? null,
|
|
347
|
+
active: 1,
|
|
348
|
+
indexed_at: new Date().toISOString(),
|
|
349
|
+
});
|
|
350
|
+
if (!changed)
|
|
351
|
+
continue;
|
|
352
|
+
const chunks = await chunker.chunk(content, relPath);
|
|
353
|
+
store.insertChunks(fileId, chunks.map((c, seq) => ({
|
|
354
|
+
hash: createHash("sha256").update(c.content).digest("hex"),
|
|
355
|
+
seq,
|
|
356
|
+
start_line: c.startLine,
|
|
357
|
+
end_line: c.endLine,
|
|
358
|
+
chunk_type: c.type,
|
|
359
|
+
name: c.name ?? null,
|
|
360
|
+
content: c.content,
|
|
361
|
+
metadata: c.metadata ? JSON.stringify(c.metadata) : null,
|
|
362
|
+
})));
|
|
363
|
+
indexed++;
|
|
364
|
+
}
|
|
365
|
+
console.log(`Re-indexed ${indexed} files`);
|
|
366
|
+
}
|
|
367
|
+
// Cleanup and update commit
|
|
368
|
+
const cleaned = store.cleanup();
|
|
369
|
+
if (cleaned.deletedChunks > 0) {
|
|
370
|
+
console.log(`Cleaned up ${cleaned.deletedChunks} orphan chunks, ${cleaned.deletedVectors} vectors`);
|
|
371
|
+
}
|
|
372
|
+
store.upsertRepository({
|
|
373
|
+
id: repoId,
|
|
374
|
+
path: sourcePath,
|
|
375
|
+
last_commit: gitInfo.currentCommit,
|
|
376
|
+
indexed_at: new Date().toISOString(),
|
|
377
|
+
});
|
|
378
|
+
console.log("Git incremental update complete.");
|
|
379
|
+
// Auto-embed
|
|
380
|
+
const unembedded = store.getUnembeddedChunks(1);
|
|
381
|
+
store.close();
|
|
382
|
+
closed = true;
|
|
383
|
+
if (unembedded.length > 0) {
|
|
384
|
+
console.log("Embedding new chunks...");
|
|
385
|
+
await cmdEmbed([name], flags);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Fallback: hash-based full scan (works for both git and non-git)
|
|
392
|
+
console.log(isGit ? "Full hash-based scan (no previous commit)..." : "Hash-based scan (non-git directory)...");
|
|
393
|
+
const scanResult = await scanRepository(sourcePath, config.scanner);
|
|
394
|
+
console.log(`Found ${scanResult.files.length} files (${(scanResult.totalSize / 1024).toFixed(1)} KB)`);
|
|
395
|
+
const chunker = createChunker(config.chunker);
|
|
396
|
+
let indexed = 0;
|
|
397
|
+
let skipped = 0;
|
|
398
|
+
// Track scanned paths to detect deleted files
|
|
399
|
+
const scannedPaths = new Set();
|
|
400
|
+
for (let i = 0; i < scanResult.files.length; i++) {
|
|
401
|
+
const file = scanResult.files[i];
|
|
402
|
+
scannedPaths.add(file.path);
|
|
403
|
+
const progress = `[${i + 1}/${scanResult.files.length}]`;
|
|
404
|
+
const { id: fileId, changed } = store.upsertFile({
|
|
405
|
+
repo_id: repoId,
|
|
406
|
+
path: file.path,
|
|
407
|
+
hash: file.hash,
|
|
408
|
+
language: file.language ?? null,
|
|
409
|
+
active: 1,
|
|
410
|
+
indexed_at: new Date().toISOString(),
|
|
411
|
+
});
|
|
412
|
+
if (!changed) {
|
|
413
|
+
skipped++;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const content = readFileSync(file.absolutePath, "utf-8");
|
|
417
|
+
const chunks = await chunker.chunk(content, file.path);
|
|
418
|
+
store.insertChunks(fileId, chunks.map((c, seq) => ({
|
|
419
|
+
hash: createHash("sha256").update(c.content).digest("hex"),
|
|
420
|
+
seq,
|
|
421
|
+
start_line: c.startLine,
|
|
422
|
+
end_line: c.endLine,
|
|
423
|
+
chunk_type: c.type,
|
|
424
|
+
name: c.name ?? null,
|
|
425
|
+
content: c.content,
|
|
426
|
+
metadata: c.metadata ? JSON.stringify(c.metadata) : null,
|
|
427
|
+
})));
|
|
428
|
+
indexed++;
|
|
429
|
+
process.stdout.write(`\r${progress} Indexed: ${indexed}, Skipped: ${skipped}`);
|
|
430
|
+
}
|
|
431
|
+
console.log(`\nIndexed: ${indexed}, Skipped (unchanged): ${skipped}`);
|
|
432
|
+
// Deactivate files that are no longer present on disk
|
|
433
|
+
const activeFiles = store.getActiveFiles(repoId);
|
|
434
|
+
const deletedPaths = activeFiles
|
|
435
|
+
.filter((f) => !scannedPaths.has(f.path))
|
|
436
|
+
.map((f) => f.path);
|
|
437
|
+
if (deletedPaths.length > 0) {
|
|
438
|
+
store.deactivateFiles(repoId, deletedPaths);
|
|
439
|
+
console.log(`Deactivated ${deletedPaths.length} deleted files`);
|
|
440
|
+
}
|
|
441
|
+
// Cleanup orphaned data
|
|
442
|
+
const cleaned = store.cleanup();
|
|
443
|
+
if (cleaned.deletedChunks > 0) {
|
|
444
|
+
console.log(`Cleaned up ${cleaned.deletedChunks} orphan chunks, ${cleaned.deletedVectors} vectors`);
|
|
445
|
+
}
|
|
446
|
+
// Update repository metadata
|
|
447
|
+
const updateData = {
|
|
448
|
+
id: repoId,
|
|
449
|
+
path: sourcePath,
|
|
450
|
+
indexed_at: new Date().toISOString(),
|
|
451
|
+
};
|
|
452
|
+
if (isGit) {
|
|
453
|
+
try {
|
|
454
|
+
updateData.last_commit = getCurrentCommit(sourcePath);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// ignore git errors
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
store.upsertRepository(updateData);
|
|
461
|
+
console.log("Update complete.");
|
|
462
|
+
// Auto-embed
|
|
463
|
+
const unembedded = store.getUnembeddedChunks(1);
|
|
464
|
+
store.close();
|
|
465
|
+
closed = true;
|
|
466
|
+
if (unembedded.length > 0) {
|
|
467
|
+
console.log("Embedding new chunks...");
|
|
468
|
+
await cmdEmbed([name], flags);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
if (!closed) {
|
|
473
|
+
store.close();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function cmdSearch(positional, flags) {
|
|
478
|
+
const name = positional[0];
|
|
479
|
+
const query = positional.slice(1).join(" ");
|
|
480
|
+
if (!name || !query) {
|
|
481
|
+
console.error("Usage: qsc search <name> <query>");
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
const dbPath = resolveCollectionDb(name);
|
|
485
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
486
|
+
const config = loadConfig(sourcePath);
|
|
487
|
+
const store = openStore(dbPath, config);
|
|
488
|
+
try {
|
|
489
|
+
const limit = parseInt(getFlag(flags, "limit", "10"), 10);
|
|
490
|
+
const benchmark = hasFlag(flags, "benchmark");
|
|
491
|
+
const pipeline = createSearchPipeline(store);
|
|
492
|
+
const response = await pipeline.search(query, {
|
|
493
|
+
mode: "bm25",
|
|
494
|
+
limit,
|
|
495
|
+
benchmark,
|
|
496
|
+
});
|
|
497
|
+
printResults(response.results, "BM25");
|
|
498
|
+
if (benchmark)
|
|
499
|
+
printBenchmark(response);
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
store.close();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async function cmdQuery(positional, flags) {
|
|
506
|
+
const name = positional[0];
|
|
507
|
+
const query = positional.slice(1).join(" ");
|
|
508
|
+
if (!name || !query) {
|
|
509
|
+
console.error("Usage: qsc query <name> <query>");
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
const dbPath = resolveCollectionDb(name);
|
|
513
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
514
|
+
const config = loadConfig(sourcePath);
|
|
515
|
+
const store = openStore(dbPath, config);
|
|
516
|
+
try {
|
|
517
|
+
const limit = parseInt(getFlag(flags, "limit", "10"), 10);
|
|
518
|
+
const noExpand = hasFlag(flags, "no-expand");
|
|
519
|
+
const noRerank = hasFlag(flags, "no-rerank");
|
|
520
|
+
const benchmark = hasFlag(flags, "benchmark");
|
|
521
|
+
let embedder;
|
|
522
|
+
let llm;
|
|
523
|
+
try {
|
|
524
|
+
embedder = await createEmbedder(config.embedder);
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
console.error(`Warning: Could not create embedder (${err.message}). Vector search disabled.`);
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
llm = await createLLMProvider(config.llm);
|
|
531
|
+
}
|
|
532
|
+
catch (err) {
|
|
533
|
+
console.error(`Warning: Could not create LLM provider (${err.message}). Expansion/reranking disabled.`);
|
|
534
|
+
}
|
|
535
|
+
const pipeline = createSearchPipeline(store, embedder, llm);
|
|
536
|
+
const mode = embedder ? "hybrid" : "bm25";
|
|
537
|
+
const response = await pipeline.search(query, {
|
|
538
|
+
mode,
|
|
539
|
+
limit,
|
|
540
|
+
expand: !noExpand && !!llm,
|
|
541
|
+
rerank: !noRerank && !!llm,
|
|
542
|
+
benchmark,
|
|
543
|
+
});
|
|
544
|
+
printResults(response.results, mode);
|
|
545
|
+
if (benchmark)
|
|
546
|
+
printBenchmark(response);
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
store.close();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function cmdGet(positional, _flags) {
|
|
553
|
+
const name = positional[0];
|
|
554
|
+
const filePath = positional[1];
|
|
555
|
+
if (!name || !filePath) {
|
|
556
|
+
console.error("Usage: qsc get <name> <file-path>");
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
const dbPath = resolveCollectionDb(name);
|
|
560
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
561
|
+
const config = loadConfig(sourcePath);
|
|
562
|
+
const store = openStore(dbPath, config);
|
|
563
|
+
const repoId = getRepoId(sourcePath);
|
|
564
|
+
try {
|
|
565
|
+
const file = store.getFileByPath(repoId, filePath);
|
|
566
|
+
if (!file) {
|
|
567
|
+
console.error(`File not found in index: ${filePath}`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
console.log(`File: ${file.path}`);
|
|
571
|
+
console.log(` ID: ${file.id}`);
|
|
572
|
+
console.log(` Hash: ${file.hash}`);
|
|
573
|
+
console.log(` Language: ${file.language ?? "unknown"}`);
|
|
574
|
+
console.log(` Active: ${file.active ? "yes" : "no"}`);
|
|
575
|
+
console.log(` Indexed at: ${file.indexed_at ?? "unknown"}`);
|
|
576
|
+
const chunks = store.getChunksByFileId(file.id);
|
|
577
|
+
console.log(`\nChunks (${chunks.length}):\n`);
|
|
578
|
+
for (const chunk of chunks) {
|
|
579
|
+
const lineInfo = chunk.start_line != null
|
|
580
|
+
? `L${chunk.start_line}-${chunk.end_line}`
|
|
581
|
+
: "";
|
|
582
|
+
const nameInfo = chunk.name ? ` ${chunk.name}` : "";
|
|
583
|
+
const typeInfo = chunk.chunk_type ? ` [${chunk.chunk_type}]` : "";
|
|
584
|
+
console.log(` #${chunk.seq} ${lineInfo}${nameInfo}${typeInfo}`);
|
|
585
|
+
console.log(` ${truncate(chunk.content.split("\n")[0], 80)}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
store.close();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function cmdStatus(positional, _flags) {
|
|
593
|
+
const name = positional[0];
|
|
594
|
+
if (!name) {
|
|
595
|
+
console.error("Usage: qsc status <name>");
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
const dbPath = resolveCollectionDb(name);
|
|
599
|
+
const sourcePath = resolveCollectionSourcePath(name);
|
|
600
|
+
const config = loadConfig(sourcePath);
|
|
601
|
+
const store = openStore(dbPath, config);
|
|
602
|
+
try {
|
|
603
|
+
const stats = store.getStats();
|
|
604
|
+
console.log("QSC Index Status");
|
|
605
|
+
console.log("================");
|
|
606
|
+
console.log(`Collection: ${name}`);
|
|
607
|
+
console.log(`Database: ${dbPath}`);
|
|
608
|
+
console.log(`Source: ${sourcePath}`);
|
|
609
|
+
console.log(`Repositories: ${stats.repositories}`);
|
|
610
|
+
console.log(`Files (total): ${stats.files}`);
|
|
611
|
+
console.log(`Files (active): ${stats.active_files}`);
|
|
612
|
+
console.log(`Chunks: ${stats.chunks}`);
|
|
613
|
+
console.log(`Embedded: ${stats.embedded_chunks}`);
|
|
614
|
+
console.log(`Pending embed: ${stats.pending_chunks}`);
|
|
615
|
+
}
|
|
616
|
+
finally {
|
|
617
|
+
store.close();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function cmdConfig() {
|
|
621
|
+
const config = loadConfig();
|
|
622
|
+
console.log("QSC Configuration");
|
|
623
|
+
console.log("==================");
|
|
624
|
+
console.log("\nEmbedder:");
|
|
625
|
+
console.log(` Provider: ${config.embedder.provider}`);
|
|
626
|
+
console.log(` Model: ${config.embedder.model}`);
|
|
627
|
+
console.log(` Dimensions: ${config.embedder.dimensions}`);
|
|
628
|
+
console.log(` API Key: ${config.embedder.api_key_env} (env var)`);
|
|
629
|
+
console.log("\nLLM:");
|
|
630
|
+
console.log(` Provider: ${config.llm.provider}`);
|
|
631
|
+
console.log(` Model: ${config.llm.model}`);
|
|
632
|
+
console.log(` API Key: ${config.llm.api_key_env} (env var)`);
|
|
633
|
+
console.log("\nChunker:");
|
|
634
|
+
console.log(` Max Tokens: ${config.chunker.max_tokens}`);
|
|
635
|
+
console.log(` Overlap: ${config.chunker.overlap}`);
|
|
636
|
+
console.log("\nScanner:");
|
|
637
|
+
console.log(` Max File Size: ${(config.scanner.max_file_size / 1024).toFixed(0)} KB`);
|
|
638
|
+
console.log(` Exclude:`);
|
|
639
|
+
for (const pattern of config.scanner.exclude) {
|
|
640
|
+
console.log(` - ${pattern}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function cmdList() {
|
|
644
|
+
const collections = listCollections();
|
|
645
|
+
const names = Object.keys(collections);
|
|
646
|
+
if (names.length === 0) {
|
|
647
|
+
console.log("No collections found. Run 'qsc init <name> <path>' to create one.");
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
console.log("Collections:");
|
|
651
|
+
console.log("============");
|
|
652
|
+
for (const name of names.sort()) {
|
|
653
|
+
const meta = collections[name];
|
|
654
|
+
console.log(` ${name}`);
|
|
655
|
+
console.log(` Source: ${meta.sourcePath}`);
|
|
656
|
+
console.log(` DB: ${meta.dbPath}`);
|
|
657
|
+
console.log(` Created: ${meta.createdAt}`);
|
|
658
|
+
if (meta.updateCommand) {
|
|
659
|
+
console.log(` Update cmd: ${meta.updateCommand}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function cmdSetUpdateCmd(positional, _flags) {
|
|
664
|
+
const name = positional[0];
|
|
665
|
+
const cmd = positional.slice(1).join(" ");
|
|
666
|
+
if (!name) {
|
|
667
|
+
console.error("Usage: qsc set-update-cmd <collection> <command>");
|
|
668
|
+
console.error(" qsc set-update-cmd <collection> (removes the command)");
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
const updateCommand = cmd || "";
|
|
672
|
+
const meta = updateCollectionMeta(name, { updateCommand });
|
|
673
|
+
if (updateCommand) {
|
|
674
|
+
console.log(`Update command for '${name}' set to: ${updateCommand}`);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
console.log(`Update command for '${name}' has been removed.`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async function cmdCopy(positional) {
|
|
681
|
+
const sourceName = positional[0];
|
|
682
|
+
const destName = positional[1];
|
|
683
|
+
const path = positional[2];
|
|
684
|
+
if (!sourceName || !destName || !path) {
|
|
685
|
+
console.error("Usage: qsc copy <source-name> <dest-name> <path>");
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
const meta = copyCollection(sourceName, destName, path);
|
|
689
|
+
console.log(`Collection '${destName}' created (copied from '${sourceName}').`);
|
|
690
|
+
console.log(` Database: ${meta.dbPath}`);
|
|
691
|
+
console.log(` Source: ${meta.sourcePath}`);
|
|
692
|
+
}
|
|
693
|
+
async function cmdImport(positional) {
|
|
694
|
+
const name = positional[0];
|
|
695
|
+
const sqlitePath = positional[1];
|
|
696
|
+
const sourcePath = positional[2];
|
|
697
|
+
if (!name || !sqlitePath || !sourcePath) {
|
|
698
|
+
console.error("Usage: qsc import <name> <sqlite-path> <source-path>");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const meta = importCollection(name, sqlitePath, sourcePath);
|
|
702
|
+
console.log(`Collection '${name}' imported.`);
|
|
703
|
+
console.log(` Database: ${meta.dbPath}`);
|
|
704
|
+
console.log(` Source: ${meta.sourcePath}`);
|
|
705
|
+
}
|
|
706
|
+
async function cmdExport(positional) {
|
|
707
|
+
const name = positional[0];
|
|
708
|
+
const outputPath = positional[1];
|
|
709
|
+
if (!name || !outputPath) {
|
|
710
|
+
console.error("Usage: qsc export <name> <output-path>");
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
exportCollection(name, outputPath);
|
|
714
|
+
console.log(`Collection '${name}' exported to ${resolve(outputPath)}`);
|
|
715
|
+
}
|
|
716
|
+
function printHelp() {
|
|
717
|
+
console.log(`
|
|
718
|
+
QSC - Query Source Code
|
|
719
|
+
=======================
|
|
720
|
+
|
|
721
|
+
Usage: qsc <command> [options]
|
|
722
|
+
|
|
723
|
+
Commands:
|
|
724
|
+
init <name> <path> Create a collection for the source at <path>
|
|
725
|
+
index <name> Index source code (scan -> chunk -> store)
|
|
726
|
+
embed <name> Generate vector embeddings for unembedded chunks
|
|
727
|
+
update <name> Incremental update (hash-based, git-optimized if available)
|
|
728
|
+
search <name> <query> BM25 full-text search
|
|
729
|
+
query <name> <query> Hybrid search (BM25 + Vector + LLM reranking)
|
|
730
|
+
get <name> <file-path> Get file info and chunks
|
|
731
|
+
status <name> Show index statistics
|
|
732
|
+
list List all collections
|
|
733
|
+
set-update-cmd <name> <command> Set a pre-update command for a collection
|
|
734
|
+
copy <source> <dest> <path> Copy a collection DB to a new collection
|
|
735
|
+
import <name> <sqlite-path> <path> Import an external SQLite DB as a collection
|
|
736
|
+
export <name> <output-path> Export a collection's SQLite DB
|
|
737
|
+
config Show current configuration
|
|
738
|
+
mcp Start MCP server (stdio transport)
|
|
739
|
+
help Show this help message
|
|
740
|
+
|
|
741
|
+
Options:
|
|
742
|
+
--limit <n> Max results for search/query (default: 10)
|
|
743
|
+
--batch <n> Batch size for embed (default: 100)
|
|
744
|
+
--update-cmd <cmd> Set pre-update command (init command)
|
|
745
|
+
--no-expand Disable query expansion (query command)
|
|
746
|
+
--no-rerank Disable LLM reranking (query command)
|
|
747
|
+
--benchmark Show timing info for search/query
|
|
748
|
+
--collection <name> Collection name for MCP server
|
|
749
|
+
--help Show help
|
|
750
|
+
`);
|
|
751
|
+
}
|
|
752
|
+
// --- Main ---
|
|
753
|
+
async function main() {
|
|
754
|
+
const { command, positional, flags } = parseArgs(process.argv);
|
|
755
|
+
if (hasFlag(flags, "help")) {
|
|
756
|
+
printHelp();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
switch (command) {
|
|
761
|
+
case "init":
|
|
762
|
+
await cmdInit(positional, flags);
|
|
763
|
+
break;
|
|
764
|
+
case "index":
|
|
765
|
+
await cmdIndex(positional, flags);
|
|
766
|
+
break;
|
|
767
|
+
case "embed":
|
|
768
|
+
await cmdEmbed(positional, flags);
|
|
769
|
+
break;
|
|
770
|
+
case "update":
|
|
771
|
+
await cmdUpdate(positional, flags);
|
|
772
|
+
break;
|
|
773
|
+
case "search":
|
|
774
|
+
await cmdSearch(positional, flags);
|
|
775
|
+
break;
|
|
776
|
+
case "query":
|
|
777
|
+
await cmdQuery(positional, flags);
|
|
778
|
+
break;
|
|
779
|
+
case "get":
|
|
780
|
+
await cmdGet(positional, flags);
|
|
781
|
+
break;
|
|
782
|
+
case "status":
|
|
783
|
+
await cmdStatus(positional, flags);
|
|
784
|
+
break;
|
|
785
|
+
case "list":
|
|
786
|
+
await cmdList();
|
|
787
|
+
break;
|
|
788
|
+
case "set-update-cmd":
|
|
789
|
+
await cmdSetUpdateCmd(positional, flags);
|
|
790
|
+
break;
|
|
791
|
+
case "copy":
|
|
792
|
+
await cmdCopy(positional);
|
|
793
|
+
break;
|
|
794
|
+
case "import":
|
|
795
|
+
await cmdImport(positional);
|
|
796
|
+
break;
|
|
797
|
+
case "export":
|
|
798
|
+
await cmdExport(positional);
|
|
799
|
+
break;
|
|
800
|
+
case "config":
|
|
801
|
+
await cmdConfig();
|
|
802
|
+
break;
|
|
803
|
+
case "mcp": {
|
|
804
|
+
const { startMcpServer } = await import("./mcp.js");
|
|
805
|
+
await startMcpServer();
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
case "help":
|
|
809
|
+
printHelp();
|
|
810
|
+
break;
|
|
811
|
+
default:
|
|
812
|
+
console.error(`Unknown command: ${command}`);
|
|
813
|
+
printHelp();
|
|
814
|
+
process.exit(1);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
819
|
+
console.error(`Error: ${message}`);
|
|
820
|
+
process.exit(1);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
main();
|
|
824
|
+
//# sourceMappingURL=index.js.map
|