opencode-rag-plugin 1.15.1 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/ReadMe.md +52 -9
  2. package/dist/api.js +44 -24
  3. package/dist/chunker/base.d.ts +34 -3
  4. package/dist/chunker/base.js +70 -21
  5. package/dist/chunker/factory.d.ts +4 -1
  6. package/dist/chunker/factory.js +12 -1
  7. package/dist/chunker/grammar.js +3 -0
  8. package/dist/chunker/image.js +8 -8
  9. package/dist/chunker/pdf.js +11 -13
  10. package/dist/chunker/xml.d.ts +2 -0
  11. package/dist/chunker/xml.js +2 -0
  12. package/dist/cli/commands/index-command.js +0 -4
  13. package/dist/cli/commands/index.d.ts +1 -0
  14. package/dist/cli/commands/index.js +1 -0
  15. package/dist/cli/commands/init-helpers.d.ts +12 -0
  16. package/dist/cli/commands/init-helpers.js +87 -2
  17. package/dist/cli/commands/init.js +20 -2
  18. package/dist/cli/commands/query.js +1 -0
  19. package/dist/cli/commands/setup.d.ts +2 -0
  20. package/dist/cli/commands/setup.js +113 -0
  21. package/dist/cli/commands/status.js +64 -2
  22. package/dist/cli/commands/update.d.ts +4 -6
  23. package/dist/cli/commands/update.js +44 -58
  24. package/dist/cli/index.js +2 -1
  25. package/dist/content/image.js +24 -3
  26. package/dist/content/reader.d.ts +6 -1
  27. package/dist/content/reader.js +49 -4
  28. package/dist/core/config.d.ts +29 -1
  29. package/dist/core/config.js +105 -8
  30. package/dist/core/desc-cache.d.ts +31 -0
  31. package/dist/core/desc-cache.js +124 -0
  32. package/dist/core/interfaces.d.ts +58 -3
  33. package/dist/core/manifest.d.ts +24 -1
  34. package/dist/core/manifest.js +34 -3
  35. package/dist/core/resolve-api-key.js +4 -2
  36. package/dist/core/runtime-overrides.js +12 -8
  37. package/dist/core/setup-runtime.d.ts +23 -0
  38. package/dist/core/setup-runtime.js +231 -0
  39. package/dist/core/version-check.d.ts +85 -0
  40. package/dist/core/version-check.js +160 -0
  41. package/dist/describer/anthropic.d.ts +2 -2
  42. package/dist/describer/anthropic.js +5 -7
  43. package/dist/describer/describer.d.ts +2 -2
  44. package/dist/describer/describer.js +6 -8
  45. package/dist/describer/gemini.d.ts +2 -2
  46. package/dist/describer/gemini.js +5 -7
  47. package/dist/embedder/cohere.d.ts +1 -1
  48. package/dist/embedder/cohere.js +2 -2
  49. package/dist/embedder/factory.d.ts +3 -1
  50. package/dist/embedder/factory.js +8 -2
  51. package/dist/embedder/health.js +5 -1
  52. package/dist/embedder/http.d.ts +1 -1
  53. package/dist/embedder/http.js +74 -43
  54. package/dist/embedder/ollama.d.ts +1 -1
  55. package/dist/embedder/ollama.js +2 -2
  56. package/dist/embedder/openai.d.ts +1 -1
  57. package/dist/embedder/openai.js +2 -2
  58. package/dist/eval/compare-merge.d.ts +10 -0
  59. package/dist/eval/compare-merge.js +537 -0
  60. package/dist/eval/compare-rankings.d.ts +10 -0
  61. package/dist/eval/compare-rankings.js +245 -0
  62. package/dist/eval/dump-descriptions.d.ts +7 -0
  63. package/dist/eval/dump-descriptions.js +58 -0
  64. package/dist/eval/fast-index.d.ts +8 -0
  65. package/dist/eval/fast-index.js +283 -0
  66. package/dist/eval/run-branch-compare.d.ts +7 -0
  67. package/dist/eval/run-branch-compare.js +220 -0
  68. package/dist/eval/run-token-test.js +1 -0
  69. package/dist/eval/test-kw.d.ts +1 -0
  70. package/dist/eval/test-kw.js +22 -0
  71. package/dist/eval/update-descriptions.d.ts +7 -0
  72. package/dist/eval/update-descriptions.js +84 -0
  73. package/dist/index.d.ts +4 -0
  74. package/dist/index.js +3 -0
  75. package/dist/indexer/embed-stage.js +2 -1
  76. package/dist/indexer/git-diff.js +21 -9
  77. package/dist/indexer/pipeline.d.ts +6 -0
  78. package/dist/indexer/pipeline.js +304 -40
  79. package/dist/indexer/watch.js +1 -3
  80. package/dist/indexer/worker.d.ts +15 -2
  81. package/dist/indexer/worker.js +25 -12
  82. package/dist/mcp/handlers.d.ts +9 -0
  83. package/dist/mcp/handlers.js +23 -6
  84. package/dist/mcp/server.js +2 -0
  85. package/dist/opencode/create-read-tool.d.ts +2 -0
  86. package/dist/opencode/create-read-tool.js +8 -2
  87. package/dist/opencode/read-fallback.d.ts +1 -5
  88. package/dist/opencode/read-fallback.js +1 -18
  89. package/dist/opencode/read-format.js +5 -3
  90. package/dist/opencode/tools.js +5 -7
  91. package/dist/plugin.js +223 -71
  92. package/dist/retriever/keyword-index.d.ts +3 -2
  93. package/dist/retriever/keyword-index.js +25 -1
  94. package/dist/retriever/retriever.d.ts +4 -1
  95. package/dist/retriever/retriever.js +34 -56
  96. package/dist/tui.js +18 -1
  97. package/dist/vectorstore/lancedb.d.ts +35 -4
  98. package/dist/vectorstore/lancedb.js +146 -23
  99. package/dist/vectorstore/memory.d.ts +6 -1
  100. package/dist/vectorstore/memory.js +58 -0
  101. package/dist/watcher.d.ts +2 -0
  102. package/dist/watcher.js +5 -1
  103. package/dist/web/api.js +10 -2
  104. package/dist/web/server.js +18 -3
  105. package/package.json +8 -9
  106. package/scripts/postinstall-setup.js +82 -0
  107. package/dist/api.js.map +0 -1
  108. package/dist/chunker/base.js.map +0 -1
  109. package/dist/chunker/bash.js.map +0 -1
  110. package/dist/chunker/c.js.map +0 -1
  111. package/dist/chunker/cpp.js.map +0 -1
  112. package/dist/chunker/csharp.js.map +0 -1
  113. package/dist/chunker/css.js.map +0 -1
  114. package/dist/chunker/doc.js.map +0 -1
  115. package/dist/chunker/dockerfile.js.map +0 -1
  116. package/dist/chunker/docx.js.map +0 -1
  117. package/dist/chunker/excel.js.map +0 -1
  118. package/dist/chunker/factory.js.map +0 -1
  119. package/dist/chunker/fallback.js.map +0 -1
  120. package/dist/chunker/go.js.map +0 -1
  121. package/dist/chunker/grammar.js.map +0 -1
  122. package/dist/chunker/html.js.map +0 -1
  123. package/dist/chunker/image.js.map +0 -1
  124. package/dist/chunker/ini.js.map +0 -1
  125. package/dist/chunker/java.js.map +0 -1
  126. package/dist/chunker/javascript.js.map +0 -1
  127. package/dist/chunker/json.js.map +0 -1
  128. package/dist/chunker/kotlin.js.map +0 -1
  129. package/dist/chunker/loader.js.map +0 -1
  130. package/dist/chunker/markdown.js.map +0 -1
  131. package/dist/chunker/pdf.js.map +0 -1
  132. package/dist/chunker/php.js.map +0 -1
  133. package/dist/chunker/powershell.js.map +0 -1
  134. package/dist/chunker/python.js.map +0 -1
  135. package/dist/chunker/razor.js.map +0 -1
  136. package/dist/chunker/ruby.js.map +0 -1
  137. package/dist/chunker/rust.js.map +0 -1
  138. package/dist/chunker/sln.js.map +0 -1
  139. package/dist/chunker/sql.js.map +0 -1
  140. package/dist/chunker/ssl.js.map +0 -1
  141. package/dist/chunker/swift.js.map +0 -1
  142. package/dist/chunker/tex.js.map +0 -1
  143. package/dist/chunker/toml.js.map +0 -1
  144. package/dist/chunker/typescript.js.map +0 -1
  145. package/dist/chunker/uuid.js.map +0 -1
  146. package/dist/chunker/xml.js.map +0 -1
  147. package/dist/chunker/yaml.js.map +0 -1
  148. package/dist/cli/commands/clear.js.map +0 -1
  149. package/dist/cli/commands/describe-image.js.map +0 -1
  150. package/dist/cli/commands/dump.js.map +0 -1
  151. package/dist/cli/commands/eval.js.map +0 -1
  152. package/dist/cli/commands/index-command.js.map +0 -1
  153. package/dist/cli/commands/index.js.map +0 -1
  154. package/dist/cli/commands/init-helpers.js.map +0 -1
  155. package/dist/cli/commands/init.js.map +0 -1
  156. package/dist/cli/commands/list.js.map +0 -1
  157. package/dist/cli/commands/mcp.js.map +0 -1
  158. package/dist/cli/commands/query.js.map +0 -1
  159. package/dist/cli/commands/show.js.map +0 -1
  160. package/dist/cli/commands/status.js.map +0 -1
  161. package/dist/cli/commands/ui.js.map +0 -1
  162. package/dist/cli/commands/update.js.map +0 -1
  163. package/dist/cli/format.js.map +0 -1
  164. package/dist/cli/helpers.js.map +0 -1
  165. package/dist/cli/index.js.map +0 -1
  166. package/dist/cli/progress.d.ts +0 -42
  167. package/dist/cli/progress.js +0 -137
  168. package/dist/cli/progress.js.map +0 -1
  169. package/dist/cli/types.js.map +0 -1
  170. package/dist/cli.js.map +0 -1
  171. package/dist/content/doc.js.map +0 -1
  172. package/dist/content/docx.js.map +0 -1
  173. package/dist/content/excel.js.map +0 -1
  174. package/dist/content/image.js.map +0 -1
  175. package/dist/content/pdf.js.map +0 -1
  176. package/dist/content/reader.js.map +0 -1
  177. package/dist/content/types.js.map +0 -1
  178. package/dist/core/bootstrap.js.map +0 -1
  179. package/dist/core/config.js.map +0 -1
  180. package/dist/core/doc-progress.js.map +0 -1
  181. package/dist/core/fileLogger.js.map +0 -1
  182. package/dist/core/interfaces.js.map +0 -1
  183. package/dist/core/manifest.js.map +0 -1
  184. package/dist/core/provider-defaults.js.map +0 -1
  185. package/dist/core/rag-injection-flag.js.map +0 -1
  186. package/dist/core/resolve-api-key.js.map +0 -1
  187. package/dist/core/runtime-overrides.js.map +0 -1
  188. package/dist/describer/anthropic.js.map +0 -1
  189. package/dist/describer/describer.js.map +0 -1
  190. package/dist/describer/factory.js.map +0 -1
  191. package/dist/describer/gemini.js.map +0 -1
  192. package/dist/describer/shared.js.map +0 -1
  193. package/dist/embedder/cohere.js.map +0 -1
  194. package/dist/embedder/factory.js.map +0 -1
  195. package/dist/embedder/health.js.map +0 -1
  196. package/dist/embedder/http.js.map +0 -1
  197. package/dist/embedder/ollama.js.map +0 -1
  198. package/dist/embedder/openai.js.map +0 -1
  199. package/dist/eval/index.js.map +0 -1
  200. package/dist/eval/run-token-test.js.map +0 -1
  201. package/dist/eval/session-logger.js.map +0 -1
  202. package/dist/eval/storage.js.map +0 -1
  203. package/dist/eval/token-analysis.js.map +0 -1
  204. package/dist/eval/token-counter.js.map +0 -1
  205. package/dist/eval/types.js.map +0 -1
  206. package/dist/index.js.map +0 -1
  207. package/dist/indexer/description-stage.js.map +0 -1
  208. package/dist/indexer/embed-stage.js.map +0 -1
  209. package/dist/indexer/git-diff.js.map +0 -1
  210. package/dist/indexer/metadata.js.map +0 -1
  211. package/dist/indexer/pipeline.js.map +0 -1
  212. package/dist/indexer/stats.js.map +0 -1
  213. package/dist/indexer/watch.js.map +0 -1
  214. package/dist/indexer/worker.js.map +0 -1
  215. package/dist/indexer.js.map +0 -1
  216. package/dist/mcp/cli.js.map +0 -1
  217. package/dist/mcp/handlers.js.map +0 -1
  218. package/dist/mcp/server.js.map +0 -1
  219. package/dist/opencode/create-read-tool.js.map +0 -1
  220. package/dist/opencode/read-fallback.js.map +0 -1
  221. package/dist/opencode/read-format.js.map +0 -1
  222. package/dist/opencode/read-query.js.map +0 -1
  223. package/dist/opencode/tool-args.js.map +0 -1
  224. package/dist/opencode/tools.js.map +0 -1
  225. package/dist/plugin-entry.js.map +0 -1
  226. package/dist/plugin.js.map +0 -1
  227. package/dist/retriever/context-optimizer.js.map +0 -1
  228. package/dist/retriever/keyword-index.js.map +0 -1
  229. package/dist/retriever/retriever.js.map +0 -1
  230. package/dist/tui.js.map +0 -1
  231. package/dist/updater.d.ts +0 -45
  232. package/dist/updater.js +0 -175
  233. package/dist/updater.js.map +0 -1
  234. package/dist/vectorstore/factory.js.map +0 -1
  235. package/dist/vectorstore/lancedb.js.map +0 -1
  236. package/dist/vectorstore/memory.js.map +0 -1
  237. package/dist/watcher.js.map +0 -1
  238. package/dist/web/api.js.map +0 -1
  239. package/dist/web/server.js.map +0 -1
  240. package/dist/web/static.js.map +0 -1
