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