@gmickel/gno 1.2.0 → 1.3.0

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 (43) hide show
  1. package/README.md +1 -1
  2. package/assets/skill/SKILL.md +3 -0
  3. package/assets/skill/cli-reference.md +5 -0
  4. package/assets/skill/examples.md +2 -0
  5. package/package.json +1 -1
  6. package/src/app/constants.ts +64 -8
  7. package/src/cli/commands/embed.ts +6 -2
  8. package/src/cli/commands/get.ts +15 -5
  9. package/src/cli/commands/index-cmd.ts +4 -0
  10. package/src/cli/commands/multi-get.ts +62 -1
  11. package/src/cli/commands/query.ts +8 -2
  12. package/src/cli/commands/search.ts +8 -2
  13. package/src/cli/commands/shared.ts +18 -1
  14. package/src/cli/commands/status.ts +4 -2
  15. package/src/cli/commands/update.ts +6 -1
  16. package/src/cli/commands/vsearch.ts +8 -2
  17. package/src/cli/format/search-results.ts +1 -1
  18. package/src/cli/program.ts +22 -1
  19. package/src/ingestion/chunker.ts +6 -0
  20. package/src/llm/cache.ts +162 -28
  21. package/src/llm/errors.ts +32 -0
  22. package/src/llm/lockfile.ts +49 -4
  23. package/src/llm/nodeLlamaCpp/embedding.ts +69 -3
  24. package/src/llm/nodeLlamaCpp/lifecycle.ts +60 -4
  25. package/src/mcp/resources/index.ts +13 -4
  26. package/src/mcp/server.ts +2 -0
  27. package/src/mcp/tools/get.ts +7 -2
  28. package/src/mcp/tools/multi-get.ts +2 -2
  29. package/src/mcp/tools/query.ts +2 -1
  30. package/src/mcp/tools/search.ts +2 -1
  31. package/src/mcp/tools/vsearch.ts +2 -1
  32. package/src/pipeline/explain.ts +12 -2
  33. package/src/pipeline/hybrid.ts +9 -1
  34. package/src/pipeline/search.ts +16 -7
  35. package/src/pipeline/types.ts +2 -0
  36. package/src/pipeline/vsearch.ts +29 -15
  37. package/src/publish/export-service.ts +27 -2
  38. package/src/sdk/client.ts +83 -28
  39. package/src/store/content-batch.ts +38 -0
  40. package/src/store/sqlite/adapter.ts +38 -2
  41. package/src/store/types.ts +8 -0
  42. package/src/store/vector/sqlite-vec.ts +10 -4
  43. package/src/store/vector/types.ts +2 -0
package/README.md CHANGED
@@ -778,7 +778,7 @@ graph TD
778
778
 
779
779
  ## Local Models
780
780
 
781
- Models auto-download on first use to `~/.cache/gno/models/`. For deterministic startup, set `GNO_NO_AUTO_DOWNLOAD=1` and use `gno models pull` explicitly. Alternatively, offload to a GPU server on your network using HTTP backends.
781
+ Models auto-download on first use to `~/.cache/gno/models/`. GNO validates cached GGUF files before loading and removes intercepted HTML/non-GGUF cache entries with a clear recovery error. For deterministic startup, set `GNO_NO_AUTO_DOWNLOAD=1` and use `gno models pull` explicitly. Alternatively, offload to a GPU server on your network using HTTP backends.
782
782
 
783
783
  | Model | Purpose | Size |
784
784
  | :--------------------- | :------------------------------------ | :----------- |
@@ -167,6 +167,9 @@ gno graph -c notes --similar # Include similarity edges
167
167
  --no-pager Disable paging
168
168
  ```
169
169
 
170
+ Non-default index search results may include `?index=<name>` on `gno://` URIs.
171
+ Keep that query string when passing the URI to `gno get`.
172
+
170
173
  ## Important: Embedding After Changes
171
174
 
172
175
  If you edit/create files that should be searchable via vector search:
@@ -196,6 +196,7 @@ gno get <ref> [--from <line>] [-l <lines>] [--line-numbers] [--source]
196
196
  Ref formats:
