@voidwire/lore 0.1.5 → 0.1.7

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
@@ -11,7 +11,7 @@
11
11
  * lore search <query> Search all sources
12
12
  * lore search <source> <query> Search specific source
13
13
  * lore list <domain> List domain entries
14
- * lore capture task|knowledge|note Capture knowledge
14
+ * lore capture task|knowledge|note|teaching Capture knowledge
15
15
  *
16
16
  * Exit codes:
17
17
  * 0 - Success
@@ -29,6 +29,7 @@ import {
29
29
  captureTask,
30
30
  captureKnowledge,
31
31
  captureNote,
32
+ captureTeaching,
32
33
  semanticSearch,
33
34
  isOllamaAvailable,
34
35
  hasEmbeddings,
@@ -40,6 +41,7 @@ import {
40
41
  type TaskInput,
41
42
  type KnowledgeInput,
42
43
  type NoteInput,
44
+ type TeachingInput,
43
45
  type KnowledgeCaptureType,
44
46
  } from "./index";
45
47
 
@@ -436,6 +438,34 @@ function handleCaptureNote(args: string[]): void {
436
438
  }
437
439
  }
438
440
 
441
+ function handleCaptureTeaching(args: string[]): void {
442
+ const parsed = parseArgs(args);
443
+
444
+ const required = ["domain", "confidence", "text"];
445
+ const missing = required.filter((f) => !parsed.has(f));
446
+ if (missing.length > 0) {
447
+ fail(`Missing required fields: ${missing.join(", ")}`);
448
+ }
449
+
450
+ const input: TeachingInput = {
451
+ domain: parsed.get("domain")!,
452
+ confidence: parsed.get("confidence")!,
453
+ text: parsed.get("text")!,
454
+ source: parsed.get("source"),
455
+ };
456
+
457
+ const result = captureTeaching(input);
458
+ output(result);
459
+
460
+ if (result.success) {
461
+ console.error("✅ Teaching logged");
462
+ process.exit(0);
463
+ } else {
464
+ console.error(`❌ ${result.error}`);
465
+ process.exit(2);
466
+ }
467
+ }
468
+
439
469
  function handleCapture(args: string[]): void {
440
470
  if (hasFlag(args, "help")) {
441
471
  showCaptureHelp();
@@ -458,9 +488,12 @@ function handleCapture(args: string[]): void {
458
488
  case "note":
459
489
  handleCaptureNote(captureArgs);
460
490
  break;
491
+ case "teaching":
492
+ handleCaptureTeaching(captureArgs);
493
+ break;
461
494
  default:
462
495
  fail(
463
- `Unknown capture type: ${captureType}. Use: task, knowledge, or note`,
496
+ `Unknown capture type: ${captureType}. Use: task, knowledge, note, or teaching`,
464
497
  );
465
498
  }
466
499
  }
@@ -484,7 +517,7 @@ Usage:
484
517
  lore search --sources List indexed sources
485
518
  lore list <domain> List domain entries
486
519
  lore list --domains List available domains
487
- lore capture task|knowledge|note Capture knowledge
520
+ lore capture task|knowledge|note|teaching Capture knowledge
488
521
 
489
522
  Search Options:
490
523
  --exact Use FTS5 text search (bypasses semantic search)
@@ -518,6 +551,12 @@ Capture Types:
518
551
  --tags Comma-separated tags
519
552
  --context Optional context
520
553
 
554
+ teaching Log teaching/learning
555
+ --domain Subject area (required)
556
+ --confidence Certainty level (required)
557
+ --text Teaching content (required)
558
+ --source Optional source identifier
559
+
521
560
  Examples:
522
561
  lore search "authentication"
523
562
  lore search blogs "typescript patterns"
@@ -623,6 +662,7 @@ Usage:
623
662
  lore capture task Log task completion
624
663
  lore capture knowledge Log insight/learning
625
664
  lore capture note Quick note
665
+ lore capture teaching Log teaching moment
626
666
 
627
667
  Capture Types:
628
668
 
@@ -654,10 +694,19 @@ Capture Types:
654
694
  --tags Comma-separated tags
655
695
  --context Optional context
656
696
 
697
+ teaching - Log teaching or learning moment
698
+ Required:
699
+ --domain Subject area (e.g., typescript, architecture)
700
+ --confidence Certainty level (e.g., high, medium, low)
701
+ --text Teaching content
702
+ Optional:
703
+ --source Source identifier (defaults to "manual")
704
+
657
705
  Examples:
658
706
  lore capture task --project=lore --name="Add help" --problem="No subcommand help" --solution="Added per-command help functions"
659
707
  lore capture knowledge --context=lore --text="Unified CLI works" --type=learning
660
708
  lore capture note --text="Remember to update docs" --tags=docs,todo
709
+ lore capture teaching --domain=patterns --confidence=high --text="Prefer composition over inheritance"
661
710
  `);
662
711
  process.exit(0);
663
712
  }
package/index.ts CHANGED
@@ -46,11 +46,13 @@ export {
46
46
  captureKnowledge,
47
47
  captureTask,
48
48
  captureNote,
49
+ captureTeaching,
49
50
  type CaptureResult,
50
51
  type KnowledgeInput,
51
52
  type KnowledgeCaptureType,
52
53
  type TaskInput,
53
54
  type NoteInput,
55
+ type TeachingInput,
54
56
  type CaptureEvent,
55
57
  } from "./lib/capture";
56
58
 
package/lib/capture.ts CHANGED
@@ -50,6 +50,13 @@ export interface NoteInput {
50
50
  context?: string;
51
51
  }
52
52
 
53
+ export interface TeachingInput {
54
+ domain: string;
55
+ confidence: string;
56
+ text: string;
57
+ source?: string;
58
+ }
59
+
53
60
  interface TaskEvent {
54
61
  event: "captured";
55
62
  type: "task";
@@ -91,7 +98,19 @@ interface NoteEvent {
91
98
  };
92
99
  }
93
100
 
94
- type CaptureEvent = TaskEvent | KnowledgeEvent | NoteEvent;
101
+ interface TeachingEvent {
102
+ event: "captured";
103
+ type: "teaching";
104
+ timestamp: string;
105
+ data: {
106
+ domain: string;
107
+ confidence: string;
108
+ text: string;
109
+ source: string;
110
+ };
111
+ }
112
+
113
+ type CaptureEvent = TaskEvent | KnowledgeEvent | NoteEvent | TeachingEvent;
95
114
 
96
115
  function getLogPath(): string {
97
116
  const dataHome =
@@ -209,4 +228,23 @@ export function captureNote(input: NoteInput): CaptureResult {
209
228
  return writeEvent(event);
210
229
  }
211
230
 
231
+ /**
232
+ * Capture a teaching moment
233
+ */
234
+ export function captureTeaching(input: TeachingInput): CaptureResult {
235
+ const event: TeachingEvent = {
236
+ event: "captured",
237
+ type: "teaching",
238
+ timestamp: "",
239
+ data: {
240
+ domain: input.domain,
241
+ confidence: input.confidence,
242
+ text: input.text,
243
+ source: input.source || "manual",
244
+ },
245
+ };
246
+
247
+ return writeEvent(event);
248
+ }
249
+
212
250
  export type { CaptureEvent };
package/lib/prismis.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  * Prismis API integration
3
3
  *
4
4
  * Queries prismis daemon REST API for semantic search across content.
5
- * Reads host and API key from ~/.config/prismis/config.toml
5
+ * Config priority:
6
+ * 1. ~/.config/lore/config.toml [remote] section
7
+ * 2. ~/.config/prismis/config.toml [api] section (local daemon fallback)
6
8
  */
7
9
 
8
10
  import { readFileSync, existsSync } from "fs";
@@ -18,6 +20,12 @@ export interface PrismisSearchResult {
18
20
  }
19
21
 
20
22
  const DEFAULT_PORT = 8989;
23
+ const LORE_CONFIG_PATH = join(
24
+ process.env.HOME ?? "",
25
+ ".config",
26
+ "lore",
27
+ "config.toml",
28
+ );
21
29
  const PRISMIS_CONFIG_PATH = join(
22
30
  process.env.HOME ?? "",
23
31
  ".config",
@@ -30,7 +38,7 @@ export interface PrismisSearchOptions {
30
38
  }
31
39
 
32
40
  interface PrismisConfig {
33
- host: string;
41
+ url: string;
34
42
  apiKey: string;
35
43
  }
36
44
 
@@ -55,19 +63,41 @@ interface PrismisResponse {
55
63
  }
56
64
 
57
65
  /**
58
- * Read prismis config from config.toml
66
+ * Try to read [remote] section from lore config
59
67
  */
60
- function readPrismisConfig(): PrismisConfig {
68
+ function readLoreRemoteConfig(): PrismisConfig | null {
69
+ if (!existsSync(LORE_CONFIG_PATH)) {
70
+ return null;
71
+ }
72
+
73
+ const content = readFileSync(LORE_CONFIG_PATH, "utf-8");
74
+
75
+ const urlMatch = content.match(/\[remote\][^[]*url\s*=\s*"([^"]+)"/s);
76
+ const keyMatch = content.match(/\[remote\][^[]*key\s*=\s*"([^"]+)"/s);
77
+
78
+ if (urlMatch && keyMatch) {
79
+ return {
80
+ url: urlMatch[1],
81
+ apiKey: keyMatch[1],
82
+ };
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ /**
89
+ * Read prismis config from local prismis config.toml
90
+ */
91
+ function readLocalPrismisConfig(): PrismisConfig {
61
92
  if (!existsSync(PRISMIS_CONFIG_PATH)) {
62
93
  throw new Error(
63
- `Prismis config not found at ${PRISMIS_CONFIG_PATH}. ` +
64
- "Install prismis and run: prismis-cli source add <url>",
94
+ `Prismis config not found. Add [remote] to ~/.config/lore/config.toml ` +
95
+ `or install prismis locally.`,
65
96
  );
66
97
  }
67
98
 
68
99
  const content = readFileSync(PRISMIS_CONFIG_PATH, "utf-8");
69
100
 
70
- // Parse [api] section
71
101
  const keyMatch = content.match(/\[api\][^[]*key\s*=\s*"([^"]+)"/);
72
102
  if (!keyMatch) {
73
103
  throw new Error(
@@ -84,11 +114,22 @@ function readPrismisConfig(): PrismisConfig {
84
114
  }
85
115
 
86
116
  return {
87
- host,
117
+ url: `http://${host}:${DEFAULT_PORT}`,
88
118
  apiKey: keyMatch[1],
89
119
  };
