@voidwire/lore 0.2.0 → 0.4.0

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
@@ -10,7 +10,7 @@
10
10
  * Usage:
11
11
  * lore search <query> Search all sources
12
12
  * lore search <source> <query> Search specific source
13
- * lore list <domain> List domain entries
13
+ * lore list <source> List source entries
14
14
  * lore capture task|knowledge|note|teaching Capture knowledge
15
15
  *
16
16
  * Exit codes:
@@ -25,11 +25,12 @@ import {
25
25
  searchAtuin,
26
26
  listSources,
27
27
  list,
28
- listDomains,
29
28
  formatBriefList,
30
29
  info,
31
30
  formatInfoHuman,
32
31
  projects,
32
+ about,
33
+ formatBriefAbout,
33
34
  captureTask,
34
35
  captureKnowledge,
35
36
  captureNote,
@@ -37,11 +38,11 @@ import {
37
38
  semanticSearch,
38
39
  formatBriefSearch,
39
40
  hasEmbeddings,
40
- DOMAINS,
41
+ SOURCES,
41
42
  type SearchResult,
42
43
  type ListResult,
43
44
  type ListEntry,
44
- type Domain,
45
+ type Source,
45
46
  type TaskInput,
46
47
  type KnowledgeInput,
47
48
  type NoteInput,
@@ -287,7 +288,7 @@ async function handleSearch(args: string[]): Promise<void> {
287
288
  // ============================================================================
288
289
 
289
290
  function formatHumanOutput(result: ListResult): string {
290
- const lines: string[] = [`${result.domain} (${result.count} entries):`, ""];
291
+ const lines: string[] = [`${result.source} (${result.count} entries):`, ""];
291
292
 
292
293
  for (const entry of result.entries) {
293
294
  lines.push(` ${entry.title}`);
@@ -307,21 +308,23 @@ function handleList(args: string[]): void {
307
308
  const parsed = parseArgs(args);
308
309
  const positional = getPositionalArgs(args);
309
310
 
310
- // Handle --domains flag
311
+ // Handle --domains flag (deprecated)
311
312
  if (hasFlag(args, "domains")) {
312
- output({ success: true, domains: listDomains() });
313
- console.error(`✅ ${DOMAINS.length} domains available`);
313
+ console.error("⚠️ --domains is deprecated. Use 'lore sources' instead.");
314
+ const sources = listSources();
315
+ output({ success: true, sources });
316
+ console.error(`✅ ${SOURCES.length} sources available`);
314
317
  process.exit(0);
315
318
  }
316
319
 
317
320
  if (positional.length === 0) {
318
- fail(`Missing domain. Available: ${DOMAINS.join(", ")}`);
321
+ fail(`Missing source. Available: ${SOURCES.join(", ")}`);
319
322
  }
320
323
 
321
- const domain = positional[0] as Domain;
324
+ const source = positional[0] as Source;
322
325
 
323
- if (!DOMAINS.includes(domain)) {
324
- fail(`Invalid domain: ${domain}. Available: ${DOMAINS.join(", ")}`);
326
+ if (!SOURCES.includes(source)) {
327
+ fail(`Invalid source: ${source}. Available: ${SOURCES.join(", ")}`);
325
328
  }
326
329
 
327
330
  const limit = parsed.has("limit")
@@ -332,7 +335,7 @@ function handleList(args: string[]): void {
332
335
  const brief = hasFlag(args, "brief");
333
336
 
334
337
  try {
335
- const result = list(domain, { limit, project });
338
+ const result = list(source, { limit, project });
336
339
 
337
340
  if (brief) {
338
341
  console.log(formatBriefList(result));
@@ -345,13 +348,13 @@ function handleList(args: string[]): void {
345
348
  } else {
346
349
  output({
347
350
  success: true,
348
- domain: result.domain,
351
+ source: result.source,
349
352
  entries: result.entries,
350
353
  count: result.count,
351
354
  });
352
355
  }
353
356
 
354
- console.error(`✅ ${result.count} entries in ${domain}`);
357
+ console.error(`✅ ${result.count} entries in ${source}`);
355
358
  process.exit(0);
356
359
  } catch (error) {
357
360
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -417,6 +420,99 @@ function handleProjects(args: string[]): void {
417
420
  }
418
421
  }
419
422
 
423
+ // ============================================================================
424
+ // About Command
425
+ // ============================================================================
426
+
427
+ function handleAbout(args: string[]): void {
428
+ if (hasFlag(args, "help")) {
429
+ showAboutHelp();
430
+ }
431
+
432
+ const parsed = parseArgs(args);
433
+ const positional = getPositionalArgs(args);
434
+
435
+ if (positional.length === 0) {
436
+ fail("Missing project name. Use: lore about <project>");
437
+ }
438
+
439
+ const project = positional[0];
440
+ const brief = hasFlag(args, "brief");
441
+ const limit = parsed.has("limit")
442
+ ? parseInt(parsed.get("limit")!, 10)
443
+ : undefined;
444
+
445
+ try {
446
+ const result = about(project, { brief, limit });
447
+
448
+ if (brief) {
449
+ console.log(formatBriefAbout(result));
450
+ } else {
451
+ output({
452
+ success: true,
453
+ ...result,
454
+ });
455
+ }
456
+
457
+ const totalCount =
458
+ result.commits.count +
459
+ result.captures.count +
460
+ result.tasks.count +
461
+ result.teachings.count +
462
+ result.sessions.count;
463
+
464
+ console.error(`✅ ${totalCount} entries for project: ${project}`);
465
+ process.exit(0);
466
+ } catch (error) {
467
+ const message = error instanceof Error ? error.message : "Unknown error";
468
+ fail(message, 2);
469
+ }
470
+ }
471
+
472
+ // ============================================================================
473
+ // Sources Command
474
+ // ============================================================================
475
+
476
+ function handleSources(args: string[]): void {
477
+ if (hasFlag(args, "help")) {
478
+ showSourcesHelp();
479
+ }
480
+
481
+ try {
482
+ const sources = listSources();
483
+ output({
484
+ success: true,
485
+ sources,
486
+ });
487
+ console.error(`✅ ${sources.length} sources indexed`);
488
+ process.exit(0);
489
+ } catch (error) {
490
+ const message = error instanceof Error ? error.message : "Unknown error";
491
+ fail(message, 2);
492
+ }
493
+ }
494
+
495
+ function showSourcesHelp(): void {
496
+ console.log(`
497
+ lore sources - List all indexed sources with counts
498
+
499
+ Usage:
500
+ lore sources List all sources with entry counts
501
+
502
+ Options:
503
+ --help Show this help
504
+
505
+ Output:
506
+ JSON array of {source, count} objects, sorted by count descending.
507
+
508
+ Examples:
509
+ lore sources
510
+ lore sources | jq '.[0]'
511
+ lore sources | jq -r '.[] | "\\(.source): \\(.count)"'
512
+ `);
513
+ process.exit(0);
514
+ }
515
+
420
516
  // ============================================================================
421
517
  // Capture Command
422
518
  // ============================================================================
@@ -584,11 +680,12 @@ Philosophy:
584
680
  Usage:
585
681
  lore search <query> Search all sources
586
682
  lore search <source> <query> Search specific source
587
- lore search --sources List indexed sources
588
- lore list <domain> List domain entries
589
- lore list --domains List available domains
683
+ lore sources List indexed sources with counts
684
+ lore list <source> List source entries
590
685
  lore info Show indexed sources and counts
591
686
  lore info --human Human-readable info
687
+ lore about <project> Aggregate view of project knowledge
688
+ lore about <project> --brief Compact project summary
592
689
  lore capture task|knowledge|note|teaching Capture knowledge
593
690
 
594
691
  Search Options:
@@ -597,7 +694,6 @@ Search Options:
597
694
  --project <name> Filter results by project
598
695
  --brief Compact output (titles only)
599
696
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
600
- --sources List indexed sources with counts
601
697
 
602
698
  Passthrough Sources:
603
699
  prismis Semantic search via prismis daemon (requires prismis-daemon running)
@@ -607,7 +703,7 @@ List Options:
607
703
  --limit <n> Maximum entries
608
704
  --format <fmt> Output format: json (default), jsonl, human
609
705
  --brief Compact output (titles only)
610
- --domains List available domains
706
+ --project <name> Filter by project name
611
707
 
612
708
  Capture Types:
613
709
  task Log task completion
@@ -635,6 +731,7 @@ Capture Types:
635
731
  Examples:
636
732
  lore search "authentication"
637
733
  lore search blogs "typescript patterns"
734
+ lore sources
638
735
  lore list development
639
736
  lore list commits --limit 10 --format human
640
737
  lore capture knowledge --context=lore --text="Unified CLI works" --type=learning
@@ -649,7 +746,6 @@ lore search - Search indexed knowledge
649
746
  Usage:
650
747
  lore search <query> Search all sources
651
748
  lore search <source> <query> Search specific source
652
- lore search --sources List indexed sources
653
749
 
654
750
  Options:
655
751
  --exact Use FTS5 text search (bypasses semantic search)
@@ -657,7 +753,6 @@ Options:
657
753
  --project <name> Filter results by project (post-filters KNN results)
658
754
  --brief Compact output (titles only)
659
755
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
660
- --sources List indexed sources with counts
661
756
  --help Show this help
662
757
 
663
758
  Indexed Sources:
@@ -672,6 +767,7 @@ Indexed Sources:
672
767
  readmes Project README files
673
768
  sessions Claude Code session transcripts
674
769
  tasks Logged development tasks
770
+ teachings Teaching moments
675
771
 
676
772
  Passthrough Sources:
677
773
  prismis Semantic search via prismis daemon
@@ -679,6 +775,9 @@ Passthrough Sources:
679
775
  atuin Shell history search
680
776
  (queries ~/.local/share/atuin/history.db directly)
681
777
 
778
+ See also:
779
+ lore sources List all sources with entry counts
780
+
682
781
  Examples:
683
782
  lore search "authentication"
684
783
  lore search blogs "typescript patterns"
@@ -693,21 +792,19 @@ Examples:
693
792
 
694
793
  function showListHelp(): void {
695
794
  console.log(`
696
- lore list - List domain entries
795
+ lore list - List source entries
697
796
 
698
797
  Usage:
699
- lore list <domain> List entries in domain
700
- lore list --domains List available domains
798
+ lore list <source> List entries in source
701
799
 
702
800
  Options:
703
801
  --limit <n> Maximum entries (default: all)
704
802
  --format <fmt> Output format: json (default), jsonl, human
705
803
  --project <name> Filter by project name
706
804
  --brief Compact output (titles only)
707
- --domains List available domains
708
805
  --help Show this help
709
806
 
710
- Available Domains:
807
+ Available Sources:
711
808
  blogs Blog posts
712
809
  books Books read
713
810
  captures Quick captures
@@ -720,11 +817,14 @@ Available Domains:
720
817
  movies Movies watched
721
818
  obsidian Obsidian notes
722
819
  people People/contacts
723
- personal Personal data aggregate
724
820
  podcasts Podcasts listened
725
821
  readmes Project READMEs
726
822
  sessions Claude Code sessions
727
823
  tasks Development tasks
824
+ teachings Teaching moments
825
+
826
+ See also:
827
+ lore sources List all sources with entry counts
728
828
 
729
829
  Examples:
730
830
  lore list development
@@ -790,6 +890,51 @@ Examples:
790
890
  process.exit(0);
791
891
  }
792
892
 
893
+ function showAboutHelp(): void {
894
+ console.log(`
895
+ lore about - Show everything about a project
896
+
897
+ Usage:
898
+ lore about <project> Aggregate view of project knowledge
899
+ lore about <project> --brief Compact output
900
+
901
+ Options:
902
+ --brief Compact output (titles only)
903
+ --limit <n> Results per source (default: 10)
904
+ --help Show this help
905
+
906
+ Sources queried:
907
+ commits Git commits for project
908
+ captures Quick captures in project context
909
+ tasks Development tasks for project
910
+ teachings Teachings from project
911
+ sessions Claude Code sessions for project
912
+
913
+ Output (JSON):
914
+ {
915
+ "project": "name",
916
+ "commits": [...],
917
+ "captures": [...],
918
+ "tasks": [...],
919
+ "teachings": [...],
920
+ "sessions": [...]
921
+ }
922
+
923
+ Output (--brief):
924
+ commits (3):
925
+ project: hash - commit message
926
+
927
+ captures (2):
928
+ project: insight text
929
+
930
+ Examples:
931
+ lore about momentum --brief
932
+ lore about lore | jq '.commits | length'
933
+ lore about momentum --limit 5
934
+ `);
935
+ process.exit(0);
936
+ }
937
+
793
938
  function showCaptureHelp(): void {
794
939
  console.log(`
795
940
  lore capture - Capture knowledge
@@ -865,18 +1010,24 @@ function main(): void {
865
1010
  case "list":
866
1011
  handleList(commandArgs);
867
1012
  break;
1013
+ case "sources":
1014
+ handleSources(commandArgs);
1015
+ break;
868
1016
  case "info":
869
1017
  handleInfo(commandArgs);
870
1018
  break;
871
1019
  case "projects":
872
1020
  handleProjects(commandArgs);
873
1021
  break;
1022
+ case "about":
1023
+ handleAbout(commandArgs);
1024
+ break;
874
1025
  case "capture":
875
1026
  handleCapture(commandArgs);
876
1027
  break;
877
1028
  default:
878
1029
  fail(
879
- `Unknown command: ${command}. Use: search, list, info, projects, or capture`,
1030
+ `Unknown command: ${command}. Use: search, list, sources, info, projects, about, or capture`,
880
1031
  );
881
1032
  }
882
1033
  }
package/index.ts CHANGED
@@ -19,10 +19,9 @@ export {
19
19
  // List
20
20
  export {
21
21
  list,
22
- listDomains,
23
22
  formatBriefList,
24
- DOMAINS,
25
- type Domain,
23
+ SOURCES,
24
+ type Source,
26
25
  type ListOptions,
27
26
  type ListEntry,
28
27
  type ListResult,
@@ -39,6 +38,14 @@ export {
39
38
  // Projects
40
39
  export { projects } from "./lib/projects";
41
40
 
41
+ // About
42
+ export {
43
+ about,
44
+ formatBriefAbout,
45
+ type AboutResult,
46
+ type AboutOptions,
47
+ } from "./lib/about";
48
+
42
49
  // Prismis integration
43
50
  export {
44
51
  searchPrismis,
package/lib/about.ts ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * lib/about.ts - Project knowledge aggregation
3
+ *
4
+ * Aggregates all knowledge sources for a given project.
5
+ * Uses parallel queries via Promise.all for performance.
6
+ */
7
+
8
+ import { list, formatBriefList, type ListResult, type Source } from "./list";
9
+
10
+ export interface AboutOptions {
11
+ brief?: boolean;
12
+ limit?: number;
13
+ }
14
+
15
+ export interface AboutResult {
16
+ project: string;
17
+ commits: ListResult;
18
+ captures: ListResult;
19
+ tasks: ListResult;
20
+ teachings: ListResult;
21
+ sessions: ListResult;
22
+ }
23
+
24
+ /**
25
+ * Sources to query for project knowledge
26
+ * Each source has a different field for project mapping (handled by list.ts)
27
+ * Note: "insights" will be added when task 2.1 is complete
28
+ */
29
+ const ABOUT_SOURCES: Source[] = [
30
+ "commits",
31
+ "captures",
32
+ "tasks",
33
+ "teachings",
34
+ "sessions",
35
+ ];
36
+
37
+ /**
38
+ * Get aggregated knowledge about a project across all sources
39
+ *
40
+ * @param project - Project name to query
41
+ * @param options - Optional brief flag and limit
42
+ * @returns AboutResult with data from all sources, or formatted string if brief
43
+ */
44
+ export function about(
45
+ project: string,
46
+ options: AboutOptions = {},
47
+ ): AboutResult {
48
+ const limit = options.limit ?? 10;
49
+
50
+ // Query all sources in parallel
51
+ const results = ABOUT_SOURCES.map((src) => {
52
+ try {
53
+ return list(src, { project, limit });
54
+ } catch {
55
+ // Source doesn't exist or has no data - return empty result
56
+ return {
57
+ source: src,
58
+ entries: [],
59
+ count: 0,
60
+ } as ListResult;
61
+ }
62
+ });
63
+
64
+ return {
65
+ project,
66
+ commits: results[0],
67
+ captures: results[1],
68
+ tasks: results[2],
69
+ teachings: results[3],
70
+ sessions: results[4],
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Format about result as brief, compact output
76
+ * Groups by source, skips empty sources
77
+ */
78
+ export function formatBriefAbout(result: AboutResult): string {
79
+ const sections: string[] = [];
80
+
81
+ // Format each non-empty source
82
+ if (result.commits.count > 0) {
83
+ sections.push(formatBriefList(result.commits));
84
+ }
85
+ if (result.captures.count > 0) {
86
+ sections.push(formatBriefList(result.captures));
87
+ }
88
+ if (result.tasks.count > 0) {
89
+ sections.push(formatBriefList(result.tasks));
90
+ }
91
+ if (result.teachings.count > 0) {
92
+ sections.push(formatBriefList(result.teachings));
93
+ }
94
+ if (result.sessions.count > 0) {
95
+ sections.push(formatBriefList(result.sessions));
96
+ }
97
+
98
+ if (sections.length === 0) {
99
+ return `(no results for project: ${result.project})`;
100
+ }
101
+
102
+ return sections.join("\n\n");
103
+ }
package/lib/list.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * lib/list.ts - Domain listing functions
2
+ * lib/list.ts - Source listing functions
3
3
  *
4
- * Browse indexed domains without search queries.
4
+ * Browse indexed sources without search queries.
5
5
  * Uses Bun's built-in SQLite for zero external dependencies.
6
6
  */
7
7
 
@@ -9,8 +9,8 @@ import { Database } from "bun:sqlite";
9
9
  import { homedir } from "os";
10
10
  import { existsSync } from "fs";
11
11
 
12
- // Domain types - sources that can be listed
13
- export type Domain =
12
+ // Source types - data sources that can be listed
13
+ export type Source =
14
14
  | "development"
15
15
  | "tasks"
16
16
  | "events"
@@ -29,7 +29,7 @@ export type Domain =
29
29
  | "teachings"
30
30
  | "sessions";
31
31
 
32
- export const DOMAINS: Domain[] = [
32
+ export const SOURCES: Source[] = [
33
33
  "development",
34
34
  "tasks",
35
35
  "events",
@@ -49,8 +49,8 @@ export const DOMAINS: Domain[] = [
49
49
  "sessions",
50
50
  ];
51
51
 
52
- // Domains that query the 'personal' source with type filter
53
- const PERSONAL_SUBTYPES: Partial<Record<Domain, string>> = {
52
+ // Sources that query the 'personal' source with type filter
53
+ const PERSONAL_SUBTYPES: Partial<Record<Source, string>> = {
54
54
  books: "book",
55
55
  movies: "movie",
56
56
  podcasts: "podcast",
@@ -80,7 +80,7 @@ export interface ListEntry {
80
80
  }
81
81
 
82
82
  export interface ListResult {
83
- domain: Domain;
83
+ source: Source;
84
84
  entries: ListEntry[];
85
85
  count: number;
86
86
  }
@@ -160,17 +160,17 @@ function queryPersonalType(
160
160
  }
161
161
 
162
162
  /**
163
- * List all entries in a domain
163
+ * List all entries in a source
164
164
  *
165
- * @param domain - The domain to list (development, tasks, blogs, etc.)
165
+ * @param source - The source to list (development, tasks, blogs, etc.)
166
166
  * @param options - Optional limit
167
167
  * @returns ListResult with entries and count
168
- * @throws Error if database doesn't exist or domain is invalid
168
+ * @throws Error if database doesn't exist or source is invalid
169
169
  */
170
- export function list(domain: Domain, options: ListOptions = {}): ListResult {
171
- if (!DOMAINS.includes(domain)) {
170
+ export function list(source: Source, options: ListOptions = {}): ListResult {
171
+ if (!SOURCES.includes(source)) {
172
172
  throw new Error(
173
- `Invalid domain: ${domain}. Valid domains: ${DOMAINS.join(", ")}`,
173
+ `Invalid source: ${source}. Valid sources: ${SOURCES.join(", ")}`,
174
174
  );
175
175
  }
176
176
 
@@ -185,16 +185,16 @@ export function list(domain: Domain, options: ListOptions = {}): ListResult {
185
185
  try {
186
186
  let entries: ListEntry[];
187
187
 
188
- // Check if this is a personal subtype domain
189
- const personalType = PERSONAL_SUBTYPES[domain];
188
+ // Check if this is a personal subtype source
189
+ const personalType = PERSONAL_SUBTYPES[source];
190
190
  if (personalType) {
191
191
  entries = queryPersonalType(db, personalType, options.limit);
192
192
  } else {
193
- entries = queryBySource(db, domain, options.limit, options.project);
193
+ entries = queryBySource(db, source, options.limit, options.project);
194
194
  }
195
195
 
196
196
  return {
197
- domain,
197
+ source,
198
198
  entries,
199
199
  count: entries.length,
200
200
  };
@@ -204,28 +204,28 @@ export function list(domain: Domain, options: ListOptions = {}): ListResult {
204
204
  }
205
205
 
206
206
  /**
207
- * Get available domains
207
+ * Get available sources
208
208
  */
209
- export function listDomains(): Domain[] {
210
- return [...DOMAINS];
209
+ export function listSources(): Source[] {
210
+ return [...SOURCES];
211
211
  }
212
212
 
213
213
  /**
214
214
  * Extract project name from entry metadata
215
215
  */
216
- function extractProjectFromEntry(entry: ListEntry, domain: string): string {
217
- const field = PROJECT_FIELD[domain];
216
+ function extractProjectFromEntry(entry: ListEntry, source: string): string {
217
+ const field = PROJECT_FIELD[source];
218
218
  if (!field) return "unknown";
219
219
  return (entry.metadata[field] as string) || "unknown";
220
220
  }
221
221
 
222
222
  /**
223
- * Extract identifier from entry based on domain type
223
+ * Extract identifier from entry based on source type
224
224
  */
225
- function extractIdentifier(entry: ListEntry, domain: string): string {
225
+ function extractIdentifier(entry: ListEntry, source: string): string {
226
226
  const metadata = entry.metadata;
227
227
 
228
- switch (domain) {
228
+ switch (source) {
229
229
  case "commits":
230
230
  return (metadata.sha as string)?.substring(0, 7) || "";
231
231
  case "sessions":
@@ -239,8 +239,8 @@ function extractIdentifier(entry: ListEntry, domain: string): string {
239
239
  * Get the best display text for an entry
240
240
  * Commits use content (commit message), others use title
241
241
  */
242
- function getDisplayText(entry: ListEntry, domain: string): string {
243
- if (domain === "commits") {
242
+ function getDisplayText(entry: ListEntry, source: string): string {
243
+ if (source === "commits") {
244
244
  return entry.content || entry.title;
245
245
  }
246
246
  return entry.title;
@@ -251,12 +251,12 @@ function getDisplayText(entry: ListEntry, domain: string): string {
251
251
  * One line per entry: " project: identifier - title"
252
252
  */
253
253
  export function formatBriefList(result: ListResult): string {
254
- const lines = [`${result.domain} (${result.count}):`];
254
+ const lines = [`${result.source} (${result.count}):`];
255
255
 
256
256
  result.entries.forEach((entry) => {
257
- const project = extractProjectFromEntry(entry, result.domain);
258
- const identifier = extractIdentifier(entry, result.domain);
259
- const displayText = getDisplayText(entry, result.domain);
257
+ const project = extractProjectFromEntry(entry, result.source);
258
+ const identifier = extractIdentifier(entry, result.source);
259
+ const displayText = getDisplayText(entry, result.source);
260
260
 
261
261
  const line = identifier
262
262
  ? ` ${project}: ${identifier} - ${displayText}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",