@voidwire/lore 0.1.12 → 0.1.14

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
@@ -28,6 +28,7 @@ import {
28
28
  listDomains,
29
29
  info,
30
30
  formatInfoHuman,
31
+ projects,
31
32
  captureTask,
32
33
  captureKnowledge,
33
34
  captureNote,
@@ -317,9 +318,10 @@ function handleList(args: string[]): void {
317
318
  ? parseInt(parsed.get("limit")!, 10)
318
319
  : undefined;
319
320
  const format = parsed.get("format") || "json";
321
+ const project = parsed.get("project");
320
322
 
321
323
  try {
322
- const result = list(domain, { limit });
324
+ const result = list(domain, { limit, project });
323
325
 
324
326
  if (format === "human") {
325
327
  console.log(formatHumanOutput(result));
@@ -377,6 +379,31 @@ function handleInfo(args: string[]): void {
377
379
  }
378
380
  }
379
381
 
382
+ // ============================================================================
383
+ // Projects Command
384
+ // ============================================================================
385
+
386
+ function handleProjects(args: string[]): void {
387
+ if (hasFlag(args, "help")) {
388
+ showProjectsHelp();
389
+ }
390
+
391
+ try {
392
+ const result = projects();
393
+
394
+ output({
395
+ success: true,
396
+ projects: result,
397
+ });
398
+
399
+ console.error(`✅ ${result.length} projects found`);
400
+ process.exit(0);
401
+ } catch (error) {
402
+ const message = error instanceof Error ? error.message : "Unknown error";
403
+ fail(message, 2);
404
+ }
405
+ }
406
+
380
407
  // ============================================================================
381
408
  // Capture Command
382
409
  // ============================================================================
@@ -656,6 +683,7 @@ Usage:
656
683
  Options:
657
684
  --limit <n> Maximum entries (default: all)
658
685
  --format <fmt> Output format: json (default), jsonl, human
686
+ --project <name> Filter by project name
659
687
  --domains List available domains
660
688
  --help Show this help
661
689
 
@@ -681,6 +709,7 @@ Available Domains:
681
709
  Examples:
682
710
  lore list development
683
711
  lore list commits --limit 10 --format human
712
+ lore list commits --project=momentum --limit 5
684
713
  lore list books --format jsonl
685
714
  `);
686
715
  process.exit(0);
@@ -700,7 +729,7 @@ Options:
700
729
 
701
730
  Output Fields:
702
731
  sources Array of {name, count} for each indexed source
703
- projects Known projects (not yet implemented)
732
+ projects Known projects across all sources
704
733
  last_indexed Most recent timestamp from indexed data
705
734
  total_entries Total number of indexed entries
706
735
 
@@ -712,6 +741,35 @@ Examples:
712
741
  process.exit(0);
713
742
  }
714
743
 
744
+ function showProjectsHelp(): void {
745
+ console.log(`
746
+ lore projects - List all known projects
747
+
748
+ Usage:
749
+ lore projects List all unique project names
750
+
751
+ Options:
752
+ --help Show this help
753
+
754
+ Output:
755
+ JSON array of project names, sorted alphabetically.
756
+ Projects are extracted from metadata fields across all sources.
757
+
758
+ Sources checked:
759
+ commits project field
760
+ sessions project field
761
+ tasks project field
762
+ captures context field
763
+ teachings source field
764
+
765
+ Examples:
766
+ lore projects
767
+ lore projects | jq -r '.projects[]'
768
+ lore projects | jq '.projects | length'
769
+ `);
770
+ process.exit(0);
771
+ }
772
+
715
773
  function showCaptureHelp(): void {
716
774
  console.log(`
717
775
  lore capture - Capture knowledge
@@ -790,11 +848,16 @@ function main(): void {
790
848
  case "info":
791
849
  handleInfo(commandArgs);
792
850
  break;
851
+ case "projects":
852
+ handleProjects(commandArgs);
853
+ break;
793
854
  case "capture":
794
855
  handleCapture(commandArgs);
795
856
  break;
796
857
  default:
797
- fail(`Unknown command: ${command}. Use: search, list, info, or capture`);
858
+ fail(
859
+ `Unknown command: ${command}. Use: search, list, info, projects, or capture`,
860
+ );
798
861
  }
799
862
  }
800
863
 
package/index.ts CHANGED
@@ -35,6 +35,9 @@ export {
35
35
  type InfoOutput,
36
36
  } from "./lib/info";
37
37
 
38
+ // Projects
39
+ export { projects } from "./lib/projects";
40
+
38
41
  // Prismis integration
39
42
  export {
40
43
  searchPrismis,
package/lib/info.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { Database } from "bun:sqlite";
9
9
  import { homedir } from "os";
10
10
  import { existsSync } from "fs";
11
+ import { projects as getProjects } from "./projects.js";
11
12
 
12
13
  export interface SourceInfo {
13
14
  name: string;
@@ -72,7 +73,7 @@ export function info(): InfoOutput {
72
73
 
73
74
  return {
74
75
  sources,
75
- projects: [], // TODO: implement in task 1.3
76
+ projects: getProjects(),
76
77
  last_indexed,
77
78
  total_entries,
78
79
  };
package/lib/list.ts CHANGED
@@ -59,8 +59,18 @@ const PERSONAL_SUBTYPES: Partial<Record<Domain, string>> = {
59
59
  habits: "habit",
60
60
  };
61
61
 
62
+ // Maps source to metadata field containing project name
63
+ const PROJECT_FIELD: Record<string, string> = {
64
+ commits: "project",
65
+ sessions: "project",
66
+ tasks: "project",
67
+ captures: "context",
68
+ teachings: "source",
69
+ };
70
+
62
71
  export interface ListOptions {
63
72
  limit?: number;
73
+ project?: string;
64
74
  }
65
75
 
66
76
  export interface ListEntry {
@@ -93,13 +103,27 @@ function queryBySource(
93
103
  db: Database,
94
104
  source: string,
95
105
  limit?: number,
106
+ project?: string,
96
107
  ): ListEntry[] {
97
- const sql = limit
98
- ? `SELECT title, content, metadata FROM search WHERE source = ? LIMIT ?`
99
- : `SELECT title, content, metadata FROM search WHERE source = ?`;
108
+ let sql = "SELECT title, content, metadata FROM search WHERE source = ?";
109
+ const params: (string | number)[] = [source];
110
+
111
+ // Add project filter if provided and source has a project field
112
+ if (project) {
113
+ const field = PROJECT_FIELD[source];
114
+ if (field) {
115
+ sql += ` AND json_extract(metadata, '$.${field}') = ?`;
116
+ params.push(project);
117
+ }
118
+ }
119
+
120
+ if (limit) {
121
+ sql += " LIMIT ?";
122
+ params.push(limit);
123
+ }
100
124
 
101
- const stmt = limit ? db.prepare(sql) : db.prepare(sql);
102
- const rows = (limit ? stmt.all(source, limit) : stmt.all(source)) as RawRow[];
125
+ const stmt = db.prepare(sql);
126
+ const rows = stmt.all(...params) as RawRow[];
103
127
 
104
128
  return rows.map((row) => ({
105
129
  title: row.title,
@@ -166,7 +190,7 @@ export function list(domain: Domain, options: ListOptions = {}): ListResult {
166
190
  if (personalType) {
167
191
  entries = queryPersonalType(db, personalType, options.limit);
168
192
  } else {
169
- entries = queryBySource(db, domain, options.limit);
193
+ entries = queryBySource(db, domain, options.limit, options.project);
170
194
  }
171
195
 
172
196
  return {
@@ -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.12",
3
+ "version": "0.1.14",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",