@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 +14 -0
- package/cli.ts +41 -1
- package/index.ts +7 -0
- package/lib/atuin.ts +160 -0
- package/package.json +6 -6
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
|
-
|
|
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.
|
|
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": "
|
|
46
|
-
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"test": "bun test"
|
|
48
|
+
"bun-types": "1.3.5"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|