@voidwire/lore 0.1.7 → 0.1.9

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/cli.ts CHANGED
@@ -31,7 +31,6 @@ import {
31
31
  captureNote,
32
32
  captureTeaching,
33
33
  semanticSearch,
34
- isOllamaAvailable,
35
34
  hasEmbeddings,
36
35
  DOMAINS,
37
36
  type SearchResult,
@@ -252,10 +251,6 @@ async function handleSearch(args: string[]): Promise<void> {
252
251
  fail("No embeddings found. Run lore-embed-all first.", 2);
253
252
  }
254
253
 
255
- if (!(await isOllamaAvailable())) {
256
- fail("Ollama not available. Start Ollama or check SQLITE_VEC_PATH.", 2);
257
- }
258
-
259
254
  try {
260
255
  const results = await semanticSearch(query, { source, limit });
261
256
  output({
package/index.ts CHANGED
@@ -60,7 +60,6 @@ export {
60
60
  export {
61
61
  semanticSearch,
62
62
  embedQuery,
63
- isOllamaAvailable,
64
63
  hasEmbeddings,
65
64
  type SemanticResult,
66
65
  type SemanticSearchOptions,
package/lib/prismis.ts CHANGED
@@ -35,6 +35,7 @@ const PRISMIS_CONFIG_PATH = join(
35
35
 
36
36
  export interface PrismisSearchOptions {
37
37
  limit?: number;
38
+ source?: string;
38
39
  }
39
40
 
40
41
  interface PrismisConfig {
@@ -175,6 +176,10 @@ export async function searchPrismis(
175
176
  compact: "true",
176
177
  });
177
178
 
179
+ if (options.source) {
180
+ params.set("source", options.source);
181
+ }
182
+
178
183
  const response = await fetch(`${apiBase}/api/search?${params}`, {
179
184
  headers: {
180
185
  "X-API-Key": config.apiKey,
package/lib/semantic.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * lib/semantic.ts - Semantic search via Ollama embeddings
2
+ * lib/semantic.ts - Semantic search via local embeddings
3
3
  *
4
- * Query embedding and KNN search against sqlite-vec virtual table.
4
+ * Query embedding using @huggingface/transformers with nomic-embed-text-v1.5.
5
+ * KNN search against sqlite-vec virtual table.
5
6
  * Uses Bun's built-in SQLite with sqlite-vec extension.
6
7
  *
7
8
  * Note: macOS ships Apple's SQLite which disables extension loading.
@@ -10,7 +11,8 @@
10
11
 
11
12
  import { Database } from "bun:sqlite";
12
13
  import { homedir } from "os";
13
- import { existsSync, readFileSync } from "fs";
14
+ import { existsSync } from "fs";
15
+ import { pipeline } from "@huggingface/transformers";
14
16
 
15
17
  // Use Homebrew SQLite on macOS to enable extension loading
16
18
  // Must be called before any Database instances are created
@@ -32,120 +34,70 @@ export interface SemanticSearchOptions {
32
34
  limit?: number;
33
35
  }
34
36
 
35
- interface EmbeddingConfig {
36
- endpoint: string;
37
- model: string;
37
+ const MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5";
38
+
39
+ interface EmbeddingPipeline {
40
+ (
41
+ text: string,
42
+ options?: { pooling?: string; normalize?: boolean },
43
+ ): Promise<{
44
+ data: Float32Array;
45
+ }>;
38
46
  }
39
47
 
40
- const DEFAULT_CONFIG: EmbeddingConfig = {
41
- endpoint: "http://localhost:11434",
42
- model: "nomic-embed-text",
43
- };
48
+ // Cache the pipeline to avoid reloading on every query
49
+ let cachedPipeline: EmbeddingPipeline | null = null;
44
50
 
45
51
  function getDatabasePath(): string {
46
52
  return `${homedir()}/.local/share/lore/lore.db`;
47
53
  }
48
54
 
49
- function getConfigPath(): string {
50
- return `${homedir()}/.config/lore/config.toml`;
51
- }
52
-
53
55
  /**
54
- * Load embedding config from config.toml
55
- * Falls back to [llm].api_base if [embedding].endpoint not set
56
+ * Get or create the embedding pipeline
57
+ * Pipeline is cached after first load for performance
56
58
  */
57
- function loadEmbeddingConfig(): EmbeddingConfig {
58
- const configPath = getConfigPath();
59
-
60
- if (!existsSync(configPath)) {
61
- return DEFAULT_CONFIG;
59
+ async function getEmbeddingPipeline(): Promise<EmbeddingPipeline> {
60
+ if (cachedPipeline) {
61
+ return cachedPipeline;
62
62
  }
63
63
 
64
64
  try {
65
- const content = readFileSync(configPath, "utf-8");
66
-
67
- // Extract [embedding].endpoint first
68
- const endpointMatch = content.match(
69
- /\[embedding\][^[]*endpoint\s*=\s*"([^"]+)"/s,
70
- );
71
- if (endpointMatch) {
72
- const modelMatch = content.match(
73
- /\[embedding\][^[]*model\s*=\s*"([^"]+)"/s,
74
- );
75
- return {
76
- endpoint: endpointMatch[1],
77
- model: modelMatch?.[1] ?? DEFAULT_CONFIG.model,
78
- };
79
- }
80
-
81
- // Fall back to [llm].api_base
82
- const apiBaseMatch = content.match(/\[llm\][^[]*api_base\s*=\s*"([^"]+)"/s);
83
- if (apiBaseMatch) {
84
- const modelMatch = content.match(
85
- /\[embedding\][^[]*model\s*=\s*"([^"]+)"/s,
86
- );
87
- return {
88
- endpoint: apiBaseMatch[1],
89
- model: modelMatch?.[1] ?? DEFAULT_CONFIG.model,
90
- };
91
- }
92
-
93
- return DEFAULT_CONFIG;
94
- } catch {
95
- return DEFAULT_CONFIG;
96
- }
97
- }
98
-
99
- /**
100
- * Check if Ollama is available at configured endpoint
101
- */
102
- export async function isOllamaAvailable(): Promise<boolean> {
103
- const config = loadEmbeddingConfig();
104
- try {
105
- const controller = new AbortController();
106
- const timeout = setTimeout(() => controller.abort(), 2000);
107
-
108
- const response = await fetch(`${config.endpoint}/api/tags`, {
109
- method: "GET",
110
- signal: controller.signal,
65
+ const p = await pipeline("feature-extraction", MODEL_NAME, {
66
+ dtype: "fp32",
111
67
  });
112
-
113
- clearTimeout(timeout);
114
- return response.ok;
115
- } catch {
116
- return false;
68
+ cachedPipeline = p as unknown as EmbeddingPipeline;
69
+ return cachedPipeline;
70
+ } catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ throw new Error(
73
+ `Failed to load embedding model: ${message}\n` +
74
+ `Note: First run downloads ~500MB model to ~/.cache/huggingface/hub`,
75
+ );
117
76
  }
118
77
  }
119
78
 
120
79
  /**
121
- * Embed a query string using Ollama
80
+ * Embed a query string using local transformers.js model
81
+ * Uses "search_query: " prefix as required by nomic-embed-text
122
82
  * @returns 768-dimensional embedding vector
123
83
  */
124
84
  export async function embedQuery(query: string): Promise<number[]> {
125
- const config = loadEmbeddingConfig();
126
- const url = `${config.endpoint}/api/embeddings`;
127
-
128
- const response = await fetch(url, {
129
- method: "POST",
130
- headers: { "Content-Type": "application/json" },
131
- body: JSON.stringify({
132
- model: config.model,
133
- prompt: query,
134
- }),
85
+ const embedder = await getEmbeddingPipeline();
86
+
87
+ // nomic model requires "search_query: " prefix for queries
88
+ // (FastEmbed uses "search_document: " prefix during indexing)
89
+ const prefixedQuery = `search_query: ${query}`;
90
+ const output = await embedder(prefixedQuery, {
91
+ pooling: "mean",
92
+ normalize: true,
135
93
  });
136
94
 
137
- if (!response.ok) {
138
- throw new Error(
139
- `Ollama API error: ${response.status} ${response.statusText}`,
140
- );
141
- }
142
-
143
- const result = (await response.json()) as { embedding?: number[] };
144
- const embedding = result.embedding;
95
+ // Output is a Tensor, convert to array
96
+ const embedding = Array.from(output.data as Float32Array);
145
97
 
146
- if (!Array.isArray(embedding) || embedding.length !== 768) {
98
+ if (embedding.length !== 768) {
147
99
  throw new Error(
148
- `Invalid embedding: expected 768 dims, got ${embedding?.length ?? 0}`,
100
+ `Invalid embedding: expected 768 dims, got ${embedding.length}`,
149
101
  );
150
102
  }
151
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -41,6 +41,9 @@
41
41
  "engines": {
42
42
  "bun": ">=1.0.0"
43
43
  },
44
+ "dependencies": {
45
+ "@huggingface/transformers": "^3.2.6"
46
+ },
44
47
  "devDependencies": {
45
48
  "bun-types": "1.3.5"
46
49
  },