@rejacky/opencode-insights 0.1.6 → 0.1.9

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.
@@ -42,7 +42,7 @@ interface SqliteDb {
42
42
  sync(): void;
43
43
  close(): void;
44
44
  }
45
- declare function openDatabase(path: string): Promise<SqliteDb | undefined>;
45
+ declare function openDatabase(path: string, readonly?: boolean): Promise<SqliteDb | undefined>;
46
46
  declare function extractEventType(payload: Record<string, unknown>): string | null;
47
47
  declare class SqliteCaptureStore implements CaptureStore {
48
48
  private readonly path;
@@ -229,6 +229,14 @@ function getSubagentSidebarModel(state, parentID, options = {}) {
229
229
  }))
230
230
  };
231
231
  }
232
+ function getSubagentSidebarRowAtLine(model, line) {
233
+ let rowStart = 2;
234
+ for (const [index, row] of model.rows.entries()) {
235
+ if (index > 0) rowStart += 1;
236
+ if (line === rowStart || line === rowStart + 1) return row;
237
+ rowStart += 2;
238
+ }
239
+ }
232
240
  function renderSubagentSidebar(state, parentID, options = {}) {
233
241
  const model = getSubagentSidebarModel(state, parentID, options);
234
242
  if (!model) return "";
@@ -450,6 +458,7 @@ export {
450
458
  getSubagentItems,
451
459
  pruneStaleSubagents,
452
460
  getSubagentSidebarModel,
461
+ getSubagentSidebarRowAtLine,
453
462
  renderSubagentSidebar,
454
463
  renderSubagentFooter
455
464
  };
@@ -1,6 +1,6 @@
1
1
  // src/capture.ts
2
2
  import { mkdir, appendFile, readFile, writeFile } from "fs/promises";
3
- import { existsSync } from "fs";
3
+ import { existsSync, readFileSync } from "fs";
4
4
  import { dirname, join } from "path";
5
5
  import { homedir } from "os";
6
6
  var DEFAULT_RETENTION_DAYS = 1;
@@ -191,7 +191,7 @@ var JsonlCaptureStore = class {
191
191
  }
192
192
  }
193
193
  };
