@voidwire/lore 0.1.11 → 0.1.13

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
@@ -26,6 +26,9 @@ import {
26
26
  listSources,
27
27
  list,
28
28
  listDomains,
29
+ info,
30
+ formatInfoHuman,
31
+ projects,
29
32
  captureTask,
30
33
  captureKnowledge,
31
34
  captureNote,
@@ -342,6 +345,64 @@ function handleList(args: string[]): void {
342
345
  }
343
346
  }
344
347
 
348
+ // ============================================================================
349
+ // Info Command
350
+ // ============================================================================
351
+
352
+ function handleInfo(args: string[]): void {
353
+ if (hasFlag(args, "help")) {
354
+ showInfoHelp();
355
+ }
356
+
357
+ const human = hasFlag(args, "human");
358
+
359
+ try {
360
+ const result = info();
361
+
362
+ if (human) {
363
+ console.log(formatInfoHuman(result));
364
+ } else {
365
+ output({
366
+ success: true,
367
+ ...result,
368
+ });
369
+ }
370
+
371
+ console.error(
372
+ `✅ ${result.sources.length} sources, ${result.total_entries} total entries`,
373
+ );
374
+ process.exit(0);
375
+ } catch (error) {
376
+ const message = error instanceof Error ? error.message : "Unknown error";
377
+ fail(message, 2);
378
+ }
379
+ }
380
+
381
+ // ============================================================================
382
+ // Projects Command
383
+ // ============================================================================
384
+
385
+ function handleProjects(args: string[]): void {
386
+ if (hasFlag(args, "help")) {
387
+ showProjectsHelp();
388
+ }
389
+
390
+ try {
391
+ const result = projects();
392
+
393
+ output({
394
+ success: true,
395
+ projects: result,
396
+ });
397
+
398
+ console.error(`✅ ${result.length} projects found`);
399
+ process.exit(0);
400
+ } catch (error) {
401
+ const message = error instanceof Error ? error.message : "Unknown error";
402
+ fail(message, 2);
403
+ }
404
+ }
405
+
345
406
  // ============================================================================
346
407
  // Capture Command
347
408
  // ============================================================================
@@ -512,6 +573,8 @@ Usage:
512
573
  lore search --sources List indexed sources
513
574
  lore list <domain> List domain entries
514
575
  lore list --domains List available domains
576
+ lore info Show indexed sources and counts
577
+ lore info --human Human-readable info
515
578
  lore capture task|knowledge|note|teaching Capture knowledge
516
579
 
517
580
  Search Options:
@@ -649,6 +712,61 @@ Examples:
649
712
  process.exit(0);
650
713
  }
651
714
 
