@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 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 sources = listSources();
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(`✅ ${sources.length} sources indexed`);
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
- if (args.length === 0 || hasFlag(args, "help") || args[0] === "-h") {
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
@@ -27,6 +27,13 @@ export {
27
27
  type ListResult,
28
28
  } from "./lib/list";
29
29
 
30
+ // Prismis integration
31
+ export {
32
+ searchPrismis,
33
+ type PrismisSearchResult,
34
+ type PrismisSearchOptions,
35
+ } from "./lib/prismis";
36
+
30
37
  // Capture
31
38
  export {
32
39
  captureKnowledge,
package/lib/capture.ts CHANGED
@@ -12,6 +12,7 @@ import { homedir } from "os";
12
12
  export interface CaptureResult {
13
13
  success: boolean;
14
14
  error?: string;
15
+ [key: string]: unknown;
15
16
  }
16
17
 
17
18
  export type KnowledgeCaptureType =
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.0",
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
+ }