@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 +14 -0
- package/cli.ts +52 -3
- 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 {
|
|
@@ -122,9 +137,19 @@ function handleSearch(args: string[]): void {
|
|
|
122
137
|
|
|
123
138
|
// Handle --sources flag
|
|
124
139
|
if (hasFlag(args, "sources")) {
|
|
125
|
-
const
|
|
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(
|
|
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.
|
|
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
|
+
}
|