90
120
  }
91
121
 
122
+ /**
123
+ * Read prismis config - tries lore [remote] first, falls back to local prismis
124
+ */
125
+ function readPrismisConfig(): PrismisConfig {
126
+ const remoteConfig = readLoreRemoteConfig();
127
+ if (remoteConfig) {
128
+ return remoteConfig;
129
+ }
130
+ return readLocalPrismisConfig();
131
+ }
132
+
92
133
  /**
93
134
  * Check if prismis daemon is running
94
135
  */
@@ -122,7 +163,7 @@ export async function searchPrismis(
122
163
  ): Promise<PrismisSearchResult[]> {
123
164
  // Read config
124
165
  const config = readPrismisConfig();
125
- const apiBase = `http://${config.host}:${DEFAULT_PORT}`;
166
+ const apiBase = config.url;
126
167
 
127
168
  // Check daemon is running
128
169
  await checkPrismisDaemon(apiBase);
package/lib/semantic.ts CHANGED
@@ -223,8 +223,8 @@ export async function semanticSearch(
223
223
 
224
224
  const limit = options.limit ?? 20;
225
225
 
226
- // KNN query with join to search table
227
- // Group by doc_id to return best chunk per document
226
+ // KNN query - 1:1 mapping between search rows and embeddings
227
+ // Content is pre-chunked at ingest time
228
228
  let sql: string;
229
229
  const params: (Uint8Array | string | number)[] = [queryBlob];
230
230
 
@@ -235,17 +235,16 @@ export async function semanticSearch(
235
235
  s.title,
236
236
  s.content,
237
237
  s.metadata,
238
- MIN(e.distance) as distance
238
+ e.distance
239
239
  FROM embeddings e
240
240
  JOIN search s ON e.doc_id = s.rowid
241
241
  WHERE e.embedding MATCH ?
242
242
  AND k = ?
243
243
  AND s.source = ?
244
- GROUP BY s.rowid
245
- ORDER BY distance
244
+ ORDER BY e.distance
246
245
  LIMIT ?
247
246
  `;
248
- params.push(limit * 3); // Fetch more for grouping
247
+ params.push(limit);
249
248
  params.push(options.source);
250
249
  params.push(limit);
251
250
  } else {
@@ -255,16 +254,15 @@ export async function semanticSearch(
255
254
  s.title,
256
255
  s.content,
257
256
  s.metadata,
258
- MIN(e.distance) as distance
257
+ e.distance
259
258
  FROM embeddings e
260
259
  JOIN search s ON e.doc_id = s.rowid
261
260
  WHERE e.embedding MATCH ?
262
261
  AND k = ?
263
- GROUP BY s.rowid
264
- ORDER BY distance
262
+ ORDER BY e.distance
265
263
  LIMIT ?
266
264
  `;
267
- params.push(limit * 3); // Fetch more for grouping
265
+ params.push(limit);
268
266
  params.push(limit);
269
267
  }
270
268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",