@@ -2,6 +2,7 @@
2
2
  * @fileoverview File hash manifest for tracking indexing state across sessions.
3
3
  * Provides load/save with atomic writes, schema versioning, and corruption detection.
4
4
  */
5
+ import type { RagConfig } from "./config.js";
5
6
  /** Metadata entry for a single indexed file. */
6
7
  export interface ManifestEntry {
7
8
  /** SHA-256 hash of the file content. */
@@ -16,9 +17,22 @@ export interface ManifestEntry {
16
17
  size?: number;
17
18
  /** Whether the description generation step failed for this file. */
18
19
  descriptionFailed?: boolean;
20
+ /**
21
+ * Hash of the description config (provider, model, baseUrl, prompts) that
22
+ * was active when descriptions were generated for this file. Skipped when
23
+ * no description provider is configured. Used to avoid re-describing
24
+ * files whose source content and description config are both unchanged.
25
+ */
26
+ descHash?: string;
19
27
  }
20
28
  /** Current schema version for manifest files. */
21
- export declare const SCHEMA_VERSION = 1;
29
+ export declare const SCHEMA_VERSION = 3;
30
+ /**
31
+ * Oldest schema version still accepted as valid.
32
+ * Bumping this intentionally marks older manifests corrupt and forces a
33
+ * rebuild, so only raise it when a migration is not practical.
34
+ */
35
+ export declare const MIN_SUPPORTED_SCHEMA_VERSION = 3;
22
36
  /** Persistent manifest tracking indexing state across sessions. */
23
37
  export interface FileManifest {
24
38
  /** Unix timestamp of the last full index pass. */
@@ -49,6 +63,15 @@ export declare function manifestPathFor(dbPath: string): string;
49
63
  export declare function normalizeFilePath(filePath: string): string;
50
64
  /** Compute the SHA-256 hex hash of a string. */
51
65
  export declare function computeFileHash(content: string): string;
66
+ /**
67
+ * Compute a hash of the description configuration (provider, model, baseUrl,
68
+ * systemPrompt, image prompt, etc.) so that files are only re-described when
69
+ * the config actually changes.
70
+ *
71
+ * Returns `undefined` when neither `description` nor `imageDescription` is
72
+ * configured — callers treat this as "no descriptions needed".
73
+ */
74
+ export declare function computeDescriptionConfigHash(config: RagConfig): string | undefined;
52
75
  /** Load a manifest from disk, handling missing or corrupt files gracefully. */
53
76
  export declare function loadManifest(dbPath: string): Promise<LoadedManifest>;
54
77
  /** Persist a manifest to disk with atomic write (temp file + rename, with Windows fallback). */
@@ -6,7 +6,13 @@ import { createHash } from "node:crypto";
6
6
  import fs from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  /** Current schema version for manifest files. */
9
- export const SCHEMA_VERSION = 1;
9
+ export const SCHEMA_VERSION = 3;
10
+ /**
11
+ * Oldest schema version still accepted as valid.
12
+ * Bumping this intentionally marks older manifests corrupt and forces a
13
+ * rebuild, so only raise it when a migration is not practical.
14
+ */
15
+ export const MIN_SUPPORTED_SCHEMA_VERSION = 3;
10
16
  /** Create a new empty manifest with the current schema version. */
11
17
  export function createEmptyManifest() {
12
18
  return { files: {}, schemaVersion: SCHEMA_VERSION };
@@ -23,6 +29,30 @@ export function normalizeFilePath(filePath) {
23
29
  export function computeFileHash(content) {
24
30
  return createHash("sha256").update(content).digest("hex");
25
31
  }
32
+ /**
33
+ * Compute a hash of the description configuration (provider, model, baseUrl,
34
+ * systemPrompt, image prompt, etc.) so that files are only re-described when
35
+ * the config actually changes.
36
+ *
37
+ * Returns `undefined` when neither `description` nor `imageDescription` is
38
+ * configured — callers treat this as "no descriptions needed".
39
+ */
40
+ export function computeDescriptionConfigHash(config) {
41
+ const desc = config.description;
42
+ const img = config.imageDescription;
43
+ if (!desc && !img)
44
+ return undefined;
45
+ const parts = [];
46
+ if (desc) {
47
+ parts.push(`desc:${desc.provider}|${desc.model}|${desc.baseUrl}|${desc.systemPrompt}`);
48
+ }
49
+ if (img) {
50
+ parts.push(`img:${img.provider}|${img.model}|${img.baseUrl}|${img.prompt}`);
51
+ }
52
+ if (parts.length === 0)
53
+ return undefined;
54
+ return createHash("sha256").update(parts.join("||")).digest("hex").slice(0, 16);
55
+ }
26
56
  /** Load a manifest from disk, handling missing or corrupt files gracefully. */
27
57
  export async function loadManifest(dbPath) {
28
58
  const filePath = manifestPathFor(dbPath);
@@ -32,14 +62,15 @@ export async function loadManifest(dbPath) {
32
62
  if (!parsed || typeof parsed !== "object" || !parsed.files || typeof parsed.files !== "object") {
33
63
  return { manifest: createEmptyManifest(), path: filePath, status: "corrupt" };
34
64
  }
65
+ const version = parsed.schemaVersion ?? 0;
35
66
  return {
36
67
  manifest: {
37
68
  lastIndexedAt: typeof parsed.lastIndexedAt === "number" ? parsed.lastIndexedAt : undefined,
38
- schemaVersion: parsed.schemaVersion,
69
+ schemaVersion: version,
39
70
  files: parsed.files,
40
71
  },
41
72
  path: filePath,
42
- status: parsed.schemaVersion === SCHEMA_VERSION ? "ok" : "corrupt",
73
+ status: version >= MIN_SUPPORTED_SCHEMA_VERSION ? "ok" : "corrupt",
43
74
  };
44
75
  }
45
76
  catch (error) {
@@ -41,7 +41,7 @@ function resolveForSection(provider, section, worktree) {
41
41
  // so createEmbedder can throw a clear error about the missing key
42
42
  }
43
43
  function stripJsoncComments(text) {
44
- return text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
44
+ return text.replace(/("[^"\\]*(?:\\.[^"\\]*)*")|(\/\/[^\n]*|\/\*[\s\S]*?\*\/)/g, (_, string) => string ?? "");
45
45
  }
46
46
  function readOpenCodeProviderKey(worktree, providerId) {
47
47
  const locations = [
@@ -57,7 +57,9 @@ function readOpenCodeProviderKey(worktree, providerId) {
57
57
  if (!existsSync(loc))
58
58
  continue;
59
59
  const raw = readFileSync(loc, "utf-8");
60
- const cleaned = stripJsoncComments(raw);
60
+ // Only strip JSONC comments from .jsonc files; plain JSON may contain
61
+ // "https://" URLs that look like line-comment markers after "//".
62
+ const cleaned = loc.endsWith(".jsonc") ? stripJsoncComments(raw) : raw;
61
63
  const config = JSON.parse(cleaned);
62
64
  const providerSection = config.provider;
63
65
  if (!providerSection)
@@ -11,7 +11,9 @@ export function loadRuntimeOverrides(storePath) {
11
11
  if (!existsSync(overridePath))
12
12
  return {};
13
13
  try {
14
- return JSON.parse(readFileSync(overridePath, "utf-8"));
14
+ const raw = readFileSync(overridePath, "utf-8");
15
+ const stripped = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
16
+ return JSON.parse(stripped);
15
17
  }
16
18
  catch {
17
19
  return {};
@@ -80,41 +82,43 @@ export function applyRuntimeOverrides(cfg, overrides) {
80
82
  merged.embedding.baseUrl = overrides.embedding.baseUrl;
81
83
  }
82
84
  if (overrides.description) {
85
+ const defaultDesc = DEFAULT_CONFIG.description;
83
86
  if (overrides.description.enabled !== undefined) {
84
87
  if (!merged.description)
85
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
88
+ merged.description = { ...defaultDesc };
86
89
  merged.description.enabled = overrides.description.enabled;
87
90
  }
88
91
  if (overrides.description.provider !== undefined) {
89
92
  if (!merged.description)
90
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
93
+ merged.description = { ...defaultDesc };
91
94
  merged.description.provider = overrides.description.provider;
92
95
  }
93
96
  if (overrides.description.model !== undefined) {
94
97
  if (!merged.description)
95
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
98
+ merged.description = { ...defaultDesc };
96
99
  merged.description.model = overrides.description.model;
97
100
  }
98
101
  if (overrides.description.baseUrl !== undefined) {
99
102
  if (!merged.description)
100
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
103
+ merged.description = { ...defaultDesc };
101
104
  merged.description.baseUrl = overrides.description.baseUrl;
102
105
  }
103
106
  }
104
107
  if (overrides.imageDescription) {
108
+ const defaultImg = DEFAULT_CONFIG.imageDescription;
105
109
  if (overrides.imageDescription.enabled !== undefined) {
106
110
  if (!merged.imageDescription)
107
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
111
+ merged.imageDescription = { ...defaultImg };
108
112
  merged.imageDescription.enabled = overrides.imageDescription.enabled;
109
113
  }
110
114
  if (overrides.imageDescription.provider !== undefined) {
111
115
  if (!merged.imageDescription)
112
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
116
+ merged.imageDescription = { ...defaultImg };
113
117
  merged.imageDescription.provider = overrides.imageDescription.provider;
114
118
  }
115
119
  if (overrides.imageDescription.model !== undefined) {
116
120
  if (!merged.imageDescription)
117
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
121
+ merged.imageDescription = { ...defaultImg };
118
122
  merged.imageDescription.model = overrides.imageDescription.model;
119
123
  }
120
124
  }
@@ -0,0 +1,23 @@
1
+ export interface SetupResult {
2
+ success: boolean;
3
+ errors: string[];
4
+ }
5
+ export declare function getRuntimeDir(): string;
6
+ export declare function getNpmGlobalRoot(): string;
7
+ export declare function getVersionFile(runtimeDir: string): string;
8
+ export declare function readVersionFile(versionFile: string): string | null;
9
+ /**
10
+ * npm-generated .cmd / .ps1 wrappers on Windows invoke `.js` files directly
11
+ * via file association. If the `.js` association points to a text editor
12
+ * (e.g. Notepad++) instead of Node.js, the CLI opens the editor instead of
13
+ * running. This function patches the wrappers to call `node` explicitly.
14
+ *
15
+ * Runs only on Windows. Safe to call on every setup — detects already-patched
16
+ * wrappers by checking for the `node` prefix.
17
+ */
18
+ export declare function patchWindowsWrappers(npmGlobalRoot: string): void;
19
+ export declare function setupRuntime(options?: {
20
+ force?: boolean;
21
+ silent?: boolean;
22
+ version?: string;
23
+ }): Promise<SetupResult>;
@@ -0,0 +1,231 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, symlinkSync, lstatSync, } from "node:fs";
4
+ import { execSync } from "node:child_process";
5
+ const PLUGIN_NAME = "opencode-rag-plugin";
6
+ export function getRuntimeDir() {
7
+ return path.join(os.homedir(), ".opencode");
8
+ }
9
+ export function getNpmGlobalRoot() {
10
+ return execSync("npm root -g", {
11
+ encoding: "utf-8",
12
+ timeout: 10_000,
13
+ }).trim();
14
+ }
15
+ function createJunction(targetPath, linkPath) {
16
+ const type = process.platform === "win32" ? "junction" : "dir";
17
+ symlinkSync(targetPath, linkPath, type);
18
+ }
19
+ function removeIfExists(targetPath) {
20
+ if (existsSync(targetPath)) {
21
+ rmSync(targetPath, { recursive: true, force: true });
22
+ }
23
+ }
24
+ export function getVersionFile(runtimeDir) {
25
+ return path.join(runtimeDir, ".bundle-version");
26
+ }
27
+ export function readVersionFile(versionFile) {
28
+ try {
29
+ return readFileSync(versionFile, "utf-8").trim();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /** Bin name npm generates for this package (without .cmd/.ps1). */
36
+ function getBinName() {
37
+ const pkgJson = path.join(getNpmGlobalRoot(), PLUGIN_NAME, "package.json");
38
+ try {
39
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf-8"));
40
+ if (pkg.bin && typeof pkg.bin === "object") {
41
+ return Object.keys(pkg.bin)[0] ?? PLUGIN_NAME;
42
+ }
43
+ if (typeof pkg.bin === "string") {
44
+ return PLUGIN_NAME;
45
+ }
46
+ }
47
+ catch {
48
+ // fall through
49
+ }
50
+ return PLUGIN_NAME;
51
+ }
52
+ /**
53
+ * npm-generated .cmd / .ps1 wrappers on Windows invoke `.js` files directly
54
+ * via file association. If the `.js` association points to a text editor
55
+ * (e.g. Notepad++) instead of Node.js, the CLI opens the editor instead of
56
+ * running. This function patches the wrappers to call `node` explicitly.
57
+ *
58
+ * Runs only on Windows. Safe to call on every setup — detects already-patched
59
+ * wrappers by checking for the `node` prefix.
60
+ */
61
+ export function patchWindowsWrappers(npmGlobalRoot) {
62
+ if (process.platform !== "win32")
63
+ return;
64
+ const binDir = path.resolve(npmGlobalRoot, "..");
65
+ const binName = getBinName();
66
+ // ── .cmd wrapper ──────────────────────────────────────────────
67
+ const cmdFile = path.join(binDir, `${binName}.cmd`);
68
+ if (existsSync(cmdFile)) {
69
+ const content = readFileSync(cmdFile, "utf-8");
70
+ // npm generates: "%dp0%\node_modules\opencode-rag-plugin\dist\cli\index.js" %*
71
+ // We want: node "%dp0%\node_modules\opencode-rag-plugin\dist\cli\index.js" %*
72
+ const cmdJsLine = /^(?!node\s)("[^"]+\.js"\s+%\*)$/m;
73
+ if (cmdJsLine.test(content)) {
74
+ const patched = content.replace(cmdJsLine, "node $1");
75
+ writeFileSync(cmdFile, patched, "utf-8");
76
+ }
77
+ }
78
+ // ── PowerShell wrapper ────────────────────────────────────────
79
+ const ps1File = path.join(binDir, `${binName}.ps1`);
80
+ if (existsSync(ps1File)) {
81
+ let content = readFileSync(ps1File, "utf-8");
82
+ // Skip if already patched
83
+ if (content.includes('node "$basedir'))
84
+ return;
85
+ // npm generates:
86
+ // $input | & "$basedir/node_modules/.../index.js" $args
87
+ // & "$basedir/node_modules/.../index.js" $args
88
+ // Replace to: & node "$basedir/.../index.js" $args
89
+ const ps1DirectLine = /(&\s+)("\$basedir\/[^"]+\.js"\s+\$args)/g;
90
+ if (ps1DirectLine.test(content)) {
91
+ content = content.replace(ps1DirectLine, "$1node $2");
92
+ writeFileSync(ps1File, content, "utf-8");
93
+ }
94
+ }
95
+ }
96
+ export async function setupRuntime(options) {
97
+ const errors = [];
98
+ const pluginVersion = options?.version || process.env.OPCODE_RAG_VERSION || "0.0.0";
99
+ const runtimeDir = getRuntimeDir();
100
+ const versionFile = getVersionFile(runtimeDir);
101
+ const runtimePluginDir = path.join(runtimeDir, "node_modules", PLUGIN_NAME);
102
+ const runtimeSdkDir = path.join(runtimeDir, "node_modules", "@opencode-ai");
103
+ const runtimeSdkPluginDir = path.join(runtimeSdkDir, "plugin");
104
+ const installedVersion = readVersionFile(versionFile);
105
+ const runtimeDist = path.join(runtimePluginDir, "dist");
106
+ const alreadyInstalled = existsSync(runtimeDist);
107
+ if (alreadyInstalled && installedVersion === pluginVersion && !options?.force) {
108
+ return { success: true, errors: [] };
109
+ }
110
+ let npmGlobalRoot;
111
+ try {
112
+ npmGlobalRoot = getNpmGlobalRoot();
113
+ }
114
+ catch {
115
+ errors.push("npm is not available on PATH. Cannot determine global package location.");
116
+ return { success: false, errors };
117
+ }
118
+ const globalPluginDir = path.join(npmGlobalRoot, PLUGIN_NAME);
119
+ const globalSdkPluginDir = path.join(npmGlobalRoot, "@opencode-ai", "plugin");
120
+ if (!existsSync(globalPluginDir)) {
121
+ errors.push(`Plugin not found at: ${globalPluginDir}`);
122
+ return { success: false, errors };
123
+ }
124
+ if (!existsSync(path.join(globalPluginDir, "dist", "cli.js"))) {
125
+ errors.push(`Global install seems incomplete: dist/ not found in ${globalPluginDir}`);
126
+ return { success: false, errors };
127
+ }
128
+ mkdirSync(runtimeDir, { recursive: true });
129
+ const runtimePkg = path.join(runtimeDir, "package.json");
130
+ if (!existsSync(runtimePkg)) {
131
+ writeFileSync(runtimePkg, JSON.stringify({ private: true, type: "module" }, null, 2) + "\n", "utf-8");
132
+ }
133
+ removeIfExists(runtimePluginDir);
134
+ // Ensure stale directory is fully gone before creating junction
135
+ let retries = 3;
136
+ while (retries > 0 && existsSync(runtimePluginDir)) {
137
+ try {
138
+ rmSync(runtimePluginDir, { recursive: true, force: true });
139
+ }
140
+ catch { /* retry */ }
141
+ retries--;
142
+ }
143
+ mkdirSync(path.dirname(runtimePluginDir), { recursive: true });
144
+ let junctionOk = false;
145
+ try {
146
+ createJunction(globalPluginDir, runtimePluginDir);
147
+ junctionOk = process.platform !== "win32";
148
+ if (process.platform === "win32") {
149
+ const stat = lstatSync(runtimePluginDir);
150
+ junctionOk = stat.isSymbolicLink();
151
+ }
152
+ }
153
+ catch {
154
+ // fall through to cpSync
155
+ }
156
+ if (!junctionOk) {
157
+ if (!options?.silent) {
158
+ console.error(" [warn] Junction not supported, falling back to copy...");
159
+ }
160
+ const { cpSync } = await import("node:fs");
161
+ cpSync(globalPluginDir, runtimePluginDir, { recursive: true });
162
+ }
163
+ // Ensure the @opencode-ai/plugin SDK is available globally.
164
+ // We must NOT run `npm install` inside the runtime dir because npm
165
+ // re-resolves all of node_modules/ and replaces the plugin junction
166
+ // with the published npm version (corrupting the local link).
167
+ if (!existsSync(globalSdkPluginDir)) {
168
+ try {
169
+ execSync(`npm install -g @opencode-ai/plugin`, {
170
+ stdio: "pipe",
171
+ timeout: 60_000,
172
+ });
173
+ }
174
+ catch (cause) {
175
+ errors.push(`Failed to install @opencode-ai/plugin SDK globally: ${cause.message}`);
176
+ return { success: false, errors };
177
+ }
178
+ }
179
+ // Create junction from global SDK to runtime (same pattern as plugin above)
180
+ if (existsSync(globalSdkPluginDir)) {
181
+ removeIfExists(runtimeSdkPluginDir);
182
+ retries = 3;
183
+ while (retries > 0 && existsSync(runtimeSdkPluginDir)) {
184
+ try {
185
+ rmSync(runtimeSdkPluginDir, { recursive: true, force: true });
186
+ }
187
+ catch { /* retry */ }
188
+ retries--;
189
+ }
190
+ mkdirSync(runtimeSdkDir, { recursive: true });
191
+ let sdkJunctionOk = false;
192
+ try {
193
+ createJunction(globalSdkPluginDir, runtimeSdkPluginDir);
194
+ sdkJunctionOk = process.platform !== "win32";
195
+ if (process.platform === "win32") {
196
+ const stat = lstatSync(runtimeSdkPluginDir);
197
+ sdkJunctionOk = stat.isSymbolicLink();
198
+ }
199
+ }
200
+ catch {
201
+ // fall through to cpSync
202
+ }
203
+ if (!sdkJunctionOk) {
204
+ if (!options?.silent) {
205
+ console.error(" [warn] SDK junction not supported, falling back to copy...");
206
+ }
207
+ const { cpSync } = await import("node:fs");
208
+ cpSync(globalSdkPluginDir, runtimeSdkPluginDir, { recursive: true });
209
+ }
210
+ }
211
+ else {
212
+ errors.push(`@opencode-ai/plugin SDK not available after global install`);
213
+ return { success: false, errors };
214
+ }
215
+ writeFileSync(versionFile, pluginVersion, "utf-8");
216
+ patchWindowsWrappers(npmGlobalRoot);
217
+ const cliEntry = path.join(runtimePluginDir, "dist", "cli.js");
218
+ const pluginEntry = path.join(runtimePluginDir, "dist", "plugin-entry.js");
219
+ const sdkPkg = path.join(runtimeSdkPluginDir, "package.json");
220
+ const success = existsSync(cliEntry) && existsSync(pluginEntry) && existsSync(sdkPkg);
221
+ if (!success) {
222
+ if (!existsSync(cliEntry))
223
+ errors.push(`CLI entry missing: ${cliEntry}`);
224
+ if (!existsSync(pluginEntry))
225
+ errors.push(`Plugin entry missing: ${pluginEntry}`);
226
+ if (!existsSync(sdkPkg))
227
+ errors.push(`Plugin SDK missing: ${sdkPkg}`);
228
+ }
229
+ return { success, errors };
230
+ }
231
+ //# sourceMappingURL=setup-runtime.js.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @fileoverview Version check and self-update functionality. Checks GitHub
3
+ * releases for new versions and installs the newest version via npm, then
4
+ * re-syncs the OpenCode runtime junctions.
5
+ */
6
+ import { setupRuntime } from "./setup-runtime.js";
7
+ /** Information about an available update. */
8
+ export interface UpdateInfo {
9
+ /** The currently installed version string. */
10
+ currentVersion: string;
11
+ /** The latest available version on GitHub. */
12
+ latestVersion: string;
13
+ /** Whether a newer version than the current one exists. */
14
+ updateAvailable: boolean;
15
+ /** URL to the GitHub release page. */
16
+ releaseUrl: string;
17
+ /** ISO date string of when the release was published. */
18
+ publishedAt: string;
19
+ }
20
+ /** Result of an install-update attempt. */
21
+ export interface InstallUpdateResult {
22
+ /** Whether the install completed successfully. */
23
+ success: boolean;
24
+ /** Human-readable status message. */
25
+ message: string;
26
+ /** Version before the update. */
27
+ fromVersion: string;
28
+ /** Version after the update (undefined if the install failed). */
29
+ toVersion?: string;
30
+ }
31
+ /** Callable shape for the npm runner (a subset of execSync's signature). */
32
+ type NpmRunner = (command: string, options: {
33
+ stdio: "inherit" | "pipe";
34
+ timeout: number;
35
+ }) => unknown;
36
+ /**
37
+ * Read the current version from the package.json sitting next to this module.
38
+ *
39
+ * Resolves the package root via `import.meta.url` so it works both from the
40
+ * source tree and from the compiled `dist/` output. Returns `"0.0.0"` if the
41
+ * file cannot be read or parsed (best-effort — never throws).
42
+ *
43
+ * @returns The current package version string.
44
+ */
45
+ export declare function getCurrentVersion(): string;
46
+ /**
47
+ * Compare two semver-ish strings.
48
+ * @returns 1 if a > b, -1 if a < b, 0 if equal.
49
+ */
50
+ export declare function compareVersions(a: string, b: string): number;
51
+ /**
52
+ * Check the GitHub releases API for a newer version of OpenCodeRAG.
53
+ *
54
+ * Uses a 5-second timeout; failures (network errors, non-OK responses, missing
55
+ * `tag_name`) are silently caught and reported as "no update available" so the
56
+ * caller never has to handle a rejection.
57
+ *
58
+ * @param currentVersion - The version string to compare against.
59
+ * @returns UpdateInfo indicating whether an update is available.
60
+ */
61
+ export declare function checkForUpdate(currentVersion: string): Promise<UpdateInfo>;
62
+ /**
63
+ * Install the newest published version of OpenCodeRAG.
64
+ *
65
+ * Runs `npm install -g <package>@latest` to refresh the global install, then
66
+ * calls {@link setupRuntime} with `force: true` to re-create the
67
+ * `~/.opencode/node_modules/` junctions so OpenCode picks up the new build on
68
+ * the next restart.
69
+ *
70
+ * @param options - Optional verbosity flag. When `verbose` is true, npm output
71
+ * is streamed to the console; otherwise it is captured silently. The
72
+ * `_execSync` and `_setupRuntime` seams are for testing only.
73
+ * @returns An {@link InstallUpdateResult} with success status, message, and the
74
+ * from/to versions.
75
+ */
76
+ export declare function installLatestUpdate(options?: {
77
+ verbose?: boolean;
78
+ /** Test seam: override the npm runner. */
79
+ _execSync?: NpmRunner;
80
+ /** Test seam: override the runtime sync. */
81
+ _setupRuntime?: typeof setupRuntime;
82
+ /** Test seam: override the version reader for the "to" version. */
83
+ _getCurrentVersion?: typeof getCurrentVersion;
84
+ }): Promise<InstallUpdateResult>;
85
+ export {};