opencode-rag-plugin 1.3.5 → 1.4.2
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/ReadMe.md +148 -418
- package/dist/chunker/pdf.js +25 -4
- package/dist/chunker/pdf.js.map +1 -1
- package/dist/cli.d.ts +0 -5
- package/dist/cli.js +245 -102
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +16 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/interfaces.d.ts +6 -1
- package/dist/core/manifest.d.ts +2 -0
- package/dist/core/manifest.js +5 -2
- package/dist/core/manifest.js.map +1 -1
- package/dist/core/runtime-overrides.d.ts +28 -0
- package/dist/core/runtime-overrides.js +83 -0
- package/dist/core/runtime-overrides.js.map +1 -0
- package/dist/describer/describer.d.ts +12 -0
- package/dist/describer/describer.js +177 -0
- package/dist/describer/describer.js.map +1 -0
- package/dist/describer/factory.d.ts +3 -0
- package/dist/describer/factory.js +5 -0
- package/dist/describer/factory.js.map +1 -0
- package/dist/embedder/factory.d.ts +1 -1
- package/dist/embedder/factory.js +2 -2
- package/dist/embedder/factory.js.map +1 -1
- package/dist/embedder/ollama.d.ts +1 -1
- package/dist/embedder/ollama.js +1 -1
- package/dist/embedder/ollama.js.map +1 -1
- package/dist/embedder/openai.d.ts +1 -1
- package/dist/embedder/openai.js +6 -2
- package/dist/embedder/openai.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +2 -1
- package/dist/indexer.js +36 -1
- package/dist/indexer.js.map +1 -1
- package/dist/opencode/create-read-tool.js +2 -2
- package/dist/opencode/create-read-tool.js.map +1 -1
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +42 -13
- package/dist/plugin.js.map +1 -1
- package/dist/retriever/retriever.d.ts +1 -0
- package/dist/retriever/retriever.js +2 -1
- package/dist/retriever/retriever.js.map +1 -1
- package/dist/tui.js +285 -11
- package/dist/tui.js.map +1 -1
- package/dist/vectorstore/lancedb.d.ts +19 -0
- package/dist/vectorstore/lancedb.js +162 -41
- package/dist/vectorstore/lancedb.js.map +1 -1
- package/dist/watcher.d.ts +6 -1
- package/dist/watcher.js +72 -26
- package/dist/watcher.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -6,13 +6,34 @@ import os from "node:os";
|
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import chokidar from "chokidar";
|
|
9
|
+
import pc from "picocolors";
|
|
9
10
|
import { loadConfig, DEFAULT_CONFIG, resolveLogConfig } from "./core/config.js";
|
|
10
11
|
import { loadChunkersFromConfig } from "./chunker/loader.js";
|
|
11
12
|
import { createEmbedder } from "./embedder/factory.js";
|
|
13
|
+
import { createDescriptionProvider } from "./describer/factory.js";
|
|
12
14
|
import { retrieve } from "./retriever/retriever.js";
|
|
13
15
|
import { createWatchPassScheduler, createWatchIgnore, getIndexStatusSummary, runIndexPass, } from "./indexer.js";
|
|
16
|
+
const c = {
|
|
17
|
+
heading: (s) => pc.bold(pc.cyan(s)),
|
|
18
|
+
label: (s) => pc.dim(s),
|
|
19
|
+
dim: (s) => pc.dim(s),
|
|
20
|
+
value: (s) => pc.green(s),
|
|
21
|
+
num: (s) => pc.green(String(s)),
|
|
22
|
+
file: (s) => pc.yellow(s),
|
|
23
|
+
lang: (s) => pc.cyan(s),
|
|
24
|
+
score: (s) => pc.magenta(s),
|
|
25
|
+
desc: (s) => pc.dim(s),
|
|
26
|
+
success: (s) => pc.green(s),
|
|
27
|
+
warn: (s) => pc.yellow(s),
|
|
28
|
+
error: (s) => pc.red(s),
|
|
29
|
+
enabled: (s) => pc.green(s),
|
|
30
|
+
disabled: (s) => pc.yellow(s),
|
|
31
|
+
created: (s) => pc.green(s),
|
|
32
|
+
updated: (s) => pc.yellow(s),
|
|
33
|
+
exists: (s) => pc.dim(s),
|
|
34
|
+
};
|
|
14
35
|
function logCliError(logFilePath, scope, message, error) {
|
|
15
|
-
console.error(message);
|
|
36
|
+
console.error(c.error(message));
|
|
16
37
|
//appendDebugLog(logFilePath, { scope, message, error });
|
|
17
38
|
}
|
|
18
39
|
function logCliInfo(logFilePath, scope, message) {
|
|
@@ -25,12 +46,12 @@ async function resolveConfig(opt, logFilePath) {
|
|
|
25
46
|
const configPath = path.resolve(opt.config);
|
|
26
47
|
const cfg = loadConfig(configPath);
|
|
27
48
|
await loadChunkersFromConfig(cfg, path.dirname(configPath));
|
|
28
|
-
logCliInfo(logFilePath, "config",
|
|
49
|
+
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(configPath)}`);
|
|
29
50
|
return logConfigDetails(logFilePath, cfg);
|
|
30
51
|
}
|
|
31
52
|
catch (err) {
|
|
32
53
|
logCliError(logFilePath, "config", `Could not load config from ${opt.config}, using defaults`, err);
|
|
33
|
-
console.error(`Could not load config from ${opt.config}, using defaults`);
|
|
54
|
+
console.error(c.warn(`Could not load config from ${opt.config}, using defaults`));
|
|
34
55
|
}
|
|
35
56
|
}
|
|
36
57
|
for (const loc of ["opencode-rag.json", ".opencode/opencode-rag.json", ".opencode/rag.json"]) {
|
|
@@ -38,32 +59,32 @@ async function resolveConfig(opt, logFilePath) {
|
|
|
38
59
|
try {
|
|
39
60
|
const cfg = loadConfig(configPath);
|
|
40
61
|
await loadChunkersFromConfig(cfg, path.dirname(configPath));
|
|
41
|
-
logCliInfo(logFilePath, "config",
|
|
62
|
+
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(configPath)}`);
|
|
42
63
|
return logConfigDetails(logFilePath, cfg);
|
|
43
64
|
}
|
|
44
65
|
catch (err) {
|
|
45
66
|
logCliError(logFilePath, "config", `Failed to load config from ${configPath}`, err);
|
|
46
67
|
}
|
|
47
68
|
}
|
|
48
|
-
logCliInfo(logFilePath, "config",
|
|
69
|
+
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.dim("using defaults (no opencode-rag.json found)")}`);
|
|
49
70
|
return logConfigDetails(logFilePath, DEFAULT_CONFIG);
|
|
50
71
|
}
|
|
51
72
|
async function loadCliKeywordIndex(storePath, logFilePath) {
|
|
52
73
|
const { KeywordIndex } = await import("./retriever/keyword-index.js");
|
|
53
74
|
try {
|
|
54
75
|
const index = await KeywordIndex.load(storePath);
|
|
55
|
-
logCliInfo(logFilePath, "keyword-index",
|
|
76
|
+
logCliInfo(logFilePath, "keyword-index", `${c.label("Keyword index loaded")} (${c.num(index.count())} chunks)`);
|
|
56
77
|
return index;
|
|
57
78
|
}
|
|
58
79
|
catch {
|
|
59
|
-
logCliInfo(logFilePath, "keyword-index", "Creating keyword index");
|
|
80
|
+
logCliInfo(logFilePath, "keyword-index", c.warn("Creating keyword index"));
|
|
60
81
|
return new KeywordIndex(storePath);
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
function logConfigDetails(logFilePath, config) {
|
|
64
|
-
logCliInfo(logFilePath, "config", ` Embedding provider: ${config.embedding.provider}`);
|
|
65
|
-
logCliInfo(logFilePath, "config", ` Embedding model: ${config.embedding.model}`);
|
|
66
|
-
logCliInfo(logFilePath, "config", ` Vector store: ${config.vectorStore.path}`);
|
|
85
|
+
logCliInfo(logFilePath, "config", ` ${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
|
|
86
|
+
logCliInfo(logFilePath, "config", ` ${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
|
|
87
|
+
logCliInfo(logFilePath, "config", ` ${c.label("Vector store:")} ${c.file(config.vectorStore.path)}`);
|
|
67
88
|
return config;
|
|
68
89
|
}
|
|
69
90
|
function formatTimestamp(timestamp) {
|
|
@@ -72,14 +93,14 @@ function formatTimestamp(timestamp) {
|
|
|
72
93
|
return new Date(timestamp).toLocaleString();
|
|
73
94
|
}
|
|
74
95
|
function logIndexSummary(logFilePath, stats) {
|
|
75
|
-
logCliInfo(logFilePath, "index", ` New: ${stats.newFiles}`);
|
|
76
|
-
logCliInfo(logFilePath, "index", ` Modified: ${stats.modifiedFiles}`);
|
|
77
|
-
logCliInfo(logFilePath, "index", ` Unchanged: ${stats.unchangedFiles}`);
|
|
78
|
-
logCliInfo(logFilePath, "index", ` Deleted: ${stats.deletedFiles}`);
|
|
79
|
-
logCliInfo(logFilePath, "index", ` Removed: ${stats.removedFiles}`);
|
|
80
|
-
logCliInfo(logFilePath, "index", ` Empty skipped: ${stats.skippedEmptyFiles}`);
|
|
81
|
-
logCliInfo(logFilePath, "index", ` Small skipped: ${stats.skippedSmallFiles}`);
|
|
82
|
-
logCliInfo(logFilePath, "index", ` Chunks written: ${stats.totalChunks}`);
|
|
96
|
+
logCliInfo(logFilePath, "index", ` ${c.label("New:")} ${c.num(stats.newFiles)}`);
|
|
97
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Modified:")} ${c.num(stats.modifiedFiles)}`);
|
|
98
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Unchanged:")} ${c.num(stats.unchangedFiles)}`);
|
|
99
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Deleted:")} ${c.num(stats.deletedFiles)}`);
|
|
100
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Removed:")} ${c.num(stats.removedFiles)}`);
|
|
101
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Empty skipped:")} ${c.num(stats.skippedEmptyFiles)}`);
|
|
102
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Small skipped:")} ${c.num(stats.skippedSmallFiles)}`);
|
|
103
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Chunks written:")} ${c.num(stats.totalChunks)}`);
|
|
83
104
|
}
|
|
84
105
|
function formatDuration(ms) {
|
|
85
106
|
const seconds = (ms / 1000).toFixed(1);
|
|
@@ -251,7 +272,7 @@ function installWorkspaceDependencies(opencodeDir) {
|
|
|
251
272
|
let lastError;
|
|
252
273
|
for (const attempt of attempts) {
|
|
253
274
|
if (attempt.retry) {
|
|
254
|
-
console.log(" Retrying dependency install without native module compilation...");
|
|
275
|
+
console.log(c.warn(" Retrying dependency install without native module compilation..."));
|
|
255
276
|
}
|
|
256
277
|
const result = process.platform === "win32"
|
|
257
278
|
? spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `npm ${attempt.args.map(quoteForCmd).join(" ")}`], {
|
|
@@ -289,7 +310,7 @@ program
|
|
|
289
310
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
290
311
|
const config = await resolveConfig(options, logFilePath);
|
|
291
312
|
logFilePath = path.resolve(cwd, resolveLogConfig(config).logFilePath);
|
|
292
|
-
logCliInfo(logFilePath, "index", "
|
|
313
|
+
logCliInfo(logFilePath, "index", `\n${c.heading("Indexing workspace...")}`);
|
|
293
314
|
const embedder = createEmbedder(config);
|
|
294
315
|
// Detect actual vector dimension from the model
|
|
295
316
|
const probe = await embedder.embed(["dimension-probe"]);
|
|
@@ -297,11 +318,19 @@ program
|
|
|
297
318
|
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
298
319
|
vectorDimension = probe[0].length;
|
|
299
320
|
}
|
|
300
|
-
logCliInfo(logFilePath, "index", ` Vector dimension: ${vectorDimension}`);
|
|
321
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Vector dimension:")} ${c.num(vectorDimension)}`);
|
|
301
322
|
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
302
323
|
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path), vectorDimension);
|
|
303
324
|
const keywordIndex = await loadCliKeywordIndex(path.resolve(cwd, config.vectorStore.path), logFilePath);
|
|
304
|
-
|
|
325
|
+
// Create description provider (enabled by default)
|
|
326
|
+
const descriptionConfig = config.description ?? { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
|
|
327
|
+
const descriptionProvider = descriptionConfig.enabled
|
|
328
|
+
? createDescriptionProvider(descriptionConfig)
|
|
329
|
+
: undefined;
|
|
330
|
+
if (descriptionProvider) {
|
|
331
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Description LLM:")} ${c.value(descriptionConfig.model)} (${descriptionConfig.provider})`);
|
|
332
|
+
}
|
|
333
|
+
logCliInfo(logFilePath, "index", `${c.label("Scanning:")} ${c.file(cwd)}`);
|
|
305
334
|
const runPass = async (watchTriggered = false) => {
|
|
306
335
|
const passStarted = Date.now();
|
|
307
336
|
const stats = await runIndexPass({
|
|
@@ -311,6 +340,7 @@ program
|
|
|
311
340
|
store,
|
|
312
341
|
embedder,
|
|
313
342
|
keywordIndex,
|
|
343
|
+
descriptionProvider,
|
|
314
344
|
force: Boolean(options.force && !watchTriggered),
|
|
315
345
|
logger: {
|
|
316
346
|
info: (message) => logCliInfo(logFilePath, "index", message),
|
|
@@ -318,17 +348,16 @@ program
|
|
|
318
348
|
},
|
|
319
349
|
});
|
|
320
350
|
logIndexSummary(logFilePath, stats);
|
|
321
|
-
logCliInfo(logFilePath, "index", `\
|
|
351
|
+
logCliInfo(logFilePath, "index", `\n${c.success("Indexing complete.")} ${c.num(stats.finalCount)} chunks stored (${formatDuration(Date.now() - passStarted)}).`);
|
|
322
352
|
};
|
|
323
353
|
await runPass(false);
|
|
324
354
|
if (!options.watch) {
|
|
325
355
|
return;
|
|
326
356
|
}
|
|
327
|
-
logCliInfo(logFilePath, "index", "
|
|
357
|
+
logCliInfo(logFilePath, "index", `\n${c.heading("Watching for changes...")}`);
|
|
328
358
|
const scheduler = createWatchPassScheduler(() => runPass(true), (error) => {
|
|
329
359
|
const message = error.message || String(error);
|
|
330
|
-
logCliError(logFilePath, "watch",
|
|
331
|
-
console.error(`\nWatch reindex failed: ${message}`);
|
|
360
|
+
logCliError(logFilePath, "watch", `\nWatch reindex failed: ${message}`, error);
|
|
332
361
|
}, 300);
|
|
333
362
|
const watcher = chokidar.watch(cwd, {
|
|
334
363
|
ignored: createWatchIgnore(cwd, config, path.resolve(cwd, config.vectorStore.path)),
|
|
@@ -343,7 +372,7 @@ program
|
|
|
343
372
|
watcher.on("addDir", handleChange);
|
|
344
373
|
watcher.on("error", (error) => {
|
|
345
374
|
logCliError(logFilePath, "watch", `Watcher error: ${error.message}`, error);
|
|
346
|
-
console.error(`\nWatcher error: ${error.message}`);
|
|
375
|
+
console.error(c.error(`\nWatcher error: ${error.message}`));
|
|
347
376
|
});
|
|
348
377
|
const shutdown = async () => {
|
|
349
378
|
scheduler.close();
|
|
@@ -353,15 +382,14 @@ program
|
|
|
353
382
|
process.once("SIGINT", () => void shutdown());
|
|
354
383
|
process.once("SIGTERM", () => void shutdown());
|
|
355
384
|
const duration = formatDuration(Date.now() - started);
|
|
356
|
-
logCliInfo(logFilePath, "index",
|
|
385
|
+
logCliInfo(logFilePath, "index", `${c.success("Watcher ready")} (${duration} startup). Press Ctrl+C to stop.`);
|
|
357
386
|
}
|
|
358
387
|
catch (err) {
|
|
359
388
|
const message = err.message || String(err);
|
|
360
389
|
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
361
|
-
logCliError(logFilePath, "index",
|
|
362
|
-
console.error(`\nIndexing failed: ${message}`);
|
|
390
|
+
logCliError(logFilePath, "index", `\nIndexing failed: ${message}`, err);
|
|
363
391
|
if (message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")) {
|
|
364
|
-
console.error("Hint: Is your embedding provider running?");
|
|
392
|
+
console.error(c.warn("Hint: Is your embedding provider running?"));
|
|
365
393
|
}
|
|
366
394
|
process.exit(1);
|
|
367
395
|
}
|
|
@@ -379,41 +407,41 @@ program
|
|
|
379
407
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
380
408
|
const config = await resolveConfig(options, logFilePath);
|
|
381
409
|
logFilePath = path.resolve(cwd, resolveLogConfig(config).logFilePath);
|
|
382
|
-
logCliInfo(logFilePath, "query", `\
|
|
383
|
-
logCliInfo(logFilePath, "query",
|
|
410
|
+
logCliInfo(logFilePath, "query", `\n${c.heading("Querying:")} "${query}"`);
|
|
411
|
+
logCliInfo(logFilePath, "query", `${c.label("Top-K:")} ${c.num(parseInt(options.topK ?? "10", 10))}`);
|
|
384
412
|
const embedder = createEmbedder(config);
|
|
385
413
|
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
386
414
|
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
387
415
|
const indexedCount = await store.count();
|
|
388
416
|
if (indexedCount === 0) {
|
|
389
|
-
logCliInfo(logFilePath, "query", "No indexed chunks found. Run 'opencode-rag index' first
|
|
417
|
+
logCliInfo(logFilePath, "query", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
|
|
390
418
|
return;
|
|
391
419
|
}
|
|
392
|
-
logCliInfo(logFilePath, "query",
|
|
420
|
+
logCliInfo(logFilePath, "query", `${c.label("Searching")} ${c.num(indexedCount)} indexed chunks...`);
|
|
393
421
|
const topK = parseInt(options.topK ?? "10", 10);
|
|
394
422
|
const minScore = config.retrieval.minScore;
|
|
395
423
|
const keywordIndex = await loadCliKeywordIndex(path.resolve(cwd, config.vectorStore.path), logFilePath);
|
|
396
424
|
const hybridCfg = config.retrieval.hybridSearch;
|
|
397
|
-
const
|
|
425
|
+
const rawResults = await retrieve(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight: hybridCfg?.keywordWeight, queryPrefix: config.embedding.queryPrefix });
|
|
426
|
+
const results = dedupeResults(rawResults);
|
|
398
427
|
if (results.length === 0) {
|
|
399
|
-
logCliInfo(logFilePath, "query", "No results found.");
|
|
428
|
+
logCliInfo(logFilePath, "query", c.warn("No results found."));
|
|
400
429
|
return;
|
|
401
430
|
}
|
|
402
431
|
const duration = formatDuration(Date.now() - started);
|
|
403
|
-
logCliInfo(logFilePath, "query", `\n${results.length} result(s) in ${duration}:\n`);
|
|
432
|
+
logCliInfo(logFilePath, "query", `\n${c.num(results.length)} result(s) in ${duration}:\n`);
|
|
404
433
|
for (const r of results) {
|
|
405
|
-
logCliInfo(logFilePath, "query", ` ${r.chunk.metadata.filePath}:${r.chunk.metadata.startLine}-${r.chunk.metadata.endLine}`);
|
|
406
|
-
logCliInfo(logFilePath, "query", ` Score: ${r.score.toFixed(4)}`);
|
|
407
|
-
logCliInfo(logFilePath, "query", ` ${r.chunk.content.slice(0, 200).replace(/\n/g, "\n ")}`);
|
|
434
|
+
logCliInfo(logFilePath, "query", ` ${c.file(r.chunk.metadata.filePath)}:${c.value(String(r.chunk.metadata.startLine))}-${c.value(String(r.chunk.metadata.endLine))}`);
|
|
435
|
+
logCliInfo(logFilePath, "query", ` ${c.label("Score:")} ${c.score(r.score.toFixed(4))}`);
|
|
436
|
+
logCliInfo(logFilePath, "query", ` ${pc.dim(r.chunk.content.slice(0, 200).replace(/\n/g, "\n "))}`);
|
|
408
437
|
}
|
|
409
438
|
}
|
|
410
439
|
catch (err) {
|
|
411
440
|
const message = err.message || String(err);
|
|
412
441
|
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
413
|
-
logCliError(logFilePath, "query",
|
|
414
|
-
console.error(`\nQuery failed: ${message}`);
|
|
442
|
+
logCliError(logFilePath, "query", `\nQuery failed: ${message}`, err);
|
|
415
443
|
if (message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")) {
|
|
416
|
-
console.error("Hint: Is your embedding provider running?");
|
|
444
|
+
console.error(c.warn("Hint: Is your embedding provider running?"));
|
|
417
445
|
}
|
|
418
446
|
process.exit(1);
|
|
419
447
|
}
|
|
@@ -432,20 +460,18 @@ program
|
|
|
432
460
|
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
433
461
|
const prevCount = await store.count();
|
|
434
462
|
if (prevCount === 0) {
|
|
435
|
-
logCliInfo(logFilePath, "clear", "No indexed data to clear.");
|
|
436
|
-
|
|
463
|
+
logCliInfo(logFilePath, "clear", c.warn("No indexed data to clear."));
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
logCliInfo(logFilePath, "clear", `${c.label("Clearing")} ${c.num(prevCount)} indexed chunks...`);
|
|
437
467
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const { KeywordIndex } = await import("./retriever/keyword-index.js");
|
|
441
|
-
await KeywordIndex.clearFile(path.resolve(cwd, config.vectorStore.path));
|
|
442
|
-
logCliInfo(logFilePath, "clear", `Done. ${prevCount} chunks removed, keyword index cleared.`);
|
|
468
|
+
await store.dropDatabase();
|
|
469
|
+
logCliInfo(logFilePath, "clear", `${c.success("Done.")} vector database directory removed.`);
|
|
443
470
|
}
|
|
444
471
|
catch (err) {
|
|
445
472
|
const message = err.message || String(err);
|
|
446
473
|
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
447
|
-
logCliError(logFilePath, "clear",
|
|
448
|
-
console.error(`\nClear failed: ${message}`);
|
|
474
|
+
logCliError(logFilePath, "clear", `\nClear failed: ${message}`, err);
|
|
449
475
|
process.exit(1);
|
|
450
476
|
}
|
|
451
477
|
});
|
|
@@ -463,33 +489,132 @@ program
|
|
|
463
489
|
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
464
490
|
const count = await store.count();
|
|
465
491
|
const summary = await getIndexStatusSummary(cwd, path.resolve(cwd, config.vectorStore.path), config, store);
|
|
466
|
-
logCliInfo(logFilePath, "status", `\
|
|
467
|
-
logCliInfo(logFilePath, "status",
|
|
468
|
-
logCliInfo(logFilePath, "status",
|
|
469
|
-
logCliInfo(logFilePath, "status",
|
|
470
|
-
logCliInfo(logFilePath, "status",
|
|
471
|
-
logCliInfo(logFilePath, "status",
|
|
472
|
-
logCliInfo(logFilePath, "status",
|
|
473
|
-
logCliInfo(logFilePath, "status",
|
|
474
|
-
logCliInfo(logFilePath, "status",
|
|
475
|
-
logCliInfo(logFilePath, "status",
|
|
476
|
-
logCliInfo(logFilePath, "status",
|
|
477
|
-
logCliInfo(logFilePath, "status",
|
|
478
|
-
logCliInfo(logFilePath, "status",
|
|
479
|
-
logCliInfo(logFilePath, "status",
|
|
492
|
+
logCliInfo(logFilePath, "status", `\n${c.heading("Indexed chunks:")} ${c.num(count)}`);
|
|
493
|
+
logCliInfo(logFilePath, "status", `${c.label("Store path:")} ${c.file(path.resolve(cwd, config.vectorStore.path))}`);
|
|
494
|
+
logCliInfo(logFilePath, "status", `${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
|
|
495
|
+
logCliInfo(logFilePath, "status", `${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
|
|
496
|
+
logCliInfo(logFilePath, "status", `${c.label("File extensions:")} ${config.indexing.includeExtensions.join(", ")}`);
|
|
497
|
+
logCliInfo(logFilePath, "status", `${c.label("Excluded dirs:")} ${config.indexing.excludeDirs.join(", ")}`);
|
|
498
|
+
logCliInfo(logFilePath, "status", `${c.label("Default top-K:")} ${c.num(config.retrieval.topK)}`);
|
|
499
|
+
logCliInfo(logFilePath, "status", `${c.label("Plugin enabled:")} ${config.openCode.enabled ? c.enabled("yes") : c.disabled("no")}`);
|
|
500
|
+
logCliInfo(logFilePath, "status", `${c.label("Manifest status:")} ${summary.manifestStatus}`);
|
|
501
|
+
logCliInfo(logFilePath, "status", `${c.label("Manifest entries:")} ${c.num(summary.manifestEntries)}`);
|
|
502
|
+
logCliInfo(logFilePath, "status", `${c.label("Last indexed:")} ${c.value(formatTimestamp(summary.lastIndexedAt))}`);
|
|
503
|
+
logCliInfo(logFilePath, "status", `${c.label("Up-to-date files:")} ${c.num(summary.upToDateFiles)}`);
|
|
504
|
+
logCliInfo(logFilePath, "status", `${c.label("Pending files:")} ${c.num(summary.pendingFiles)}`);
|
|
505
|
+
logCliInfo(logFilePath, "status", `${c.label("Watch mode:")} ${c.dim("off")}`);
|
|
480
506
|
const kiCount = config.retrieval.hybridSearch?.enabled
|
|
481
507
|
? (await loadCliKeywordIndex(path.resolve(cwd, config.vectorStore.path), logFilePath))?.count() ?? 0
|
|
482
508
|
: 0;
|
|
483
|
-
logCliInfo(logFilePath, "status",
|
|
509
|
+
logCliInfo(logFilePath, "status", `${c.label("Keyword index:")} ${config.retrieval.hybridSearch?.enabled ? c.enabled("enabled") : c.disabled("disabled")} (${c.num(kiCount)} chunks)`);
|
|
484
510
|
if (summary.rebuildRequired) {
|
|
485
|
-
logCliInfo(logFilePath, "status",
|
|
511
|
+
logCliInfo(logFilePath, "status", `${c.label("Rebuild required:")} ${c.warn("yes")} (manifest missing/corrupt)`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (err) {
|
|
515
|
+
const message = err.message || String(err);
|
|
516
|
+
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
517
|
+
logCliError(logFilePath, "status", `\nStatus check failed: ${message}`, err);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
program
|
|
522
|
+
.command("list")
|
|
523
|
+
.description("List all indexed files with chunk counts")
|
|
524
|
+
.option("-c, --config <path>", "path to config file")
|
|
525
|
+
.action(async (options) => {
|
|
526
|
+
try {
|
|
527
|
+
const cwd = process.cwd();
|
|
528
|
+
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
529
|
+
const config = await resolveConfig(options, logFilePath);
|
|
530
|
+
logFilePath = path.resolve(cwd, resolveLogConfig(config).logFilePath);
|
|
531
|
+
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
532
|
+
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
533
|
+
const files = await store.listFiles();
|
|
534
|
+
if (files.length === 0) {
|
|
535
|
+
logCliInfo(logFilePath, "list", `${c.warn("No indexed files found.")} Run 'opencode-rag index' first.`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
logCliInfo(logFilePath, "list", `\n${c.num(files.length)} file(s) indexed:\n`);
|
|
539
|
+
for (const f of files) {
|
|
540
|
+
logCliInfo(logFilePath, "list", ` ${c.file(f.filePath)} ${c.label("(")}${c.lang(f.language)}${c.label(", ")}${c.num(f.chunkCount)} chunk${f.chunkCount === 1 ? "" : "s"}${c.label(")")}`);
|
|
486
541
|
}
|
|
487
542
|
}
|
|
488
543
|
catch (err) {
|
|
489
544
|
const message = err.message || String(err);
|
|
490
545
|
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
491
|
-
logCliError(logFilePath, "
|
|
492
|
-
|
|
546
|
+
logCliError(logFilePath, "list", `\nList failed: ${message}`, err);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
program
|
|
551
|
+
.command("show <file>")
|
|
552
|
+
.description("Show chunks for a specific file")
|
|
553
|
+
.option("-c, --config <path>", "path to config file")
|
|
554
|
+
.action(async (file, options) => {
|
|
555
|
+
try {
|
|
556
|
+
const cwd = process.cwd();
|
|
557
|
+
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
558
|
+
const config = await resolveConfig(options, logFilePath);
|
|
559
|
+
logFilePath = path.resolve(cwd, resolveLogConfig(config).logFilePath);
|
|
560
|
+
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
561
|
+
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
562
|
+
const chunks = await store.getChunksByFilePath(file);
|
|
563
|
+
if (chunks.length === 0) {
|
|
564
|
+
logCliInfo(logFilePath, "show", `${c.warn(`No chunks found for '${file}'.`)}`);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
logCliInfo(logFilePath, "show", `\n${c.num(chunks.length)} chunk(s) for ${c.file(file)}:\n`);
|
|
568
|
+
for (const chunk of chunks) {
|
|
569
|
+
logCliInfo(logFilePath, "show", ` ${c.label("[")}${c.value(String(chunk.metadata.startLine))}${c.label("-")}${c.value(String(chunk.metadata.endLine))}${c.label("]")} ${c.label("(")}${c.lang(chunk.metadata.language)}${c.label(")")} ${pc.dim(chunk.id)}`);
|
|
570
|
+
if (chunk.description) {
|
|
571
|
+
logCliInfo(logFilePath, "show", ` ${c.desc(">")} ${c.desc(chunk.description)}`);
|
|
572
|
+
}
|
|
573
|
+
logCliInfo(logFilePath, "show", ` ${chunk.content}\n`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
const message = err.message || String(err);
|
|
578
|
+
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
579
|
+
logCliError(logFilePath, "show", `\nShow failed: ${message}`, err);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
program
|
|
584
|
+
.command("dump")
|
|
585
|
+
.description("Dump all indexed chunks")
|
|
586
|
+
.option("-c, --config <path>", "path to config file")
|
|
587
|
+
.option("--offset <number>", "start at chunk offset", "0")
|
|
588
|
+
.option("--limit <number>", "max number of chunks to dump", "25")
|
|
589
|
+
.action(async (options) => {
|
|
590
|
+
try {
|
|
591
|
+
const cwd = process.cwd();
|
|
592
|
+
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
593
|
+
const config = await resolveConfig(options, logFilePath);
|
|
594
|
+
logFilePath = path.resolve(cwd, resolveLogConfig(config).logFilePath);
|
|
595
|
+
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
596
|
+
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
597
|
+
const total = await store.count();
|
|
598
|
+
if (total === 0) {
|
|
599
|
+
logCliInfo(logFilePath, "dump", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const offset = parseInt(options.offset ?? "0", 10);
|
|
603
|
+
const limit = parseInt(options.limit ?? "25", 10);
|
|
604
|
+
const chunks = await store.getChunks(offset, limit);
|
|
605
|
+
logCliInfo(logFilePath, "dump", `\n${c.heading("Chunks")} ${c.value(String(offset + 1))}${c.label("-")}${c.value(String(offset + chunks.length))} of ${c.num(total)}:\n`);
|
|
606
|
+
for (const chunk of chunks) {
|
|
607
|
+
logCliInfo(logFilePath, "dump", ` ${c.file(chunk.filePath)}:${c.value(String(chunk.startLine))}${c.label("-")}${c.value(String(chunk.endLine))} ${c.label("(")}${c.lang(chunk.language)}${c.label(")")}`);
|
|
608
|
+
logCliInfo(logFilePath, "dump", ` ${chunk.content}\n`);
|
|
609
|
+
}
|
|
610
|
+
if (offset + limit < total) {
|
|
611
|
+
logCliInfo(logFilePath, "dump", ` ${c.dim(`... ${total - offset - limit} more (use --offset ${offset + limit} to continue)`)}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
const message = err.message || String(err);
|
|
616
|
+
const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
|
|
617
|
+
logCliError(logFilePath, "dump", `\nDump failed: ${message}`, err);
|
|
493
618
|
process.exit(1);
|
|
494
619
|
}
|
|
495
620
|
});
|
|
@@ -557,125 +682,143 @@ program
|
|
|
557
682
|
const tuiPluginEntryPath = path.join(pluginsDir, "rag-tui.js");
|
|
558
683
|
const tuiConfigPath = path.join(opencodeDir, "tui.json");
|
|
559
684
|
const opencodePackagePath = path.join(opencodeDir, "package.json");
|
|
560
|
-
console.log("Initializing OpenCodeRAG in workspace
|
|
685
|
+
console.log(`\n${c.heading("Initializing OpenCodeRAG in workspace...")}\n`);
|
|
561
686
|
if (!existsSync(opencodeDir)) {
|
|
562
687
|
mkdirSync(opencodeDir, { recursive: true });
|
|
563
|
-
console.log("
|
|
688
|
+
console.log(` ${c.created("Created:")} .opencode/`);
|
|
564
689
|
}
|
|
565
690
|
else {
|
|
566
|
-
console.log("
|
|
691
|
+
console.log(` ${c.exists("Exists:")} .opencode/`);
|
|
567
692
|
}
|
|
568
693
|
if (!existsSync(pluginsDir)) {
|
|
569
694
|
mkdirSync(pluginsDir, { recursive: true });
|
|
570
|
-
console.log("
|
|
695
|
+
console.log(` ${c.created("Created:")} .opencode/plugins/`);
|
|
571
696
|
}
|
|
572
697
|
else {
|
|
573
|
-
console.log("
|
|
698
|
+
console.log(` ${c.exists("Exists:")} .opencode/plugins/`);
|
|
574
699
|
}
|
|
575
700
|
const gitignoreExists = existsSync(gitignorePath);
|
|
576
701
|
const nextGitignoreContent = mergeGitignoreContent(gitignoreExists ? readFileSync(gitignorePath, "utf-8") : undefined);
|
|
577
702
|
if (!gitignoreExists || options.force || readFileSync(gitignorePath, "utf-8") !== nextGitignoreContent) {
|
|
578
703
|
writeFileSync(gitignorePath, nextGitignoreContent, "utf-8");
|
|
579
|
-
console.log(` ${gitignoreExists ? "Updated" : "Created"}
|
|
704
|
+
console.log(` ${gitignoreExists ? c.updated("Updated:") : c.created("Created:")} .opencode/.gitignore`);
|
|
580
705
|
}
|
|
581
706
|
else {
|
|
582
|
-
console.log("
|
|
707
|
+
console.log(` ${c.exists("Exists:")} .opencode/.gitignore`);
|
|
583
708
|
}
|
|
584
709
|
const opencodeConfigExists = existsSync(opencodeConfigPath);
|
|
585
710
|
const nextOpencodeConfig = buildOpencodeConfig(readJsonObject(opencodeConfigPath));
|
|
586
711
|
if (!opencodeConfigExists || options.force) {
|
|
587
712
|
writeJsonFile(opencodeConfigPath, nextOpencodeConfig);
|
|
588
|
-
console.log(` ${opencodeConfigExists ? "Updated" : "Created"}
|
|
713
|
+
console.log(` ${opencodeConfigExists ? c.updated("Updated:") : c.created("Created:")} .opencode/opencode.json`);
|
|
589
714
|
}
|
|
590
715
|
else if (JSON.stringify(readJsonObject(opencodeConfigPath)) !== JSON.stringify(nextOpencodeConfig)) {
|
|
591
716
|
writeJsonFile(opencodeConfigPath, nextOpencodeConfig);
|
|
592
|
-
console.log("
|
|
717
|
+
console.log(` ${c.updated("Updated:")} .opencode/opencode.json`);
|
|
593
718
|
}
|
|
594
719
|
else {
|
|
595
|
-
console.log("
|
|
720
|
+
console.log(` ${c.exists("Exists:")} .opencode/opencode.json`);
|
|
596
721
|
}
|
|
597
722
|
const pluginEntryExists = existsSync(pluginEntryPath);
|
|
598
723
|
const pluginEntryContent = generateWorkspacePluginFile(packageMetadata.name);
|
|
599
724
|
if (!pluginEntryExists || options.force) {
|
|
600
725
|
writeFileSync(pluginEntryPath, pluginEntryContent, "utf-8");
|
|
601
|
-
console.log(` ${pluginEntryExists ? "Updated" : "Created"}
|
|
726
|
+
console.log(` ${pluginEntryExists ? c.updated("Updated:") : c.created("Created:")} .opencode/plugins/rag-plugin.js`);
|
|
602
727
|
}
|
|
603
728
|
else if (readFileSync(pluginEntryPath, "utf-8") !== pluginEntryContent) {
|
|
604
729
|
writeFileSync(pluginEntryPath, pluginEntryContent, "utf-8");
|
|
605
|
-
console.log("
|
|
730
|
+
console.log(` ${c.updated("Updated:")} .opencode/plugins/rag-plugin.js`);
|
|
606
731
|
}
|
|
607
732
|
else {
|
|
608
|
-
console.log("
|
|
733
|
+
console.log(` ${c.exists("Exists:")} .opencode/plugins/rag-plugin.js`);
|
|
609
734
|
}
|
|
610
735
|
const tuiPluginEntryExists = existsSync(tuiPluginEntryPath);
|
|
611
736
|
const tuiPluginEntryContent = generateWorkspaceTuiPluginFile(packageMetadata.name);
|
|
612
737
|
if (!tuiPluginEntryExists || options.force) {
|
|
613
738
|
writeFileSync(tuiPluginEntryPath, tuiPluginEntryContent, "utf-8");
|
|
614
|
-
console.log(` ${tuiPluginEntryExists ? "Updated" : "Created"}
|
|
739
|
+
console.log(` ${tuiPluginEntryExists ? c.updated("Updated:") : c.created("Created:")} .opencode/plugins/rag-tui.js`);
|
|
615
740
|
}
|
|
616
741
|
else if (readFileSync(tuiPluginEntryPath, "utf-8") !== tuiPluginEntryContent) {
|
|
617
742
|
writeFileSync(tuiPluginEntryPath, tuiPluginEntryContent, "utf-8");
|
|
618
|
-
console.log("
|
|
743
|
+
console.log(` ${c.updated("Updated:")} .opencode/plugins/rag-tui.js`);
|
|
619
744
|
}
|
|
620
745
|
else {
|
|
621
|
-
console.log("
|
|
746
|
+
console.log(` ${c.exists("Exists:")} .opencode/plugins/rag-tui.js`);
|
|
622
747
|
}
|
|
623
748
|
const tuiConfigExists = existsSync(tuiConfigPath);
|
|
624
749
|
const nextTuiConfig = { plugin: ["./plugins/rag-tui.js"] };
|
|
625
750
|
if (!tuiConfigExists || options.force) {
|
|
626
751
|
writeJsonFile(tuiConfigPath, nextTuiConfig);
|
|
627
|
-
console.log(` ${tuiConfigExists ? "Updated" : "Created"}
|
|
752
|
+
console.log(` ${tuiConfigExists ? c.updated("Updated:") : c.created("Created:")} .opencode/tui.json`);
|
|
628
753
|
}
|
|
629
754
|
else if (JSON.stringify(readJsonObject(tuiConfigPath)) !== JSON.stringify(nextTuiConfig)) {
|
|
630
755
|
writeJsonFile(tuiConfigPath, nextTuiConfig);
|
|
631
|
-
console.log("
|
|
756
|
+
console.log(` ${c.updated("Updated:")} .opencode/tui.json`);
|
|
632
757
|
}
|
|
633
758
|
else {
|
|
634
|
-
console.log("
|
|
759
|
+
console.log(` ${c.exists("Exists:")} .opencode/tui.json`);
|
|
635
760
|
}
|
|
636
761
|
const workspacePackageExists = existsSync(opencodePackagePath);
|
|
637
762
|
const nextWorkspacePackage = buildWorkspacePackageJson(readJsonObject(opencodePackagePath), packageMetadata, opencodeDir);
|
|
638
763
|
if (!workspacePackageExists || options.force) {
|
|
639
764
|
writeJsonFile(opencodePackagePath, nextWorkspacePackage);
|
|
640
|
-
console.log(` ${workspacePackageExists ? "Updated" : "Created"}
|
|
765
|
+
console.log(` ${workspacePackageExists ? c.updated("Updated:") : c.created("Created:")} .opencode/package.json`);
|
|
641
766
|
}
|
|
642
767
|
else if (JSON.stringify(readJsonObject(opencodePackagePath)) !== JSON.stringify(nextWorkspacePackage)) {
|
|
643
768
|
writeJsonFile(opencodePackagePath, nextWorkspacePackage);
|
|
644
|
-
console.log("
|
|
769
|
+
console.log(` ${c.updated("Updated:")} .opencode/package.json`);
|
|
645
770
|
}
|
|
646
771
|
else {
|
|
647
|
-
console.log("
|
|
772
|
+
console.log(` ${c.exists("Exists:")} .opencode/package.json`);
|
|
648
773
|
}
|
|
649
774
|
const configExists = existsSync(configPath);
|
|
650
775
|
if (!configExists || options.force) {
|
|
651
776
|
writeFileSync(configPath, generateDefaultConfigJson(), "utf-8");
|
|
652
|
-
console.log(` ${configExists ? "Updated" : "Created"}
|
|
777
|
+
console.log(` ${configExists ? c.updated("Updated:") : c.created("Created:")} opencode-rag.json`);
|
|
653
778
|
}
|
|
654
779
|
else {
|
|
655
|
-
console.log("
|
|
780
|
+
console.log(` ${c.exists("Exists:")} opencode-rag.json`);
|
|
656
781
|
}
|
|
657
782
|
if (!options.skipInstall) {
|
|
658
|
-
console.log("
|
|
783
|
+
console.log(`\n${c.heading("Installing workspace-local plugin dependencies...")}\n`);
|
|
659
784
|
installWorkspaceDependencies(opencodeDir);
|
|
660
|
-
console.log(
|
|
785
|
+
console.log(`\n ${c.success("Installed:")} .opencode/node_modules/`);
|
|
661
786
|
const updatedGlobalConfigs = removeStaleGlobalPluginRegistrations(os.homedir(), packageMetadata.name);
|
|
662
787
|
if (updatedGlobalConfigs.length > 0) {
|
|
663
788
|
for (const configPath of updatedGlobalConfigs) {
|
|
664
|
-
console.log(` Removed stale plugin registration from ${configPath}`);
|
|
789
|
+
console.log(` ${c.warn("Removed stale plugin registration from")} ${configPath}`);
|
|
665
790
|
}
|
|
666
791
|
}
|
|
667
|
-
console.log("
|
|
792
|
+
console.log(` ${c.dim("OpenCode loads the plugin from .opencode/plugins/rag-plugin.js; no global plugin registration is required.")}`);
|
|
668
793
|
}
|
|
669
794
|
else {
|
|
670
|
-
console.log(
|
|
795
|
+
console.log(`\n ${c.exists("Skipped:")} dependency installation (--skip-install)`);
|
|
671
796
|
}
|
|
672
|
-
console.log("
|
|
797
|
+
console.log(`\n${c.success("Done.")} Restart OpenCode if it is running, then run ${c.file("'opencode-rag index'")} in this workspace.`);
|
|
673
798
|
});
|
|
674
799
|
/**
|
|
675
800
|
* Determine whether the CLI should auto-run for the current module.
|
|
676
801
|
* Resolves the first argv entry so symlinked binaries compare against the
|
|
677
802
|
* real file path, and returns false if the path cannot be resolved.
|
|
678
803
|
*/
|
|
804
|
+
function dedupeResults(results) {
|
|
805
|
+
const seen = new Set();
|
|
806
|
+
const deduped = [];
|
|
807
|
+
for (const result of results) {
|
|
808
|
+
const chunk = result.chunk;
|
|
809
|
+
const key = [
|
|
810
|
+
chunk.metadata.filePath,
|
|
811
|
+
chunk.metadata.startLine,
|
|
812
|
+
chunk.metadata.endLine,
|
|
813
|
+
chunk.content,
|
|
814
|
+
].join(":");
|
|
815
|
+
if (seen.has(key))
|
|
816
|
+
continue;
|
|
817
|
+
seen.add(key);
|
|
818
|
+
deduped.push(result);
|
|
819
|
+
}
|
|
820
|
+
return deduped;
|
|
821
|
+
}
|
|
679
822
|
export function shouldAutoRunCli(moduleUrl, argv1) {
|
|
680
823
|
if (!argv1) {
|
|
681
824
|
return false;
|
|
@@ -695,7 +838,7 @@ if (shouldAutoRunCli(import.meta.url, process.argv[1])) {
|
|
|
695
838
|
else {
|
|
696
839
|
// Fallback: if the module appears to be running as a CLI (has argv with commands like 'init', 'index', etc.)
|
|
697
840
|
// and not being imported as a library, parse the arguments anyway
|
|
698
|
-
const commands = ['init', 'index', 'query', 'clear', 'status'];
|
|
841
|
+
const commands = ['init', 'index', 'query', 'clear', 'status', 'list', 'show', 'dump'];
|
|
699
842
|
const cmd = process.argv[2];
|
|
700
843
|
if (process.argv.length > 2 && cmd && commands.includes(cmd.toLowerCase())) {
|
|
701
844
|
void program.parseAsync(process.argv);
|