@voidwire/lore 0.1.15 → 0.2.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
@@ -26,6 +26,7 @@ import {
26
26
  listSources,
27
27
  list,
28
28
  listDomains,
29
+ formatBriefList,
29
30
  info,
30
31
  formatInfoHuman,
31
32
  projects,
@@ -34,6 +35,7 @@ import {
34
35
  captureNote,
35
36
  captureTeaching,
36
37
  semanticSearch,
38
+ formatBriefSearch,
37
39
  hasEmbeddings,
38
40
  DOMAINS,
39
41
  type SearchResult,
@@ -81,7 +83,7 @@ function parseArgs(args: string[]): Map<string, string> {
81
83
  }
82
84
 
83
85
  // Boolean flags that don't take values
84
- const BOOLEAN_FLAGS = new Set(["help", "sources", "domains", "exact"]);
86
+ const BOOLEAN_FLAGS = new Set(["help", "sources", "domains", "exact", "brief"]);
85
87
 
86
88
  function getPositionalArgs(args: string[]): string[] {
87
89
  const result: string[] = [];
@@ -255,14 +257,21 @@ async function handleSearch(args: string[]): Promise<void> {
255
257
  fail("No embeddings found. Run lore-embed-all first.", 2);
256
258
  }
257
259
 
260
+ const brief = hasFlag(args, "brief");
261
+
258
262
  try {
259
263
  const results = await semanticSearch(query, { source, limit, project });
260
- output({
261
- success: true,
262
- results,
263
- count: results.length,
264
- mode: "semantic",
265
- });
264
+
265
+ if (brief) {
266
+ console.log(formatBriefSearch(results));
267
+ } else {
268
+ output({
269
+ success: true,
270
+ results,
271
+ count: results.length,
272
+ mode: "semantic",
273
+ });
274
+ }
266
275
  console.error(
267
276
  `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found (semantic)`,
268
277
  );
@@ -320,11 +329,14 @@ function handleList(args: string[]): void {
320
329
  : undefined;
321
330
  const format = parsed.get("format") || "json";
322
331
  const project = parsed.get("project");
332
+ const brief = hasFlag(args, "brief");
323
333
 
324
334
  try {
325
335
  const result = list(domain, { limit, project });
326
336
 
327
- if (format === "human") {
337
+ if (brief) {
338
+ console.log(formatBriefList(result));
339
+ } else if (format === "human") {
328
340
  console.log(formatHumanOutput(result));
329
341
  } else if (format === "jsonl") {
330
342
  for (const entry of result.entries) {
@@ -583,6 +595,7 @@ Search Options:
583
595
  --exact Use FTS5 text search (bypasses semantic search)
584
596
  --limit <n> Maximum results (default: 20)
585
597
  --project <name> Filter results by project
598
+ --brief Compact output (titles only)
586
599
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
587
600
  --sources List indexed sources with counts
588
601
 
@@ -593,6 +606,7 @@ Passthrough Sources:
593
606
  List Options:
594
607
  --limit <n> Maximum entries
595
608
  --format <fmt> Output format: json (default), jsonl, human
609
+ --brief Compact output (titles only)
596
610
  --domains List available domains
597
611
 
598
612
  Capture Types:
@@ -641,6 +655,7 @@ Options:
641
655
  --exact Use FTS5 text search (bypasses semantic search)
642
656
  --limit <n> Maximum results (default: 20)
643
657
  --project <name> Filter results by project (post-filters KNN results)
658
+ --brief Compact output (titles only)
644
659
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
645
660
  --sources List indexed sources with counts
646
661
  --help Show this help
@@ -688,6 +703,7 @@ Options:
688
703
  --limit <n> Maximum entries (default: all)
689
704
  --format <fmt> Output format: json (default), jsonl, human
690
705
  --project <name> Filter by project name
706
+ --brief Compact output (titles only)
691
707
  --domains List available domains
692
708
  --help Show this help
693
709
 
package/index.ts CHANGED
@@ -20,6 +20,7 @@ export {
20
20
  export {
21
21
  list,
22
22
  listDomains,
23
+ formatBriefList,
23
24
  DOMAINS,
24
25
  type Domain,
25
26
  type ListOptions,
@@ -70,6 +71,7 @@ export {
70
71
  // Semantic search
71
72
  export {
72
73
  semanticSearch,
74
+ formatBriefSearch,
73
75
  embedQuery,
74
76
  hasEmbeddings,
75
77
  type SemanticResult,
package/lib/list.ts CHANGED
@@ -209,3 +209,61 @@ export function list(domain: Domain, options: ListOptions = {}): ListResult {
209
209
  export function listDomains(): Domain[] {
210
210
  return [...DOMAINS];
211
211
  }
212
+
213
+ /**
214
+ * Extract project name from entry metadata
215
+ */
216
+ function extractProjectFromEntry(entry: ListEntry, domain: string): string {
217
+ const field = PROJECT_FIELD[domain];
218
+ if (!field) return "unknown";
219
+ return (entry.metadata[field] as string) || "unknown";
220
+ }
221
+
222
+ /**
223
+ * Extract identifier from entry based on domain type
224
+ */
225
+ function extractIdentifier(entry: ListEntry, domain: string): string {
226
+ const metadata = entry.metadata;
227
+
228
+ switch (domain) {
229
+ case "commits":
230
+ return (metadata.sha as string)?.substring(0, 7) || "";
231
+ case "sessions":
232
+ return (metadata.session_id as string)?.substring(0, 8) || "";
233
+ default:
234
+ return (metadata.id as string) || "";
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Get the best display text for an entry
240
+ * Commits use content (commit message), others use title
241
+ */
242
+ function getDisplayText(entry: ListEntry, domain: string): string {
243
+ if (domain === "commits") {
244
+ return entry.content || entry.title;
245
+ }
246
+ return entry.title;
247
+ }
248
+
249
+ /**
250
+ * Format list result as brief, compact output
251
+ * One line per entry: " project: identifier - title"
252
+ */
253
+ export function formatBriefList(result: ListResult): string {
254
+ const lines = [`${result.domain} (${result.count}):`];
255
+
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);
260
+
261
+ const line = identifier
262
+ ? ` ${project}: ${identifier} - ${displayText}`
263
+ : ` ${project}: ${displayText}`;
264
+
265
+ lines.push(line);
266
+ });
267
+
268
+ return lines.join("\n");
269
+ }
package/lib/semantic.ts CHANGED
@@ -259,3 +259,90 @@ export async function semanticSearch(
259
259
  db.close();
260
260
  }
261
261
  }
262
+
263
+ /**
264
+ * Extract project from result metadata
265
+ */
266
+ function extractProjectFromMetadata(metadata: string, source: string): string {
267
+ const field = PROJECT_FIELD[source];
268
+ if (!field) return "unknown";
269
+
270
+ try {
271
+ const parsed = JSON.parse(metadata);
272
+ return parsed[field] || "unknown";
273
+ } catch {
274
+ return "unknown";
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Extract identifier from semantic result
280
+ */
281
+ function extractIdentifierFromResult(result: SemanticResult): string {
282
+ try {
283
+ const metadata = JSON.parse(result.metadata);
284
+
285
+ switch (result.source) {
286
+ case "commits":
287
+ return metadata.sha?.substring(0, 7) || "";
288
+ case "sessions":
289
+ return metadata.session_id?.substring(0, 8) || "";
290
+ default:
291
+ return metadata.id || "";
292
+ }
293
+ } catch {
294
+ return "";
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get the best display text for a result
300
+ * Commits use content (commit message), others use title
301
+ */
302
+ function getDisplayText(result: SemanticResult): string {
303
+ if (result.source === "commits") {
304
+ return result.content || result.title;
305
+ }
306
+ return result.title;
307
+ }
308
+
309
+ /**
310
+ * Format semantic search results as brief, compact output
311
+ * Groups by source type, one line per result
312
+ */
313
+ export function formatBriefSearch(results: SemanticResult[]): string {
314
+ if (results.length === 0) {
315
+ return "(no results)";
316
+ }
317
+
318
+ // Group results by source
319
+ const grouped = new Map<string, SemanticResult[]>();
320
+ results.forEach((result) => {
321
+ const existing = grouped.get(result.source) || [];
322
+ existing.push(result);
323
+ grouped.set(result.source, existing);
324
+ });
325
+
326
+ const sections: string[] = [];
327
+
328
+ // Format each source group
329
+ grouped.forEach((sourceResults, source) => {
330
+ const lines = [`${source} (${sourceResults.length}):`];
331
+
332
+ sourceResults.forEach((r) => {
333
+ const project = extractProjectFromMetadata(r.metadata, r.source);
334
+ const identifier = extractIdentifierFromResult(r);
335
+ const displayText = getDisplayText(r);
336
+
337
+ const line = identifier
338
+ ? ` ${project}: ${identifier} - ${displayText}`
339
+ : ` ${project}: ${displayText}`;
340
+
341
+ lines.push(line);
342
+ });
343
+
344
+ sections.push(lines.join("\n"));
345
+ });
346
+
347
+ return sections.join("\n\n");
348
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.15",
3
+ "version": "0.2.0",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",