197
197
 
198
198
  - `gno://collection/path` — Full URI
199
+ - `gno://collection/path?index=name` — Full URI emitted from a non-default index
199
200
  - `collection/path` — Relative path
200
201
  - `#docid` — Document ID
201
202
  - `gno://docs/file.md:120` — With line number
@@ -378,6 +379,10 @@ Presets: `slim` (~1GB), `balanced` (~2GB), `quality` (~2.5GB)
378
379
  gno models pull [--all|--embed|--rerank|--gen] [--force]
379
380
  ```
380
381
 
382
+ Cached/local model files are validated as GGUF before use. If the cache contains
383
+ HTML or another non-GGUF response, rerun with `--force` after fixing network
384
+ access.
385
+
381
386
  ### gno models clear
382
387
 
383
388
  ```bash
@@ -90,6 +90,7 @@ gno ask "summarize project goals" --answer --max-answer-tokens 200
90
90
  ```bash
91
91
  # By URI
92
92
  gno get gno://work/readme.md
93
+ gno get "gno://work/readme.md?index=research"
93
94
 
94
95
  # By document ID
95
96
  gno get "#a1b2c3d4"
@@ -204,6 +205,7 @@ gno models use quality
204
205
 
205
206
  # Download models
206
207
  gno models pull
208
+ gno models pull --force
207
209
  ```
208
210
 
209
211
  ## Note Linking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -244,22 +244,39 @@ export function getModelsCachePath(dirs: ResolvedDirs = resolveDirs()): string {
244
244
  /**
245
245
  * Build a gno:// URI from collection and relative path.
246
246
  */