715
+ function showInfoHelp(): void {
716
+ console.log(`
717
+ lore info - Show indexed sources and counts
718
+
719
+ Usage:
720
+ lore info Show all sources and counts
721
+ lore info --human Human-readable format
722
+
723
+ Options:
724
+ --human Human-readable format (default: JSON)
725
+ --help Show this help
726
+
727
+ Output Fields:
728
+ sources Array of {name, count} for each indexed source
729
+ projects Known projects across all sources
730
+ last_indexed Most recent timestamp from indexed data
731
+ total_entries Total number of indexed entries
732
+
733
+ Examples:
734
+ lore info
735
+ lore info | jq '.sources | length'
736
+ lore info --human
737
+ `);
738
+ process.exit(0);
739
+ }
740
+
741
+ function showProjectsHelp(): void {
742
+ console.log(`
743
+ lore projects - List all known projects
744
+
745
+ Usage:
746
+ lore projects List all unique project names
747
+
748
+ Options:
749
+ --help Show this help
750
+
751
+ Output:
752
+ JSON array of project names, sorted alphabetically.
753
+ Projects are extracted from metadata fields across all sources.
754
+
755
+ Sources checked:
756
+ commits project field
757
+ sessions project field
758
+ tasks project field
759
+ captures context field
760
+ teachings source field
761
+
762
+ Examples:
763
+ lore projects
764
+ lore projects | jq -r '.projects[]'
765
+ lore projects | jq '.projects | length'
766
+ `);
767
+ process.exit(0);
768
+ }
769
+
652
770
  function showCaptureHelp(): void {
653
771
  console.log(`
654
772
  lore capture - Capture knowledge
@@ -724,11 +842,19 @@ function main(): void {
724
842
  case "list":
725
843
  handleList(commandArgs);
726
844
  break;
845
+ case "info":
846
+ handleInfo(commandArgs);
847
+ break;
848
+ case "projects":
849
+ handleProjects(commandArgs);
850
+ break;
727
851
  case "capture":
728
852
  handleCapture(commandArgs);
729
853
  break;
730
854
  default:
731
- fail(`Unknown command: ${command}. Use: search, list, or capture`);
855
+ fail(
856
+ `Unknown command: ${command}. Use: search, list, info, projects, or capture`,
857
+ );
732
858
  }
733
859
  }
734
860
 
package/index.ts CHANGED
@@ -27,6 +27,17 @@ export {
27
27
  type ListResult,
28
28
  } from "./lib/list";
29
29
 
30
+ // Info
31
+ export {
32
+ info,
33
+ formatInfoHuman,
34
+ type SourceInfo,
35
+ type InfoOutput,
36
+ } from "./lib/info";
37
+
38
+ // Projects
39
+ export { projects } from "./lib/projects";
40
+
30
41
  // Prismis integration
31
42
  export {
32
43
  searchPrismis,
package/lib/info.ts ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * lib/info.ts - Discovery command for indexed sources
3
+ *
4
+ * Shows what's indexed, counts per source, and last indexed timestamp.
5
+ * Uses Bun's built-in SQLite for zero external dependencies.
6
+ */
7
+
8
+ import { Database } from "bun:sqlite";
9
+ import { homedir } from "os";
10
+ import { existsSync } from "fs";
11
+ import { projects as getProjects } from "./projects.js";
12
+
13
+ export interface SourceInfo {
14
+ name: string;
15
+ count: number;
16
+ }
17
+
18
+ export interface InfoOutput {
19
+ sources: SourceInfo[];
20
+ projects: string[];
21
+ last_indexed: string;
22
+ total_entries: number;
23
+ }
24
+
25
+ function getDatabasePath(): string {
26
+ return `${homedir()}/.local/share/lore/lore.db`;
27
+ }
28
+
29
+ /**
30
+ * Get info about indexed sources
31
+ *
32
+ * @returns InfoOutput with sources, counts, and metadata
33
+ * @throws Error if database doesn't exist
34
+ */
35
+ export function info(): InfoOutput {
36
+ const dbPath = getDatabasePath();
37
+
38
+ if (!existsSync(dbPath)) {
39
+ // Return empty info if database doesn't exist
40
+ return {
41
+ sources: [],
42
+ projects: [],
43
+ last_indexed: new Date().toISOString(),
44
+ total_entries: 0,
45
+ };
46
+ }
47
+
48
+ const db = new Database(dbPath, { readonly: true });
49
+
50
+ try {
51
+ // Get distinct sources with counts
52
+ const sourcesStmt = db.prepare(`
53
+ SELECT source as name, COUNT(*) as count
54
+ FROM search
55
+ GROUP BY source
56
+ ORDER BY count DESC
57
+ `);
58
+ const sources = sourcesStmt.all() as SourceInfo[];
59
+
60
+ // Get total entries
61
+ const totalStmt = db.prepare(`SELECT COUNT(*) as total FROM search`);
62
+ const totalResult = totalStmt.get() as { total: number };
63
+ const total_entries = totalResult?.total ?? 0;
64
+
65
+ // Get last indexed timestamp from metadata
66
+ const tsStmt = db.prepare(`
67
+ SELECT MAX(json_extract(metadata, '$.timestamp')) as ts
68
+ FROM search
69
+ WHERE json_extract(metadata, '$.timestamp') IS NOT NULL
70
+ `);
71
+ const tsResult = tsStmt.get() as { ts: string | null };
72
+ const last_indexed = tsResult?.ts ?? new Date().toISOString();
73
+
74
+ return {
75
+ sources,
76
+ projects: getProjects(),
77
+ last_indexed,
78
+ total_entries,
79
+ };
80
+ } finally {
81
+ db.close();
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Format info output for human readability
87
+ */
88
+ export function formatInfoHuman(data: InfoOutput): string {
89
+ const lines: string[] = [
90
+ "Lore Knowledge Index",
91
+ "====================",
92
+ "",
93
+ `Total entries: ${data.total_entries.toLocaleString()}`,
94
+ `Last indexed: ${data.last_indexed}`,
95
+ "",
96
+ "Sources:",
97
+ ];
98
+
99
+ for (const source of data.sources) {
100
+ lines.push(
101
+ ` ${source.name.padEnd(15)} ${source.count.toLocaleString().padStart(8)} entries`,
102
+ );
103
+ }
104
+
105
+ if (data.projects.length > 0) {
106
+ lines.push("");
107
+ lines.push("Projects:");
108
+ for (const project of data.projects) {
109
+ lines.push(` ${project}`);
110
+ }
111
+ }
112
+
113
+ return lines.join("\n");
114
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * lib/projects.ts - List all known projects across sources
3
+ *
4
+ * Queries distinct project values from metadata fields, handling
5
+ * different field names per source type.
6
+ */
7
+
8
+ import { Database } from "bun:sqlite";
9
+ import { homedir } from "os";
10
+ import { existsSync } from "fs";
11
+
12
+ const PROJECT_FIELD: Record<string, string> = {
13
+ commits: "project",
14
+ sessions: "project",
15
+ tasks: "project",
16
+ captures: "context",
17
+ teachings: "source",
18
+ };
19
+
20
+ function getDatabasePath(): string {
21
+ return `${homedir()}/.local/share/lore/lore.db`;
22
+ }
23
+
24
+ /**
25
+ * Get all unique project names across sources
26
+ *
27
+ * @returns Sorted array of unique project names
28
+ */
29
+ export function projects(): string[] {
30
+ const dbPath = getDatabasePath();
31
+
32
+ if (!existsSync(dbPath)) {
33
+ return [];
34
+ }
35
+
36
+ const db = new Database(dbPath, { readonly: true });
37
+
38
+ try {
39
+ const allProjects = new Set<string>();
40
+
41
+ for (const [source, field] of Object.entries(PROJECT_FIELD)) {
42
+ const stmt = db.prepare(`
43
+ SELECT DISTINCT json_extract(metadata, '$.${field}') as proj
44
+ FROM search
45
+ WHERE source = ? AND json_extract(metadata, '$.${field}') IS NOT NULL
46
+ `);
47
+ const results = stmt.all(source) as { proj: string | null }[];
48
+
49
+ for (const r of results) {
50
+ if (r.proj) {
51
+ allProjects.add(r.proj);
52
+ }
53
+ }
54
+ }
55
+
56
+ return Array.from(allProjects).sort();
57
+ } finally {
58
+ db.close();
59
+ }
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",