@voidwire/lore 0.1.2 → 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 {
@@ -125,6 +140,7 @@ function handleSearch(args: string[]): void {
125
140
  const indexed = listSources();
126
141
  const passthrough = [
127
142
  { source: "prismis", count: null, type: "passthrough" },
143
+ { source: "atuin", count: null, type: "passthrough" },
128
144
  ];
129
145
  const sources = [
130
146
  ...indexed.map((s) => ({ ...s, type: "indexed" })),
@@ -177,6 +193,26 @@ function handleSearch(args: string[]): void {
177
193
  return;
178
194
  }
179
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
+
180
216
  try {
181
217
  const results = search(query, { source, limit, since });
182
218
  output({
@@ -415,6 +451,7 @@ Search Options:
415
451
 
416
452
  Passthrough Sources:
417
453
  prismis Semantic search via prismis daemon (requires prismis-daemon running)
454
+ atuin Shell history search (queries ~/.local/share/atuin/history.db directly)
418
455
 
419
456
  List Options:
420
457
  --limit <n> Maximum entries
@@ -479,12 +516,15 @@ Indexed Sources:
479
516
  Passthrough Sources:
480
517
  prismis Semantic search via prismis daemon
481
518
  (requires prismis-daemon running)
519
+ atuin Shell history search
520
+ (queries ~/.local/share/atuin/history.db directly)
482
521
 
483
522
  Examples:
484
523
  lore search "authentication"
485
524
  lore search blogs "typescript patterns"
486
525
  lore search commits --since this-week "refactor"
487
526
  lore search prismis "kubernetes security"
527
+ lore search atuin "docker build"
488
528
  `);
489
529
  process.exit(0);
490
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.2",
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
+ }