@javierkaiser/opencode-indexing 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.1] - 2026-10-14
11
+
12
+ ### Changed
13
+
14
+ - npm releases are now automated: pushing a `v*` tag triggers a GitHub Actions
15
+ workflow that publishes through npm trusted publishing (OIDC), with no
16
+ long-lived token.
17
+
10
18
  ## [0.1.0] - 2026-10-09
11
19
 
12
20
  First public release.
@@ -29,5 +37,6 @@ First public release.
29
37
  - Cross-platform installer (`install.mjs`, plus `install.sh` / `install.ps1`
30
38
  wrappers).
31
39
 
32
- [Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.1.0...HEAD
40
+ [Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.1.1...HEAD
41
+ [0.1.1]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.1
33
42
  [0.1.0]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
5
5
  [![node: >=22.6](https://img.shields.io/badge/node-%3E%3D22.6-brightgreen.svg)](https://nodejs.org)
6
6
 
7
- OpenCode plugin that adds **semantic code search** by reusing the code index created by [Kilo Code](https://kilo.ai) and maintaining its **own independent index** (Qdrant or embedded LanceDB), so search keeps working even when Kilo isn't installed, is disabled, or its index is stale.
7
+ OpenCode plugin that adds **semantic code search** by reusing the code index created by [Kilo Code](https://kilo.ai) and maintaining its **own independent index** (embedded LanceDB by default, or Qdrant if you prefer), so search keeps working even when Kilo isn't installed, is disabled, or its index is stale.
8
8
 
9
9
  ## TL;DR
10
10
 
@@ -69,7 +69,7 @@ If no package manager is available the copy still happens and the plugin runs wi
69
69
 
70
70
  ```
71
71
  OpenCode ──► indexing_search ──┬──► Kilo's index (read-only) Qdrant ws-* · Kilo LanceDB
72
- └──► own index (read/write) Qdrant oc-* · own LanceDB
72
+ └──► own index (read/write) own LanceDB (default) · Qdrant oc-*
73
73
  ```
74
74
 
75
75
  - **Read-only reuse**: the plugin computes the same collection name Kilo uses (`ws-` + sha256 of the workspace path, first 16 hex chars), validates the embedding profile stored in the metadata point, and queries with an embedding from the *same* model (e.g. Mistral `codestral-embed-2505`, 1536d).
@@ -181,7 +181,9 @@ Configurable from all three:
181
181
  - **Auto-refresh on search** toggle.
182
182
  - Build / refresh / import actions and a status summary.
183
183
 
184
- Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `indexing.json` → environment → `~/.config/kilo/kilo.jsonc` → defaults). Plugin options in `opencode.json` still work and take precedence. API keys are never written to `indexing.json` in plain text; set them through the environment or plugin options.
184
+ Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `indexing.json` → environment → defaults). Plugin options in `opencode.json` still work and take precedence. API keys are never written to `indexing.json` in plain text; set them through the environment or plugin options.
185
+
186
+ `~/.config/kilo/kilo.jsonc` is still read, and still supplies the embedding provider, model, dimensions and API keys — and it still says where *Kilo* keeps its index. It no longer decides where this plugin's own index is written; that stopped being coupled so that the default could actually take effect for people running Kilo. See [Where each index lives](#where-each-index-lives).
185
187
 
186
188
  ## Configuration (optional, `opencode.json`)
187
189
 
@@ -200,7 +202,7 @@ Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `i
200
202
 
201
203
  | Variable | Meaning |
202
204
  | --- | --- |
203
- | `KILO_INDEX_VECTOR_STORE` | `qdrant` (default) or `lancedb` for the own index |
205
+ | `KILO_INDEX_VECTOR_STORE` | `lancedb` (default) or `qdrant` for the own index |
204
206
  | `KILO_INDEX_QDRANT_URL` / `KILO_INDEX_QDRANT_API_KEY` | Qdrant connection |
205
207
  | `KILO_INDEX_LANCEDB_DIR` | Base directory for the own LanceDB stores |
206
208
  | `KILO_INDEX_KILO_LANCEDB_DIR` | Override Kilo's LanceDB directory (import source) |
@@ -211,9 +213,56 @@ Word about data safety: Kilo's indexes (`ws-*` collections and its LanceDB direc
211
213
  ## Requirements
212
214
 
213
215
  - OpenCode v2 (plugin API `@opencode/plugin@2.x`).
214
- - For search/index: a Qdrant server (default `http://localhost:6333`) and/or the optional LanceDB module (`@lancedb/lancedb@0.26.2`).
216
+ - **For the own index: nothing to run.** The default store is embedded LanceDB
217
+ (`@lancedb/lancedb@0.26.2`), so there is no server to start, no port to free and
218
+ no container to keep alive.
219
+ - **A Qdrant server is only needed if you ask for it**, either by setting
220
+ `vectorStore=qdrant` or because a Kilo index you want to import lives in Qdrant.
221
+ In that case the default URL is `http://localhost:6333`.
215
222
  - Embedding credentials compatible with the collection you search (the profile is read from the metadata point).
216
223
 
224
+ ### Where each index lives
225
+
226
+ The own index and Kilo's index are independent, and each one is chosen on its
227
+ own:
228
+
229
+ | Index | Default | Configured by |
230
+ | --- | --- | --- |
231
+ | Own index | embedded LanceDB, under `~/.local/state/opencode-indexing/lancedb` | `vectorStore` |
232
+ | Kilo index (read-only) | whatever `kilo.jsonc` says | Kilo itself |
233
+
234
+ Kilo's `vectorStore` used to decide where this plugin's own index was written,
235
+ because both were read from the same setting chain. That is no longer the case:
236
+ Kilo decides where an import **reads from**, and your own index decides where it
237
+ **writes to**. If Kilo is on Qdrant and you have not set `vectorStore` yourself,
238
+ the own index still goes to LanceDB and `indexing_status` says so.
239
+
240
+ ### Platform support for the embedded store
241
+
242
+ `@lancedb/lancedb` is a JavaScript wrapper around a native library, with one
243
+ prebuilt binary per platform and no WebAssembly fallback. The platforms it ships
244
+ a binary for are:
245
+
246
+ | Platform | `platform-arch` | Own index on LanceDB |
247
+ | --- | --- | --- |
248
+ | Windows x64 / arm64 | `win32-x64`, `win32-arm64` | yes |
249
+ | Linux x64 / arm64 (glibc and musl) | `linux-x64`, `linux-arm64` | yes |
250
+ | macOS Apple Silicon | `darwin-arm64` | yes |
251
+ | **macOS Intel** | `darwin-x64` | **no binary is published** |
252
+
253
+ On macOS Intel the default store cannot load, and installing it again will not
254
+ help. The plugin says so explicitly and asks you to pick Qdrant:
255
+
256
+ ```
257
+ LanceDB is the default store for the own index but has no native binary for
258
+ darwin-x64. It cannot run on this platform and reinstalling will not change
259
+ that. Set vectorStore=qdrant explicitly in
260
+ ~/.config/opencode/indexing.json to use a Qdrant server instead.
261
+ ```
262
+
263
+ Set `vectorStore=qdrant` in `~/.config/opencode/indexing.json` (or run
264
+ `/indexing-config set vectorstore=qdrant`) to use a Qdrant server instead.
265
+
217
266
  ## Semantic search vs. code graphs (roadmap)
218
267
 
219
268
  `opencode-indexing` implements **semantic (vector) search**. It is worth being
package/install.sh CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javierkaiser/opencode-indexing",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenCode plugin: semantic code search that reuses Kilo Code indexes (Qdrant/LanceDB) and maintains its own independent index",
5
5
  "license": "MIT",
6
6
  "author": "Carlos Javier Lust <javierkaiser@gmail.com>",
package/src/config.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as os from "node:os"
2
2
 
3
3
  import type { EmbedderProvider, IndexingSettings, ProviderCredentials } from "./types.ts"
4
- import { defaultLanceDbDirectory, readSettingsFile, type SettingsFile } from "./settings.ts"
4
+ import { defaultLanceDbDirectory, readSettingsFile, settingsFilePath, type SettingsFile } from "./settings.ts"
5
+ import { lanceDbSupport } from "./lancedb-support.ts"
5
6
  import { stripJsonComments } from "./jsonc.ts"
6
7
  import {
7
8
  DEFAULT_EMBEDDING_BATCH_SIZE,
@@ -184,9 +185,33 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
184
185
  nonEmpty(kilo?.openrouter?.apiKey),
185
186
  }
186
187
 
187
- const vectorStore =
188
- pickVectorStore(options.vectorStore, file?.vectorStore, process.env.KILO_INDEX_VECTOR_STORE, kilo?.vectorStore) ??
189
- "qdrant"
188
+ // Kilo decides where an import reads FROM; it does not decide where the own
189
+ // index writes TO. Those were coupled through this one chain, and that is
190
+ // what made "LanceDB by default" unreachable for exactly the people most
191
+ // likely to have Kilo installed: kilo.jsonc's vectorStore sat above the
192
+ // fallback, so a Kilo-on-Qdrant setup silently kept pinning the own index to
193
+ // Qdrant. The coupling is reported below rather than dropped in silence.
194
+ const kiloVectorStore = pickVectorStore(kilo?.vectorStore)
195
+ const ownVectorStore = pickVectorStore(
196
+ options.vectorStore,
197
+ file?.vectorStore,
198
+ process.env.KILO_INDEX_VECTOR_STORE,
199
+ )
200
+ if (kiloVectorStore && !ownVectorStore && kiloVectorStore !== "lancedb") {
201
+ warnings.push(
202
+ `Kilo indexes with ${kiloVectorStore}; your own index no longer inherits that. ` +
203
+ `Set vectorStore explicitly in ${settingsFilePath(options.homeDir)} to change it.`,
204
+ )
205
+ }
206
+ const vectorStore = ownVectorStore ?? "lancedb"
207
+
208
+ if (vectorStore === "lancedb" && !lanceDbSupport().supported) {
209
+ const { id } = lanceDbSupport()
210
+ warnings.push(
211
+ `LanceDB is the default store but has no native binary for ${id}. ` +
212
+ `Indexing will fail until you set vectorStore=qdrant in ${settingsFilePath(options.homeDir)}.`,
213
+ )
214
+ }
190
215
 
191
216
  const qdrantUrl =
192
217
  nonEmpty(options.qdrantUrl) ??
@@ -264,6 +289,7 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
264
289
  modelId,
265
290
  dimension: dimension ?? 0,
266
291
  vectorStore,
292
+ kiloVectorStore: kiloVectorStore ?? null,
267
293
  qdrantUrl,
268
294
  qdrantApiKey,
269
295
  lancedbDirectory,
@@ -3,6 +3,7 @@ import * as path from "node:path"
3
3
 
4
4
  import { workspaceHash } from "./registry.ts"
5
5
  import { defaultLanceDbDirectory } from "./settings.ts"
6
+ import { lanceDbUnavailableMessage } from "./lancedb-support.ts"
6
7
  import type { VectorStoreAdapter, VectorStoreInfo, StoreSearchOptions, ExportBatch } from "./vector-store.ts"
7
8
  import type { EmbeddingProfile, QdrantPoint, QueryHit } from "./types.ts"
8
9
  import { describeError } from "./errors.ts"
@@ -71,9 +72,7 @@ let lancedbPromise: Promise<any> | undefined
71
72
  function loadLanceDb(): Promise<any> {
72
73
  lancedbPromise ??= import("@lancedb/lancedb").catch((error) => {
73
74
  lancedbPromise = undefined
74
- throw new Error(
75
- `LanceDB no está disponible (instala @lancedb/lancedb@0.26.2 o usa vectorStore=qdrant): ${describeError(error)}`,
76
- )
75
+ throw new Error(lanceDbUnavailableMessage(error), { cause: error })
77
76
  })
78
77
  return lancedbPromise
79
78
  }
@@ -0,0 +1,83 @@
1
+ import { settingsFilePath } from "./settings.ts"
2
+
3
+ /**
4
+ * Which platforms the LanceDB binding can actually run on.
5
+ *
6
+ * `@lancedb/lancedb` is a JavaScript wrapper that `require()`s one of seven
7
+ * per-platform native binaries, each published as its own optional dependency.
8
+ * There is no WASM build, so a platform outside that list cannot use LanceDB at
9
+ * all, and installing it again will not change that.
10
+ *
11
+ * macOS on Intel is the one that bites: `lancedb-darwin-x64` is not published,
12
+ * so LanceDB works on Apple Silicon only. The list is here so the plugin can say
13
+ * so plainly instead of surfacing a module-resolution stack trace; the import
14
+ * itself stays the authority on whether the binary loaded.
15
+ */
16
+
17
+ export const LANCEDB_VERSION = "0.26.2"
18
+
19
+ /** `platform-arch`, the way a native package would be named. */
20
+ export function platformId(platform: string = process.platform, arch: string = process.arch): string {
21
+ return `${platform}-${arch}`
22
+ }
23
+
24
+ const SUPPORTED = new Set([
25
+ "win32-x64",
26
+ "win32-arm64",
27
+ "darwin-arm64",
28
+ "linux-x64",
29
+ "linux-arm64",
30
+ ])
31
+
32
+ export interface LanceDbSupport {
33
+ /** `platform-arch` of the current process. */
34
+ id: string
35
+ /** Whether a native binary is published for it. */
36
+ supported: boolean
37
+ }
38
+
39
+ export function lanceDbSupport(
40
+ platform: string = process.platform,
41
+ arch: string = process.arch,
42
+ ): LanceDbSupport {
43
+ const id = platformId(platform, arch)
44
+ return { id, supported: SUPPORTED.has(id) }
45
+ }
46
+
47
+ /**
48
+ * Node appends the resolution chain to a MODULE_NOT_FOUND message, which buries
49
+ * the one line that says what went wrong under seven lines of internal files.
50
+ * The cause is still there; it is just what a reader acts on that is kept.
51
+ */
52
+ function causeSummary(cause: unknown): string {
53
+ const message = cause instanceof Error ? cause.message : String(cause)
54
+ const atRequireStack = message.indexOf("Require stack:")
55
+ return (atRequireStack === -1 ? message : message.slice(0, atRequireStack)).trim()
56
+ }
57
+
58
+ /**
59
+ * The message shown when LanceDB cannot be loaded.
60
+ *
61
+ * The two cases need different advice, so they get different words: a platform
62
+ * with no binary cannot be fixed by installing anything, while a supported
63
+ * platform that failed to load usually means the optional dependency was
64
+ * skipped.
65
+ */
66
+ export function lanceDbUnavailableMessage(
67
+ cause: unknown,
68
+ support: LanceDbSupport = lanceDbSupport(),
69
+ ): string {
70
+ const { id, supported } = support
71
+ if (!supported) {
72
+ return (
73
+ `LanceDB is the default store for the own index but has no native binary for ${id}. ` +
74
+ `It cannot run on this platform and reinstalling will not change that. ` +
75
+ `Set vectorStore=qdrant explicitly in ${settingsFilePath()} to use a Qdrant server instead.`
76
+ )
77
+ }
78
+ return (
79
+ `LanceDB could not be loaded for ${id}. It ships as an optional dependency: ` +
80
+ `install @lancedb/lancedb@${LANCEDB_VERSION}, or set vectorStore=qdrant. ` +
81
+ `Cause: ${causeSummary(cause)}`
82
+ )
83
+ }
package/src/status.ts CHANGED
@@ -56,13 +56,26 @@ const KILO_CACHE_PREFIX = "kiloCollection/"
56
56
 
57
57
  export async function getStatus(deps: StatusDeps, options?: { checkFreshness?: boolean }): Promise<IndexStatus> {
58
58
  const { qdrant, kv, root, settings } = deps
59
- const warnings: string[] = []
59
+ // Configuration warnings are produced once per resolution and were previously
60
+ // never shown, so a store that cannot work on this platform looked simply
61
+ // absent rather than misconfigured.
62
+ const warnings: string[] = [...settings.warnings]
60
63
  let reachable = true
61
64
  try {
62
65
  await qdrant.listCollections()
63
66
  } catch (error) {
64
67
  reachable = false
65
- warnings.push(`Qdrant unreachable at ${settings.qdrantUrl}: ${describeError(error)}`)
68
+ // Only worth reporting when Qdrant is actually part of this setup. With the
69
+ // own index on LanceDB and no Qdrant-backed Kilo index, a dead Qdrant is
70
+ // not a problem the user has, and saying so would train them to ignore it.
71
+ const qdrantIsOurs = settings.vectorStore === "qdrant" || settings.kiloVectorStore === "qdrant"
72
+ if (qdrantIsOurs) {
73
+ warnings.push(
74
+ settings.vectorStore === "qdrant"
75
+ ? `Qdrant unreachable at ${settings.qdrantUrl}: ${describeError(error)}`
76
+ : `Qdrant unreachable at ${settings.qdrantUrl}, so a Kilo index stored there cannot be imported: ${describeError(error)}`,
77
+ )
78
+ }
66
79
  }
67
80
 
68
81
  const ownStore = createOwnStore(settings, root)
package/src/types.ts CHANGED
@@ -39,6 +39,15 @@ export interface ProviderCredentials {
39
39
  export interface IndexingSettings extends EmbeddingProfile {
40
40
  /** Backend for the plugin's OWN index. */
41
41
  vectorStore: "qdrant" | "lancedb"
42
+ /**
43
+ * Store Kilo keeps its own index in, when kilo.jsonc says so.
44
+ *
45
+ * Kept alongside `vectorStore` rather than folded into it: Kilo decides where
46
+ * an import reads from, the own index decides where it writes, and the status
47
+ * report needs both to know whether an unreachable Qdrant is a problem here or
48
+ * simply not this user's business.
49
+ */
50
+ kiloVectorStore: "qdrant" | "lancedb" | null
42
51
  qdrantUrl: string
43
52
  qdrantApiKey?: string
44
53
  /** Base directory for the plugin's own LanceDB stores. */