opencode-rag-plugin 1.12.17 → 1.14.7

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.
Files changed (336) hide show
  1. package/ReadMe.md +15 -13
  2. package/dist/api.d.ts +54 -2
  3. package/dist/api.js +41 -1
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunker/base.js +2 -0
  6. package/dist/chunker/base.js.map +1 -1
  7. package/dist/chunker/bash.d.ts +5 -0
  8. package/dist/chunker/bash.js +5 -0
  9. package/dist/chunker/bash.js.map +1 -1
  10. package/dist/chunker/c.d.ts +6 -0
  11. package/dist/chunker/c.js +6 -0
  12. package/dist/chunker/c.js.map +1 -1
  13. package/dist/chunker/cpp.d.ts +6 -0
  14. package/dist/chunker/cpp.js +6 -0
  15. package/dist/chunker/cpp.js.map +1 -1
  16. package/dist/chunker/csharp.d.ts +6 -0
  17. package/dist/chunker/csharp.js +6 -0
  18. package/dist/chunker/csharp.js.map +1 -1
  19. package/dist/chunker/css.d.ts +6 -0
  20. package/dist/chunker/css.js +6 -0
  21. package/dist/chunker/css.js.map +1 -1
  22. package/dist/chunker/doc.d.ts +18 -0
  23. package/dist/chunker/doc.js +18 -0
  24. package/dist/chunker/doc.js.map +1 -1
  25. package/dist/chunker/dockerfile.d.ts +6 -0
  26. package/dist/chunker/dockerfile.js +6 -0
  27. package/dist/chunker/dockerfile.js.map +1 -1
  28. package/dist/chunker/docx.d.ts +18 -0
  29. package/dist/chunker/docx.js +18 -0
  30. package/dist/chunker/docx.js.map +1 -1
  31. package/dist/chunker/excel.d.ts +19 -0
  32. package/dist/chunker/excel.js +19 -0
  33. package/dist/chunker/excel.js.map +1 -1
  34. package/dist/chunker/go.d.ts +5 -0
  35. package/dist/chunker/go.js +5 -0
  36. package/dist/chunker/go.js.map +1 -1
  37. package/dist/chunker/grammar.d.ts +62 -0
  38. package/dist/chunker/grammar.js +133 -0
  39. package/dist/chunker/grammar.js.map +1 -1
  40. package/dist/chunker/html.d.ts +5 -0
  41. package/dist/chunker/html.js +5 -0
  42. package/dist/chunker/html.js.map +1 -1
  43. package/dist/chunker/image.d.ts +31 -0
  44. package/dist/chunker/image.js +50 -4
  45. package/dist/chunker/image.js.map +1 -1
  46. package/dist/chunker/ini.d.ts +5 -0
  47. package/dist/chunker/ini.js +5 -0
  48. package/dist/chunker/ini.js.map +1 -1
  49. package/dist/chunker/java.d.ts +5 -0
  50. package/dist/chunker/java.js +5 -0
  51. package/dist/chunker/java.js.map +1 -1
  52. package/dist/chunker/javascript.d.ts +6 -0
  53. package/dist/chunker/javascript.js +6 -0
  54. package/dist/chunker/javascript.js.map +1 -1
  55. package/dist/chunker/json.d.ts +5 -0
  56. package/dist/chunker/json.js +5 -0
  57. package/dist/chunker/json.js.map +1 -1
  58. package/dist/chunker/kotlin.d.ts +6 -0
  59. package/dist/chunker/kotlin.js +6 -0
  60. package/dist/chunker/kotlin.js.map +1 -1
  61. package/dist/chunker/loader.d.ts +11 -0
  62. package/dist/chunker/loader.js +20 -0
  63. package/dist/chunker/loader.js.map +1 -1
  64. package/dist/chunker/markdown.d.ts +5 -0
  65. package/dist/chunker/markdown.js +5 -0
  66. package/dist/chunker/markdown.js.map +1 -1
  67. package/dist/chunker/pdf.d.ts +19 -0
  68. package/dist/chunker/pdf.js +30 -0
  69. package/dist/chunker/pdf.js.map +1 -1
  70. package/dist/chunker/php.d.ts +5 -0
  71. package/dist/chunker/php.js +5 -0
  72. package/dist/chunker/php.js.map +1 -1
  73. package/dist/chunker/powershell.d.ts +5 -0
  74. package/dist/chunker/powershell.js +5 -0
  75. package/dist/chunker/powershell.js.map +1 -1
  76. package/dist/chunker/python.d.ts +5 -0
  77. package/dist/chunker/python.js +5 -0
  78. package/dist/chunker/python.js.map +1 -1
  79. package/dist/chunker/razor.d.ts +12 -0
  80. package/dist/chunker/razor.js +25 -0
  81. package/dist/chunker/razor.js.map +1 -1
  82. package/dist/chunker/ruby.d.ts +5 -0
  83. package/dist/chunker/ruby.js +5 -0
  84. package/dist/chunker/ruby.js.map +1 -1
  85. package/dist/chunker/rust.d.ts +6 -0
  86. package/dist/chunker/rust.js +6 -0
  87. package/dist/chunker/rust.js.map +1 -1
  88. package/dist/chunker/sln.d.ts +11 -0
  89. package/dist/chunker/sln.js +11 -0
  90. package/dist/chunker/sln.js.map +1 -1
  91. package/dist/chunker/sql.d.ts +5 -0
  92. package/dist/chunker/sql.js +5 -0
  93. package/dist/chunker/sql.js.map +1 -1
  94. package/dist/chunker/ssl.d.ts +12 -0
  95. package/dist/chunker/ssl.js +21 -2
  96. package/dist/chunker/ssl.js.map +1 -1
  97. package/dist/chunker/swift.d.ts +5 -0
  98. package/dist/chunker/swift.js +5 -0
  99. package/dist/chunker/swift.js.map +1 -1
  100. package/dist/chunker/tex.d.ts +12 -0
  101. package/dist/chunker/tex.js +12 -0
  102. package/dist/chunker/tex.js.map +1 -1
  103. package/dist/chunker/toml.d.ts +5 -0
  104. package/dist/chunker/toml.js +5 -0
  105. package/dist/chunker/toml.js.map +1 -1
  106. package/dist/chunker/typescript.d.ts +6 -0
  107. package/dist/chunker/typescript.js +6 -0
  108. package/dist/chunker/typescript.js.map +1 -1
  109. package/dist/chunker/xml.d.ts +5 -0
  110. package/dist/chunker/xml.js +5 -0
  111. package/dist/chunker/xml.js.map +1 -1
  112. package/dist/chunker/yaml.d.ts +5 -0
  113. package/dist/chunker/yaml.js +5 -0
  114. package/dist/chunker/yaml.js.map +1 -1
  115. package/dist/cli/commands/clear.d.ts +12 -0
  116. package/dist/cli/commands/clear.js +81 -0
  117. package/dist/cli/commands/clear.js.map +1 -0
  118. package/dist/cli/commands/describe-image.d.ts +14 -0
  119. package/dist/cli/commands/describe-image.js +68 -0
  120. package/dist/cli/commands/describe-image.js.map +1 -0
  121. package/dist/cli/commands/dump.d.ts +13 -0
  122. package/dist/cli/commands/dump.js +55 -0
  123. package/dist/cli/commands/dump.js.map +1 -0
  124. package/dist/cli/commands/eval.d.ts +32 -0
  125. package/dist/cli/commands/eval.js +158 -0
  126. package/dist/cli/commands/eval.js.map +1 -0
  127. package/dist/cli/commands/index-command.d.ts +17 -0
  128. package/dist/cli/commands/index-command.js +167 -0
  129. package/dist/cli/commands/index-command.js.map +1 -0
  130. package/dist/cli/commands/index.d.ts +16 -0
  131. package/dist/cli/commands/index.js +17 -0
  132. package/dist/cli/commands/index.js.map +1 -0
  133. package/dist/cli/commands/init-helpers.d.ts +101 -0
  134. package/dist/cli/commands/init-helpers.js +398 -0
  135. package/dist/cli/commands/init-helpers.js.map +1 -0
  136. package/dist/cli/commands/init.d.ts +19 -0
  137. package/dist/cli/commands/init.js +251 -0
  138. package/dist/cli/commands/init.js.map +1 -0
  139. package/dist/cli/commands/list.d.ts +13 -0
  140. package/dist/cli/commands/list.js +46 -0
  141. package/dist/cli/commands/list.js.map +1 -0
  142. package/dist/cli/commands/mcp.d.ts +14 -0
  143. package/dist/cli/commands/mcp.js +35 -0
  144. package/dist/cli/commands/mcp.js.map +1 -0
  145. package/dist/cli/commands/query.d.ts +14 -0
  146. package/dist/cli/commands/query.js +86 -0
  147. package/dist/cli/commands/query.js.map +1 -0
  148. package/dist/cli/commands/show.d.ts +13 -0
  149. package/dist/cli/commands/show.js +51 -0
  150. package/dist/cli/commands/show.js.map +1 -0
  151. package/dist/cli/commands/status.d.ts +14 -0
  152. package/dist/cli/commands/status.js +104 -0
  153. package/dist/cli/commands/status.js.map +1 -0
  154. package/dist/cli/commands/ui.d.ts +14 -0
  155. package/dist/cli/commands/ui.js +69 -0
  156. package/dist/cli/commands/ui.js.map +1 -0
  157. package/dist/cli/commands/update.d.ts +14 -0
  158. package/dist/cli/commands/update.js +76 -0
  159. package/dist/cli/commands/update.js.map +1 -0
  160. package/dist/cli/format.d.ts +112 -0
  161. package/dist/cli/format.js +184 -0
  162. package/dist/cli/format.js.map +1 -0
  163. package/dist/cli/helpers.d.ts +51 -0
  164. package/dist/cli/helpers.js +86 -0
  165. package/dist/cli/helpers.js.map +1 -0
  166. package/dist/cli/index.d.ts +28 -0
  167. package/dist/cli/index.js +107 -0
  168. package/dist/cli/index.js.map +1 -0
  169. package/dist/cli/progress.d.ts +39 -0
  170. package/dist/cli/progress.js +137 -0
  171. package/dist/cli/progress.js.map +1 -0
  172. package/dist/cli/types.d.ts +40 -0
  173. package/dist/cli/types.js +3 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/cli.d.ts +11 -4
  176. package/dist/cli.js +9 -1253
  177. package/dist/cli.js.map +1 -1
  178. package/dist/content/doc.d.ts +1 -1
  179. package/dist/content/doc.js +1 -1
  180. package/dist/content/doc.js.map +1 -1
  181. package/dist/content/docx.d.ts +1 -1
  182. package/dist/content/docx.js +1 -1
  183. package/dist/content/docx.js.map +1 -1
  184. package/dist/content/excel.d.ts +1 -1
  185. package/dist/content/excel.js +1 -1
  186. package/dist/content/excel.js.map +1 -1
  187. package/dist/content/pdf.d.ts +1 -1
  188. package/dist/content/pdf.js +1 -1
  189. package/dist/content/pdf.js.map +1 -1
  190. package/dist/content/reader.d.ts +3 -1
  191. package/dist/content/reader.js +42 -10
  192. package/dist/content/reader.js.map +1 -1
  193. package/dist/core/bootstrap.d.ts +18 -0
  194. package/dist/core/bootstrap.js +9 -15
  195. package/dist/core/bootstrap.js.map +1 -1
  196. package/dist/core/config.d.ts +146 -0
  197. package/dist/core/config.js +66 -14
  198. package/dist/core/config.js.map +1 -1
  199. package/dist/core/doc-progress.d.ts +15 -0
  200. package/dist/core/doc-progress.js +61 -0
  201. package/dist/core/doc-progress.js.map +1 -0
  202. package/dist/core/fileLogger.d.ts +7 -0
  203. package/dist/core/fileLogger.js +2 -0
  204. package/dist/core/fileLogger.js.map +1 -1
  205. package/dist/core/interfaces.d.ts +52 -1
  206. package/dist/core/manifest.d.ts +25 -0
  207. package/dist/core/manifest.js +15 -1
  208. package/dist/core/manifest.js.map +1 -1
  209. package/dist/core/provider-defaults.d.ts +8 -0
  210. package/dist/core/provider-defaults.js +3 -0
  211. package/dist/core/provider-defaults.js.map +1 -1
  212. package/dist/core/rag-injection-flag.d.ts +3 -0
  213. package/dist/core/rag-injection-flag.js +2 -0
  214. package/dist/core/rag-injection-flag.js.map +1 -1
  215. package/dist/core/resolve-api-key.d.ts +1 -0
  216. package/dist/core/resolve-api-key.js +1 -0
  217. package/dist/core/resolve-api-key.js.map +1 -1
  218. package/dist/core/runtime-overrides.d.ts +4 -0
  219. package/dist/core/runtime-overrides.js +4 -1
  220. package/dist/core/runtime-overrides.js.map +1 -1
  221. package/dist/describer/anthropic.d.ts +22 -2
  222. package/dist/describer/anthropic.js +43 -111
  223. package/dist/describer/anthropic.js.map +1 -1
  224. package/dist/describer/describer.d.ts +21 -4
  225. package/dist/describer/describer.js +53 -112
  226. package/dist/describer/describer.js.map +1 -1
  227. package/dist/describer/gemini.d.ts +1 -2
  228. package/dist/describer/gemini.js +21 -114
  229. package/dist/describer/gemini.js.map +1 -1
  230. package/dist/describer/shared.d.ts +3 -0
  231. package/dist/describer/shared.js +23 -0
  232. package/dist/describer/shared.js.map +1 -0
  233. package/dist/embedder/factory.d.ts +25 -0
  234. package/dist/embedder/factory.js +25 -0
  235. package/dist/embedder/factory.js.map +1 -1
  236. package/dist/embedder/health.d.ts +10 -0
  237. package/dist/embedder/health.js +26 -14
  238. package/dist/embedder/health.js.map +1 -1
  239. package/dist/embedder/http.d.ts +47 -0
  240. package/dist/embedder/http.js +61 -3
  241. package/dist/embedder/http.js.map +1 -1
  242. package/dist/eval/index.d.ts +6 -0
  243. package/dist/eval/index.js.map +1 -1
  244. package/dist/eval/run-token-test.js +1 -5
  245. package/dist/eval/run-token-test.js.map +1 -1
  246. package/dist/eval/session-logger.d.ts +5 -0
  247. package/dist/eval/session-logger.js +4 -0
  248. package/dist/eval/session-logger.js.map +1 -1
  249. package/dist/eval/storage.d.ts +9 -0
  250. package/dist/eval/storage.js +9 -5
  251. package/dist/eval/storage.js.map +1 -1
  252. package/dist/eval/token-analysis.d.ts +8 -0
  253. package/dist/eval/token-analysis.js +6 -2
  254. package/dist/eval/token-analysis.js.map +1 -1
  255. package/dist/eval/token-counter.d.ts +1 -3
  256. package/dist/eval/token-counter.js +4 -5
  257. package/dist/eval/token-counter.js.map +1 -1
  258. package/dist/eval/types.d.ts +6 -0
  259. package/dist/eval/types.js +2 -0
  260. package/dist/eval/types.js.map +1 -1
  261. package/dist/index.d.ts +11 -0
  262. package/dist/index.js +12 -3
  263. package/dist/index.js.map +1 -1
  264. package/dist/indexer/description-stage.d.ts +1 -1
  265. package/dist/indexer/description-stage.js +25 -5
  266. package/dist/indexer/description-stage.js.map +1 -1
  267. package/dist/indexer/embed-stage.js +0 -8
  268. package/dist/indexer/embed-stage.js.map +1 -1
  269. package/dist/indexer/pipeline.d.ts +27 -3
  270. package/dist/indexer/pipeline.js +325 -143
  271. package/dist/indexer/pipeline.js.map +1 -1
  272. package/dist/indexer/stats.d.ts +35 -0
  273. package/dist/indexer/stats.js +9 -0
  274. package/dist/indexer/stats.js.map +1 -1
  275. package/dist/indexer/watch.d.ts +25 -0
  276. package/dist/indexer/watch.js +21 -0
  277. package/dist/indexer/watch.js.map +1 -1
  278. package/dist/indexer/worker.d.ts +96 -2
  279. package/dist/indexer/worker.js +109 -24
  280. package/dist/indexer/worker.js.map +1 -1
  281. package/dist/indexer.d.ts +11 -1
  282. package/dist/indexer.js +9 -0
  283. package/dist/indexer.js.map +1 -1
  284. package/dist/mcp/cli.d.ts +3 -0
  285. package/dist/mcp/cli.js +1 -0
  286. package/dist/mcp/cli.js.map +1 -1
  287. package/dist/mcp/handlers.d.ts +41 -0
  288. package/dist/mcp/handlers.js +40 -1
  289. package/dist/mcp/handlers.js.map +1 -1
  290. package/dist/mcp/server.d.ts +8 -0
  291. package/dist/mcp/server.js +18 -1
  292. package/dist/mcp/server.js.map +1 -1
  293. package/dist/opencode/create-read-tool.js +1 -0
  294. package/dist/opencode/create-read-tool.js.map +1 -1
  295. package/dist/opencode/tools.d.ts +8 -17
  296. package/dist/opencode/tools.js +74 -109
  297. package/dist/opencode/tools.js.map +1 -1
  298. package/dist/plugin-entry.d.ts +6 -0
  299. package/dist/plugin-entry.js +6 -0
  300. package/dist/plugin-entry.js.map +1 -1
  301. package/dist/plugin.d.ts +37 -0
  302. package/dist/plugin.js +278 -41
  303. package/dist/plugin.js.map +1 -1
  304. package/dist/retriever/keyword-index.js +9 -9
  305. package/dist/retriever/keyword-index.js.map +1 -1
  306. package/dist/retriever/retriever.js +14 -4
  307. package/dist/retriever/retriever.js.map +1 -1
  308. package/dist/tui.d.ts +5 -0
  309. package/dist/tui.js +101 -13
  310. package/dist/tui.js.map +1 -1
  311. package/dist/types/opencode-plugin.d.ts +4 -1
  312. package/dist/updater.d.ts +23 -1
  313. package/dist/updater.js +55 -16
  314. package/dist/updater.js.map +1 -1
  315. package/dist/vectorstore/factory.d.ts +13 -0
  316. package/dist/vectorstore/factory.js +13 -0
  317. package/dist/vectorstore/factory.js.map +1 -1
  318. package/dist/vectorstore/lancedb.d.ts +78 -1
  319. package/dist/vectorstore/lancedb.js +195 -21
  320. package/dist/vectorstore/lancedb.js.map +1 -1
  321. package/dist/vectorstore/memory.d.ts +5 -0
  322. package/dist/vectorstore/memory.js +11 -0
  323. package/dist/vectorstore/memory.js.map +1 -1
  324. package/dist/watcher.d.ts +24 -0
  325. package/dist/watcher.js +17 -35
  326. package/dist/watcher.js.map +1 -1
  327. package/dist/web/api.d.ts +37 -0
  328. package/dist/web/api.js +54 -0
  329. package/dist/web/api.js.map +1 -1
  330. package/dist/web/server.d.ts +15 -0
  331. package/dist/web/server.js +14 -0
  332. package/dist/web/server.js.map +1 -1
  333. package/dist/web/static.d.ts +8 -0
  334. package/dist/web/static.js +8 -0
  335. package/dist/web/static.js.map +1 -1
  336. package/package.json +11 -15
