@voidwire/lore 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -35,6 +35,20 @@ lore capture task|knowledge|note # Capture knowledge
35
35
  - `--since <date>` — Filter by date (today, yesterday, this-week, YYYY-MM-DD)
36
36
  - `--sources` — List indexed sources with counts
37
37
 
38
+ ### Passthrough Sources
39
+
40
+ Some sources query external services rather than the local index:
41
+
42
+ ```bash
43
+ lore search prismis "kubernetes security" # Semantic search via prismis
44
+ ```
45
+
46
+ | Source | Description | Requires |
47
+ |--------|-------------|----------|
48
+ | `prismis` | Semantic search across saved articles | prismis-daemon running |
49
+
50
+ Passthrough sources appear in `lore search --sources` with `type: "passthrough"`.
51
+
38
52
  ### List Options
39
53
 
40
54
  - `--limit <n>` — Maximum entries
package/cli.ts CHANGED
@@ -22,6 +22,7 @@
22
22
  import {
23
23
  search,
24
24
  searchPrismis,
25
+ searchAtuin,
25
26
  listSources,
26
27
  list,
27
28
  listDomains,
@@ -73,7 +74,21 @@ function parseArgs(args: string[]): Map<string, string> {
73
74
  }
74
75
 
75
76
  function getPositionalArgs(args: string[]): string[] {
76
- return args.filter((arg) => !arg.startsWith("--"));
77
+ const result: string[] = [];
78
+ let skipNext = false;
79
+ for (const arg of args) {
80
+ if (skipNext) {
81
+ skipNext = false;
82
+ continue;
83
+ }
84
+ if (arg.startsWith("--")) {
85
+ // Skip this flag and its value (if next arg doesn't start with -)
86
+ skipNext = true;
87
+ continue;
88
+ }
89
+ result.push(arg);
90
+ }
91
+ return result;
77
92
  }
78
93
 
79
94
  function hasFlag(args: string[], flag: string): boolean {
@@ -122,9 +137,19 @@ function handleSearch(args: string[]): void {
122
137
 
123
138
  // Handle --sources flag
124
139
  if (hasFlag(args, "sources")) {
125
- const sources = listSources();
140
+ const indexed = listSources();
141
+ const passthrough = [
142
+ { source: "prismis", count: null, type: "passthrough" },
143
+ { source: "atuin", count: null, type: "passthrough" },
144
+ ];
145
+ const sources = [
146
+ ...indexed.map((s) => ({ ...s, type: "indexed" })),
147
+ ...passthrough,
148
+ ];
126
149
  output({ success: true, sources });
127
- console.error(`✅ ${sources.length} sources indexed`);
150
+ console.error(
151
+ `✅ ${indexed.length} indexed sources + ${passthrough.length} passthrough`,
152
+ );
128
153
  process.exit(0);
129
154
  }
130
155
 
@@ -168,6 +193,26 @@ function handleSearch(args: string[]): void {
168
193
  return;
169
194
  }
170
195
 
196
+ // Handle atuin passthrough
197
+ if (source === "atuin") {
198
+ try {
199
+ const results = searchAtuin(query, { limit });
200
+ output({
201
+ success: true,
202
+ results,
203
+ count: results.length,
204
+ });
205
+ console.error(
206
+ `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found`,
207
+ );
208
+ process.exit(0);
209
+ } catch (error) {
210
+ const message = error instanceof Error ? error.message : "Unknown error";
211
+ fail(message, 2);
212
+ }
213
+ return;
214
+ }
215
+
171
216
  try {
172
217
  const results = search(query, { source, limit, since });
173
218
  output({
@@ -406,6 +451,7 @@ Search Options:
406
451
 
407
452
  Passthrough Sources:
408
453
  prismis Semantic search via prismis daemon (requires prismis-daemon running)
454
+ atuin Shell history search (queries ~/.local/share/atuin/history.db directly)
409
455
 
410
456
  List Options:
411
457
  --limit <n> Maximum entries
@@ -470,12 +516,15 @@ Indexed Sources:
470
516
  Passthrough Sources:
471
517
  prismis Semantic search via prismis daemon
472
518
  (requires prismis-daemon running)
519
+ atuin Shell history search
520
+ (queries ~/.local/share/atuin/history.db directly)
473
521
 
474
522
  Examples:
475
523
  lore search "authentication"
476
524
  lore search blogs "typescript patterns"
477
525
  lore search commits --since this-week "refactor"
478
526
  lore search prismis "kubernetes security"
527
+ lore search atuin "docker build"
479
528
  `);
480
529
  process.exit(0);
481
530
  }
package/index.ts CHANGED
@@ -34,6 +34,13 @@ export {
34
34
  type PrismisSearchOptions,
35
35
  } from "./lib/prismis";
36
36
 
37
+ // Atuin integration
38
+ export {
39
+ searchAtuin,
40
+ type AtuinSearchResult,
41
+ type AtuinSearchOptions,
42
+ } from "./lib/atuin";
43
+
37
44
  // Capture
38
45
  export {
39
46
  captureKnowledge,
package/lib/atuin.ts ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Atuin shell history integration
3
+ *
4
+ * Queries Atuin SQLite database directly for shell command history.
5
+ * Filters sensitive commands containing passwords, tokens, secrets, API keys.
6
+ */
7
+
8
+ import { existsSync } from "fs";
9
+ import { join } from "path";
10
+ import { Database } from "bun:sqlite";
11
+
12
+ export interface AtuinSearchResult {
13
+ source: string;
14
+ title: string;
15
+ content: string;
16
+ metadata: string;
17
+ rank: number;
18
+ }
19
+
20
+ export interface AtuinSearchOptions {
21
+ limit?: number;
22
+ cwd?: string;
23
+ exitCode?: number;
24
+ }
25
+
26
+ const ATUIN_DB_PATH = join(
27
+ process.env.HOME ?? "",
28
+ ".local",
29
+ "share",
30
+ "atuin",
31
+ "history.db",
32
+ );
33
+
34
+ // Patterns that indicate sensitive data - exclude these commands
35
+ const SENSITIVE_PATTERNS = [
36
+ "%--password%",
37
+ "%--token%",
38
+ "%--secret%",
39
+ "%PASSWORD=%",
40
+ "%TOKEN=%",
41
+ "%SECRET=%",
42
+ "%API_KEY=%",
43
+ "%APIKEY=%",
44
+ "%_KEY=%",
45
+ "export %KEY%",
46
+ "export %TOKEN%",
47
+ "export %SECRET%",
48
+ "export %PASSWORD%",
49
+ "%X-API-Key:%",
50
+ "%Authorization:%",
51
+ "echo $%KEY%",
52
+ "echo $%TOKEN%",
53
+ "echo $%SECRET%",
54
+ ];
55
+
56
+ interface AtuinRow {
57
+ command: string;
58
+ cwd: string;
59
+ exit: number;
60
+ duration: number;
61
+ timestamp: number;
62
+ hostname: string;
63
+ }
64
+
65
+ /**
66
+ * Search Atuin shell history
67
+ */
68
+ export function searchAtuin(
69
+ query: string,
70
+ options: AtuinSearchOptions = {},
71
+ ): AtuinSearchResult[] {
72
+ if (!existsSync(ATUIN_DB_PATH)) {
73
+ throw new Error(
74
+ `Atuin database not found at ${ATUIN_DB_PATH}. ` +
75
+ "Install Atuin: https://atuin.sh",
76
+ );
77
+ }
78
+
79
+ const db = new Database(ATUIN_DB_PATH, { readonly: true });
80
+ const limit = options.limit ?? 20;
81
+
82
+ try {
83
+ // Build WHERE clause with sensitive data filtering
84
+ const conditions = [
85
+ "deleted_at IS NULL",
86
+ "command LIKE ?",
87
+ ...SENSITIVE_PATTERNS.map(() => "command NOT LIKE ?"),
88
+ ];
89
+
90
+ if (options.cwd) {
91
+ conditions.push("cwd = ?");
92
+ }
93
+
94
+ if (options.exitCode !== undefined) {
95
+ conditions.push("exit = ?");
96
+ }
97
+
98
+ const whereClause = conditions.join(" AND ");
99
+
100
+ const sql = `
101
+ SELECT command, cwd, exit, duration, timestamp, hostname
102
+ FROM history
103
+ WHERE ${whereClause}
104
+ ORDER BY timestamp DESC
105
+ LIMIT ?
106
+ `;
107
+
108
+ // Build parameters
109
+ const params: (string | number)[] = [`%${query}%`, ...SENSITIVE_PATTERNS];
110
+
111
+ if (options.cwd) {
112
+ params.push(options.cwd);
113
+ }
114
+
115
+ if (options.exitCode !== undefined) {
116
+ params.push(options.exitCode);
117
+ }
118
+
119
+ params.push(limit);
120
+
121
+ const stmt = db.prepare(sql);
122
+ const rows = stmt.all(...params) as AtuinRow[];
123
+
124
+ return rows.map((row, index) => {
125
+ // Convert nanosecond timestamp to ISO date
126
+ const timestampSec = Math.floor(row.timestamp / 1_000_000_000);
127
+ const date = new Date(timestampSec * 1000);
128
+ const dateStr = date.toISOString().split("T")[0];
129
+
130
+ // Convert duration from nanoseconds to milliseconds
131
+ const durationMs = Math.floor(row.duration / 1_000_000);
132
+
133
+ // Build title: truncate command to 80 chars
134
+ const title =
135
+ row.command.length > 80
136
+ ? `[shell] ${row.command.slice(0, 77)}...`
137
+ : `[shell] ${row.command}`;
138
+
139
+ // Normalize cwd
140
+ const cwd = row.cwd === "unknown" ? "" : row.cwd;
141
+
142
+ return {
143
+ source: "atuin",
144
+ title,
145
+ content: row.command,
146
+ metadata: JSON.stringify({
147
+ command: row.command,
148
+ cwd,
149
+ exit_code: row.exit,
150
+ duration_ms: durationMs,
151
+ date: dateStr,
152
+ hostname: row.hostname,
153
+ }),
154
+ rank: -index, // Simple ranking by recency
155
+ };
156
+ });
157
+ } finally {
158
+ db.close();
159
+ }
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -18,6 +18,9 @@
18
18
  "README.md",
19
19
  "LICENSE"
20
20
  ],
21
+ "scripts": {
22
+ "test": "bun test"
23
+ },
21
24
  "keywords": [
22
25
  "knowledge",
23
26
  "search",
@@ -42,9 +45,6 @@
42
45
  "bun": ">=1.0.0"
43
46
  },
44
47
  "devDependencies": {
45
- "bun-types": "latest"
46
- },
47
- "scripts": {
48
- "test": "bun test"
48
+ "bun-types": "1.3.5"
49
49
  }
50
- }
50
+ }