194
- async function openDatabase(path) {
194
+ async function openDatabase(path, readonly = false) {
195
195
  try {
196
196
  const mod = await import("bun:sqlite").catch(() => void 0);
197
197
  if (mod) {
@@ -233,6 +233,34 @@ async function openDatabase(path) {
233
233
  }
234
234
  } catch {
235
235
  }
236
+ if (readonly) {
237
+ try {
238
+ const initSqlJs = await import("sql.js").catch(() => void 0);
239
+ if (initSqlJs) {
240
+ const SQL = await initSqlJs.default();
241
+ const data = readFileSync(path);
242
+ const db = new SQL.Database(data);
243
+ return {
244
+ all(sql, ...params) {
245
+ const stmt = db.prepare(sql);
246
+ if (params.length > 0) stmt.bind(params);
247
+ const rows = [];
248
+ while (stmt.step()) rows.push(stmt.getAsObject());
249
+ stmt.free();
250
+ return rows;
251
+ },
252
+ run() {
253
+ },
254
+ sync() {
255
+ },
256
+ close() {
257
+ db.close();
258
+ }
259
+ };
260
+ }
261
+ } catch {
262
+ }
263
+ }
236
264
  return void 0;
237
265
  }
238
266
  function extractEventType(payload) {
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as CaptureRecord } from './capture-Dy799i3Z.js';
2
+ import { C as CaptureRecord } from './capture-BMWWI5GR.js';
3
3
 
4
4
  type HistoryMessage = {
5
5
  id: string;
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  openDatabase,
4
4
  resolveCapturePath
5
- } from "./chunk-XCHFXQIL.js";
5
+ } from "./chunk-FGTKNB7T.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { execFile as execFile2 } from "child_process";
@@ -62,7 +62,7 @@ async function readCaptureRecord(id, options = {}) {
62
62
  const dbPath = resolveCapturePath(options);
63
63
  if (!existsSync(dbPath)) return void 0;
64
64
  try {
65
- const db = await openDatabase(dbPath);
65
+ const db = await openDatabase(dbPath, true);
66
66
  if (db) {
67
67
  try {
68
68
  const rows = db.all("select id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json from captures where id = ?", id);
@@ -269,7 +269,7 @@ function ensureEventTypeColumn(db) {
269
269
  async function readSqliteCaptures(path, limit) {
270
270
  if (!existsSync(path)) return void 0;
271
271
  try {
272
- const db = await openDatabase(path);
272
+ const db = await openDatabase(path, true);
273
273
  if (!db) return readSqliteCapturesWithCli(path, limit);
274
274
  try {
275
275
  ensureEventTypeColumn(db);
@@ -303,7 +303,7 @@ async function readSqliteCapturesWithCli(path, limit) {
303
303
  async function readSqliteViewerCaptures(path, limit) {
304
304
  if (!existsSync(path)) return void 0;
305
305
  try {
306
- const db = await openDatabase(path);
306
+ const db = await openDatabase(path, true);
307
307
  if (!db) return readSqliteViewerCapturesWithCli(path, limit);
308
308
  try {
309
309
  ensureEventTypeColumn(db);
@@ -355,10 +355,9 @@ function recentCaptureSql(limit) {
355
355
  order by timestamp desc`;
356
356
  }
357
357
  function viewerCaptureSql(limit) {
358
- return `select id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json
359
- from captures
360
- where id in (
361
- select id from captures
358
+ return `with recent_model as (
359
+ select id, session_id
360
+ from captures
362
361
  where kind in (
363
362
  'chat.params',
364
363
  'chat.message',
@@ -366,9 +365,10 @@ function viewerCaptureSql(limit) {
366
365
  )
367
366
  order by timestamp desc
368
367
  limit ${limit}
369
- )
370
- or id in (
371
- select id from captures
368
+ ),
369
+ recent_events as (
370
+ select id, session_id, payload_json
371
+ from captures
372
372
  where kind = 'event'
373
373
  and event_type in (
374
374
  'message.updated',
@@ -379,7 +379,36 @@ function viewerCaptureSql(limit) {
379
379
  )
380
380
  order by timestamp desc
381
381
  limit ${limit}
382
+ ),
383
+ recent_sessions as (
384
+ select session_id from recent_model where session_id is not null
385
+ union
386
+ select session_id from recent_events where session_id is not null
387
+ union
388
+ select json_extract(payload_json, '$.event.properties.sessionID') from recent_events where json_extract(payload_json, '$.event.properties.sessionID') is not null
389
+ union
390
+ select json_extract(payload_json, '$.event.properties.info.sessionID') from recent_events where json_extract(payload_json, '$.event.properties.info.sessionID') is not null
391
+ ),
392
+ metadata_events as (
393
+ select id
394
+ from captures
395
+ where kind = 'event'
396
+ and event_type in ('message.updated', 'session.updated', 'session.created')
397
+ and coalesce(
398
+ session_id,
399
+ json_extract(payload_json, '$.event.properties.sessionID'),
400
+ json_extract(payload_json, '$.event.properties.info.sessionID')
401
+ ) in (select session_id from recent_sessions)
402
+ and (
403
+ json_extract(payload_json, '$.event.properties.info.path.cwd') is not null
404
+ or json_extract(payload_json, '$.event.properties.info.path.root') is not null
405
+ )
382
406
  )
407
+ select id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json
408
+ from captures
409
+ where id in (select id from recent_model)
410
+ or id in (select id from recent_events)
411
+ or id in (select id from metadata_events)
383
412
  order by timestamp desc`;
384
413
  }
385
414
  function isViewerCaptureKind(kind) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from '@opencode-ai/plugin';
2
2
  import { TuiPlugin } from '@opencode-ai/plugin/tui';
3
- export { a as CaptureKind, C as CaptureRecord, b as CaptureStore, I as InsightsOptions, J as JsonlCaptureStore, S as SqliteCaptureStore, c as SqliteDb, d as createCaptureStore, e as defaultDataDir, f as extractEventType, n as normalizeChatHeadersCapture, g as normalizeChatMessageCapture, h as normalizeChatParamsCapture, i as normalizeEventCapture, j as normalizeExperimentalChatMessagesTransformCapture, k as normalizeExperimentalChatSystemTransformCapture, l as normalizeToolCapture, o as openDatabase, r as resolveCapturePath, m as resolveRetentionDays } from './capture-Dy799i3Z.js';
3
+ export { a as CaptureKind, C as CaptureRecord, b as CaptureStore, I as InsightsOptions, J as JsonlCaptureStore, S as SqliteCaptureStore, c as SqliteDb, d as createCaptureStore, e as defaultDataDir, f as extractEventType, n as normalizeChatHeadersCapture, g as normalizeChatMessageCapture, h as normalizeChatParamsCapture, i as normalizeEventCapture, j as normalizeExperimentalChatMessagesTransformCapture, k as normalizeExperimentalChatSystemTransformCapture, l as normalizeToolCapture, o as openDatabase, r as resolveCapturePath, m as resolveRetentionDays } from './capture-BMWWI5GR.js';
4
4
 
5
5
  type StreamSample = {
6
6
  at: number;
@@ -94,6 +94,7 @@ declare function getSubagentSidebarModel(state: SubagentState, parentID: string,
94
94
  now?: number;
95
95
  staleMs?: number;
96
96
  }): SubagentSidebarModel | undefined;
97
+ declare function getSubagentSidebarRowAtLine(model: SubagentSidebarModel, line: number): SubagentSidebarRow | undefined;
97
98
  declare function renderSubagentSidebar(state: SubagentState, parentID: string, options?: {
98
99
  now?: number;
99
100
  }): string;
@@ -111,4 +112,4 @@ declare const _default: {
111
112
  server: Plugin;
112
113
  };
113
114
 
114
- export { type MessageTiming, type MetricsState, OpenCodeInsights, type SessionAverage, type StreamSample, type SubagentInfo, type SubagentSidebarModel, type SubagentSidebarRow, type SubagentState, type SubagentStatus, applySubagentEvent, createMetricsState, createSubagentState, _default as default, estimateStreamTokens, getSubagentItems, getSubagentSidebarModel, id, pruneStaleSubagents, recordAssistantDelta, recordAssistantMessage, recordToolActivity, renderMetricsText, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, rootTui as tui };
115
+ export { type MessageTiming, type MetricsState, OpenCodeInsights, type SessionAverage, type StreamSample, type SubagentInfo, type SubagentSidebarModel, type SubagentSidebarRow, type SubagentState, type SubagentStatus, applySubagentEvent, createMetricsState, createSubagentState, _default as default, estimateStreamTokens, getSubagentItems, getSubagentSidebarModel, getSubagentSidebarRowAtLine, id, pruneStaleSubagents, recordAssistantDelta, recordAssistantMessage, recordToolActivity, renderMetricsText, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, rootTui as tui };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  estimateStreamTokens,
6
6
  getSubagentItems,
7
7
  getSubagentSidebarModel,
8
+ getSubagentSidebarRowAtLine,
8
9
  pruneStaleSubagents,
9
10
  recordAssistantDelta,
10
11
  recordAssistantMessage,
@@ -13,7 +14,7 @@ import {
13
14
  renderSubagentFooter,
14
15
  renderSubagentSidebar,
15
16
  renderSubagentStatus
16
- } from "./chunk-36ZNCLI3.js";
17
+ } from "./chunk-7M32TU5P.js";
17
18
  import {
18
19
  JsonlCaptureStore,
19
20
  SqliteCaptureStore,
@@ -30,7 +31,7 @@ import {
30
31
  openDatabase,
31
32
  resolveCapturePath,
32
33
  resolveRetentionDays
33
- } from "./chunk-XCHFXQIL.js";
34
+ } from "./chunk-FGTKNB7T.js";
34
35
 
35
36
  // src/cli-shim.ts
36
37
  import { existsSync } from "fs";
@@ -145,6 +146,7 @@ export {
145
146
  extractEventType,
146
147
  getSubagentItems,
147
148
  getSubagentSidebarModel,
149
+ getSubagentSidebarRowAtLine,
148
150
  id,
149
151
  normalizeChatHeadersCapture,
150
152
  normalizeChatMessageCapture,
package/dist/tui.js CHANGED
@@ -3,11 +3,12 @@ import {
3
3
  createMetricsState,
4
4
  createSubagentState,
5
5
  getSubagentSidebarModel,
6
+ getSubagentSidebarRowAtLine,
6
7
  recordAssistantDelta,
7
8
  recordAssistantMessage,
8
9
  recordToolActivity,
9
10
  renderMetricsText
10
- } from "./chunk-36ZNCLI3.js";
11
+ } from "./chunk-7M32TU5P.js";
11
12
 
12
13
  // src/tui.tsx
13
14
  import { createTextAttributes, StyledText } from "@opentui/core";
@@ -43,17 +44,41 @@ function PromptRight(props) {
43
44
  function SubagentSidebar(props) {
44
45
  let text;
45
46
  const [collapsed, setCollapsed] = createSignal(false);
47
+ const [hoveredRowID, setHoveredRowID] = createSignal();
46
48
  const titleAttributes = createTextAttributes({ bold: true });
47
- const toggle = () => {
49
+ const toggle = (event) => {
50
+ if (!text || event.y !== text.y) return;
48
51
  setCollapsed((prev) => !prev);
49
52
  props.api.renderer.requestRender();
50
53
  };
54
+ const openSubagent = (event) => {
55
+ if (!text || collapsed()) return;
56
+ const model = getSubagentSidebarModel(props.state, props.sessionID);
57
+ if (!model) return;
58
+ const row = getSubagentSidebarRowAtLine(model, event.y - text.y);
59
+ if (!row) return;
60
+ props.api.route.navigate("session", { sessionID: row.id });
61
+ };
62
+ const hoverSubagent = (event) => {
63
+ if (!text || collapsed()) return;
64
+ const model = getSubagentSidebarModel(props.state, props.sessionID);
65
+ const row = model && getSubagentSidebarRowAtLine(model, event.y - text.y);
66
+ const nextRowID = row?.id;
67
+ if (nextRowID === hoveredRowID()) return;
68
+ setHoveredRowID(nextRowID);
69
+ sync();
70
+ };
71
+ const clearHoveredSubagent = () => {
72
+ if (!hoveredRowID()) return;
73
+ setHoveredRowID(void 0);
74
+ sync();
75
+ };
51
76
  const sync = () => {
52
77
  if (!text) return;
53
78
  const model = getSubagentSidebarModel(props.state, props.sessionID);
54
79
  text.visible = !!model;
55
80
  text.height = model ? "auto" : 0;
56
- text.content = model ? renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes, collapsed()) : "";
81
+ text.content = model ? renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes, collapsed(), hoveredRowID()) : "";
57
82
  props.api.renderer.requestRender();
58
83
  };
59
84
  const unsubscribe = props.subscribe(sync);
@@ -70,12 +95,15 @@ function SubagentSidebar(props) {
70
95
  sync();
71
96
  },
72
97
  onMouseDown: toggle,
98
+ onMouseUp: openSubagent,
99
+ onMouseMove: hoverSubagent,
100
+ onMouseOut: clearHoveredSubagent,
73
101
  fg: props.api.theme.current.textMuted,
74
102
  children: ""
75
103
  }
76
104
  );
77
105
  }
78
- function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, collapsed) {
106
+ function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, collapsed, hoveredRowID) {
79
107
  const model = getSubagentSidebarModel(state, sessionID);
80
108
  if (!model) return "";
81
109
  const indicator = collapsed ? "\u25B6 " : "\u25BC ";
@@ -89,20 +117,22 @@ function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, col
89
117
  for (const [index, row] of model.rows.entries()) {
90
118
  if (index > 0) chunks.push(textChunk("\n"));
91
119
  const dotColor = row.status === "running" ? api.theme.current.success : row.status === "error" ? api.theme.current.error : api.theme.current.textMuted;
92
- chunks.push(textChunk("\u2022 ", dotColor));
120
+ const background = row.id === hoveredRowID ? api.theme.current.backgroundElement : void 0;
121
+ chunks.push(textChunk("\u2022 ", dotColor, void 0, background));
93
122
  chunks.push(textChunk(`${row.title}
94
- `, api.theme.current.text));
95
- chunks.push(textChunk(row.subtitle, api.theme.current.textMuted));
123
+ `, api.theme.current.text, void 0, background));
124
+ chunks.push(textChunk(row.subtitle, api.theme.current.textMuted, void 0, background));
96
125
  }
97
126
  }
98
127
  return new StyledText(chunks);
99
128
  }
100
- function textChunk(text, fg, attributes) {
129
+ function textChunk(text, fg, attributes, bg) {
101
130
  return {
102
131
  __isChunk: true,
103
132
  text,
104
133
  ...fg === void 0 ? {} : { fg },
105
- ...attributes === void 0 ? {} : { attributes }
134
+ ...attributes === void 0 ? {} : { attributes },
135
+ ...bg === void 0 ? {} : { bg }
106
136
  };
107
137
  }
108
138
  var tui = async (api) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@rejacky/opencode-insights",
4
- "version": "0.1.6",
4
+ "version": "0.1.9",
5
5
  "description": "OpenCode plugin for local request capture, TPS metrics, and subagent status visibility.",
6
6
  "type": "module",
7
7
  "author": "opencode-insights contributors",
@@ -47,6 +47,7 @@
47
47
  "scripts": {
48
48
  "build": "tsup",
49
49
  "debug": "npm run build && node dist/cli.js debug",
50
+ "postinstall": "npm rebuild better-sqlite3",
50
51
  "test": "vitest run",
51
52
  "typecheck": "tsc --noEmit",
52
53
  "verify": "npm run typecheck && npm run test && npm run build",