@voidwire/lore 0.1.0 → 0.1.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.
- package/cli.ts +180 -7
- package/index.ts +7 -0
- package/lib/capture.ts +1 -0
- package/lib/prismis.ts +168 -0
- package/package.json +5 -5
package/cli.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
search,
|
|
24
|
+
searchPrismis,
|
|
24
25
|
listSources,
|
|
25
26
|
list,
|
|
26
27
|
listDomains,
|
|
@@ -112,14 +113,27 @@ function fail(error: string, code: number = 1): never {
|
|
|
112
113
|
// ============================================================================
|
|
113
114
|
|
|
114
115
|
function handleSearch(args: string[]): void {
|
|
116
|
+
if (hasFlag(args, "help")) {
|
|
117
|
+
showSearchHelp();
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
const parsed = parseArgs(args);
|
|
116
121
|
const positional = getPositionalArgs(args);
|
|
117
122
|
|
|
118
123
|
// Handle --sources flag
|
|
119
124
|
if (hasFlag(args, "sources")) {
|
|
120
|
-
const
|
|
125
|
+
const indexed = listSources();
|
|
126
|
+
const passthrough = [
|
|
127
|
+
{ source: "prismis", count: null, type: "passthrough" },
|
|
128
|
+
];
|
|
129
|
+
const sources = [
|
|
130
|
+
...indexed.map((s) => ({ ...s, type: "indexed" })),
|
|
131
|
+
...passthrough,
|
|
132
|
+
];
|
|
121
133
|
output({ success: true, sources });
|
|
122
|
-
console.error(
|
|
134
|
+
console.error(
|
|
135
|
+
`✅ ${indexed.length} indexed sources + ${passthrough.length} passthrough`,
|
|
136
|
+
);
|
|
123
137
|
process.exit(0);
|
|
124
138
|
}
|
|
125
139
|
|
|
@@ -141,6 +155,28 @@ function handleSearch(args: string[]): void {
|
|
|
141
155
|
const limit = parsed.has("limit") ? parseInt(parsed.get("limit")!, 10) : 20;
|
|
142
156
|
const since = parsed.get("since");
|
|
143
157
|
|
|
158
|
+
// Handle prismis passthrough
|
|
159
|
+
if (source === "prismis") {
|
|
160
|
+
searchPrismis(query, { limit })
|
|
161
|
+
.then((results) => {
|
|
162
|
+
output({
|
|
163
|
+
success: true,
|
|
164
|
+
results,
|
|
165
|
+
count: results.length,
|
|
166
|
+
});
|
|
167
|
+
console.error(
|
|
168
|
+
`✅ ${results.length} result${results.length !== 1 ? "s" : ""} found`,
|
|
169
|
+
);
|
|
170
|
+
process.exit(0);
|
|
171
|
+
})
|
|
172
|
+
.catch((error) => {
|
|
173
|
+
const message =
|
|
174
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
175
|
+
fail(message, 2);
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
144
180
|
try {
|
|
145
181
|
const results = search(query, { source, limit, since });
|
|
146
182
|
output({
|
|
@@ -176,6 +212,10 @@ function formatHumanOutput(result: ListResult): string {
|
|
|
176
212
|
}
|
|
177
213
|
|
|
178
214
|
function handleList(args: string[]): void {
|
|
215
|
+
if (hasFlag(args, "help")) {
|
|
216
|
+
showListHelp();
|
|
217
|
+
}
|
|
218
|
+
|
|
179
219
|
const parsed = parseArgs(args);
|
|
180
220
|
const positional = getPositionalArgs(args);
|
|
181
221
|
|
|
@@ -319,6 +359,10 @@ function handleCaptureNote(args: string[]): void {
|
|
|
319
359
|
}
|
|
320
360
|
|
|
321
361
|
function handleCapture(args: string[]): void {
|
|
362
|
+
if (hasFlag(args, "help")) {
|
|
363
|
+
showCaptureHelp();
|
|
364
|
+
}
|
|
365
|
+
|
|
322
366
|
if (args.length === 0) {
|
|
323
367
|
fail("Missing capture type. Use: task, knowledge, or note");
|
|
324
368
|
}
|
|
@@ -369,6 +413,9 @@ Search Options:
|
|
|
369
413
|
--since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
|
|
370
414
|
--sources List indexed sources with counts
|
|
371
415
|
|
|
416
|
+
Passthrough Sources:
|
|
417
|
+
prismis Semantic search via prismis daemon (requires prismis-daemon running)
|
|
418
|
+
|
|
372
419
|
List Options:
|
|
373
420
|
--limit <n> Maximum entries
|
|
374
421
|
--format <fmt> Output format: json (default), jsonl, human
|
|
@@ -401,10 +448,140 @@ Examples:
|
|
|
401
448
|
process.exit(0);
|
|
402
449
|
}
|
|
403
450
|
|
|
451
|
+
function showSearchHelp(): void {
|
|
452
|
+
console.log(`
|
|
453
|
+
lore search - Search indexed knowledge
|
|
454
|
+
|
|
455
|
+
Usage:
|
|
456
|
+
lore search <query> Search all sources
|
|
457
|
+
lore search <source> <query> Search specific source
|
|
458
|
+
lore search --sources List indexed sources
|
|
459
|
+
|
|
460
|
+
Options:
|
|
461
|
+
--limit <n> Maximum results (default: 20)
|
|
462
|
+
--since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
|
|
463
|
+
--sources List indexed sources with counts
|
|
464
|
+
--help Show this help
|
|
465
|
+
|
|
466
|
+
Indexed Sources:
|
|
467
|
+
blogs Blog posts and articles
|
|
468
|
+
captures Quick captures and notes
|
|
469
|
+
commits Git commit history
|
|
470
|
+
development Active development projects
|
|
471
|
+
events Calendar events and meetings
|
|
472
|
+
explorations Technical explorations
|
|
473
|
+
obsidian Obsidian vault notes
|
|
474
|
+
personal Personal data (books, movies, etc.)
|
|
475
|
+
readmes Project README files
|
|
476
|
+
sessions Claude Code session transcripts
|
|
477
|
+
tasks Logged development tasks
|
|
478
|
+
|
|
479
|
+
Passthrough Sources:
|
|
480
|
+
prismis Semantic search via prismis daemon
|
|
481
|
+
(requires prismis-daemon running)
|
|
482
|
+
|
|
483
|
+
Examples:
|
|
484
|
+
lore search "authentication"
|
|
485
|
+
lore search blogs "typescript patterns"
|
|
486
|
+
lore search commits --since this-week "refactor"
|
|
487
|
+
lore search prismis "kubernetes security"
|
|
488
|
+
`);
|
|
489
|
+
process.exit(0);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function showListHelp(): void {
|
|
493
|
+
console.log(`
|
|
494
|
+
lore list - List domain entries
|
|
495
|
+
|
|
496
|
+
Usage:
|
|
497
|
+
lore list <domain> List entries in domain
|
|
498
|
+
lore list --domains List available domains
|
|
499
|
+
|
|
500
|
+
Options:
|
|
501
|
+
--limit <n> Maximum entries (default: all)
|
|
502
|
+
--format <fmt> Output format: json (default), jsonl, human
|
|
503
|
+
--domains List available domains
|
|
504
|
+
--help Show this help
|
|
505
|
+
|
|
506
|
+
Available Domains:
|
|
507
|
+
blogs Blog posts
|
|
508
|
+
books Books read
|
|
509
|
+
captures Quick captures
|
|
510
|
+
commits Git commits
|
|
511
|
+
development Development projects
|
|
512
|
+
events Calendar events
|
|
513
|
+
explorations Technical explorations
|
|
514
|
+
habits Tracked habits
|
|
515
|
+
interests Personal interests
|
|
516
|
+
movies Movies watched
|
|
517
|
+
obsidian Obsidian notes
|
|
518
|
+
people People/contacts
|
|
519
|
+
personal Personal data aggregate
|
|
520
|
+
podcasts Podcasts listened
|
|
521
|
+
readmes Project READMEs
|
|
522
|
+
sessions Claude Code sessions
|
|
523
|
+
tasks Development tasks
|
|
524
|
+
|
|
525
|
+
Examples:
|
|
526
|
+
lore list development
|
|
527
|
+
lore list commits --limit 10 --format human
|
|
528
|
+
lore list books --format jsonl
|
|
529
|
+
`);
|
|
530
|
+
process.exit(0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function showCaptureHelp(): void {
|
|
534
|
+
console.log(`
|
|
535
|
+
lore capture - Capture knowledge
|
|
536
|
+
|
|
537
|
+
Usage:
|
|
538
|
+
lore capture task Log task completion
|
|
539
|
+
lore capture knowledge Log insight/learning
|
|
540
|
+
lore capture note Quick note
|
|
541
|
+
|
|
542
|
+
Capture Types:
|
|
543
|
+
|
|
544
|
+
task - Log completed development task
|
|
545
|
+
Required:
|
|
546
|
+
--project Project name
|
|
547
|
+
--name Task name
|
|
548
|
+
--problem Problem solved
|
|
549
|
+
--solution Solution pattern
|
|
550
|
+
Optional:
|
|
551
|
+
--code Code snippet
|
|
552
|
+
--discoveries Comma-separated discoveries
|
|
553
|
+
--deviations Deviation from plan
|
|
554
|
+
--pattern Pattern name
|
|
555
|
+
--keywords Comma-separated keywords
|
|
556
|
+
--tech Comma-separated technologies
|
|
557
|
+
--difficulty Difficulty level
|
|
558
|
+
|
|
559
|
+
knowledge - Log insight or learning
|
|
560
|
+
Required:
|
|
561
|
+
--context Context/project name
|
|
562
|
+
--text Insight text
|
|
563
|
+
--type Type: decision, learning, gotcha, preference
|
|
564
|
+
|
|
565
|
+
note - Quick note capture
|
|
566
|
+
Required:
|
|
567
|
+
--text Note content
|
|
568
|
+
Optional:
|
|
569
|
+
--tags Comma-separated tags
|
|
570
|
+
--context Optional context
|
|
571
|
+
|
|
572
|
+
Examples:
|
|
573
|
+
lore capture task --project=lore --name="Add help" --problem="No subcommand help" --solution="Added per-command help functions"
|
|
574
|
+
lore capture knowledge --context=lore --text="Unified CLI works" --type=learning
|
|
575
|
+
lore capture note --text="Remember to update docs" --tags=docs,todo
|
|
576
|
+
`);
|
|
577
|
+
process.exit(0);
|
|
578
|
+
}
|
|
579
|
+
|
|
404
580
|
function main(): void {
|
|
405
581
|
const args = process.argv.slice(2);
|
|
406
582
|
|
|
407
|
-
|
|
583
|
+
// Show global help only when no args or help is first arg
|
|
584
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
408
585
|
showHelp();
|
|
409
586
|
}
|
|
410
587
|
|
|
@@ -421,10 +598,6 @@ function main(): void {
|
|
|
421
598
|
case "capture":
|
|
422
599
|
handleCapture(commandArgs);
|
|
423
600
|
break;
|
|
424
|
-
case "--help":
|
|
425
|
-
case "-h":
|
|
426
|
-
showHelp();
|
|
427
|
-
break;
|
|
428
601
|
default:
|
|
429
602
|
fail(`Unknown command: ${command}. Use: search, list, or capture`);
|
|
430
603
|
}
|
package/index.ts
CHANGED
package/lib/capture.ts
CHANGED
package/lib/prismis.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prismis API integration
|
|
3
|
+
*
|
|
4
|
+
* Queries prismis daemon REST API for semantic search across content.
|
|
5
|
+
* Reads host and API key from ~/.config/prismis/config.toml
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
|
|
11
|
+
// Local interface to avoid bun:sqlite dependency from search.ts
|
|
12
|
+
export interface PrismisSearchResult {
|
|
13
|
+
source: string;
|
|
14
|
+
title: string;
|
|
15
|
+
content: string;
|
|
16
|
+
metadata: string;
|
|
17
|
+
rank: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_PORT = 8989;
|
|
21
|
+
const PRISMIS_CONFIG_PATH = join(
|
|
22
|
+
process.env.HOME ?? "",
|
|
23
|
+
".config",
|
|
24
|
+
"prismis",
|
|
25
|
+
"config.toml",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export interface PrismisSearchOptions {
|
|
29
|
+
limit?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PrismisConfig {
|
|
33
|
+
host: string;
|
|
34
|
+
apiKey: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PrismisItem {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
url: string;
|
|
41
|
+
priority: string;
|
|
42
|
+
relevance_score: number;
|
|
43
|
+
published_at: string;
|
|
44
|
+
source_name: string;
|
|
45
|
+
summary: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface PrismisResponse {
|
|
49
|
+
success: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
data: {
|
|
52
|
+
items: PrismisItem[];
|
|
53
|
+
total: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read prismis config from config.toml
|
|
59
|
+
*/
|
|
60
|
+
function readPrismisConfig(): PrismisConfig {
|
|
61
|
+
if (!existsSync(PRISMIS_CONFIG_PATH)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Prismis config not found at ${PRISMIS_CONFIG_PATH}. ` +
|
|
64
|
+
"Install prismis and run: prismis-cli source add <url>",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const content = readFileSync(PRISMIS_CONFIG_PATH, "utf-8");
|
|
69
|
+
|
|
70
|
+
// Parse [api] section
|
|
71
|
+
const keyMatch = content.match(/\[api\][^[]*key\s*=\s*"([^"]+)"/);
|
|
72
|
+
if (!keyMatch) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"Prismis API key not found in config.toml. Add [api] key=... to config.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const hostMatch = content.match(/\[api\][^[]*host\s*=\s*"([^"]+)"/);
|
|
79
|
+
let host = hostMatch?.[1] ?? "localhost";
|
|
80
|
+
|
|
81
|
+
// Map server bind addresses to client-usable addresses
|
|
82
|
+
if (host === "0.0.0.0" || host === "127.0.0.1") {
|
|
83
|
+
host = "localhost";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
host,
|
|
88
|
+
apiKey: keyMatch[1],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if prismis daemon is running
|
|
94
|
+
*/
|
|
95
|
+
async function checkPrismisDaemon(apiBase: string): Promise<void> {
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(`${apiBase}/health`, {
|
|
98
|
+
signal: AbortSignal.timeout(2000),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error("Prismis daemon unhealthy");
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Prismis daemon not responding at ${apiBase}. Start with: prismis-daemon`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Prismis daemon not running at ${apiBase}. Start with: prismis-daemon`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Search prismis content via API
|
|
118
|
+
*/
|
|
119
|
+
export async function searchPrismis(
|
|
120
|
+
query: string,
|
|
121
|
+
options: PrismisSearchOptions = {},
|
|
122
|
+
): Promise<PrismisSearchResult[]> {
|
|
123
|
+
// Read config
|
|
124
|
+
const config = readPrismisConfig();
|
|
125
|
+
const apiBase = `http://${config.host}:${DEFAULT_PORT}`;
|
|
126
|
+
|
|
127
|
+
// Check daemon is running
|
|
128
|
+
await checkPrismisDaemon(apiBase);
|
|
129
|
+
|
|
130
|
+
// Build search URL
|
|
131
|
+
const params = new URLSearchParams({
|
|
132
|
+
q: query,
|
|
133
|
+
limit: String(options.limit ?? 20),
|
|
134
|
+
compact: "true",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const response = await fetch(`${apiBase}/api/search?${params}`, {
|
|
138
|
+
headers: {
|
|
139
|
+
"X-API-Key": config.apiKey,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const text = await response.text();
|
|
145
|
+
throw new Error(`Prismis API error (${response.status}): ${text}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const data: PrismisResponse = await response.json();
|
|
149
|
+
|
|
150
|
+
if (!data.success) {
|
|
151
|
+
throw new Error(`Prismis search failed: ${data.message}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Map prismis items to SearchResult format
|
|
155
|
+
return data.data.items.map((item) => ({
|
|
156
|
+
source: "prismis",
|
|
157
|
+
title: item.title,
|
|
158
|
+
content: item.summary || "",
|
|
159
|
+
metadata: JSON.stringify({
|
|
160
|
+
id: item.id,
|
|
161
|
+
url: item.url,
|
|
162
|
+
priority: item.priority,
|
|
163
|
+
published_at: item.published_at,
|
|
164
|
+
source_name: item.source_name,
|
|
165
|
+
}),
|
|
166
|
+
rank: item.relevance_score,
|
|
167
|
+
}));
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidwire/lore",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -18,9 +18,6 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"test": "bun test"
|
|
23
|
-
},
|
|
24
21
|
"keywords": [
|
|
25
22
|
"knowledge",
|
|
26
23
|
"search",
|
|
@@ -46,5 +43,8 @@
|
|
|
46
43
|
},
|
|
47
44
|
"devDependencies": {
|
|
48
45
|
"bun-types": "latest"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "bun test"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|