@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 +52 -3
- package/index.ts +2 -0
- package/lib/capture.ts +39 -1
- package/lib/prismis.ts +50 -9
- package/lib/semantic.ts +8 -10
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
41
|
+
url: string;
|
|
34
42
|
apiKey: string;
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -55,19 +63,41 @@ interface PrismisResponse {
|
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
/**
|
|
58
|
-
*
|
|
66
|
+
* Try to read [remote] section from lore config
|
|
59
67
|
*/
|
|
60
|
-
function
|
|
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
|
|
64
|
-
|
|
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 =
|
|
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
|
|
227
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
ORDER BY distance
|
|
244
|
+
ORDER BY e.distance
|
|
246
245
|
LIMIT ?
|
|
247
246
|
`;
|
|
248
|
-
params.push(limit
|
|
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
|
-
|
|
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
|
-
|
|
264
|
-
ORDER BY distance
|
|
262
|
+
ORDER BY e.distance
|
|
265
263
|
LIMIT ?
|
|
266
264
|
`;
|
|
267
|
-
params.push(limit
|
|
265
|
+
params.push(limit);
|
|
268
266
|
params.push(limit);
|
|
269
267
|
}
|
|
270
268
|
|