247
- export function buildUri(collection: string, relativePath: string): string {
247
+ export interface BuildUriOptions {
248
+ indexName?: string;
249
+ }
250
+
251
+ export interface ParsedGnoUri {
252
+ collection: string;
253
+ path: string;
254
+ indexName?: string;
255
+ }
256
+
257
+ export function buildUri(
258
+ collection: string,
259
+ relativePath: string,
260
+ options: BuildUriOptions = {}
261
+ ): string {
248
262
  // URL-encode special chars in path segments but preserve slashes
249
263
  const encodedPath = relativePath
250
264
  .split("/")
251
265
  .map((segment) => encodeURIComponent(segment))
252
266
  .join("/");
253
- return `${URI_PREFIX}${collection}/${encodedPath}`;
267
+ const uri = `${URI_PREFIX}${collection}/${encodedPath}`;
268
+ const indexName = options.indexName?.trim();
269
+ if (!indexName || indexName === DEFAULT_INDEX_NAME) {
270
+ return uri;
271
+ }
272
+ return `${uri}?index=${encodeURIComponent(indexName)}`;
254
273
  }
255
274
 
256
275
  /**
257
276
  * Parse a gno:// URI into collection and path components.
258
277
  * Returns null if not a valid gno:// URI or if decoding fails.
259
278
  */
260
- export function parseUri(
261
- uri: string
262
- ): { collection: string; path: string } | null {
279
+ export function parseUri(uri: string): ParsedGnoUri | null {
263
280
  if (!uri.startsWith(URI_PREFIX)) {
264
281
  return null;
265
282
  }
@@ -269,20 +286,59 @@ export function parseUri(
269
286
 
270
287
  if (slashIndex === -1) {
271
288
  // gno://collection (no path)
272
- return { collection: rest, path: "" };
289
+ const [collectionWithQuery, query = ""] = rest.split("?", 2);
290
+ const indexName = new URLSearchParams(query).get("index")?.trim();
291
+ return indexName
292
+ ? {
293
+ collection: collectionWithQuery ?? rest,
294
+ path: "",
295
+ indexName,
296
+ }
297
+ : { collection: collectionWithQuery ?? rest, path: "" };
273
298
  }
274
299
 
275
300
  const collection = rest.slice(0, slashIndex);
301
+ const pathAndQuery = rest.slice(slashIndex + 1);
302
+ const queryIndex = pathAndQuery.indexOf("?");
303
+ const encodedPath =
304
+ queryIndex === -1 ? pathAndQuery : pathAndQuery.slice(0, queryIndex);
305
+ const query = queryIndex === -1 ? "" : pathAndQuery.slice(queryIndex + 1);
276
306
 
277
307
  // decodeURIComponent throws on malformed percent-encoding
278
308
  try {
279
- const path = decodeURIComponent(rest.slice(slashIndex + 1));
280
- return { collection, path };
309
+ const path = decodeURIComponent(encodedPath);
310
+ const indexName = new URLSearchParams(query).get("index")?.trim();
311
+ return indexName ? { collection, path, indexName } : { collection, path };
281
312
  } catch {
282
313
  return null;
283
314
  }
284
315
  }
285
316
 
317
+ /**
318
+ * Add output-only index metadata to a canonical gno:// URI.
319
+ */
320
+ export function decorateUriForIndex(uri: string, indexName?: string): string {
321
+ const parsed = parseUri(uri);
322
+ const normalizedIndex = indexName?.trim();
323
+ if (!parsed || !normalizedIndex || normalizedIndex === DEFAULT_INDEX_NAME) {
324
+ return stripUriIndex(uri);
325
+ }
326
+ return buildUri(parsed.collection, parsed.path, {
327
+ indexName: normalizedIndex,
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Remove output-only index metadata from a gno:// URI.
333
+ */
334
+ export function stripUriIndex(uri: string): string {
335
+ const parsed = parseUri(uri);
336
+ if (!parsed) {
337
+ return uri;
338
+ }
339
+ return buildUri(parsed.collection, parsed.path);
340
+ }
341
+
286
342
  // ─────────────────────────────────────────────────────────────────────────────
287
343
  // docid Utilities
288
344
  // ─────────────────────────────────────────────────────────────────────────────
@@ -45,6 +45,8 @@ import {
45
45
  export interface EmbedOptions {
46
46
  /** Override config path */
47
47
  configPath?: string;
48
+ /** Index name */
49
+ indexName?: string;
48
50
  /** Restrict embedding work to a single collection */
49
51
  collection?: string;
50
52
  /** Override model URI */
@@ -417,6 +419,7 @@ interface EmbedContext {
417
419
  */
418
420
  async function initEmbedContext(
419
421
  configPath?: string,
422
+ indexName?: string,
420
423
  collection?: string,
421
424
  model?: string
422
425
  ): Promise<({ ok: true } & EmbedContext) | { ok: false; error: string }> {
@@ -440,9 +443,9 @@ async function initEmbedContext(
440
443
  const modelUri = resolveModelUri(config, "embed", model, collection);
441
444
 
442
445
  const store = new SqliteAdapter();
443
- const dbPath = getIndexDbPath();
446
+ const dbPath = getIndexDbPath(indexName);
444
447
  const paths = getConfigPaths();
445
- store.setConfigPath(paths.configFile);
448
+ store.setConfigPath(configPath ?? paths.configFile);
446
449
 
447
450
  const openResult = await store.open(dbPath, config.ftsTokenizer);
448
451
  if (!openResult.ok) {
@@ -467,6 +470,7 @@ export async function embed(options: EmbedOptions = {}): Promise<EmbedResult> {
467
470
  // Initialize config and store
468
471
  const initResult = await initEmbedContext(
469
472
  options.configPath,
473
+ options.indexName,
470
474
  options.collection,
471
475
  options.model
472
476
  );
@@ -8,6 +8,7 @@
8
8
  import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
9
9
  import type { ParsedRef } from "./ref-parser";
10
10
 
11
+ import { decorateUriForIndex, parseUri } from "../../app/constants";
11
12
  import {
12
13
  getDocumentCapabilities,
13
14
  type DocumentCapabilities,
@@ -22,6 +23,8 @@ import { initStore } from "./shared";
22
23
  export interface GetCommandOptions {
23
24
  /** Override config path */
24
25
  configPath?: string;
26
+ /** Index name */
27
+ indexName?: string;
25
28
  /** --from <line>, overrides :line suffix */
26
29
  from?: number;
27
30
  /** -l <lines> */
@@ -109,9 +112,13 @@ export async function get(
109
112
  if ("error" in parsed) {
110
113
  return { success: false, error: parsed.error, isValidation: true };
111
114
  }
115
+ const explicitIndexName =
116
+ parsed.type === "uri" ? parseUri(parsed.value)?.indexName : undefined;
117
+ const indexName = explicitIndexName ?? options.indexName;
112
118
 
113
119
  const initResult = await initStore({
114
120
  configPath: options.configPath,
121
+ indexName,
115
122
  syncConfig: false,
116
123
  });
117
124
  if (!initResult.ok) {
@@ -120,7 +127,7 @@ export async function get(
120
127
  const { store, config } = initResult;
121
128
 
122
129
  try {
123
- return await fetchDocument(store, parsed, options, config);
130
+ return await fetchDocument(store, parsed, options, config, indexName);
124
131
  } finally {
125
132
  await store.close();
126
133
  }
@@ -152,7 +159,8 @@ async function fetchDocument(
152
159
  store: StorePort,
153
160
  parsed: ParsedRef,
154
161
  options: GetCommandOptions,
155
- config: ConfigLike
162
+ config: ConfigLike,
163
+ indexName?: string
156
164
  ): Promise<GetResult> {
157
165
  const docResult = await lookupDocument(store, parsed);
158
166
  if (!docResult.ok) {
@@ -182,6 +190,7 @@ async function fetchDocument(
182
190
  parsed,
183
191
  options,
184
192
  config,
193
+ indexName,
185
194
  });
186
195
  }
187
196
 
@@ -191,10 +200,11 @@ interface BuildResponseContext {
191
200
  parsed: ParsedRef;
192
201
  options: GetCommandOptions;
193
202
  config: ConfigLike;
203
+ indexName?: string;
194
204
  }
195
205
 
196
206
  function buildResponse(ctx: BuildResponseContext): GetResult {
197
- const { doc, fullContent, parsed, options, config } = ctx;
207
+ const { doc, fullContent, parsed, options, config, indexName } = ctx;
198
208
  const lines = fullContent.split("\n");
199
209
  const totalLines = lines.length;
200
210
 
@@ -204,7 +214,7 @@ function buildResponse(ctx: BuildResponseContext): GetResult {
204
214
  success: true,
205
215
  data: {
206
216
  docid: doc.docid,
207
- uri: doc.uri,
217
+ uri: decorateUriForIndex(doc.uri, indexName),
208
218
  title: doc.title ?? undefined,
209
219
  content: "",
210
220
  totalLines,
@@ -232,7 +242,7 @@ function buildResponse(ctx: BuildResponseContext): GetResult {
232
242
  success: true,
233
243
  data: {
234
244
  docid: doc.docid,
235
- uri: doc.uri,
245
+ uri: decorateUriForIndex(doc.uri, indexName),
236
246
  title: doc.title ?? undefined,
237
247
  content,
238
248
  totalLines,
@@ -14,6 +14,8 @@ import { formatSyncResultLines, initStore } from "./shared";
14
14
  export interface IndexOptions {
15
15
  /** Override config path */
16
16
  configPath?: string;
17
+ /** Index name */
18
+ indexName?: string;
17
19
  /** Scope to single collection */
18
20
  collection?: string;
19
21
  /** Run ingestion only, skip embedding */
@@ -46,6 +48,7 @@ export type IndexResult =
46
48
  export async function index(options: IndexOptions = {}): Promise<IndexResult> {
47
49
  const initResult = await initStore({
48
50
  configPath: options.configPath,
51
+ indexName: options.indexName,
49
52
  collection: options.collection,
50
53
  });
51
54
  if (!initResult.ok) {
@@ -71,6 +74,7 @@ export async function index(options: IndexOptions = {}): Promise<IndexResult> {
71
74
  const { embed } = await import("./embed");
72
75
  const result = await embed({
73
76
  configPath: options.configPath,
77
+ indexName: options.indexName,
74
78
  collection: options.collection,
75
79
  verbose: options.verbose,
76
80
  });
@@ -10,6 +10,7 @@ import { minimatch } from "minimatch";
10
10
  import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
11
11
  import type { ParsedRef } from "./ref-parser";
12
12
 
13
+ import { decorateUriForIndex, parseUri } from "../../app/constants";
13
14
  import { isGlobPattern, parseRef, splitRefs } from "./ref-parser";
14
15
  import { initStore } from "./shared";
15
16
 
@@ -20,6 +21,8 @@ import { initStore } from "./shared";
20
21
  export interface MultiGetCommandOptions {
21
22
  /** Override config path */
22
23
  configPath?: string;
24
+ /** Index name */
25
+ indexName?: string;
23
26
  /** Max bytes per document (default 10240) */
24
27
  maxBytes?: number;
25
28
  /** Include line numbers */
@@ -170,6 +173,7 @@ function truncateContent(
170
173
  interface FetchContext {
171
174
  store: StorePort;
172
175
  config: ConfigLike;
176
+ indexName?: string;
173
177
  maxBytes: number;
174
178
  documents: MultiGetDocument[];
175
179
  skipped: SkippedDoc[];
@@ -222,7 +226,7 @@ async function fetchSingleDocument(
222
226
 
223
227
  ctx.documents.push({
224
228
  docid: doc.docid,
225
- uri: doc.uri,
229
+ uri: decorateUriForIndex(doc.uri, ctx.indexName),
226
230
  title: doc.title ?? undefined,
227
231
  content,
228
232
  truncated: truncated || undefined,
@@ -249,9 +253,18 @@ export async function multiGet(
249
253
  ): Promise<MultiGetResult> {
250
254
  const maxBytes = options.maxBytes ?? 10_240;
251
255
  const allRefs = splitRefs(refs);
256
+ const indexResolution = resolveMultiGetIndex(allRefs, options.indexName);
257
+ if (!indexResolution.ok) {
258
+ return {
259
+ success: false,
260
+ error: indexResolution.error,
261
+ isValidation: true,
262
+ };
263
+ }
252
264
 
253
265
  const initResult = await initStore({
254
266
  configPath: options.configPath,
267
+ indexName: indexResolution.indexName,
255
268
  syncConfig: false,
256
269
  });
257
270
  if (!initResult.ok) {
@@ -267,6 +280,7 @@ export async function multiGet(
267
280
  const ctx: FetchContext = {
268
281
  store,
269
282
  config,
283
+ indexName: indexResolution.indexName,
270
284
  maxBytes,
271
285
  documents: [],
272
286
  skipped: [],
@@ -301,6 +315,53 @@ export async function multiGet(
301
315
  }
302
316
  }
303
317
 
318
+ function resolveMultiGetIndex(
319
+ refs: string[],
320
+ globalIndexName?: string
321
+ ): { ok: true; indexName?: string } | { ok: false; error: string } {
322
+ const explicitIndexes = new Set<string>();
323
+ let hasUnindexedRef = false;
324
+
325
+ for (const ref of refs) {
326
+ const parsed = parseRef(ref);
327
+ if ("error" in parsed || parsed.type !== "uri") {
328
+ hasUnindexedRef = true;
329
+ continue;
330
+ }
331
+ const indexName = parseUri(parsed.value)?.indexName;
332
+ if (indexName) {
333
+ explicitIndexes.add(indexName);
334
+ } else {
335
+ hasUnindexedRef = true;
336
+ }
337
+ }
338
+
339
+ if (explicitIndexes.size === 0) {
340
+ return { ok: true, indexName: globalIndexName };
341
+ }
342
+ if (explicitIndexes.size > 1) {
343
+ return {
344
+ ok: false,
345
+ error: `multi-get cannot mix explicit indexes: ${[...explicitIndexes].sort().join(", ")}`,
346
+ };
347
+ }
348
+
349
+ const explicitIndex = [...explicitIndexes][0];
350
+ if (
351
+ hasUnindexedRef &&
352
+ globalIndexName &&
353
+ explicitIndex &&
354
+ globalIndexName !== explicitIndex
355
+ ) {
356
+ return {
357
+ ok: false,
358
+ error: `multi-get cannot mix indexed refs (${explicitIndex}) with unindexed refs while --index is ${globalIndexName}`,
359
+ };
360
+ }
361
+
362
+ return { ok: true, indexName: explicitIndex ?? globalIndexName };
363
+ }
364
+
304
365
  // ─────────────────────────────────────────────────────────────────────────────
305
366
  // Formatter
306
367
  // ─────────────────────────────────────────────────────────────────────────────
@@ -25,7 +25,7 @@ import {
25
25
  createProgressRenderer,
26
26
  createThrottledProgressRenderer,
27
27
  } from "../progress";
28
- import { initStore } from "./shared";
28
+ import { decorateSearchResultsForIndex, initStore } from "./shared";
29
29
 
30
30
  // ─────────────────────────────────────────────────────────────────────────────
31
31
  // Types
@@ -34,6 +34,8 @@ import { initStore } from "./shared";
34
34
  export type QueryCommandOptions = HybridSearchOptions & {
35
35
  /** Override config path */
36
36
  configPath?: string;
37
+ /** Index name */
38
+ indexName?: string;
37
39
  /** Override embedding model */
38
40
  embedModel?: string;
39
41
  /** Override expansion model */
@@ -83,6 +85,7 @@ export async function query(
83
85
 
84
86
  const initResult = await initStore({
85
87
  configPath: options.configPath,
88
+ indexName: options.indexName,
86
89
  collection: options.collection,
87
90
  syncConfig: false,
88
91
  });
@@ -208,7 +211,10 @@ export async function query(
208
211
  return { success: false, error: result.error.message };
209
212
  }
210
213
 
211
- return { success: true, data: result.value };
214
+ return {
215
+ success: true,
216
+ data: decorateSearchResultsForIndex(result.value, options.indexName),
217
+ };
212
218
  } finally {
213
219
  if (embedPort) {
214
220
  await embedPort.dispose();
@@ -12,7 +12,7 @@ import {
12
12
  type FormatOptions,
13
13
  formatSearchResults,
14
14
  } from "../format/search-results";
15
- import { initStore } from "./shared";
15
+ import { decorateSearchResultsForIndex, initStore } from "./shared";
16
16
 
17
17
  // ─────────────────────────────────────────────────────────────────────────────
18
18
  // Types
@@ -21,6 +21,8 @@ import { initStore } from "./shared";
21
21
  export type SearchCommandOptions = SearchOptions & {
22
22
  /** Override config path */
23
23
  configPath?: string;
24
+ /** Index name */
25
+ indexName?: string;
24
26
  /** Output as JSON */
25
27
  json?: boolean;
26
28
  /** Output as Markdown */
@@ -57,6 +59,7 @@ export async function search(
57
59
 
58
60
  const initResult = await initStore({
59
61
  configPath: options.configPath,
62
+ indexName: options.indexName,
60
63
  collection: options.collection,
61
64
  syncConfig: false,
62
65
  });
@@ -83,7 +86,10 @@ export async function search(
83
86
  };
84
87
  }
85
88
 
86
- return { success: true, data: result.value };
89
+ return {
90
+ success: true,
91
+ data: decorateSearchResultsForIndex(result.value, options.indexName),
92
+ };
87
93
  } finally {
88
94
  await store.close();
89
95
  }
@@ -7,8 +7,9 @@
7
7
 
8
8
  import type { Collection, Config } from "../../config/types";
9
9
  import type { SyncResult } from "../../ingestion";
10
+ import type { SearchResults } from "../../pipeline/types";
10
11
 
11
- import { getIndexDbPath } from "../../app/constants";
12
+ import { decorateUriForIndex, getIndexDbPath } from "../../app/constants";
12
13
  import { getConfigPaths, isInitialized, loadConfig } from "../../config";
13
14
  import { SqliteAdapter } from "../../store/sqlite/adapter";
14
15
 
@@ -122,6 +123,22 @@ export async function initStore(
122
123
  return { ok: true, store, config, collections, actualConfigPath };
123
124
  }
124
125
 
126
+ export function decorateSearchResultsForIndex(
127
+ data: SearchResults,
128
+ indexName?: string
129
+ ): SearchResults {
130
+ if (!indexName) {
131
+ return data;
132
+ }
133
+ return {
134
+ ...data,
135
+ results: data.results.map((result) => ({
136
+ ...result,
137
+ uri: decorateUriForIndex(result.uri, indexName),
138
+ })),
139
+ };
140
+ }
141
+
125
142
  /**
126
143
  * Format sync result lines (shared between update and index commands).
127
144
  */
@@ -18,6 +18,8 @@ import { SqliteAdapter } from "../../store/sqlite/adapter";
18
18
  export interface StatusOptions {
19
19
  /** Override config path */
20
20
  configPath?: string;
21
+ /** Index name */
22
+ indexName?: string;
21
23
  /** Output as JSON */
22
24
  json?: boolean;
23
25
  /** Output as Markdown */
@@ -137,11 +139,11 @@ export async function status(
137
139
 
138
140
  // Open database
139
141
  const store = new SqliteAdapter();
140
- const dbPath = getIndexDbPath();
142
+ const dbPath = getIndexDbPath(options.indexName);
141
143
  const paths = getConfigPaths();
142
144
 
143
145
  // Set configPath for status output
144
- store.setConfigPath(paths.configFile);
146
+ store.setConfigPath(options.configPath ?? paths.configFile);
145
147
 
146
148
  const openResult = await store.open(dbPath, config.ftsTokenizer);
147
149
  if (!openResult.ok) {
@@ -14,6 +14,8 @@ import { formatSyncResultLines, initStore } from "./shared";
14
14
  export interface UpdateOptions {
15
15
  /** Override config path */
16
16
  configPath?: string;
17
+ /** Index name */
18
+ indexName?: string;
17
19
  /** Run git pull in git repositories before scanning */
18
20
  gitPull?: boolean;
19
21
  /** Verbose output */
@@ -33,7 +35,10 @@ export type UpdateResult =
33
35
  export async function update(
34
36
  options: UpdateOptions = {}
35
37
  ): Promise<UpdateResult> {
36
- const initResult = await initStore({ configPath: options.configPath });
38
+ const initResult = await initStore({
39
+ configPath: options.configPath,
40
+ indexName: options.indexName,
41
+ });
37
42
  if (!initResult.ok) {
38
43
  return { success: false, error: initResult.error };
39
44
  }
@@ -19,7 +19,7 @@ import {
19
19
  type FormatOptions,
20
20
  formatSearchResults,
21
21
  } from "../format/search-results";
22
- import { initStore } from "./shared";
22
+ import { decorateSearchResultsForIndex, initStore } from "./shared";
23
23
 
24
24
  // ─────────────────────────────────────────────────────────────────────────────
25
25
  // Types
@@ -28,6 +28,8 @@ import { initStore } from "./shared";
28
28
  export type VsearchCommandOptions = SearchOptions & {
29
29
  /** Override config path */
30
30
  configPath?: string;
31
+ /** Index name */
32
+ indexName?: string;
31
33
  /** Override model URI */
32
34
  model?: string;
33
35
  /** Output as JSON */
@@ -66,6 +68,7 @@ export async function vsearch(
66
68
 
67
69
  const initResult = await initStore({
68
70
  configPath: options.configPath,
71
+ indexName: options.indexName,
69
72
  collection: options.collection,
70
73
  syncConfig: false,
71
74
  });
@@ -137,7 +140,10 @@ export async function vsearch(
137
140
  return { success: false, error: result.error.message };
138
141
  }
139
142
 
140
- return { success: true, data: result.value };
143
+ return {
144
+ success: true,
145
+ data: decorateSearchResultsForIndex(result.value, options.indexName),
146
+ };
141
147
  } finally {
142
148
  await embedPort.dispose();
143
149
  }
@@ -119,7 +119,7 @@ function formatTerminalUri(
119
119
 
120
120
  const target = buildTerminalLinkTarget(
121
121
  result.source.absPath,
122
- result.snippetRange?.startLine,
122
+ result.line ?? result.snippetRange?.startLine,
123
123
  links.editorUriTemplate ?? undefined
124
124
  );
125
125