package/dist/cli.js CHANGED
@@ -1,1256 +1,12 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import path from "node:path";
4
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
5
- import os from "node:os";
6
- import { spawnSync } from "node:child_process";
7
- import { fileURLToPath } from "node:url";
8
- import chokidar from "chokidar";
9
- import pc from "picocolors";
10
- import { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
11
- import { resolveRagContext } from "./core/bootstrap.js";
12
- import { getCurrentVersion, checkForUpdate, applyUpdate, } from "./updater.js";
13
- import { appendDebugLog } from "./core/fileLogger.js";
14
- import { checkProviderHealth, pullOllamaModels } from "./embedder/health.js";
15
- import { retrieve } from "./retriever/retriever.js";
16
- import { destroyAllPooledConnections } from "./embedder/http.js";
17
- import { createWatchPassScheduler, createWatchIgnore, getIndexStatusSummary, runIndexPass, } from "./indexer.js";
18
- const c = {
19
- heading: (s) => pc.bold(pc.cyan(s)),
20
- label: (s) => pc.dim(s),
21
- dim: (s) => pc.dim(s),
22
- value: (s) => pc.green(s),
23
- num: (s) => pc.green(String(s)),
24
- file: (s) => pc.yellow(s),
25
- lang: (s) => pc.cyan(s),
26
- score: (s) => pc.magenta(s),
27
- desc: (s) => pc.dim(s),
28
- success: (s) => pc.green(s),
29
- warn: (s) => pc.yellow(s),
30
- error: (s) => pc.red(s),
31
- enabled: (s) => pc.green(s),
32
- disabled: (s) => pc.yellow(s),
33
- created: (s) => pc.green(s),
34
- updated: (s) => pc.yellow(s),
35
- exists: (s) => pc.dim(s),
36
- };
37
- function logCliError(logFilePath, scope, message, error) {
38
- console.error(c.error(message));
39
- //appendDebugLog(logFilePath, { scope, message, error });
40
- }
41
- function logCliInfo(logFilePath, scope, message) {
42
- console.log(message);
43
- //appendDebugLog(logFilePath, { scope, message });
44
- }
45
- async function resolveCliContext(opt, logFilePath) {
46
- const ctx = await resolveRagContext({ configPath: opt.config });
47
- logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(ctx.logFilePath)}`);
48
- logConfigDetails(logFilePath, ctx.config);
49
- return ctx;
50
- }
51
- function logConfigDetails(logFilePath, config) {
52
- logCliInfo(logFilePath, "config", ` ${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
53
- logCliInfo(logFilePath, "config", ` ${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
54
- logCliInfo(logFilePath, "config", ` ${c.label("Vector store:")} ${c.file(config.vectorStore.path)}`);
55
- }
56
- async function cleanupContext(ctx) {
57
- await ctx.store.close();
58
- destroyAllPooledConnections();
59
- }
60
- function formatTimestamp(timestamp) {
61
- if (!timestamp)
62
- return "never";
63
- return new Date(timestamp).toLocaleString();
64
- }
65
- function logIndexSummary(logFilePath, stats) {
66
- logCliInfo(logFilePath, "index", ` ${c.label("New:")} ${c.num(stats.newFiles)}`);
67
- logCliInfo(logFilePath, "index", ` ${c.label("Modified:")} ${c.num(stats.modifiedFiles)}`);
68
- logCliInfo(logFilePath, "index", ` ${c.label("Unchanged:")} ${c.num(stats.unchangedFiles)}`);
69
- logCliInfo(logFilePath, "index", ` ${c.label("Deleted:")} ${c.num(stats.deletedFiles)}`);
70
- logCliInfo(logFilePath, "index", ` ${c.label("Removed:")} ${c.num(stats.removedFiles)}`);
71
- logCliInfo(logFilePath, "index", ` ${c.label("Empty skipped:")} ${c.num(stats.skippedEmptyFiles)}`);
72
- logCliInfo(logFilePath, "index", ` ${c.label("Small skipped:")} ${c.num(stats.skippedSmallFiles)}`);
73
- logCliInfo(logFilePath, "index", ` ${c.label("Chunks written:")} ${c.num(stats.totalChunks)}`);
74
- }
75
- function formatDuration(ms) {
76
- const seconds = (ms / 1000).toFixed(1);
77
- if (ms < 60000)
78
- return `${seconds}s`;
79
- const minutes = Math.floor(ms / 60000);
80
- const secs = ((ms % 60000) / 1000).toFixed(0);
81
- return `${minutes}m ${secs}s`;
82
- }
83
- function getPackageRoot() {
84
- return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
85
- }
86
- function getPackageMetadata() {
87
- const packageJsonPath = path.join(getPackageRoot(), "package.json");
88
- return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
89
- }
90
- function getStringRecord(value) {
91
- if (!value || typeof value !== "object" || Array.isArray(value)) {
92
- return {};
93
- }
94
- return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
95
- }
96
- function readJsonObject(filePath) {
97
- if (!existsSync(filePath)) {
98
- return undefined;
99
- }
100
- return JSON.parse(readFileSync(filePath, "utf-8"));
101
- }
102
- function writeJsonFile(filePath, value) {
103
- writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
104
- }
105
- function toPosixPath(input) {
106
- return input.split(path.sep).join("/");
107
- }
108
- function resolveWorkspacePackageSpecifier(opencodeDir, packageRoot, version) {
109
- const workspaceRoot = path.parse(opencodeDir).root.toLowerCase();
110
- const sourceRoot = path.parse(packageRoot).root.toLowerCase();
111
- if (workspaceRoot === sourceRoot) {
112
- return `file:${toPosixPath(path.relative(opencodeDir, packageRoot))}`;
113
- }
114
- return version;
115
- }
116
- function buildWorkspacePackageJson(existing, packageMetadata, opencodeDir) {
117
- const existingDependencies = getStringRecord(existing?.dependencies);
118
- const pluginVersion = existingDependencies["@opencode-ai/plugin"] ??
119
- packageMetadata.devDependencies?.["@opencode-ai/plugin"] ??
120
- packageMetadata.peerDependencies?.["@opencode-ai/plugin"] ??
121
- ">=1.0.0";
122
- return {
123
- ...existing,
124
- name: typeof existing?.name === "string" ? existing.name : ".opencode",
125
- private: true,
126
- type: "module",
127
- dependencies: {
128
- ...existingDependencies,
129
- "@opencode-ai/plugin": pluginVersion,
130
- [packageMetadata.name]: resolveWorkspacePackageSpecifier(opencodeDir, getPackageRoot(), packageMetadata.version),
131
- },
132
- };
133
- }
134
- function buildOpencodeConfig(existing) {
135
- const next = { ...(existing ?? {}) };
136
- if (typeof next.$schema !== "string") {
137
- next.$schema = "https://opencode.ai/config.json";
138
- }
139
- // Plugin is loaded via .opencode/plugins/rag-plugin.js auto-discovery,
140
- // not via npm package resolution. Stale "plugin" entries from older
141
- // init versions would trigger npm install (which fails due to native
142
- // dependencies like canvas) and produce "Plugin export is not a function".
143
- delete next.plugin;
144
- return next;
145
- }
146
- export function removeStaleGlobalPluginRegistrations(homeDir, pluginName) {
147
- const globalConfigDir = path.join(homeDir, ".config", "opencode");
148
- const updatedPaths = [];
149
- for (const cfgFile of ["opencode.jsonc", "opencode.json"]) {
150
- const configPath = path.join(globalConfigDir, cfgFile);
151
- if (!existsSync(configPath)) {
152
- continue;
153
- }
154
- try {
155
- const cfg = readJsonObject(configPath);
156
- if (!cfg || !Array.isArray(cfg.plugin)) {
157
- continue;
158
- }
159
- const nextPlugins = cfg.plugin.filter((entry) => typeof entry === "string" && entry !== pluginName);
160
- if (nextPlugins.length === cfg.plugin.length) {
161
- continue;
162
- }
163
- if (nextPlugins.length > 0) {
164
- cfg.plugin = nextPlugins;
165
- }
166
- else {
167
- delete cfg.plugin;
168
- }
169
- writeJsonFile(configPath, cfg);
170
- updatedPaths.push(configPath);
171
- }
172
- catch {
173
- // Ignore malformed OpenCode config files and leave them unchanged.
174
- }
175
- }
176
- return updatedPaths;
177
- }
178
- function generateWorkspacePluginFile(packageName) {
179
- return [
180
- `import plugin from "../node_modules/${packageName}/dist/plugin-entry.js";`,
181
- `export const id = plugin.id;`,
182
- `export const server = plugin.server;`,
183
- `export default plugin;`,
184
- "",
185
- ].join("\n");
186
- }
187
- function generateWorkspaceTuiPluginFile(packageName) {
188
- return [
189
- `import plugin from "../node_modules/${packageName}/dist/tui.js";`,
190
- `export default plugin;`,
191
- "",
192
- ].join("\n");
193
- }
194
- function generateSkillFile() {
195
- return [
196
- "---",
197
- "name: opencode-rag",
198
- "description: Semantic code retrieval via OpenCodeRAG — vector search, file skeletons, and symbol usage lookup for this workspace",
199
- "---",
200
- "",
201
- "## OpenCodeRAG Tools",
202
- "",
203
- "This workspace has OpenCodeRAG indexed for semantic code search. Use these tools BEFORE planning, editing, or answering code questions.",
204
- "",
205
- "### Decision tree — ALWAYS follow this order",
206
- "",
207
- "1. User mentions code behavior/architecture → `search_semantic(query)`",
208
- "2. User mentions a file path → `get_file_skeleton(filePath)` THEN `read` on specific lines",
209
- "3. User mentions a function/class/variable to edit → `find_usages(symbolName)` THEN `search_semantic` THEN `edit`",
210
- "4. User asks a code question → `search_semantic` to gather context before answering",
211
- "",
212
- "### When to use each tool",
213
- "",
214
- "| Tool | Use when | Example |",
215
- "|------|----------|---------|",
216
- "| `search_semantic` | Any code search — find relevant code by meaning or keyword | `\"authentication middleware\"` |",
217
- "| `get_file_skeleton` | You have a file path but need to orient before reading | `\"src/plugin.ts\"` |",
218
- "| `find_usages` | Before editing any function, class, or variable — check all call sites | `\"createRagHooks\"` |",
219
- "",
220
- "### Workflow",
221
- "",
222
- "1. **Skeleton first** — call `get_file_skeleton(filePath)` to see structure",
223
- "2. **Find usages** — call `find_usages(symbolName)` before modifying any symbol",
224
- "3. **Search** — call `search_semantic(query)` to find relevant code",
225
- "4. **Read** — use the `read` tool on specific line ranges identified above",
226
- "5. **Edit** — now you have full context to make safe changes",
227
- "",
228
- "### Anti-patterns — NEVER do these",
229
- "",
230
- "- Reading full files without calling `get_file_skeleton` first (wastes tokens)",
231
- "- Editing a function without calling `find_usages` first (breaks call sites)",
232
- "- Answering code questions without calling `search_semantic` first (you guess at behavior)",
233
- "- Using `grep`/`glob` when `search_semantic` would find the answer faster",
234
- "",
235
- "### Parameters",
236
- "",
237
- "- `search_semantic`: `query` (req), `pathHints?`, `languageHints?`, `topK?`",
238
- "- `get_file_skeleton`: `filePath` (req)",
239
- "- `find_usages`: `symbolName` (req), `pathHint?`, `topK?`",
240
- "",
241
- "### Tips",
242
- "",
243
- "- Use `pathHints` to narrow searches to specific directories",
244
- "- Use `languageHints` to filter by file type",
245
- "- `find_usages` is essential before refactoring — it shows every reference",
246
- "- If no results appear, the workspace may not be indexed yet — run `opencode-rag index`",
247
- "",
248
- ].join("\n");
249
- }
250
- function mergeGitignoreContent(existingContent) {
251
- const lines = existingContent ? existingContent.split(/\r?\n/) : [];
252
- const trimmed = new Set(lines.map((line) => line.trim()));
253
- const requiredEntries = ["node_modules/", "package-lock.json", "rag_db/", "opencode-rag.log"];
254
- const missing = requiredEntries.filter((entry) => !trimmed.has(entry));
255
- if (!existingContent) {
256
- return [
257
- "# Ignore workspace-local plugin dependencies",
258
- "node_modules/",
259
- "package-lock.json",
260
- "",
261
- "# Ignore the LanceDB vector store (binary data)",
262
- "rag_db/",
263
- "",
264
- "# Ignore logs",
265
- "opencode-rag.log",
266
- "",
267
- ].join("\n");
268
- }
269
- if (missing.length === 0) {
270
- return existingContent.endsWith("\n") ? existingContent : `${existingContent}\n`;
271
- }
272
- const merged = [...lines];
273
- const lastLine = merged.length > 0 ? (merged[merged.length - 1] ?? "") : "";
274
- if (lastLine.trim().length > 0) {
275
- merged.push("");
276
- }
277
- merged.push("# OpenCodeRAG workspace state", ...missing, "");
278
- return merged.join("\n");
279
- }
280
- function installWorkspaceDependencies(opencodeDir) {
281
- const quoteForCmd = (value) => /[\s"]/u.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
282
- const attempts = [
283
- {
284
- args: ["install", "--silent", "--no-package-lock"],
285
- retry: false,
286
- },
287
- {
288
- args: [
289
- "install",
290
- "--silent",
291
- "--no-package-lock",
292
- "--ignore-scripts",
293
- "--no-optional",
294
- ],
295
- retry: true,
296
- },
297
- ];
298
- let lastError;
299
- for (const attempt of attempts) {
300
- if (attempt.retry) {
301
- console.log(c.warn(" Retrying dependency install without native module compilation..."));
302
- }
303
- const result = process.platform === "win32"
304
- ? spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `npm ${attempt.args.map(quoteForCmd).join(" ")}`], {
305
- cwd: opencodeDir,
306
- stdio: "inherit",
307
- env: process.env,
308
- })
309
- : spawnSync("npm", attempt.args, {
310
- cwd: opencodeDir,
311
- stdio: "inherit",
312
- env: process.env,
313
- });
314
- if (result.status === 0) {
315
- return;
316
- }
317
- lastError = result.error ?? new Error(`npm install exited with code ${result.status ?? 1}`);
318
- }
319
- throw lastError ?? new Error("npm install failed for workspace dependencies");
320
- }
321
- const program = new Command();
322
- program
323
- .name("opencode-rag")
324
- .description("Local-first RAG semantic code search")
325
- .version(getPackageMetadata().version);
326
- program
327
- .command("index")
328
- .description("Index workspace files")
329
- .option("-c, --config <path>", "path to config file")
330
- .option("-f, --force", "force full re-index")
331
- .option("-w, --watch", "watch workspace and incrementally re-index on changes")
332
- .action(async (options) => {
333
- const started = Date.now();
334
- try {
335
- const cwd = process.cwd();
336
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
337
- const ctx = await resolveCliContext(options, logFilePath);
338
- const { config, embedder, store, storePath, keywordIndex, descriptionProvider, dimension } = ctx;
339
- logFilePath = ctx.logFilePath;
340
- logCliInfo(logFilePath, "index", `\n${c.heading("Indexing workspace...")}`);
341
- logCliInfo(logFilePath, "index", ` ${c.label("Vector dimension:")} ${c.num(dimension)}`);
342
- if (descriptionProvider) {
343
- const descriptionConfig = config.description ?? { provider: "ollama", model: "qwen2.5:3b" };
344
- logCliInfo(logFilePath, "index", ` ${c.label("Description LLM:")} ${c.value(descriptionConfig.model)} (${descriptionConfig.provider})`);
345
- }
346
- logCliInfo(logFilePath, "index", `${c.label("Scanning:")} ${c.file(cwd)}`);
347
- const runPass = async (watchTriggered = false) => {
348
- const passStarted = Date.now();
349
- const stats = await runIndexPass({
350
- cwd,
351
- storePath,
352
- config,
353
- store,
354
- embedder,
355
- keywordIndex,
356
- descriptionProvider,
357
- force: Boolean(options.force && !watchTriggered),
358
- logger: {
359
- info: (message) => {
360
- console.log(message);
361
- appendDebugLog(logFilePath, { scope: "index", message });
362
- },
363
- warn: (message) => {
364
- console.warn(message);
365
- appendDebugLog(logFilePath, { scope: "index", message: `WARN: ${message}` });
366
- },
367
- debug: (message) => {
368
- appendDebugLog(logFilePath, { scope: "index", message: `DEBUG: ${message}`, severity: "debug" });
369
- },
370
- },
371
- });
372
- logIndexSummary(logFilePath, stats);
373
- logCliInfo(logFilePath, "index", `\n${c.success("Indexing complete.")} ${c.num(stats.finalCount)} chunks stored (${formatDuration(Date.now() - passStarted)}).`);
374
- };
375
- await runPass(false);
376
- if (!options.watch) {
377
- await cleanupContext(ctx);
378
- return;
379
- }
380
- logCliInfo(logFilePath, "index", `\n${c.heading("Watching for changes...")}`);
381
- const scheduler = createWatchPassScheduler(() => runPass(true), (error) => {
382
- const message = error.message || String(error);
383
- logCliError(logFilePath, "watch", `\nWatch reindex failed: ${message}`, error);
384
- }, 300);
385
- const watcher = chokidar.watch(cwd, {
386
- ignored: createWatchIgnore(cwd, config, storePath),
387
- ignoreInitial: true,
388
- persistent: true,
389
- });
390
- const handleChange = () => scheduler.notifyChange();
391
- watcher.on("add", handleChange);
392
- watcher.on("change", handleChange);
393
- watcher.on("unlink", handleChange);
394
- watcher.on("unlinkDir", handleChange);
395
- watcher.on("addDir", handleChange);
396
- watcher.on("error", (error) => {
397
- logCliError(logFilePath, "watch", `Watcher error: ${error.message}`, error);
398
- console.error(c.error(`\nWatcher error: ${error.message}`));
399
- });
400
- const shutdown = async () => {
401
- scheduler.close();
402
- await scheduler.waitForIdle();
403
- await Promise.race([
404
- watcher.close(),
405
- new Promise((r) => setTimeout(r, 5000)),
406
- ]);
407
- await cleanupContext(ctx);
408
- process.exit(0);
409
- };
410
- process.once("SIGINT", () => void shutdown());
411
- process.once("SIGTERM", () => void shutdown());
412
- const duration = formatDuration(Date.now() - started);
413
- logCliInfo(logFilePath, "index", `${c.success("Watcher ready")} (${duration} startup). Press Ctrl+C to stop.`);
414
- }
415
- catch (err) {
416
- const message = err.message || String(err);
417
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
418
- logCliError(logFilePath, "index", `\nIndexing failed: ${message}`, err);
419
- if (message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")) {
420
- console.error(c.warn("Hint: Is your embedding provider running?"));
421
- }
422
- process.exit(1);
423
- }
424
- });
425
- program
426
- .command("query")
427
- .description("Search the indexed codebase")
428
- .argument("<query>", "natural language query")
429
- .option("-c, --config <path>", "path to config file")
430
- .option("-n, --top-k <number>", "number of results", "10")
431
- .option("--explain", "show hybrid score breakdown")
432
- .action(async (query, options) => {
433
- const started = Date.now();
434
- try {
435
- const cwd = process.cwd();
436
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
437
- const ctx = await resolveCliContext(options, logFilePath);
438
- const { config, embedder, store, storePath, keywordIndex } = ctx;
439
- logFilePath = ctx.logFilePath;
440
- logCliInfo(logFilePath, "query", `\n${c.heading("Querying:")} "${query}"`);
441
- logCliInfo(logFilePath, "query", `${c.label("Top-K:")} ${c.num(parseInt(options.topK ?? "10", 10))}`);
442
- const indexedCount = await store.count();
443
- if (indexedCount === 0) {
444
- logCliInfo(logFilePath, "query", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
445
- await cleanupContext(ctx);
446
- return;
447
- }
448
- logCliInfo(logFilePath, "query", `${c.label("Searching")} ${c.num(indexedCount)} indexed chunks...`);
449
- const topK = parseInt(options.topK ?? "10", 10);
450
- const minScore = config.retrieval.minScore;
451
- const hybridCfg = config.retrieval.hybridSearch;
452
- const rawResults = await retrieve(query, embedder, store, {
453
- topK,
454
- minScore,
455
- keywordIndex,
456
- keywordWeight: hybridCfg?.keywordWeight,
457
- queryPrefix: config.embedding.queryPrefix,
458
- explain: options.explain ?? false,
459
- });
460
- const results = dedupeResults(rawResults);
461
- if (results.length === 0) {
462
- logCliInfo(logFilePath, "query", c.warn("No results found."));
463
- await cleanupContext(ctx);
464
- return;
465
- }
466
- const duration = formatDuration(Date.now() - started);
467
- logCliInfo(logFilePath, "query", `\n${c.num(results.length)} result(s) in ${duration}:\n`);
468
- for (const r of results) {
469
- logCliInfo(logFilePath, "query", ` ${c.file(r.chunk.metadata.filePath)}:${c.value(String(r.chunk.metadata.startLine))}-${c.value(String(r.chunk.metadata.endLine))}`);
470
- logCliInfo(logFilePath, "query", ` ${c.label("Score:")} ${c.score(r.score.toFixed(4))}`);
471
- if (r.explanation) {
472
- const sb = r.explanation.scoreBreakdown;
473
- logCliInfo(logFilePath, "query", ` ${c.label(" Vector:")} ${c.score(sb.rawVectorScore.toFixed(4))} ${c.label("Keyword:")} ${c.score(sb.rawKeywordScore.toFixed(4))} ${c.label("KW weight:")} ${sb.keywordWeight.toFixed(2)}`);
474
- if (r.explanation.matchedTerms && r.explanation.matchedTerms.length > 0) {
475
- logCliInfo(logFilePath, "query", ` ${c.label(" Matched:")} ${c.lang(r.explanation.matchedTerms.join(", "))}`);
476
- }
477
- }
478
- logCliInfo(logFilePath, "query", ` ${pc.dim(r.chunk.content.slice(0, 200).replace(/\n/g, "\n "))}`);
479
- }
480
- await cleanupContext(ctx);
481
- }
482
- catch (err) {
483
- const message = err.message || String(err);
484
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
485
- logCliError(logFilePath, "query", `\nQuery failed: ${message}`, err);
486
- if (message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")) {
487
- console.error(c.warn("Hint: Is your embedding provider running?"));
488
- }
489
- process.exit(1);
490
- }
491
- });
492
- program
493
- .command("clear")
494
- .description("Clear all indexed data")
495
- .option("-c, --config <path>", "path to config file")
496
- .action(async (options) => {
497
- try {
498
- const cwd = process.cwd();
499
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
500
- const ctx = await resolveCliContext(options, logFilePath);
501
- const { store } = ctx;
502
- logFilePath = ctx.logFilePath;
503
- const prevCount = await store.count();
504
- if (prevCount === 0) {
505
- logCliInfo(logFilePath, "clear", c.warn("No indexed data to clear."));
506
- }
507
- else {
508
- logCliInfo(logFilePath, "clear", `${c.label("Clearing")} ${c.num(prevCount)} indexed chunks...`);
509
- }
510
- await store.clear();
511
- logCliInfo(logFilePath, "clear", `${c.success("Done.")} vector database directory removed.`);
512
- await cleanupContext(ctx);
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, "clear", `\nClear failed: ${message}`, err);
518
- process.exit(1);
519
- }
520
- });
521
- program
522
- .command("status")
523
- .description("Show indexing status")
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 ctx = await resolveCliContext(options, logFilePath);
530
- const { config, store, storePath, keywordIndex } = ctx;
531
- logFilePath = ctx.logFilePath;
532
- const count = await store.count();
533
- const summary = await getIndexStatusSummary(cwd, storePath, config, store);
534
- logCliInfo(logFilePath, "status", `\n${c.heading("Indexed chunks:")} ${c.num(count)}`);
535
- logCliInfo(logFilePath, "status", `${c.label("Store path:")} ${c.file(storePath)}`);
536
- logCliInfo(logFilePath, "status", `${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
537
- logCliInfo(logFilePath, "status", `${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
538
- logCliInfo(logFilePath, "status", `${c.label("File extensions:")} ${config.indexing.includeExtensions.join(", ")}`);
539
- logCliInfo(logFilePath, "status", `${c.label("Excluded dirs:")} ${config.indexing.excludeDirs.join(", ")}`);
540
- logCliInfo(logFilePath, "status", `${c.label("Default top-K:")} ${c.num(config.retrieval.topK)}`);
541
- logCliInfo(logFilePath, "status", `${c.label("Plugin enabled:")} ${config.openCode.enabled ? c.enabled("yes") : c.disabled("no")}`);
542
- logCliInfo(logFilePath, "status", `${c.label("Manifest status:")} ${summary.manifestStatus}`);
543
- logCliInfo(logFilePath, "status", `${c.label("Manifest entries:")} ${c.num(summary.manifestEntries)}`);
544
- logCliInfo(logFilePath, "status", `${c.label("Last indexed:")} ${c.value(formatTimestamp(summary.lastIndexedAt))}`);
545
- logCliInfo(logFilePath, "status", `${c.label("Up-to-date files:")} ${c.num(summary.upToDateFiles)}`);
546
- logCliInfo(logFilePath, "status", `${c.label("Pending files:")} ${c.num(summary.pendingFiles)}`);
547
- logCliInfo(logFilePath, "status", `${c.label("Indexed chunks:")} ${c.num(summary.storeChunkCount)}`);
548
- logCliInfo(logFilePath, "status", `${c.label("Expected chunks:")} ${c.num(summary.manifestExpectedChunks)}`);
549
- logCliInfo(logFilePath, "status", `${c.label("Watch mode:")} ${c.dim("off")}`);
550
- const kiCount = config.retrieval.hybridSearch?.enabled
551
- ? keywordIndex?.count() ?? 0
552
- : 0;
553
- logCliInfo(logFilePath, "status", `${c.label("Keyword index:")} ${config.retrieval.hybridSearch?.enabled ? c.enabled("enabled") : c.disabled("disabled")} (${c.num(kiCount)} chunks)`);
554
- if (summary.rebuildRequired) {
555
- logCliInfo(logFilePath, "status", `${c.label("Rebuild required:")} ${c.warn("yes")} (manifest missing/corrupt)`);
556
- }
557
- if (summary.storeChunkCount > 0 && summary.manifestExpectedChunks > 0 && summary.storeChunkCount < summary.manifestExpectedChunks * 0.5) {
558
- logCliInfo(logFilePath, "status", `${c.label("Data loss detected:")} ${c.warn("yes")} — store has fewer chunks than expected. Run 'opencode-rag index' to rebuild.`);
559
- }
560
- await cleanupContext(ctx);
561
- }
562
- catch (err) {
563
- const message = err.message || String(err);
564
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
565
- logCliError(logFilePath, "status", `\nStatus check failed: ${message}`, err);
566
- process.exit(1);
567
- }
568
- });
569
- program
570
- .command("list")
571
- .description("List all indexed files with chunk counts")
572
- .option("-c, --config <path>", "path to config file")
573
- .action(async (options) => {
574
- try {
575
- const cwd = process.cwd();
576
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
577
- const ctx = await resolveCliContext(options, logFilePath);
578
- const { store } = ctx;
579
- logFilePath = ctx.logFilePath;
580
- const files = await store.listFiles();
581
- if (files.length === 0) {
582
- logCliInfo(logFilePath, "list", `${c.warn("No indexed files found.")} Run 'opencode-rag index' first.`);
583
- await cleanupContext(ctx);
584
- return;
585
- }
586
- logCliInfo(logFilePath, "list", `\n${c.num(files.length)} file(s) indexed:\n`);
587
- for (const f of files) {
588
- 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(")")}`);
589
- }
590
- await cleanupContext(ctx);
591
- }
592
- catch (err) {
593
- const message = err.message || String(err);
594
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
595
- logCliError(logFilePath, "list", `\nList failed: ${message}`, err);
596
- process.exit(1);
597
- }
598
- });
599
- program
600
- .command("show <file>")
601
- .description("Show chunks for a specific file")
602
- .option("-c, --config <path>", "path to config file")
603
- .action(async (file, options) => {
604
- try {
605
- const cwd = process.cwd();
606
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
607
- const ctx = await resolveCliContext(options, logFilePath);
608
- const { store } = ctx;
609
- logFilePath = ctx.logFilePath;
610
- const chunks = await store.getChunksByFilePath(file);
611
- if (chunks.length === 0) {
612
- logCliInfo(logFilePath, "show", `${c.warn(`No chunks found for '${file}'.`)}`);
613
- await cleanupContext(ctx);
614
- return;
615
- }
616
- logCliInfo(logFilePath, "show", `\n${c.num(chunks.length)} chunk(s) for ${c.file(file)}:\n`);
617
- for (const chunk of chunks) {
618
- 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)}`);
619
- if (chunk.description) {
620
- logCliInfo(logFilePath, "show", ` ${c.desc(">")} ${c.desc(chunk.description)}`);
621
- }
622
- logCliInfo(logFilePath, "show", ` ${chunk.content}\n`);
623
- }
624
- await cleanupContext(ctx);
625
- }
626
- catch (err) {
627
- const message = err.message || String(err);
628
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
629
- logCliError(logFilePath, "show", `\nShow failed: ${message}`, err);
630
- process.exit(1);
631
- }
632
- });
633
- program
634
- .command("dump")
635
- .description("Dump all indexed chunks")
636
- .option("-c, --config <path>", "path to config file")
637
- .option("--offset <number>", "start at chunk offset", "0")
638
- .option("--limit <number>", "max number of chunks to dump", "25")
639
- .action(async (options) => {
640
- try {
641
- const cwd = process.cwd();
642
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
643
- const ctx = await resolveCliContext(options, logFilePath);
644
- const { store, storePath } = ctx;
645
- logFilePath = ctx.logFilePath;
646
- const total = await store.count();
647
- if (total === 0) {
648
- logCliInfo(logFilePath, "dump", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
649
- await cleanupContext(ctx);
650
- return;
651
- }
652
- const offset = parseInt(options.offset ?? "0", 10);
653
- const limit = parseInt(options.limit ?? "25", 10);
654
- const chunks = await store.getChunks(offset, limit);
655
- 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`);
656
- for (const chunk of chunks) {
657
- 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(")")}`);
658
- logCliInfo(logFilePath, "dump", ` ${chunk.content}\n`);
659
- }
660
- if (offset + limit < total) {
661
- logCliInfo(logFilePath, "dump", ` ${c.dim(`... ${total - offset - limit} more (use --offset ${offset + limit} to continue)`)}`);
662
- }
663
- await cleanupContext(ctx);
664
- }
665
- catch (err) {
666
- const message = err.message || String(err);
667
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
668
- logCliError(logFilePath, "dump", `\nDump failed: ${message}`, err);
669
- process.exit(1);
670
- }
671
- });
672
- program
673
- .command("ui")
674
- .description("Start the web UI for browsing the vector database")
675
- .option("-c, --config <path>", "path to config file")
676
- .option("-p, --port <number>", "port to listen on (default: from config or 3210)")
677
- .option("--no-open", "do not open browser automatically")
678
- .action(async (options) => {
679
- try {
680
- const cwd = process.cwd();
681
- let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
682
- const ctx = await resolveCliContext(options, logFilePath);
683
- const { config, storePath } = ctx;
684
- logFilePath = ctx.logFilePath;
685
- const port = parseInt(options.port ?? String(config.ui?.port ?? 3210), 10);
686
- const openBrowser = options.open !== false && (config.ui?.openBrowser ?? true);
687
- const { startWebUi } = await import("./web/server.js");
688
- const server = await startWebUi(storePath, port, cwd);
689
- const url = `http://127.0.0.1:${server.port}`;
690
- logCliInfo(logFilePath, "ui", `\n${c.heading("OpenCodeRAG Web UI")}`);
691
- logCliInfo(logFilePath, "ui", ` ${c.label("URL:")} ${c.value(url)}`);
692
- logCliInfo(logFilePath, "ui", ` ${c.dim("Press Ctrl+C to stop")}\n`);
693
- if (openBrowser) {
694
- const { spawn } = await import("node:child_process");
695
- try {
696
- if (process.platform === "win32") {
697
- spawn("cmd.exe", ["/c", "start", url], { detached: true, stdio: "ignore" }).unref();
698
- }
699
- else {
700
- const cmd = process.platform === "darwin" ? "open" : "xdg-open";
701
- spawn(cmd, [url], { detached: true, stdio: "ignore" }).unref();
702
- }
703
- }
704
- catch {
705
- console.error(c.dim(`Could not open browser automatically. Open ${url} manually.`));
706
- }
707
- }
708
- process.on("SIGINT", async () => {
709
- await server.close();
710
- process.exit(0);
711
- });
712
- process.on("SIGTERM", async () => {
713
- await server.close();
714
- process.exit(0);
715
- });
716
- }
717
- catch (err) {
718
- const message = err.message || String(err);
719
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
720
- logCliError(logFilePath, "ui", `\nUI failed: ${message}`, err);
721
- process.exit(1);
722
- }
723
- });
724
- program
725
- .command("mcp")
726
- .description("Start MCP server for semantic code retrieval (stdio transport)")
727
- .option("-c, --config <path>", "path to config file")
728
- .action(async (options) => {
729
- try {
730
- const { runMcpServer } = await import("./mcp/cli.js");
731
- await runMcpServer({
732
- configPath: options.config,
733
- cwd: process.cwd(),
734
- });
735
- }
736
- catch (err) {
737
- const message = err.message || String(err);
738
- const logFilePath = path.resolve(process.cwd(), ".opencode", "opencode-rag.log");
739
- logCliError(logFilePath, "mcp", `\nMCP server failed: ${message}`, err);
740
- process.exit(1);
741
- }
742
- });
743
- program
744
- .command("update")
745
- .description("Check for and install OpenCodeRAG updates from GitHub")
746
- .option("--check", "only check for updates, don't install")
747
- .option("-y, --yes", "skip confirmation prompt")
748
- .option("-v, --verbose", "show build/install output")
749
- .action(async (options) => {
750
- try {
751
- const currentVersion = getCurrentVersion();
752
- console.log(`\n${c.heading("OpenCodeRAG Updater")}\n`);
753
- console.log(` ${c.label("Current version:")} ${c.value(currentVersion)}`);
754
- console.log(` ${c.label("Checking for updates...")}`);
755
- const info = await checkForUpdate(currentVersion);
756
- if (!info.updateAvailable) {
757
- console.log(` ${c.success("Already up to date.")}\n`);
758
- return;
759
- }
760
- console.log(` ${c.label("Latest version:")} ${c.value(info.latestVersion)}`);
761
- if (info.releaseUrl) {
762
- console.log(` ${c.label("Release:")} ${c.file(info.releaseUrl)}`);
763
- }
764
- console.log();
765
- if (options.check) {
766
- console.log(` ${c.warn("Update available. Run `opencode-rag update` to install.")}\n`);
767
- return;
768
- }
769
- if (!options.yes) {
770
- const readline = await import("node:readline");
771
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
772
- const answer = await new Promise((resolve) => {
773
- rl.question(` Install update to ${info.latestVersion}? [y/N] `, resolve);
774
- });
775
- rl.close();
776
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
777
- console.log(` ${c.dim("Cancelled.")}\n`);
778
- return;
779
- }
780
- }
781
- console.log(` ${c.label("Applying update...")}\n`);
782
- const result = applyUpdate({
783
- repoRoot: path.resolve(getPackageRoot()),
784
- verbose: options.verbose ?? false,
785
- });
786
- if (result.success) {
787
- console.log(` ${c.success(result.message)}\n`);
788
- }
789
- else {
790
- console.error(` ${c.error(result.message)}\n`);
791
- process.exit(1);
792
- }
793
- }
794
- catch (err) {
795
- const message = err.message || String(err);
796
- console.error(c.error(`\nUpdate failed: ${message}\n`));
797
- process.exit(1);
798
- }
799
- });
800
- // ── Eval commands ──────────────────────────────────────────────
801
- program
802
- .command("eval:sessions")
803
- .description("List all logged evaluation sessions")
804
- .option("-c, --config <path>", "path to config file")
805
- .action(async (options) => {
806
- try {
807
- const cwd = process.cwd();
808
- const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
809
- const ctx = await resolveCliContext(options, logFilePath);
810
- const { storePath } = ctx;
811
- const { listSessions } = await import("./eval/storage.js");
812
- const sessions = listSessions(storePath);
813
- if (sessions.length === 0) {
814
- console.log(c.warn("\nNo evaluation sessions found. Sessions are logged automatically during OpenCode usage.\n"));
815
- return;
816
- }
817
- console.log(`\n${c.heading("Evaluation Sessions")} (${sessions.length})\n`);
818
- console.log(" ID Queries Input Tok RAG Ctx Cost");
819
- console.log(" " + "─".repeat(64));
820
- for (const s of sessions) {
821
- const id = (s.sessionID ?? "").padEnd(28);
822
- const queries = String(s.messageCount).padStart(6);
823
- const input = String(s.totalTokens.input).padStart(9);
824
- const ragCtx = String(s.ragContextTokens).padStart(8);
825
- const cost = `$${s.totalCost.toFixed(4)}`.padStart(7);
826
- console.log(` ${id} ${queries} ${input} ${ragCtx} ${cost}`);
827
- }
828
- console.log();
829
- }
830
- catch (err) {
831
- const message = err.message || String(err);
832
- console.error(c.error(`\nFailed: ${message}\n`));
833
- process.exit(1);
834
- }
835
- });
836
- program
837
- .command("eval:analyze <sessionID>")
838
- .description("Analyze token usage for a specific session")
839
- .option("-c, --config <path>", "path to config file")
840
- .action(async (sessionID, options) => {
841
- try {
842
- const cwd = process.cwd();
843
- const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
844
- const ctx = await resolveCliContext(options, logFilePath);
845
- const { storePath } = ctx;
846
- const { analyzeTokenUsage } = await import("./eval/token-analysis.js");
847
- const analysis = analyzeTokenUsage(storePath, sessionID);
848
- if (analysis.queryCount === 0) {
849
- console.log(c.warn(`\nNo messages found for session '${sessionID}'.\n`));
850
- return;
851
- }
852
- console.log(`\n${c.heading("Token Analysis")} — ${c.value(sessionID)}\n`);
853
- console.log(` Queries: ${analysis.queryCount}`);
854
- console.log(` Input tokens: ${c.num(analysis.totals.inputTokens.toLocaleString())}`);
855
- console.log(` Output tokens: ${c.num(analysis.totals.outputTokens.toLocaleString())}`);
856
- console.log(` Reasoning tokens: ${c.num(analysis.totals.reasoningTokens.toLocaleString())}`);
857
- console.log(` Cache read: ${c.num(analysis.totals.cacheRead.toLocaleString())}`);
858
- console.log(` Cost: ${c.num(`$${analysis.totals.cost.toFixed(4)}`)}`);
859
- console.log();
860
- console.log(` ${c.heading("RAG Impact")}`);
861
- console.log(` Context injected: ${c.num(analysis.totals.ragContextTokens.toLocaleString())} tokens`);
862
- console.log(` System guidance: ${c.num(analysis.totals.systemGuidanceTokens.toLocaleString())} tokens`);
863
- console.log(` Read calls: ${c.num(analysis.totals.readToolCalls)}`);
864
- console.log(` RAG tool calls: ${c.num(analysis.totals.ragToolCalls)}`);
865
- console.log();
866
- console.log(` ${c.heading("Projection")}`);
867
- console.log(` Tokens with RAG: ${c.num(analysis.estimates.tokensWithRAG.toLocaleString())}`);
868
- console.log(` Tokens without RAG: ${c.num(analysis.estimates.tokensWithoutRAG.toLocaleString())}`);
869
- const savingsColor = analysis.estimates.netSavings > 0 ? c.success : c.warn;
870
- console.log(` Net savings: ${savingsColor(`${analysis.estimates.netSavings > 0 ? "+" : ""}${analysis.estimates.netSavings.toLocaleString()} tokens (${analysis.estimates.percentSavings}%)`)}`);
871
- console.log();
872
- if (analysis.breakdowns.length > 0) {
873
- console.log(` ${c.heading("Per-Query Breakdown")}`);
874
- console.log(" # Input RAG ctx Reads RAG tools Score");
875
- console.log(" " + "─".repeat(52));
876
- for (let i = 0; i < analysis.breakdowns.length; i++) {
877
- const b = analysis.breakdowns[i];
878
- const num = String(i + 1).padStart(3);
879
- const input = String(b.inputTokens).padStart(7);
880
- const ctx = String(b.ragContextTokens).padStart(7);
881
- const reads = String(b.readToolCalls).padStart(5);
882
- const tools = String(b.ragToolCalls).padStart(9);
883
- const score = b.ragTopScore.toFixed(2);
884
- console.log(` ${num} ${input} ${ctx} ${reads} ${tools} ${score}`);
885
- }
886
- }
887
- console.log();
888
- }
889
- catch (err) {
890
- const message = err.message || String(err);
891
- console.error(c.error(`\nFailed: ${message}\n`));
892
- process.exit(1);
893
- }
894
- });
895
- program
896
- .command("eval:compare <sessionA> <sessionB>")
897
- .description("Compare token usage between two sessions (e.g. RAG-on vs RAG-off)")
898
- .option("-c, --config <path>", "path to config file")
899
- .action(async (sessionA, sessionB, options) => {
900
- try {
901
- const cwd = process.cwd();
902
- const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
903
- const ctx = await resolveCliContext(options, logFilePath);
904
- const { storePath } = ctx;
905
- const { analyzeTokenUsage, compareTokenAnalyses, formatTokenReport } = await import("./eval/token-analysis.js");
906
- const a = analyzeTokenUsage(storePath, sessionA);
907
- const b = analyzeTokenUsage(storePath, sessionB);
908
- if (a.queryCount === 0 && b.queryCount === 0) {
909
- console.log(c.warn(`\nNo messages found for sessions '${sessionA}' or '${sessionB}'.\n`));
910
- return;
911
- }
912
- const comparison = compareTokenAnalyses(a, b);
913
- const report = formatTokenReport(a, b, comparison);
914
- console.log(report);
915
- }
916
- catch (err) {
917
- const message = err.message || String(err);
918
- console.error(c.error(`\nFailed: ${message}\n`));
919
- process.exit(1);
920
- }
921
- });
922
- function generateDefaultConfigJson() {
923
- return JSON.stringify({
924
- embedding: {
925
- provider: DEFAULT_CONFIG.embedding.provider,
926
- baseUrl: DEFAULT_CONFIG.embedding.baseUrl,
927
- model: DEFAULT_CONFIG.embedding.model,
928
- timeoutMs: DEFAULT_CONFIG.embedding.timeoutMs,
929
- },
930
- indexing: {
931
- includeExtensions: DEFAULT_CONFIG.indexing.includeExtensions,
932
- excludeDirs: DEFAULT_CONFIG.indexing.excludeDirs,
933
- chunkOverlap: DEFAULT_CONFIG.indexing.chunkOverlap,
934
- minFileSizeBytes: DEFAULT_CONFIG.indexing.minFileSizeBytes,
935
- concurrency: DEFAULT_CONFIG.indexing.concurrency,
936
- embedBatchSize: DEFAULT_CONFIG.indexing.embedBatchSize,
937
- },
938
- vectorStore: {
939
- path: DEFAULT_CONFIG.vectorStore.path,
940
- },
941
- retrieval: {
942
- topK: DEFAULT_CONFIG.retrieval.topK,
943
- minScore: DEFAULT_CONFIG.retrieval.minScore,
944
- hybridSearch: {
945
- enabled: DEFAULT_CONFIG.retrieval.hybridSearch.enabled,
946
- keywordWeight: DEFAULT_CONFIG.retrieval.hybridSearch.keywordWeight,
947
- },
948
- },
949
- openCode: {
950
- enabled: DEFAULT_CONFIG.openCode.enabled,
951
- maxContextChunks: DEFAULT_CONFIG.openCode.maxContextChunks,
952
- readOverride: DEFAULT_CONFIG.openCode.readOverride,
953
- autoIndex: {
954
- enabled: DEFAULT_CONFIG.openCode.autoIndex.enabled,
955
- debounceMs: DEFAULT_CONFIG.openCode.autoIndex.debounceMs,
956
- intervalMs: DEFAULT_CONFIG.openCode.autoIndex.intervalMs,
957
- },
958
- autoInject: {
959
- enabled: DEFAULT_CONFIG.openCode.autoInject.enabled,
960
- minScore: DEFAULT_CONFIG.openCode.autoInject.minScore,
961
- maxChunks: DEFAULT_CONFIG.openCode.autoInject.maxChunks,
962
- maxTokens: DEFAULT_CONFIG.openCode.autoInject.maxTokens,
963
- contentType: DEFAULT_CONFIG.openCode.autoInject.contentType,
964
- },
965
- },
966
- imageDescription: {
967
- enabled: DEFAULT_CONFIG.imageDescription.enabled,
968
- provider: DEFAULT_CONFIG.imageDescription.provider,
969
- model: DEFAULT_CONFIG.imageDescription.model,
970
- baseUrl: DEFAULT_CONFIG.imageDescription.baseUrl,
971
- timeoutMs: DEFAULT_CONFIG.imageDescription.timeoutMs,
972
- think: DEFAULT_CONFIG.imageDescription.think,
973
- numCtx: DEFAULT_CONFIG.imageDescription.numCtx,
974
- },
975
- description: {
976
- enabled: DEFAULT_CONFIG.description.enabled,
977
- provider: DEFAULT_CONFIG.description.provider,
978
- baseUrl: DEFAULT_CONFIG.description.baseUrl,
979
- model: DEFAULT_CONFIG.description.model,
980
- think: DEFAULT_CONFIG.description.think,
981
- numCtx: DEFAULT_CONFIG.description.numCtx,
982
- timeoutMs: DEFAULT_CONFIG.description.timeoutMs,
983
- },
984
- mcp: {
985
- enabled: DEFAULT_CONFIG.mcp.enabled,
986
- },
987
- logging: {
988
- level: DEFAULT_CONFIG.logging.level,
989
- logFilePath: DEFAULT_CONFIG.logging.logFilePath,
990
- },
991
- chunking: {
992
- nodeTypes: {},
993
- },
994
- }, null, 2) + "\n";
995
- }
996
- program
997
- .command("init")
998
- .description("Configure the current workspace for OpenCodeRAG")
999
- .option("-f, --force", "overwrite existing files")
1000
- .option("--skip-install", "skip installing workspace-local plugin dependencies")
1001
- .option("--skip-health-check", "skip provider connectivity and model availability check")
1002
- .action(async (options) => {
1003
- const cwd = process.cwd();
1004
- const packageMetadata = getPackageMetadata();
1005
- const configPath = path.join(cwd, "opencode-rag.json");
1006
- const opencodeDir = path.join(cwd, ".opencode");
1007
- const gitignorePath = path.join(opencodeDir, ".gitignore");
1008
- const opencodeConfigPath = path.join(opencodeDir, "opencode.json");
1009
- const pluginsDir = path.join(opencodeDir, "plugins");
1010
- const pluginEntryPath = path.join(pluginsDir, "rag-plugin.js");
1011
- const tuiPluginEntryPath = path.join(pluginsDir, "rag-tui.js");
1012
- const tuiConfigPath = path.join(opencodeDir, "tui.json");
1013
- const opencodePackagePath = path.join(opencodeDir, "package.json");
1014
- console.log(`\n${c.heading("Initializing OpenCodeRAG in workspace...")}\n`);
1015
- if (!existsSync(opencodeDir)) {
1016
- mkdirSync(opencodeDir, { recursive: true });
1017
- console.log(` ${c.created("Created:")} .opencode/`);
1018
- }
1019
- else {
1020
- console.log(` ${c.exists("Exists:")} .opencode/`);
1021
- }
1022
- if (!existsSync(pluginsDir)) {
1023
- mkdirSync(pluginsDir, { recursive: true });
1024
- console.log(` ${c.created("Created:")} .opencode/plugins/`);
1025
- }
1026
- else {
1027
- console.log(` ${c.exists("Exists:")} .opencode/plugins/`);
1028
- }
1029
- const gitignoreExists = existsSync(gitignorePath);
1030
- const nextGitignoreContent = mergeGitignoreContent(gitignoreExists ? readFileSync(gitignorePath, "utf-8") : undefined);
1031
- if (!gitignoreExists || options.force || readFileSync(gitignorePath, "utf-8") !== nextGitignoreContent) {
1032
- writeFileSync(gitignorePath, nextGitignoreContent, "utf-8");
1033
- console.log(` ${gitignoreExists ? c.updated("Updated:") : c.created("Created:")} .opencode/.gitignore`);
1034
- }
1035
- else {
1036
- console.log(` ${c.exists("Exists:")} .opencode/.gitignore`);
1037
- }
1038
- const opencodeConfigExists = existsSync(opencodeConfigPath);
1039
- const nextOpencodeConfig = buildOpencodeConfig(readJsonObject(opencodeConfigPath));
1040
- if (!opencodeConfigExists || options.force) {
1041
- writeJsonFile(opencodeConfigPath, nextOpencodeConfig);
1042
- console.log(` ${opencodeConfigExists ? c.updated("Updated:") : c.created("Created:")} .opencode/opencode.json`);
1043
- }
1044
- else if (JSON.stringify(readJsonObject(opencodeConfigPath)) !== JSON.stringify(nextOpencodeConfig)) {
1045
- writeJsonFile(opencodeConfigPath, nextOpencodeConfig);
1046
- console.log(` ${c.updated("Updated:")} .opencode/opencode.json`);
1047
- }
1048
- else {
1049
- console.log(` ${c.exists("Exists:")} .opencode/opencode.json`);
1050
- }
1051
- const pluginEntryExists = existsSync(pluginEntryPath);
1052
- const pluginEntryContent = generateWorkspacePluginFile(packageMetadata.name);
1053
- if (!pluginEntryExists || options.force) {
1054
- writeFileSync(pluginEntryPath, pluginEntryContent, "utf-8");
1055
- console.log(` ${pluginEntryExists ? c.updated("Updated:") : c.created("Created:")} .opencode/plugins/rag-plugin.js`);
1056
- }
1057
- else if (readFileSync(pluginEntryPath, "utf-8") !== pluginEntryContent) {
1058
- writeFileSync(pluginEntryPath, pluginEntryContent, "utf-8");
1059
- console.log(` ${c.updated("Updated:")} .opencode/plugins/rag-plugin.js`);
1060
- }
1061
- else {
1062
- console.log(` ${c.exists("Exists:")} .opencode/plugins/rag-plugin.js`);
1063
- }
1064
- const tuiPluginEntryExists = existsSync(tuiPluginEntryPath);
1065
- const tuiPluginEntryContent = generateWorkspaceTuiPluginFile(packageMetadata.name);
1066
- if (!tuiPluginEntryExists || options.force) {
1067
- writeFileSync(tuiPluginEntryPath, tuiPluginEntryContent, "utf-8");
1068
- console.log(` ${tuiPluginEntryExists ? c.updated("Updated:") : c.created("Created:")} .opencode/plugins/rag-tui.js`);
1069
- }
1070
- else if (readFileSync(tuiPluginEntryPath, "utf-8") !== tuiPluginEntryContent) {
1071
- writeFileSync(tuiPluginEntryPath, tuiPluginEntryContent, "utf-8");
1072
- console.log(` ${c.updated("Updated:")} .opencode/plugins/rag-tui.js`);
1073
- }
1074
- else {
1075
- console.log(` ${c.exists("Exists:")} .opencode/plugins/rag-tui.js`);
1076
- }
1077
- const tuiConfigExists = existsSync(tuiConfigPath);
1078
- const nextTuiConfig = { plugin: ["./plugins/rag-tui.js"] };
1079
- if (!tuiConfigExists || options.force) {
1080
- writeJsonFile(tuiConfigPath, nextTuiConfig);
1081
- console.log(` ${tuiConfigExists ? c.updated("Updated:") : c.created("Created:")} .opencode/tui.json`);
1082
- }
1083
- else if (JSON.stringify(readJsonObject(tuiConfigPath)) !== JSON.stringify(nextTuiConfig)) {
1084
- writeJsonFile(tuiConfigPath, nextTuiConfig);
1085
- console.log(` ${c.updated("Updated:")} .opencode/tui.json`);
1086
- }
1087
- else {
1088
- console.log(` ${c.exists("Exists:")} .opencode/tui.json`);
1089
- }
1090
- const skillsDir = path.join(opencodeDir, "skills");
1091
- const skillDir = path.join(skillsDir, "opencode-rag");
1092
- const skillPath = path.join(skillDir, "SKILL.md");
1093
- if (!existsSync(skillDir)) {
1094
- mkdirSync(skillDir, { recursive: true });
1095
- console.log(` ${c.created("Created:")} .opencode/skills/opencode-rag/`);
1096
- }
1097
- const skillContent = generateSkillFile();
1098
- const skillExists = existsSync(skillPath);
1099
- if (!skillExists || options.force) {
1100
- writeFileSync(skillPath, skillContent, "utf-8");
1101
- console.log(` ${skillExists ? c.updated("Updated:") : c.created("Created:")} .opencode/skills/opencode-rag/SKILL.md`);
1102
- }
1103
- else if (readFileSync(skillPath, "utf-8") !== skillContent) {
1104
- writeFileSync(skillPath, skillContent, "utf-8");
1105
- console.log(` ${c.updated("Updated:")} .opencode/skills/opencode-rag/SKILL.md`);
1106
- }
1107
- else {
1108
- console.log(` ${c.exists("Exists:")} .opencode/skills/opencode-rag/SKILL.md`);
1109
- }
1110
- const workspacePackageExists = existsSync(opencodePackagePath);
1111
- const nextWorkspacePackage = buildWorkspacePackageJson(readJsonObject(opencodePackagePath), packageMetadata, opencodeDir);
1112
- if (!workspacePackageExists || options.force) {
1113
- writeJsonFile(opencodePackagePath, nextWorkspacePackage);
1114
- console.log(` ${workspacePackageExists ? c.updated("Updated:") : c.created("Created:")} .opencode/package.json`);
1115
- }
1116
- else if (JSON.stringify(readJsonObject(opencodePackagePath)) !== JSON.stringify(nextWorkspacePackage)) {
1117
- writeJsonFile(opencodePackagePath, nextWorkspacePackage);
1118
- console.log(` ${c.updated("Updated:")} .opencode/package.json`);
1119
- }
1120
- else {
1121
- console.log(` ${c.exists("Exists:")} .opencode/package.json`);
1122
- }
1123
- const configExists = existsSync(configPath);
1124
- if (!configExists || options.force) {
1125
- writeFileSync(configPath, generateDefaultConfigJson(), "utf-8");
1126
- console.log(` ${configExists ? c.updated("Updated:") : c.created("Created:")} opencode-rag.json`);
1127
- }
1128
- else {
1129
- console.log(` ${c.exists("Exists:")} opencode-rag.json`);
1130
- }
1131
- // ── Provider health check ──────────────────────────────────
1132
- if (!options.skipHealthCheck) {
1133
- console.log(`\n${c.heading("Checking provider connectivity...")}\n`);
1134
- const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
1135
- const ragConfig = loadConfig(configPath);
1136
- const results = await checkProviderHealth(ragConfig);
1137
- for (const r of results) {
1138
- const icon = r.status === "ok" ? c.success("✓") : r.status === "missing" ? c.warn("○") : c.error("✗");
1139
- const typeLabel = r.type === "image_description" ? "image description" : r.type;
1140
- const label = `${typeLabel} model`;
1141
- console.log(` ${icon} ${c.value(r.model)} (${r.provider}) — ${label}: ${r.status}`);
1142
- if (r.error)
1143
- console.log(` ${c.dim(r.error)}`);
1144
- }
1145
- const missingOllama = results.filter((r) => r.status === "missing" && r.provider === "ollama");
1146
- if (missingOllama.length > 0) {
1147
- const pullEntries = missingOllama.map((r) => {
1148
- if (r.type === "embedding") {
1149
- return { model: r.model, baseUrl: ragConfig.embedding.baseUrl, proxy: ragConfig.embedding.proxy };
1150
- }
1151
- if (r.type === "description" && ragConfig.description) {
1152
- return { model: r.model, baseUrl: ragConfig.description.baseUrl, proxy: ragConfig.description.proxy };
1153
- }
1154
- if (r.type === "image_description" && ragConfig.imageDescription) {
1155
- return { model: r.model, baseUrl: ragConfig.imageDescription.baseUrl, proxy: ragConfig.imageDescription.proxy };
1156
- }
1157
- return { model: r.model, baseUrl: ragConfig.embedding.baseUrl, proxy: ragConfig.embedding.proxy };
1158
- });
1159
- console.log(`\n ${c.warn("Models not found:")} ${pullEntries.map((e) => e.model).join(", ")}`);
1160
- const readline = await import("node:readline");
1161
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1162
- const answer = await new Promise((resolve) => {
1163
- rl.question(` Pull ${pullEntries.length === 1 ? "this model" : "these models"} now? (y/n) `, resolve);
1164
- });
1165
- rl.close();
1166
- if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
1167
- console.log();
1168
- try {
1169
- await pullOllamaModels(pullEntries, (model, line) => {
1170
- console.log(` ${c.value(model)}: ${line}`);
1171
- });
1172
- console.log(`\n ${c.success("Models pulled successfully.")}`);
1173
- }
1174
- catch (err) {
1175
- console.error(`\n ${c.error("Pull failed:")} ${err.message}`);
1176
- console.log(` ${c.dim("Pull manually with: ollama pull <model>")}`);
1177
- }
1178
- }
1179
- else {
1180
- console.log(` ${c.dim("Skipped. Pull manually with: ollama pull <model>")}`);
1181
- }
1182
- }
1183
- const hasErrors = results.some((r) => r.status === "error");
1184
- if (hasErrors) {
1185
- console.log(`\n ${c.warn("Some providers are not reachable.")} Check configuration and network, then run ${c.file("'opencode-rag index'")}.`);
1186
- }
1187
- }
1188
- if (!options.skipInstall) {
1189
- console.log(`\n${c.heading("Installing workspace-local plugin dependencies...")}\n`);
1190
- installWorkspaceDependencies(opencodeDir);
1191
- console.log(`\n ${c.success("Installed:")} .opencode/node_modules/`);
1192
- const updatedGlobalConfigs = removeStaleGlobalPluginRegistrations(os.homedir(), packageMetadata.name);
1193
- if (updatedGlobalConfigs.length > 0) {
1194
- for (const configPath of updatedGlobalConfigs) {
1195
- console.log(` ${c.warn("Removed stale plugin registration from")} ${configPath}`);
1196
- }
1197
- }
1198
- console.log(` ${c.dim("OpenCode loads the plugin from .opencode/plugins/rag-plugin.js; no global plugin registration is required.")}`);
1199
- }
1200
- else {
1201
- console.log(`\n ${c.exists("Skipped:")} dependency installation (--skip-install)`);
1202
- }
1203
- console.log(`\n${c.success("Done.")} Restart OpenCode if it is running, then run ${c.file("'opencode-rag index'")} in this workspace.`);
1204
- });
1205
1
  /**
1206
- * Determine whether the CLI should auto-run for the current module.
1207
- * Resolves the first argv entry so symlinked binaries compare against the
1208
- * real file path, and returns false if the path cannot be resolved.
2
+ * Backwards-compatibility shim — re-exports the public API from the new
3
+ * `cli/` module tree so existing tests and external consumers that import
4
+ * from `../cli.js` continue to work unchanged.
5
+ *
6
+ * The actual CLI entry point is now `cli/index.ts` (compiled to `dist/cli/index.js`).
1209
7
  */
