@rejacky/opencode-insights 0.1.4 → 0.1.5
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 +57 -43
- package/dist/{capture-DkASdFpu.d.ts → capture-B6sM1QQA.d.ts} +17 -8
- package/dist/{chunk-Q5ROKOOJ.js → chunk-PBX4AJCR.js} +141 -47
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +606 -145
- package/dist/index.d.ts +1 -2
- package/dist/index.js +64 -14
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -12,45 +12,64 @@ opencode plugin @rejacky/opencode-insights --global
|
|
|
12
12
|
|
|
13
13
|
Restart OpenCode after installing the plugin.
|
|
14
14
|
|
|
15
|
+
On startup, the plugin creates a user-local `opencode-insights` command shim in:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
~/.local/bin
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Make sure that directory is on your `PATH`, then run the CLI directly:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
opencode-insights doctor
|
|
25
|
+
```
|
|
26
|
+
|
|
15
27
|
## Uninstall
|
|
16
28
|
|
|
17
29
|
Remove this plugin from `opencode.json` / `opencode.jsonc`, remove it from `tui.json`, and delete the local Insights database files:
|
|
18
30
|
|
|
19
31
|
```bash
|
|
20
|
-
|
|
32
|
+
opencode-insights uninstall
|
|
21
33
|
```
|
|
22
34
|
|
|
23
35
|
Preview the cleanup without changing files:
|
|
24
36
|
|
|
25
37
|
```bash
|
|
26
|
-
|
|
38
|
+
opencode-insights uninstall --dry-run
|
|
27
39
|
```
|
|
28
40
|
|
|
29
41
|
Keep captured data while removing only the plugin config entries:
|
|
30
42
|
|
|
31
43
|
```bash
|
|
32
|
-
|
|
44
|
+
opencode-insights uninstall --keep-data
|
|
33
45
|
```
|
|
34
46
|
|
|
35
47
|
Use a custom OpenCode config directory or data location:
|
|
36
48
|
|
|
37
49
|
```bash
|
|
38
|
-
|
|
50
|
+
opencode-insights uninstall --config-dir ~/.config/opencode --data-dir ~/.opencode-insights
|
|
39
51
|
```
|
|
40
52
|
|
|
41
|
-
After uninstalling, restart OpenCode.
|
|
53
|
+
After uninstalling, restart OpenCode. Packages installed with `opencode plugin ... --global` are stored under OpenCode's package cache. On macOS/Linux this is typically:
|
|
42
54
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
```text
|
|
56
|
+
~/.cache/opencode/packages
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For this machine, that expands to:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
/Users/zyao/.cache/opencode/packages
|
|
46
63
|
```
|
|
47
64
|
|
|
65
|
+
The `uninstall` command removes plugin config entries and local Insights data; it does not remove cached OpenCode package directories automatically.
|
|
66
|
+
|
|
48
67
|
## What You Get
|
|
49
68
|
|
|
50
69
|
- Live TPS, average TPS, and average TTFT in the OpenCode session prompt zone.
|
|
51
70
|
- Subagent status (running, done, failed, elapsed time, and token/context usage) in the sidebar.
|
|
52
71
|
- Local capture of OpenCode hook/event data without redaction.
|
|
53
|
-
- A local web viewer for sessions,
|
|
72
|
+
- A local web viewer for reconstructed sessions, user turns, hidden request context, system/messages transforms, and assistant thinking/response sequences.
|
|
54
73
|
- Native OpenCode footer components (project directory and version) remain visible — the plugin does not override `sidebar_footer` or `home_prompt_right` slots.
|
|
55
74
|
|
|
56
75
|
## Open The Viewer
|
|
@@ -58,13 +77,13 @@ npm rm @rejacky/opencode-insights
|
|
|
58
77
|
Start the local web viewer and open it in your browser:
|
|
59
78
|
|
|
60
79
|
```bash
|
|
61
|
-
|
|
80
|
+
opencode-insights open --limit 5000 --port 8765
|
|
62
81
|
```
|
|
63
82
|
|
|
64
83
|
Or run the server only:
|
|
65
84
|
|
|
66
85
|
```bash
|
|
67
|
-
|
|
86
|
+
opencode-insights serve --limit 5000 --port 8765
|
|
68
87
|
```
|
|
69
88
|
|
|
70
89
|
Then open:
|
|
@@ -75,59 +94,66 @@ http://127.0.0.1:8765/
|
|
|
75
94
|
|
|
76
95
|
The viewer shows:
|
|
77
96
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- `Summary
|
|
81
|
-
-
|
|
97
|
+
- Project/session filters with subagent sessions nested under their parent session.
|
|
98
|
+
- User-message rows only, with each row showing visible assistant steps and hidden context count.
|
|
99
|
+
- A `Summary` view with the agent thinking/response sequence.
|
|
100
|
+
- Collapsed hidden-context previews that expand to plain text system prompt or hidden prompt-like content.
|
|
101
|
+
- A dark/light theme switcher.
|
|
82
102
|
|
|
83
103
|
## Common Commands
|
|
84
104
|
|
|
85
105
|
List recent raw captures:
|
|
86
106
|
|
|
87
107
|
```bash
|
|
88
|
-
|
|
108
|
+
opencode-insights recent --limit 20
|
|
89
109
|
```
|
|
90
110
|
|
|
91
111
|
List reconstructed sessions:
|
|
92
112
|
|
|
93
113
|
```bash
|
|
94
|
-
|
|
114
|
+
opencode-insights sessions --limit 5000
|
|
95
115
|
```
|
|
96
116
|
|
|
97
117
|
Print one reconstructed session:
|
|
98
118
|
|
|
99
119
|
```bash
|
|
100
|
-
|
|
120
|
+
opencode-insights show ses_xxx --limit 10000
|
|
101
121
|
```
|
|
102
122
|
|
|
103
123
|
Export one session to JSON:
|
|
104
124
|
|
|
105
125
|
```bash
|
|
106
|
-
|
|
126
|
+
opencode-insights export ses_xxx --limit 10000 --output ./session.json
|
|
107
127
|
```
|
|
108
128
|
|
|
109
129
|
Check DB path, table health, row counts, and SQLite readability:
|
|
110
130
|
|
|
111
131
|
```bash
|
|
112
|
-
|
|
132
|
+
opencode-insights doctor
|
|
113
133
|
```
|
|
114
134
|
|
|
115
135
|
Compact the local SQLite DB after heavy testing:
|
|
116
136
|
|
|
117
137
|
```bash
|
|
118
|
-
|
|
138
|
+
opencode-insights vacuum
|
|
119
139
|
```
|
|
120
140
|
|
|
121
141
|
Remove plugin config entries and delete local captured data:
|
|
122
142
|
|
|
123
143
|
```bash
|
|
124
|
-
|
|
144
|
+
opencode-insights uninstall
|
|
125
145
|
```
|
|
126
146
|
|
|
127
|
-
If the command is not available
|
|
147
|
+
If the command is not available, confirm `~/.local/bin` is on `PATH`, or run the installed binary directly from OpenCode's package cache:
|
|
128
148
|
|
|
129
149
|
```bash
|
|
130
|
-
|
|
150
|
+
~/.cache/opencode/packages/node_modules/.bin/opencode-insights doctor
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You can also run the published package through npm without relying on the OpenCode cache:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx -y -p @rejacky/opencode-insights opencode-insights doctor
|
|
131
157
|
```
|
|
132
158
|
|
|
133
159
|
## Storage
|
|
@@ -144,7 +170,9 @@ If SQLite is unavailable in the plugin runtime, the fallback path is:
|
|
|
144
170
|
~/.opencode-insights/insights.sqlite.jsonl
|
|
145
171
|
```
|
|
146
172
|
|
|
147
|
-
|
|
173
|
+
The database keeps one day of captures by default and auto-cleans older rows on startup and after new captures. Set `retentionDays` to another number of days, or `0` to disable auto-cleaning.
|
|
174
|
+
|
|
175
|
+
You can override storage and retention in `opencode.json` or `opencode.jsonc`:
|
|
148
176
|
|
|
149
177
|
```json
|
|
150
178
|
{
|
|
@@ -152,7 +180,8 @@ You can override storage in `opencode.json` or `opencode.jsonc`:
|
|
|
152
180
|
[
|
|
153
181
|
"@rejacky/opencode-insights",
|
|
154
182
|
{
|
|
155
|
-
"dbPath": "/absolute/path/to/insights.sqlite"
|
|
183
|
+
"dbPath": "/absolute/path/to/insights.sqlite",
|
|
184
|
+
"retentionDays": 1
|
|
156
185
|
}
|
|
157
186
|
]
|
|
158
187
|
]
|
|
@@ -165,24 +194,9 @@ This plugin intentionally does not redact anything. It stores data locally exact
|
|
|
165
194
|
|
|
166
195
|
Captured data can include prompts, system messages, provider metadata, API keys exposed inside hook payloads, tool arguments, headers, reasoning text, and response events. Use it only on machines where local full-fidelity capture is acceptable.
|
|
167
196
|
|
|
168
|
-
##
|
|
169
|
-
|
|
170
|
-
The request-interception hooks (`chat.headers`, `experimental.chat.messages.transform`, `experimental.chat.system.transform`) are disabled by default. They capture additional request context (HTTP headers, transformed messages, system prompts) but are not yet mature.
|
|
197
|
+
## Request Context Capture
|
|
171
198
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
```json
|
|
175
|
-
{
|
|
176
|
-
"plugin": [
|
|
177
|
-
[
|
|
178
|
-
"@rejacky/opencode-insights",
|
|
179
|
-
{
|
|
180
|
-
"experimental": true
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
]
|
|
184
|
-
}
|
|
185
|
-
```
|
|
199
|
+
Request-context capture is enabled by default. The plugin records OpenCode's `chat.headers`, `experimental.chat.messages.transform`, and `experimental.chat.system.transform` hooks so the viewer can show provider headers, transformed conversation messages, and system prompt content when OpenCode exposes them. The `experimental.chat.*` names are OpenCode hook names; no `experimental` plugin option is required.
|
|
186
200
|
|
|
187
201
|
## Captured Hooks
|
|
188
202
|
|
|
@@ -17,9 +17,7 @@ type CaptureStore = {
|
|
|
17
17
|
type InsightsOptions = {
|
|
18
18
|
dataDir?: unknown;
|
|
19
19
|
dbPath?: unknown;
|
|
20
|
-
|
|
21
|
-
* Disabled by default — these hooks intercept request data and are not yet mature. */
|
|
22
|
-
experimental?: boolean;
|
|
20
|
+
retentionDays?: unknown;
|
|
23
21
|
};
|
|
24
22
|
declare function defaultDataDir(): string;
|
|
25
23
|
declare function resolveCapturePath(options?: InsightsOptions): string;
|
|
@@ -32,20 +30,31 @@ declare function normalizeEventCapture(event: unknown, timestamp?: number): Capt
|
|
|
32
30
|
declare function normalizeToolCapture(kind: "tool.execute.before" | "tool.execute.after", input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
33
31
|
declare class JsonlCaptureStore implements CaptureStore {
|
|
34
32
|
private readonly path;
|
|
35
|
-
|
|
33
|
+
private readonly retentionMs;
|
|
34
|
+
constructor(path: string, retentionMs?: number | undefined);
|
|
36
35
|
initialize(): Promise<void>;
|
|
37
36
|
append(record: CaptureRecord): Promise<void>;
|
|
37
|
+
private pruneExpired;
|
|
38
38
|
}
|
|
39
|
+
interface SqliteDb {
|
|
40
|
+
all(sql: string, ...params: unknown[]): Record<string, unknown>[];
|
|
41
|
+
run(sql: string, ...params: unknown[]): void;
|
|
42
|
+
sync(): void;
|
|
43
|
+
close(): void;
|
|
44
|
+
}
|
|
45
|
+
declare function openDatabase(path: string): Promise<SqliteDb | undefined>;
|
|
39
46
|
declare class SqliteCaptureStore implements CaptureStore {
|
|
40
47
|
private readonly path;
|
|
48
|
+
private readonly retentionMs;
|
|
41
49
|
private db;
|
|
42
|
-
private
|
|
43
|
-
constructor(path: string);
|
|
50
|
+
private fallbackStore;
|
|
51
|
+
constructor(path: string, retentionMs?: number | undefined);
|
|
44
52
|
initialize(): Promise<void>;
|
|
45
53
|
append(record: CaptureRecord): Promise<void>;
|
|
46
54
|
close(): Promise<void>;
|
|
47
|
-
private
|
|
55
|
+
private pruneExpired;
|
|
48
56
|
}
|
|
49
57
|
declare function createCaptureStore(options?: InsightsOptions): CaptureStore;
|
|
58
|
+
declare function resolveRetentionDays(value: unknown): number;
|
|
50
59
|
|
|
51
|
-
export { type CaptureRecord as C, type InsightsOptions as I, JsonlCaptureStore as J, SqliteCaptureStore as S, type CaptureKind as a, type CaptureStore as b,
|
|
60
|
+
export { type CaptureRecord as C, type InsightsOptions as I, JsonlCaptureStore as J, SqliteCaptureStore as S, type CaptureKind as a, type CaptureStore as b, type SqliteDb as c, createCaptureStore as d, defaultDataDir as e, normalizeChatMessageCapture as f, normalizeChatParamsCapture as g, normalizeEventCapture as h, normalizeExperimentalChatMessagesTransformCapture as i, normalizeExperimentalChatSystemTransformCapture as j, normalizeToolCapture as k, resolveRetentionDays as l, normalizeChatHeadersCapture as n, openDatabase as o, resolveCapturePath as r };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// src/capture.ts
|
|
2
|
-
import { mkdir, appendFile, writeFile } from "fs/promises";
|
|
2
|
+
import { mkdir, appendFile, readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { existsSync } from "fs";
|
|
3
4
|
import { dirname, join } from "path";
|
|
4
5
|
import { homedir } from "os";
|
|
6
|
+
var DEFAULT_RETENTION_DAYS = 1;
|
|
7
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
5
8
|
var sequence = 0;
|
|
6
9
|
function nextID(timestamp) {
|
|
7
10
|
sequence += 1;
|
|
@@ -100,13 +103,14 @@ function normalizeChatHeadersCapture(input, output, timestamp = Date.now()) {
|
|
|
100
103
|
});
|
|
101
104
|
}
|
|
102
105
|
function normalizeExperimentalChatMessagesTransformCapture(input, output, timestamp = Date.now()) {
|
|
106
|
+
const inputRecord = isRecord(input) ? input : {};
|
|
103
107
|
const latestUserMessage = latestUserTransformedMessage(output);
|
|
104
108
|
const info = infoFromTransformedMessage(latestUserMessage);
|
|
105
109
|
return captureRecord({
|
|
106
110
|
id: nextID(timestamp),
|
|
107
111
|
kind: "experimental.chat.messages.transform",
|
|
108
112
|
timestamp,
|
|
109
|
-
sessionID: sessionIDFrom(
|
|
113
|
+
sessionID: sessionIDFrom(inputRecord),
|
|
110
114
|
messageID: messageIDFrom(info) ?? optionalString(info?.id),
|
|
111
115
|
payload: { input, output }
|
|
112
116
|
});
|
|
@@ -149,42 +153,143 @@ function normalizeToolCapture(kind, input, output, timestamp = Date.now()) {
|
|
|
149
153
|
});
|
|
150
154
|
}
|
|
151
155
|
var JsonlCaptureStore = class {
|
|
152
|
-
constructor(path) {
|
|
156
|
+
constructor(path, retentionMs = retentionMsFromDays(DEFAULT_RETENTION_DAYS)) {
|
|
153
157
|
this.path = path;
|
|
158
|
+
this.retentionMs = retentionMs;
|
|
154
159
|
}
|
|
155
160
|
path;
|
|
161
|
+
retentionMs;
|
|
156
162
|
async initialize() {
|
|
157
163
|
await mkdir(dirname(this.path), { recursive: true });
|
|
158
164
|
await writeFile(this.path, "", { flag: "a" });
|
|
165
|
+
await this.pruneExpired();
|
|
159
166
|
}
|
|
160
167
|
async append(record) {
|
|
161
168
|
await mkdir(dirname(this.path), { recursive: true });
|
|
162
169
|
await appendFile(this.path, `${JSON.stringify(record)}
|
|
163
170
|
`, "utf8");
|
|
171
|
+
await this.pruneExpired();
|
|
172
|
+
}
|
|
173
|
+
async pruneExpired(now = Date.now()) {
|
|
174
|
+
const cutoff = retentionCutoff(now, this.retentionMs);
|
|
175
|
+
if (cutoff === void 0 || !existsSync(this.path)) return;
|
|
176
|
+
try {
|
|
177
|
+
const lines = (await readFile(this.path, "utf8")).split(/\r?\n/);
|
|
178
|
+
const kept = lines.filter((line) => {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed) return false;
|
|
181
|
+
try {
|
|
182
|
+
const record = JSON.parse(trimmed);
|
|
183
|
+
return typeof record.timestamp !== "number" || record.timestamp >= cutoff;
|
|
184
|
+
} catch {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
await writeFile(this.path, kept.length ? `${kept.join("\n")}
|
|
189
|
+
` : "", "utf8");
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
164
192
|
}
|
|
165
193
|
};
|
|
194
|
+
async function openDatabase(path) {
|
|
195
|
+
try {
|
|
196
|
+
const mod = await import("bun:sqlite").catch(() => void 0);
|
|
197
|
+
if (mod) {
|
|
198
|
+
const db = new mod.Database(path);
|
|
199
|
+
return {
|
|
200
|
+
all(sql, ...params) {
|
|
201
|
+
return db.query(sql).all(...params);
|
|
202
|
+
},
|
|
203
|
+
run(sql, ...params) {
|
|
204
|
+
db.run(sql, ...params);
|
|
205
|
+
},
|
|
206
|
+
sync() {
|
|
207
|
+
},
|
|
208
|
+
close() {
|
|
209
|
+
db.close();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const mod = await import("better-sqlite3").catch(() => void 0);
|
|
217
|
+
const Database = mod?.default;
|
|
218
|
+
if (Database) {
|
|
219
|
+
const db = new Database(path);
|
|
220
|
+
return {
|
|
221
|
+
all(sql, ...params) {
|
|
222
|
+
return db.prepare(sql).all(...params);
|
|
223
|
+
},
|
|
224
|
+
run(sql, ...params) {
|
|
225
|
+
db.prepare(sql).run(...params);
|
|
226
|
+
},
|
|
227
|
+
sync() {
|
|
228
|
+
},
|
|
229
|
+
close() {
|
|
230
|
+
db.close();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
166
238
|
var SqliteCaptureStore = class {
|
|
167
|
-
constructor(path) {
|
|
239
|
+
constructor(path, retentionMs = retentionMsFromDays(DEFAULT_RETENTION_DAYS)) {
|
|
168
240
|
this.path = path;
|
|
241
|
+
this.retentionMs = retentionMs;
|
|
169
242
|
}
|
|
170
243
|
path;
|
|
244
|
+
retentionMs;
|
|
171
245
|
db;
|
|
172
|
-
|
|
246
|
+
fallbackStore;
|
|
173
247
|
async initialize() {
|
|
174
|
-
|
|
248
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
249
|
+
const db = await openDatabase(this.path);
|
|
175
250
|
if (!db) {
|
|
176
251
|
const fallbackPath = this.path.endsWith(".sqlite") ? `${this.path}.jsonl` : this.path;
|
|
177
|
-
|
|
252
|
+
this.fallbackStore = new JsonlCaptureStore(fallbackPath, this.retentionMs);
|
|
253
|
+
await this.fallbackStore.initialize();
|
|
254
|
+
return;
|
|
178
255
|
}
|
|
256
|
+
this.db = db;
|
|
257
|
+
this.db.run(
|
|
258
|
+
`create table if not exists captures (
|
|
259
|
+
id text primary key,
|
|
260
|
+
kind text not null,
|
|
261
|
+
timestamp integer not null,
|
|
262
|
+
session_id text,
|
|
263
|
+
message_id text,
|
|
264
|
+
provider_id text,
|
|
265
|
+
model_id text,
|
|
266
|
+
payload_json text not null
|
|
267
|
+
)`
|
|
268
|
+
);
|
|
269
|
+
this.db.run(`create index if not exists captures_timestamp_idx on captures(timestamp)`);
|
|
270
|
+
this.db.run(`create index if not exists captures_session_idx on captures(session_id)`);
|
|
271
|
+
this.db.run(`create index if not exists captures_kind_timestamp_idx on captures(kind, timestamp)`);
|
|
272
|
+
this.pruneExpired();
|
|
273
|
+
this.db.sync();
|
|
179
274
|
}
|
|
180
275
|
async append(record) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const fallbackPath = this.path.endsWith(".sqlite") ? `${this.path}.jsonl` : this.path;
|
|
184
|
-
await new JsonlCaptureStore(fallbackPath).append(record);
|
|
276
|
+
if (this.fallbackStore) {
|
|
277
|
+
await this.fallbackStore.append(record);
|
|
185
278
|
return;
|
|
186
279
|
}
|
|
187
|
-
db
|
|
280
|
+
if (!this.db) {
|
|
281
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
282
|
+
const db = await openDatabase(this.path);
|
|
283
|
+
if (!db) {
|
|
284
|
+
const fallbackPath = this.path.endsWith(".sqlite") ? `${this.path}.jsonl` : this.path;
|
|
285
|
+
this.fallbackStore = new JsonlCaptureStore(fallbackPath, this.retentionMs);
|
|
286
|
+
await this.fallbackStore.initialize();
|
|
287
|
+
await this.fallbackStore.append(record);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.db = db;
|
|
291
|
+
}
|
|
292
|
+
this.db.run(
|
|
188
293
|
`insert into captures (
|
|
189
294
|
id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json
|
|
190
295
|
) values (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -197,47 +302,34 @@ var SqliteCaptureStore = class {
|
|
|
197
302
|
record.modelID ?? null,
|
|
198
303
|
JSON.stringify(record.payload)
|
|
199
304
|
);
|
|
305
|
+
this.pruneExpired();
|
|
306
|
+
this.db.sync();
|
|
200
307
|
}
|
|
201
308
|
async close() {
|
|
202
|
-
this.db?.close
|
|
309
|
+
this.db?.close();
|
|
203
310
|
this.db = void 0;
|
|
311
|
+
this.fallbackStore = void 0;
|
|
204
312
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
await mkdir(dirname(this.path), { recursive: true });
|
|
210
|
-
const mod = await import("bun:sqlite").catch(() => void 0);
|
|
211
|
-
if (!mod) {
|
|
212
|
-
this.unavailable = true;
|
|
213
|
-
return void 0;
|
|
214
|
-
}
|
|
215
|
-
const db = new mod.Database(this.path);
|
|
216
|
-
initializeSchema(db);
|
|
217
|
-
this.db = db;
|
|
218
|
-
return db;
|
|
219
|
-
} catch {
|
|
220
|
-
this.unavailable = true;
|
|
221
|
-
return void 0;
|
|
222
|
-
}
|
|
313
|
+
pruneExpired(now = Date.now()) {
|
|
314
|
+
const cutoff = retentionCutoff(now, this.retentionMs);
|
|
315
|
+
if (cutoff === void 0) return;
|
|
316
|
+
this.db?.run("delete from captures where timestamp < ?", cutoff);
|
|
223
317
|
}
|
|
224
318
|
};
|
|
225
|
-
function initializeSchema(db) {
|
|
226
|
-
db.run(`create table if not exists captures (
|
|
227
|
-
id text primary key,
|
|
228
|
-
kind text not null,
|
|
229
|
-
timestamp integer not null,
|
|
230
|
-
session_id text,
|
|
231
|
-
message_id text,
|
|
232
|
-
provider_id text,
|
|
233
|
-
model_id text,
|
|
234
|
-
payload_json text not null
|
|
235
|
-
)`);
|
|
236
|
-
db.run(`create index if not exists captures_timestamp_idx on captures(timestamp)`);
|
|
237
|
-
db.run(`create index if not exists captures_session_idx on captures(session_id)`);
|
|
238
|
-
}
|
|
239
319
|
function createCaptureStore(options = {}) {
|
|
240
|
-
return new SqliteCaptureStore(resolveCapturePath(options));
|
|
320
|
+
return new SqliteCaptureStore(resolveCapturePath(options), retentionMsFromDays(resolveRetentionDays(options.retentionDays)));
|
|
321
|
+
}
|
|
322
|
+
function resolveRetentionDays(value) {
|
|
323
|
+
if (value === void 0 || value === null || value === "") return DEFAULT_RETENTION_DAYS;
|
|
324
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" ? Number.parseFloat(value) : Number.NaN;
|
|
325
|
+
if (!Number.isFinite(numeric) || numeric < 0) return DEFAULT_RETENTION_DAYS;
|
|
326
|
+
return numeric;
|
|
327
|
+
}
|
|
328
|
+
function retentionMsFromDays(days) {
|
|
329
|
+
return days <= 0 ? void 0 : days * DAY_MS;
|
|
330
|
+
}
|
|
331
|
+
function retentionCutoff(now, retentionMs) {
|
|
332
|
+
return retentionMs === void 0 ? void 0 : now - retentionMs;
|
|
241
333
|
}
|
|
242
334
|
|
|
243
335
|
export {
|
|
@@ -251,6 +343,8 @@ export {
|
|
|
251
343
|
normalizeEventCapture,
|
|
252
344
|
normalizeToolCapture,
|
|
253
345
|
JsonlCaptureStore,
|
|
346
|
+
openDatabase,
|
|
254
347
|
SqliteCaptureStore,
|
|
255
|
-
createCaptureStore
|
|
348
|
+
createCaptureStore,
|
|
349
|
+
resolveRetentionDays
|
|
256
350
|
};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as CaptureRecord } from './capture-
|
|
2
|
+
import { C as CaptureRecord } from './capture-B6sM1QQA.js';
|
|
3
3
|
|
|
4
4
|
type HistoryMessage = {
|
|
5
5
|
id: string;
|
|
@@ -22,6 +22,7 @@ type HistoryRequest = {
|
|
|
22
22
|
modelID?: string | undefined;
|
|
23
23
|
summary: string;
|
|
24
24
|
payload: Record<string, unknown>;
|
|
25
|
+
system?: HistoryRequestHeaders | undefined;
|
|
25
26
|
headers?: HistoryRequestHeaders | undefined;
|
|
26
27
|
response?: HistoryResponse | undefined;
|
|
27
28
|
};
|
|
@@ -46,8 +47,12 @@ type HistoryResponse = {
|
|
|
46
47
|
};
|
|
47
48
|
type HistorySession = {
|
|
48
49
|
id: string;
|
|
50
|
+
parentID?: string | undefined;
|
|
49
51
|
title?: string | undefined;
|
|
50
52
|
updatedAt?: number | undefined;
|
|
53
|
+
cwd?: string | undefined;
|
|
54
|
+
root?: string | undefined;
|
|
55
|
+
project?: string | undefined;
|
|
51
56
|
messages: HistoryMessage[];
|
|
52
57
|
requests: HistoryRequest[];
|
|
53
58
|
};
|
|
@@ -67,6 +72,7 @@ type CliOptions = {
|
|
|
67
72
|
port?: number | undefined;
|
|
68
73
|
output?: string | undefined;
|
|
69
74
|
configDir?: string | undefined;
|
|
75
|
+
retentionDays?: number | undefined;
|
|
70
76
|
dryRun: boolean;
|
|
71
77
|
keepData: boolean;
|
|
72
78
|
};
|