@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 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
- if (args.length === 0 || hasFlag(args, "help") || args[0] === "-h") {
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
@@ -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.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
+ }