@voidwire/lore 0.1.0 → 0.1.1
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 +169 -5
- 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,6 +113,10 @@ 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
|
|
|
@@ -141,6 +146,28 @@ function handleSearch(args: string[]): void {
|
|
|
141
146
|
const limit = parsed.has("limit") ? parseInt(parsed.get("limit")!, 10) : 20;
|
|
142
147
|
const since = parsed.get("since");
|
|
143
148
|
|
|
149
|
+
// Handle prismis passthrough
|
|
150
|
+
if (source === "prismis") {
|
|
151
|
+
searchPrismis(query, { limit })
|
|
152
|
+
.then((results) => {
|
|
153
|
+
output({
|
|
154
|
+
success: true,
|
|
155
|
+
results,
|
|
156
|
+
count: results.length,
|
|
157
|
+
});
|
|
158
|
+
console.error(
|
|
159
|
+
`✅ ${results.length} result${results.length !== 1 ? "s" : ""} found`,
|
|
160
|
+
);
|
|
161
|
+
process.exit(0);
|
|
162
|
+
})
|
|
163
|
+
.catch((error) => {
|
|
164
|
+
const message =
|
|
165
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
166
|
+
fail(message, 2);
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
try {
|
|
145
172
|
const results = search(query, { source, limit, since });
|
|
146
173
|
output({
|
|
@@ -176,6 +203,10 @@ function formatHumanOutput(result: ListResult): string {
|
|
|
176
203
|
}
|
|
177
204
|
|
|
178
205
|
function handleList(args: string[]): void {
|
|
206
|
+
if (hasFlag(args, "help")) {
|
|
207
|
+
showListHelp();
|
|
208
|
+
}
|
|
209
|
+
|
|
179
210
|
const parsed = parseArgs(args);
|
|
180
211
|
const positional = getPositionalArgs(args);
|
|
181
212
|
|
|
@@ -319,6 +350,10 @@ function handleCaptureNote(args: string[]): void {
|
|
|
319
350
|
}
|
|
320
351
|
|
|
321
352
|
function handleCapture(args: string[]): void {
|
|
353
|
+
if (hasFlag(args, "help")) {
|
|
354
|
+
showCaptureHelp();
|
|
355
|
+
}
|
|
356
|
+
|
|
322
357
|
if (args.length === 0) {
|
|
323
358
|
fail("Missing capture type. Use: task, knowledge, or note");
|
|
324
359
|
}
|
|
@@ -369,6 +404,9 @@ Search Options:
|
|
|
369
404
|
--since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
|
|
370
405
|
--sources List indexed sources with counts
|
|
371
406
|
|
|
407
|
+
Passthrough Sources:
|
|
408
|
+
prismis Semantic search via prismis daemon (requires prismis-daemon running)
|
|
409
|
+
|
|
372
410
|
List Options:
|
|
373
411
|
--limit <n> Maximum entries
|
|
374
412
|
--format <fmt> Output format: json (default), jsonl, human
|
|
@@ -401,10 +439,140 @@ Examples:
|
|
|
401
439
|
process.exit(0);
|
|
402
440
|
}
|
|
403
441
|
|
|
442
|
+
function showSearchHelp(): void {
|
|
443
|
+
console.log(`
|
|
444
|
+
lore search - Search indexed knowledge
|
|
445
|
+
|
|
446
|
+
Usage:
|
|
447
|
+
lore search <query> Search all sources
|
|
448
|
+
lore search <source> <query> Search specific source
|
|
449
|
+
lore search --sources List indexed sources
|
|
450
|
+
|
|
451
|
+
Options:
|
|
452
|
+
--limit <n> Maximum results (default: 20)
|
|
453
|
+
--since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
|
|
454
|
+
--sources List indexed sources with counts
|
|
455
|
+
--help Show this help
|
|
456
|
+
|
|
457
|
+
Indexed Sources:
|
|
458
|
+
blogs Blog posts and articles
|
|
459
|
+
captures Quick captures and notes
|
|
460
|
+
commits Git commit history
|
|
461
|
+
development Active development projects
|
|
462
|
+
events Calendar events and meetings
|
|
463
|
+
explorations Technical explorations
|
|
464
|
+
obsidian Obsidian vault notes
|
|
465
|
+
personal Personal data (books, movies, etc.)
|
|
466
|
+
readmes Project README files
|
|
467
|
+
sessions Claude Code session transcripts
|
|
468
|
+
tasks Logged development tasks
|
|
469
|
+
|
|
470
|
+
Passthrough Sources:
|
|
471
|
+
prismis Semantic search via prismis daemon
|
|
472
|
+
(requires prismis-daemon running)
|
|
473
|
+
|
|
474
|
+
Examples:
|
|
475
|
+
lore search "authentication"
|
|
476
|
+
lore search blogs "typescript patterns"
|
|
477
|
+
lore search commits --since this-week "refactor"
|
|
478
|
+
lore search prismis "kubernetes security"
|
|
479
|
+
`);
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function showListHelp(): void {
|
|
484
|
+
console.log(`
|
|
485
|
+
lore list - List domain entries
|
|
486
|
+
|
|
487
|
+
Usage:
|
|
488
|
+
lore list <domain> List entries in domain
|
|
489
|
+
lore list --domains List available domains
|
|
490
|
+
|
|
491
|
+
Options:
|
|
492
|
+
--limit <n> Maximum entries (default: all)
|
|
493
|
+
--format <fmt> Output format: json (default), jsonl, human
|
|
494
|
+
--domains List available domains
|
|
495
|
+
--help Show this help
|
|
496
|
+
|
|
497
|
+
Available Domains:
|
|
498
|
+
blogs Blog posts
|
|
499
|
+
books Books read
|
|
500
|
+
captures Quick captures
|
|
501
|
+
commits Git commits
|
|
502
|
+
development Development projects
|
|
503
|
+
events Calendar events
|
|
504
|
+
explorations Technical explorations
|
|
505
|
+
habits Tracked habits
|
|
506
|
+
interests Personal interests
|
|
507
|
+
movies Movies watched
|
|
508
|
+
obsidian Obsidian notes
|
|
509
|
+
people People/contacts
|
|
510
|
+
personal Personal data aggregate
|
|
511
|
+
podcasts Podcasts listened
|
|
512
|
+
readmes Project READMEs
|
|
513
|
+
sessions Claude Code sessions
|
|
514
|
+
tasks Development tasks
|
|
515
|
+
|
|
516
|
+
Examples:
|
|
517
|
+
lore list development
|
|
518
|
+
lore list commits --limit 10 --format human
|
|
519
|
+
lore list books --format jsonl
|
|
520
|
+
`);
|
|
521
|
+
process.exit(0);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function showCaptureHelp(): void {
|
|
525
|
+
console.log(`
|
|
526
|
+
lore capture - Capture knowledge
|
|
527
|
+
|
|
528
|
+
Usage:
|
|
529
|
+
lore capture task Log task completion
|
|
530
|
+
lore capture knowledge Log insight/learning
|
|
531
|
+
lore capture note Quick note
|
|
532
|
+
|
|
533
|
+
Capture Types:
|
|
534
|
+
|
|
535
|
+
task - Log completed development task
|
|
536
|
+
Required:
|
|
537
|
+
--project Project name
|
|
538
|
+
--name Task name
|
|
539
|
+
--problem Problem solved
|
|
540
|
+
--solution Solution pattern
|
|
541
|
+
Optional:
|
|
542
|
+
--code Code snippet
|
|
543
|
+
--discoveries Comma-separated discoveries
|
|
544
|
+
--deviations Deviation from plan
|
|
545
|
+
--pattern Pattern name
|
|
546
|
+
--keywords Comma-separated keywords
|
|
547
|
+
--tech Comma-separated technologies
|
|
548
|
+
--difficulty Difficulty level
|
|
549
|
+
|
|
550
|
+
knowledge - Log insight or learning
|
|
551
|
+
Required:
|
|
552
|
+
--context Context/project name
|
|
553
|
+
--text Insight text
|
|
554
|
+
--type Type: decision, learning, gotcha, preference
|
|
555
|
+
|
|
556
|
+
note - Quick note capture
|
|
557
|
+
Required:
|
|
558
|
+
--text Note content
|
|
559
|
+
Optional:
|
|
560
|
+
--tags Comma-separated tags
|
|
561
|
+
--context Optional context
|
|
562
|
+
|
|
563
|
+
Examples:
|
|
564
|
+
lore capture task --project=lore --name="Add help" --problem="No subcommand help" --solution="Added per-command help functions"
|
|
565
|
+
lore capture knowledge --context=lore --text="Unified CLI works" --type=learning
|
|
566
|
+
lore capture note --text="Remember to update docs" --tags=docs,todo
|
|
567
|
+
`);
|
|
568
|
+
process.exit(0);
|
|
569
|
+
}
|
|
570
|
+
|
|
404
571
|
function main(): void {
|
|
405
572
|
const args = process.argv.slice(2);
|
|
406
573
|
|
|
407
|
-
|
|
574
|
+
// Show global help only when no args or help is first arg
|
|
575
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
408
576
|
showHelp();
|
|
409
577
|
}
|
|
410
578
|
|
|
@@ -421,10 +589,6 @@ function main(): void {
|
|
|
421
589
|
case "capture":
|
|
422
590
|
handleCapture(commandArgs);
|
|
423
591
|
break;
|
|
424
|
-
case "--help":
|
|
425
|
-
case "-h":
|
|
426
|
-
showHelp();
|
|
427
|
-
break;
|
|
428
592
|
default:
|
|
429
593
|
fail(`Unknown command: ${command}. Use: search, list, or capture`);
|
|
430
594
|
}
|
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.1",
|
|
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
|
+
}
|