@tobilu/qmd 2.1.0 → 2.5.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.
@@ -60,6 +60,15 @@ export declare function setConfigSource(source?: {
60
60
  * Config file will be ~/.config/qmd/{indexName}.yml
61
61
  */
62
62
  export declare function setConfigIndexName(name: string): void;
63
+ /**
64
+ * Find a project-local QMD config by walking upward from startDir.
65
+ * The local config lives at .qmd/index.yaml or .qmd/index.yml and,
66
+ * when used by the CLI, keeps both config and index DB writes inside
67
+ * the project instead of the global ~/.config / ~/.cache locations.
68
+ */
69
+ export declare function findLocalConfigPath(startDir?: string): string | undefined;
70
+ /** Return the local SQLite index path paired with a local .qmd/index.yaml file. */
71
+ export declare function getLocalDbPath(configPath: string): string;
63
72
  /**
64
73
  * Load configuration from the configured source.
65
74
  * - Inline config: returns the in-memory object directly
@@ -5,8 +5,8 @@
5
5
  * Collections define which directories to index and their associated contexts.
6
6
  */
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
- import { join, dirname } from "path";
9
- import { homedir } from "os";
8
+ import { join, dirname, resolve } from "path";
9
+ import { qmdHomedir } from "./paths.js";
10
10
  import YAML from "yaml";
11
11
  // ============================================================================
12
12
  // Configuration paths
@@ -47,9 +47,7 @@ export function setConfigSource(source) {
47
47
  export function setConfigIndexName(name) {
48
48
  // Resolve relative paths to absolute paths and sanitize for use as filename
49
49
  if (name.includes('/')) {
50
- const { resolve } = require('path');
51
- const { cwd } = require('process');
52
- const absolutePath = resolve(cwd(), name);
50
+ const absolutePath = resolve(process.cwd(), name);
53
51
  // Replace path separators with underscores to create a valid filename
54
52
  currentIndexName = absolutePath.replace(/\//g, '_').replace(/^_/, '');
55
53
  }
@@ -66,11 +64,37 @@ function getConfigDir() {
66
64
  if (process.env.XDG_CONFIG_HOME) {
67
65
  return join(process.env.XDG_CONFIG_HOME, "qmd");
68
66
  }
69
- return join(homedir(), ".config", "qmd");
67
+ return join(qmdHomedir(), ".config", "qmd");
70
68
  }
71
69
  function getConfigFilePath() {
72
70
  return join(getConfigDir(), `${currentIndexName}.yml`);
73
71
  }
72
+ /**
73
+ * Find a project-local QMD config by walking upward from startDir.
74
+ * The local config lives at .qmd/index.yaml or .qmd/index.yml and,
75
+ * when used by the CLI, keeps both config and index DB writes inside
76
+ * the project instead of the global ~/.config / ~/.cache locations.
77
+ */
78
+ export function findLocalConfigPath(startDir = process.cwd()) {
79
+ let dir = resolve(startDir);
80
+ while (true) {
81
+ const qmdDir = join(dir, ".qmd");
82
+ const yamlPath = join(qmdDir, "index.yaml");
83
+ if (existsSync(yamlPath))
84
+ return yamlPath;
85
+ const ymlPath = join(qmdDir, "index.yml");
86
+ if (existsSync(ymlPath))
87
+ return ymlPath;
88
+ const parent = dirname(dir);
89
+ if (parent === dir)
90
+ return undefined;
91
+ dir = parent;
92
+ }
93
+ }
94
+ /** Return the local SQLite index path paired with a local .qmd/index.yaml file. */
95
+ export function getLocalDbPath(configPath) {
96
+ return join(dirname(configPath), "index.sqlite");
97
+ }
74
98
  /**
75
99
  * Ensure config directory exists
76
100
  */
@@ -101,7 +125,8 @@ export function loadConfig() {
101
125
  }
102
126
  try {
103
127
  const content = readFileSync(configPath, "utf-8");
104
- const config = YAML.parse(content);
128
+ const parsed = YAML.parse(content);
129
+ const config = parsed ?? { collections: {} };
105
130
  // Ensure collections object exists
106
131
  if (!config.collections) {
107
132
  config.collections = {};
package/dist/db.d.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  * SQLite build before creating any database instances.
12
12
  */
13
13
  export declare const isBun: boolean;
14
+ export type SQLiteValue = string | number | bigint | Buffer | Uint8Array | Float32Array | null;
15
+ export type SQLiteParams = readonly SQLiteValue[];
14
16
  /**
15
17
  * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
16
18
  */
@@ -22,15 +24,16 @@ export interface Database {
22
24
  exec(sql: string): void;
23
25
  prepare(sql: string): Statement;
24
26
  loadExtension(path: string): void;
27
+ transaction<T extends (...args: SQLiteValue[]) => unknown>(fn: T): T;
25
28
  close(): void;
26
29
  }
27
30
  export interface Statement {
28
- run(...params: any[]): {
31
+ run(...params: SQLiteValue[]): {
29
32
  changes: number;
30
33
  lastInsertRowid: number | bigint;
31
34
  };
32
- get(...params: any[]): any;
33
- all(...params: any[]): any[];
35
+ get<T = unknown>(...params: SQLiteValue[]): T | undefined;
36
+ all<T = unknown>(...params: SQLiteValue[]): T[];
34
37
  }
35
38
  /**
36
39
  * Load the sqlite-vec extension into a database.
package/dist/db.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
11
11
  * SQLite build before creating any database instances.
12
12
  */
13
- export const isBun = typeof globalThis.Bun !== "undefined";
13
+ export const isBun = "Bun" in globalThis;
14
14
  let _Database;
15
15
  let _sqliteVecLoad;
16
16
  if (isBun) {
package/dist/index.d.ts CHANGED
@@ -62,6 +62,8 @@ export interface SearchOptions {
62
62
  collections?: string[];
63
63
  /** Max results (default: 10) */
64
64
  limit?: number;
65
+ /** Max candidates to rerank (default: 40) */
66
+ candidateLimit?: number;
65
67
  /** Minimum score threshold */
66
68
  minScore?: number;
67
69
  /** Include explain traces */
@@ -186,6 +188,8 @@ export interface QMDStore {
186
188
  embed(options?: {
187
189
  force?: boolean;
188
190
  model?: string;
191
+ /** Restrict embedding to documents in one collection. */
192
+ collection?: string;
189
193
  maxDocsPerBatch?: number;
190
194
  maxBatchBytes?: number;
191
195
  chunkStrategy?: ChunkStrategy;
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * const results = await store.search({ query: "how does auth work?" })
17
17
  * await store.close()
18
18
  */
19
- import { createStore as createStoreInternal, hybridQuery, structuredSearch, extractSnippet, addLineNumbers, DEFAULT_EMBED_MODEL, DEFAULT_MULTI_GET_MAX_BYTES, reindexCollection, generateEmbeddings, listCollections as storeListCollections, syncConfigToDb, getStoreCollections, getStoreCollection, getStoreGlobalContext, getStoreContexts, upsertStoreCollection, deleteStoreCollection, renameStoreCollection, updateStoreContext, removeStoreContext, setStoreGlobalContext, vacuumDatabase, cleanupOrphanedContent, cleanupOrphanedVectors, deleteLLMCache, deleteInactiveDocuments, clearAllEmbeddings, } from "./store.js";
19
+ import { createStore as createStoreInternal, hybridQuery, structuredSearch, extractSnippet, addLineNumbers, DEFAULT_MULTI_GET_MAX_BYTES, reindexCollection, generateEmbeddings, listCollections as storeListCollections, syncConfigToDb, getStoreCollections, getStoreCollection, getStoreGlobalContext, getStoreContexts, upsertStoreCollection, deleteStoreCollection, renameStoreCollection, updateStoreContext, removeStoreContext, setStoreGlobalContext, vacuumDatabase, cleanupOrphanedContent, cleanupOrphanedVectors, deleteLLMCache, deleteInactiveDocuments, clearAllEmbeddings, } from "./store.js";
20
20
  import { LlamaCpp, } from "./llm.js";
21
21
  import { setConfigSource, loadConfig, addCollection as collectionsAddCollection, removeCollection as collectionsRemoveCollection, renameCollection as collectionsRenameCollection, addContext as collectionsAddContext, removeContext as collectionsRemoveContext, setGlobalContext as collectionsSetGlobalContext, } from "./collections.js";
22
22
  // Re-export utility functions and types used by frontends
@@ -109,6 +109,7 @@ export async function createStore(options) {
109
109
  minScore: opts.minScore,
110
110
  explain: opts.explain,
111
111
  intent: opts.intent,
112
+ candidateLimit: opts.candidateLimit,
112
113
  skipRerank,
113
114
  chunkStrategy: opts.chunkStrategy,
114
115
  });
@@ -120,12 +121,13 @@ export async function createStore(options) {
120
121
  minScore: opts.minScore,
121
122
  explain: opts.explain,
122
123
  intent: opts.intent,
124
+ candidateLimit: opts.candidateLimit,
123
125
  skipRerank,
124
126
  chunkStrategy: opts.chunkStrategy,
125
127
  });
126
128
  },
127
129
  searchLex: async (q, opts) => internal.searchFTS(q, opts?.limit, opts?.collection),
128
- searchVector: async (q, opts) => internal.searchVec(q, DEFAULT_EMBED_MODEL, opts?.limit, opts?.collection),
130
+ searchVector: async (q, opts) => internal.searchVec(q, llm.embedModelName, opts?.limit, opts?.collection),
129
131
  expandQuery: async (q, opts) => internal.expandQuery(q, undefined, opts?.intent),
130
132
  get: async (pathOrDocid, opts) => internal.findDocument(pathOrDocid, opts),
131
133
  getDocumentBody: async (pathOrDocid, opts) => {
@@ -217,6 +219,7 @@ export async function createStore(options) {
217
219
  return generateEmbeddings(internal, {
218
220
  force: embedOpts?.force,
219
221
  model: embedOpts?.model,
222
+ collection: embedOpts?.collection,
220
223
  maxDocsPerBatch: embedOpts?.maxDocsPerBatch,
221
224
  maxBatchBytes: embedOpts?.maxBatchBytes,
222
225
  chunkStrategy: embedOpts?.chunkStrategy,
package/dist/llm.d.ts CHANGED
@@ -3,7 +3,27 @@
3
3
  *
4
4
  * Provides embeddings, text generation, and reranking using local GGUF models.
5
5
  */
6
- import { type Token as LlamaToken } from "node-llama-cpp";
6
+ import type { Llama, Token as LlamaToken } from "node-llama-cpp";
7
+ type NodeLlamaCppModule = {
8
+ getLlama: (options: Record<string, unknown>) => Promise<Llama>;
9
+ getLlamaGpuTypes?: (include?: "supported" | "allValid") => Promise<LlamaGpuMode[]>;
10
+ resolveModelFile: (model: string, cacheDir: string) => Promise<string>;
11
+ LlamaChatSession: new (options: {
12
+ contextSequence: unknown;
13
+ }) => {
14
+ prompt: (prompt: string, options?: Record<string, unknown>) => Promise<string>;
15
+ };
16
+ LlamaLogLevel: {
17
+ error: unknown;
18
+ };
19
+ };
20
+ export declare function setNodeLlamaCppModuleForTest(module: NodeLlamaCppModule | null): void;
21
+ /**
22
+ * Some node-llama-cpp native build/probe paths write library noise to stdout.
23
+ * JSON APIs must reserve stdout for machine-readable payloads, so route that
24
+ * noise to stderr while native llama initialization is in progress.
25
+ */
26
+ export declare function withNativeStdoutRedirectedToStderr<T>(fn: () => Promise<T>): Promise<T>;
7
27
  /**
8
28
  * Detect if a model URI uses the Qwen3-Embedding format.
9
29
  * Qwen3-Embedding uses a different prompting style than nomic/embeddinggemma.
@@ -140,6 +160,15 @@ export declare const LFM2_INSTRUCT_MODEL = "hf:LiquidAI/LFM2.5-1.2B-Instruct-GGU
140
160
  export declare const DEFAULT_EMBED_MODEL_URI = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
141
161
  export declare const DEFAULT_RERANK_MODEL_URI = "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf";
142
162
  export declare const DEFAULT_GENERATE_MODEL_URI = "hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf";
163
+ export type ModelResolutionConfig = {
164
+ embed?: string;
165
+ generate?: string;
166
+ rerank?: string;
167
+ };
168
+ export declare function resolveEmbedModel(config?: ModelResolutionConfig): string;
169
+ export declare function resolveGenerateModel(config?: ModelResolutionConfig): string;
170
+ export declare function resolveRerankModel(config?: ModelResolutionConfig): string;
171
+ export declare function resolveModels(config?: ModelResolutionConfig): Required<ModelResolutionConfig>;
143
172
  export declare const DEFAULT_MODEL_CACHE_DIR: string;
144
173
  export type PullResult = {
145
174
  model: string;
@@ -147,6 +176,19 @@ export type PullResult = {
147
176
  sizeBytes: number;
148
177
  refreshed: boolean;
149
178
  };
179
+ export type GgufFileInspection = {
180
+ exists: boolean;
181
+ valid: boolean;
182
+ kind: "missing" | "gguf" | "html" | "invalid";
183
+ sizeBytes?: number;
184
+ magic?: string;
185
+ details: string;
186
+ };
187
+ /**
188
+ * Inspect a potential GGUF model file without mutating it.
189
+ * Used by doctor for early diagnostics and by runtime validation before load.
190
+ */
191
+ export declare function inspectGgufFile(filePath: string): GgufFileInspection;
150
192
  export declare function pullModels(models: string[], options?: {
151
193
  refresh?: boolean;
152
194
  cacheDir?: string;
@@ -211,6 +253,16 @@ export type LlamaCppConfig = {
211
253
  */
212
254
  disposeModelsOnInactivity?: boolean;
213
255
  };
256
+ export type LlamaGpuMode = "auto" | "metal" | "vulkan" | "cuda" | false;
257
+ type ParallelismOptions = {
258
+ gpu: string | false;
259
+ platform?: NodeJS.Platform;
260
+ computed: number;
261
+ envValue?: string;
262
+ };
263
+ export declare function resolveParallelismOverride(envValue?: string | undefined): number | undefined;
264
+ export declare function resolveSafeParallelism(options: ParallelismOptions): number;
265
+ export declare function resolveLlamaGpuMode(envValue?: string | undefined, forceCpuValue?: string | undefined): LlamaGpuMode;
214
266
  export declare class LlamaCpp implements LLM {
215
267
  private readonly _ciMode;
216
268
  private llama;
@@ -233,6 +285,8 @@ export declare class LlamaCpp implements LLM {
233
285
  private disposed;
234
286
  constructor(config?: LlamaCppConfig);
235
287
  get embedModelName(): string;
288
+ get generateModelName(): string;
289
+ get rerankModelName(): string;
236
290
  /**
237
291
  * Reset the inactivity timer. Called after each model operation.
238
292
  * When timer fires, models are unloaded to free memory (if no active sessions).
@@ -257,8 +311,12 @@ export declare class LlamaCpp implements LLM {
257
311
  * Initialize the llama instance (lazy)
258
312
  */
259
313
  private ensureLlama;
314
+ private isCpuOffloadForced;
315
+ private modelLoadOptions;
260
316
  /**
261
- * Resolve a model URI to a local path, downloading if needed
317
+ * Resolve a model URI to a local path, downloading if needed.
318
+ * Validates the downloaded file is actually a GGUF model (not an HTML error page
319
+ * from a proxy or firewall).
262
320
  */
263
321
  private resolveModel;
264
322
  /**
@@ -328,6 +386,7 @@ export declare class LlamaCpp implements LLM {
328
386
  * detokenizes back to text if truncation is needed.
329
387
  * Returns the (possibly truncated) text and whether truncation occurred.
330
388
  */
389
+ private resolveEmbedTokenLimit;
331
390
  private truncateToContextSize;
332
391
  embed(text: string, options?: EmbedOptions): Promise<EmbeddingResult | null>;
333
392
  /**
@@ -349,7 +408,9 @@ export declare class LlamaCpp implements LLM {
349
408
  * Get device/GPU info for status display.
350
409
  * Initializes llama if not already done.
351
410
  */
352
- getDeviceInfo(): Promise<{
411
+ getDeviceInfo(options?: {
412
+ allowBuild?: boolean;
413
+ }): Promise<{
353
414
  gpu: string | false;
354
415
  gpuOffloading: boolean;
355
416
  gpuDevices: string[];
@@ -406,3 +467,4 @@ export declare function setDefaultLlamaCpp(llm: LlamaCpp | null): void;
406
467
  * Call this before process exit to prevent NAPI crashes.
407
468
  */
408
469
  export declare function disposeDefaultLlamaCpp(): Promise<void>;
470
+ export {};