1210
- function dedupeResults(results) {
1211
- const seen = new Set();
1212
- const deduped = [];
1213
- for (const result of results) {
1214
- const chunk = result.chunk;
1215
- const key = [
1216
- chunk.metadata.filePath,
1217
- chunk.metadata.startLine,
1218
- chunk.metadata.endLine,
1219
- chunk.content,
1220
- ].join(":");
1221
- if (seen.has(key))
1222
- continue;
1223
- seen.add(key);
1224
- deduped.push(result);
1225
- }
1226
- return deduped;
1227
- }
1228
- export function shouldAutoRunCli(moduleUrl, argv1) {
1229
- if (!argv1) {
1230
- return false;
1231
- }
1232
- try {
1233
- const resolvedPath = realpathSync(argv1).replace(/\\/g, "/");
1234
- const normalizedUrl = moduleUrl.replace(/\\/g, "/");
1235
- return normalizedUrl === `file://${resolvedPath}` || normalizedUrl.endsWith(`/${resolvedPath}`) || normalizedUrl.includes(resolvedPath);
1236
- }
1237
- catch {
1238
- return false;
1239
- }
1240
- }
1241
- if (shouldAutoRunCli(import.meta.url, process.argv[1])) {
1242
- void program.parseAsync(process.argv);
1243
- }
1244
- else {
1245
- // Fallback: if the module appears to be running as a CLI (has argv with commands like 'init', 'index', etc.)
1246
- // and not being imported as a library, parse the arguments anyway
1247
- const commands = ['init', 'index', 'query', 'clear', 'status', 'list', 'show', 'dump', 'ui', 'mcp', 'update', 'eval:sessions', 'eval:analyze', 'eval:compare'];
1248
- const cmd = process.argv[2];
1249
- if (process.argv.length > 2 && cmd && commands.includes(cmd.toLowerCase())) {
1250
- void program.parseAsync(process.argv);
1251
- }
1252
- }
1253
- export async function runCli(argv = process.argv) {
1254
- await program.parseAsync(argv);
1255
- }
8
+ /** Main CLI runner and auto-launch detection. */
9
+ export { runCli, shouldAutoRunCli } from "./cli/index.js";
10
+ /** Cleanup helper that removes stale global plugin registration symlinks. */
11
+ export { removeStaleGlobalPluginRegistrations } from "./cli/commands/init-helpers.js";
1256
12
  //# sourceMappingURL=cli.js.map