@rejacky/opencode-insights 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 CHANGED
@@ -4,49 +4,13 @@ Local OpenCode observability for live TPS, subagent status, and full-fidelity re
4
4
 
5
5
  ## Install
6
6
 
7
- Install from your OpenCode config/package directory:
7
+ Install globally with OpenCode's plugin manager:
8
8
 
9
9
  ```bash
10
- cd ~/.config/opencode
11
- npm i @rejacky/opencode-insights
12
- npx opencode-insights configure
13
- ```
14
-
15
- Restart OpenCode after configuring the plugin.
16
-
17
- `opencode-insights configure` updates two config files because OpenCode loads server plugins and TUI plugins separately:
18
-
19
- ```json
20
- // ~/.config/opencode/opencode.json or opencode.jsonc
21
- {
22
- "plugin": ["@rejacky/opencode-insights"]
23
- }
10
+ opencode plugin @rejacky/opencode-insights --global
24
11
  ```
25
12
 
26
- ```json
27
- // ~/.config/opencode/tui.json
28
- {
29
- "plugin": ["@rejacky/opencode-insights/tui"]
30
- }
31
- ```
32
-
33
- Preview config changes without writing files:
34
-
35
- ```bash
36
- npx opencode-insights configure --dry-run
37
- ```
38
-
39
- OpenCode 1.17.14 loads `tui.json` for TUI plugins. It does not load `tui.jsonc`, so `configure` always writes the TUI plugin entry to `tui.json`.
40
-
41
- The TUI plugin value is the package export `@rejacky/opencode-insights/tui`, not an absolute `dist/tui.js` path.
42
-
43
- OpenCode resolves TUI plugin entries through module resolution, so this works after installing the package in your OpenCode config/package directory.
44
-
45
- Use a custom OpenCode config directory:
46
-
47
- ```bash
48
- npx opencode-insights configure --config-dir ~/.config/opencode
49
- ```
13
+ Restart OpenCode after installing the plugin.
50
14
 
51
15
  ## Uninstall
52
16
 
@@ -299,10 +299,9 @@ function extractSubagent(event) {
299
299
  const completedMs = numberFromPath(info.time, "completed");
300
300
  const explicitUpdatedMs = numberFromPath(info.time, "updated");
301
301
  const updatedMs = completedMs ?? explicitUpdatedMs ?? startedMs;
302
- const infoStatus = asString(info.status);
303
- const hasError = info.error !== void 0 || infoStatus === "error";
304
- const status = hasError ? "error" : typeof completedMs === "number" || infoStatus === "idle" ? "done" : "running";
305
- const terminalMs = typeof completedMs === "number" || infoStatus === "idle" ? updatedMs : status === "error" && typeof explicitUpdatedMs === "number" ? explicitUpdatedMs : void 0;
302
+ const hasError = info.error !== void 0 || asString(info.status) === "error";
303
+ const status = hasError ? "error" : typeof completedMs === "number" ? "done" : "running";
304
+ const terminalMs = typeof completedMs === "number" ? updatedMs : status === "error" && typeof explicitUpdatedMs === "number" ? explicitUpdatedMs : void 0;
306
305
  const endedAt = typeof terminalMs === "number" ? new Date(terminalMs).toISOString() : void 0;
307
306
  return {
308
307
  id,
@@ -324,11 +323,8 @@ function updateExistingSubagent(state, event) {
324
323
  const previous = state.children[sessionID];
325
324
  if (!previous) return void 0;
326
325
  const info = isRecord(evt.properties?.info) ? evt.properties.info : void 0;
327
- const completedMs = numberFromPath(info?.time, "completed");
328
- const messageCompleted = evt.type === "message.updated" && asString(info?.role) === "assistant" && typeof completedMs === "number";
329
- const messageFailed = messageCompleted && info?.error !== void 0;
330
- const status = messageFailed ? "error" : messageCompleted ? "done" : statusFromEvent(event) ?? previous.status;
331
- const timestamp = messageCompleted ? new Date(completedMs).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
326
+ const status = statusFromEvent(event) ?? previous.status;
327
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
332
328
  const done = status === "done" || status === "error";
333
329
  return {
334
330
  ...previous,
@@ -342,11 +338,9 @@ function statusFromEvent(event) {
342
338
  if (!isRecord(event)) return void 0;
343
339
  const evt = event;
344
340
  if (evt.type === "session.error") return "error";
345
- if (evt.type === "session.idle") return "done";
346
341
  if (evt.type === "session.status" && isRecord(evt.properties?.status)) {
347
342
  const statusType = asString(evt.properties.status.type);
348
343
  if (statusType === "busy" || statusType === "running") return "running";
349
- if (statusType === "idle") return "done";
350
344
  if (statusType === "error") return "error";
351
345
  }
352
346
  return void 0;
@@ -459,4 +453,3 @@ export {
459
453
  renderSubagentSidebar,
460
454
  renderSubagentFooter
461
455
  };
462
- //# sourceMappingURL=chunk-WF6BN2SB.js.map
@@ -254,4 +254,3 @@ export {
254
254
  SqliteCaptureStore,
255
255
  createCaptureStore
256
256
  };
257
- //# sourceMappingURL=chunk-G7E4J6OW.js.map
package/dist/cli.d.ts CHANGED
@@ -81,7 +81,7 @@ declare function summarizeSessions(sessions: ReturnType<typeof buildRequestHisto
81
81
  }[];
82
82
  declare function formatSessionSummary(rows: ReturnType<typeof summarizeSessions>): string;
83
83
  type JsonObject = Record<string, unknown>;
84
- declare function configureOpenCode(options: CliOptions): Promise<string>;
84
+ declare function configureOpenCodeDebug(options: CliOptions): Promise<string>;
85
85
  declare function defaultOpenCodeConfigDir(): string;
86
86
  declare function resolveOpenCodeConfigPath(configDir: string): string;
87
87
  declare function stripJsonCommentsAndTrailingCommas(input: string): string;
@@ -89,4 +89,4 @@ declare function addUniquePlugin(config: JsonObject, plugin: string): boolean;
89
89
  declare function removePlugin(config: JsonObject, plugin: string): boolean;
90
90
  declare function uninstallOpenCode(options: CliOptions): Promise<string>;
91
91
 
92
- export { addUniquePlugin, configureOpenCode, defaultOpenCodeConfigDir, formatSessionSummary, parseOptions, removePlugin, resolveOpenCodeConfigPath, stripJsonCommentsAndTrailingCommas, summarizeSessions, uninstallOpenCode };
92
+ export { addUniquePlugin, configureOpenCodeDebug, defaultOpenCodeConfigDir, formatSessionSummary, parseOptions, removePlugin, resolveOpenCodeConfigPath, stripJsonCommentsAndTrailingCommas, summarizeSessions, uninstallOpenCode };
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveCapturePath
4
- } from "./chunk-G7E4J6OW.js";
4
+ } from "./chunk-Q5ROKOOJ.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { execFile as execFile2 } from "child_process";
8
8
  import { existsSync as existsSync2, realpathSync } from "fs";
9
9
  import { mkdir, readFile as readFile2, rm, writeFile } from "fs/promises";
10
- import { dirname, join } from "path";
10
+ import { dirname, join, resolve } from "path";
11
11
  import { homedir } from "os";
12
12
  import { pathToFileURL } from "url";
13
13
  import { promisify as promisify2 } from "util";
@@ -398,16 +398,16 @@ async function serveViewer(options = {}) {
398
398
  response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
399
399
  response.end("Not found");
400
400
  });
401
- await new Promise((resolve, reject) => {
401
+ await new Promise((resolve2, reject) => {
402
402
  server.once("error", reject);
403
403
  server.listen(port, host, () => {
404
404
  server.off("error", reject);
405
- resolve();
405
+ resolve2();
406
406
  });
407
407
  });
408
408
  return {
409
409
  url: `http://${host}:${port}`,
410
- close: () => new Promise((resolve) => server.close(() => resolve()))
410
+ close: () => new Promise((resolve2) => server.close(() => resolve2()))
411
411
  };
412
412
  }
413
413
  async function readHistory(options = {}) {
@@ -954,7 +954,8 @@ var execFileAsync2 = promisify2(execFile2);
954
954
  var DEFAULT_RECENT_LIMIT = 20;
955
955
  var DEFAULT_HISTORY_LIMIT = 5e3;
956
956
  var SERVER_PLUGIN_SPEC = "@rejacky/opencode-insights";
957
- var TUI_PLUGIN_SPEC = "@rejacky/opencode-insights/tui";
957
+ var TUI_PLUGIN_SPEC = SERVER_PLUGIN_SPEC;
958
+ var SUBPATH_TUI_PLUGIN_SPEC = "@rejacky/opencode-insights/tui";
958
959
  async function main(argv) {
959
960
  const command = argv[2] ?? "recent";
960
961
  const options = parseOptions(argv.slice(3));
@@ -1034,8 +1035,8 @@ async function main(argv) {
1034
1035
  `);
1035
1036
  return;
1036
1037
  }
1037
- if (command === "configure") {
1038
- process.stdout.write(`${await configureOpenCode(options)}
1038
+ if (command === "debug") {
1039
+ process.stdout.write(`${await configureOpenCodeDebug(options)}
1039
1040
  `);
1040
1041
  return;
1041
1042
  }
@@ -1222,28 +1223,36 @@ async function vacuumDatabase(options) {
1222
1223
  await execFileAsync2("sqlite3", [dbPath, "vacuum;"]);
1223
1224
  return `Vacuumed ${dbPath}`;
1224
1225
  }
1225
- async function configureOpenCode(options) {
1226
+ async function configureOpenCodeDebug(options) {
1226
1227
  const configDir = options.configDir ?? defaultOpenCodeConfigDir();
1227
1228
  const opencodePath = resolveOpenCodeConfigPath(configDir);
1228
1229
  const tuiPath = join(configDir, "tui.json");
1230
+ const localServerEntry = resolve("dist/index.js");
1231
+ const localTuiEntry = resolve("dist/tui.js");
1232
+ if (!existsSync2(localServerEntry) || !existsSync2(localTuiEntry)) {
1233
+ throw new Error("Missing dist output. Run npm run build before opencode-insights debug.");
1234
+ }
1229
1235
  const opencodeConfig = await readJsonConfig(opencodePath, { plugin: [] });
1230
1236
  const tuiConfig = await readJsonConfig(tuiPath, { plugin: [] });
1231
- const opencodeChanged = addUniquePlugin(opencodeConfig, SERVER_PLUGIN_SPEC);
1232
- const tuiChanged = addUniquePlugin(tuiConfig, TUI_PLUGIN_SPEC);
1237
+ setSinglePluginSpec(opencodeConfig, SERVER_PLUGIN_SPEC, localServerEntry);
1238
+ setSinglePluginSpec(tuiConfig, TUI_PLUGIN_SPEC, localTuiEntry);
1239
+ removePlugin(tuiConfig, SUBPATH_TUI_PLUGIN_SPEC);
1233
1240
  const lines = [
1234
1241
  `OpenCode config: ${opencodePath}`,
1235
1242
  `TUI config: ${tuiPath}`,
1236
- `Server plugin: ${opencodeChanged ? "added" : "already present"} (${SERVER_PLUGIN_SPEC})`,
1237
- `TUI plugin: ${tuiChanged ? "added" : "already present"} (${TUI_PLUGIN_SPEC})`
1243
+ `Local server plugin: ${localServerEntry}`,
1244
+ `Local TUI plugin: ${localTuiEntry}`,
1245
+ `Server plugin: set local build output`,
1246
+ `TUI plugin: set local build output`
1238
1247
  ];
1239
1248
  if (options.dryRun) {
1240
1249
  lines.push("Dry run: no files written.");
1241
1250
  return lines.join("\n");
1242
1251
  }
1243
1252
  await mkdir(configDir, { recursive: true });
1244
- if (opencodeChanged || !existsSync2(opencodePath)) await writeJsonConfig(opencodePath, opencodeConfig);
1245
- if (tuiChanged || !existsSync2(tuiPath)) await writeJsonConfig(tuiPath, tuiConfig);
1246
- lines.push("Configuration written. Restart OpenCode to load the plugin.");
1253
+ await writeJsonConfig(opencodePath, opencodeConfig);
1254
+ await writeJsonConfig(tuiPath, tuiConfig);
1255
+ lines.push("Debug configuration written. Restart OpenCode to load the local build.");
1247
1256
  return lines.join("\n");
1248
1257
  }
1249
1258
  function defaultOpenCodeConfigDir() {
@@ -1332,6 +1341,19 @@ function removePlugin(config, plugin) {
1332
1341
  function isPluginEntry(entry, plugin) {
1333
1342
  return entry === plugin || Array.isArray(entry) && entry[0] === plugin;
1334
1343
  }
1344
+ function setSinglePluginSpec(config, previousPlugin, nextPlugin) {
1345
+ const current = Array.isArray(config.plugin) ? config.plugin : [];
1346
+ const next = current.filter((entry) => !isInsightsPluginEntry(entry, previousPlugin, nextPlugin));
1347
+ config.plugin = [...next, nextPlugin];
1348
+ }
1349
+ function isInsightsPluginEntry(entry, packagePlugin, localPlugin) {
1350
+ if (isPluginEntry(entry, packagePlugin) || isPluginEntry(entry, localPlugin) || isPluginEntry(entry, SUBPATH_TUI_PLUGIN_SPEC)) {
1351
+ return true;
1352
+ }
1353
+ const spec = Array.isArray(entry) ? entry[0] : entry;
1354
+ if (typeof spec !== "string") return false;
1355
+ return /(?:^|[/@-])opencode-insights.*\.tgz$/u.test(spec) || /\/opencode-insights\/dist\/(?:index|tui)\.js$/u.test(spec);
1356
+ }
1335
1357
  async function uninstallOpenCode(options) {
1336
1358
  const configDir = options.configDir ?? defaultOpenCodeConfigDir();
1337
1359
  const opencodePath = resolveOpenCodeConfigPath(configDir);
@@ -1346,8 +1368,10 @@ async function uninstallOpenCode(options) {
1346
1368
  ];
1347
1369
  const opencodeResult = await removePluginFromConfig(opencodePath, SERVER_PLUGIN_SPEC, options);
1348
1370
  const tuiResult = await removePluginFromConfig(tuiPath, TUI_PLUGIN_SPEC, options);
1371
+ const subpathTuiResult = await removePluginFromConfig(tuiPath, SUBPATH_TUI_PLUGIN_SPEC, options);
1349
1372
  lines.push(`Server plugin: ${opencodeResult}`);
1350
1373
  lines.push(`TUI plugin: ${tuiResult}`);
1374
+ lines.push(`Subpath TUI plugin: ${subpathTuiResult}`);
1351
1375
  if (options.keepData) {
1352
1376
  lines.push("Data cleanup: skipped (--keep-data).");
1353
1377
  } else {
@@ -1387,7 +1411,7 @@ async function writeJsonConfig(path, config) {
1387
1411
  function usage() {
1388
1412
  return [
1389
1413
  "Usage:",
1390
- " opencode-insights configure [--config-dir DIR] [--dry-run]",
1414
+ " opencode-insights debug [--config-dir DIR] [--dry-run]",
1391
1415
  " opencode-insights uninstall [--config-dir DIR] [--db PATH] [--data-dir DIR] [--keep-data] [--dry-run]",
1392
1416
  " opencode-insights recent [--db PATH] [--data-dir DIR] [--limit N] [--json]",
1393
1417
  " opencode-insights sessions [--db PATH] [--data-dir DIR] [--limit N] [--json]",
@@ -1418,7 +1442,7 @@ if (isDirectRun()) {
1418
1442
  }
1419
1443
  export {
1420
1444
  addUniquePlugin,
1421
- configureOpenCode,
1445
+ configureOpenCodeDebug,
1422
1446
  defaultOpenCodeConfigDir,
1423
1447
  formatSessionSummary,
1424
1448
  parseOptions,
@@ -1428,4 +1452,3 @@ export {
1428
1452
  summarizeSessions,
1429
1453
  uninstallOpenCode
1430
1454
  };
1431
- //# sourceMappingURL=cli.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Plugin } from '@opencode-ai/plugin';
2
+ import { TuiPlugin } from '@opencode-ai/plugin/tui';
2
3
  export { a as CaptureKind, C as CaptureRecord, b as CaptureStore, I as InsightsOptions, J as JsonlCaptureStore, S as SqliteCaptureStore, c as createCaptureStore, d as defaultDataDir, n as normalizeChatHeadersCapture, e as normalizeChatMessageCapture, f as normalizeChatParamsCapture, g as normalizeEventCapture, h as normalizeExperimentalChatMessagesTransformCapture, i as normalizeExperimentalChatSystemTransformCapture, j as normalizeToolCapture, r as resolveCapturePath } from './capture-gQauLsdn.js';
3
4
 
4
5
  type StreamSample = {
@@ -102,10 +103,13 @@ declare function renderSubagentFooter(state: SubagentState, parentID: string, op
102
103
 
103
104
  declare const OpenCodeInsights: Plugin;
104
105
  declare const server: Plugin;
106
+ declare const rootTui: TuiPlugin;
105
107
  declare const id = "opencode-insights";
108
+
106
109
  declare const _default: {
107
110
  id: string;
108
111
  server: Plugin;
112
+ tui: TuiPlugin;
109
113
  };
110
114
 
111
- 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 };
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, id, pruneStaleSubagents, recordAssistantDelta, recordAssistantMessage, recordToolActivity, renderMetricsText, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, rootTui as tui };
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  renderSubagentFooter,
14
14
  renderSubagentSidebar,
15
15
  renderSubagentStatus
16
- } from "./chunk-WF6BN2SB.js";
16
+ } from "./chunk-36ZNCLI3.js";
17
17
  import {
18
18
  JsonlCaptureStore,
19
19
  SqliteCaptureStore,
@@ -27,7 +27,7 @@ import {
27
27
  normalizeExperimentalChatSystemTransformCapture,
28
28
  normalizeToolCapture,
29
29
  resolveCapturePath
30
- } from "./chunk-G7E4J6OW.js";
30
+ } from "./chunk-Q5ROKOOJ.js";
31
31
 
32
32
  // src/index.ts
33
33
  var OpenCodeInsights = async (_input, options) => {
@@ -73,8 +73,12 @@ var OpenCodeInsights = async (_input, options) => {
73
73
  };
74
74
  };
75
75
  var server = OpenCodeInsights;
76
+ var rootTui = async (...args) => {
77
+ const mod = await import("./tui.js");
78
+ return mod.tui(...args);
79
+ };
76
80
  var id = "opencode-insights";
77
- var src_default = { id, server };
81
+ var src_default = { id, server, tui: rootTui };
78
82
  export {
79
83
  JsonlCaptureStore,
80
84
  OpenCodeInsights,
@@ -105,6 +109,6 @@ export {
105
109
  renderSubagentSidebar,
106
110
  renderSubagentStatus,
107
111
  resolveCapturePath,
108
- server
112
+ server,
113
+ rootTui as tui
109
114
  };
110
- //# sourceMappingURL=index.js.map
package/dist/tui.js CHANGED
@@ -8,11 +8,11 @@ import {
8
8
  recordToolActivity,
9
9
  renderMetricsText,
10
10
  renderSubagentFooter
11
- } from "./chunk-WF6BN2SB.js";
11
+ } from "./chunk-36ZNCLI3.js";
12
12
 
13
13
  // src/tui.tsx
14
14
  import { createTextAttributes, StyledText } from "@opentui/core";
15
- import { onCleanup } from "solid-js";
15
+ import { createSignal, onCleanup } from "solid-js";
16
16
  import { jsx } from "@opentui/solid/jsx-runtime";
17
17
  function isSessionID(value) {
18
18
  return typeof value === "string" && value.startsWith("ses");
@@ -21,7 +21,10 @@ function PromptRight(props) {
21
21
  let text;
22
22
  const sync = () => {
23
23
  if (!text) return;
24
- text.content = props.text();
24
+ const content = props.text();
25
+ text.content = content;
26
+ text.visible = content.length > 0;
27
+ text.height = content.length > 0 ? "auto" : 0;
25
28
  props.api.renderer.requestRender();
26
29
  };
27
30
  const unsubscribe = props.subscribe(sync);
@@ -61,10 +64,18 @@ function ReactiveText(props) {
61
64
  }
62
65
  function SubagentSidebar(props) {
63
66
  let text;
67
+ const [collapsed, setCollapsed] = createSignal(false);
64
68
  const titleAttributes = createTextAttributes({ bold: true });
69
+ const toggle = () => {
70
+ setCollapsed((prev) => !prev);
71
+ props.api.renderer.requestRender();
72
+ };
65
73
  const sync = () => {
66
74
  if (!text) return;
67
- text.content = renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes);
75
+ const model = getSubagentSidebarModel(props.state, props.sessionID);
76
+ text.visible = !!model;
77
+ text.height = model ? "auto" : 0;
78
+ text.content = model ? renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes, collapsed()) : "";
68
79
  props.api.renderer.requestRender();
69
80
  };
70
81
  const unsubscribe = props.subscribe(sync);
@@ -80,25 +91,31 @@ function SubagentSidebar(props) {
80
91
  text = ref;
81
92
  sync();
82
93
  },
94
+ onMouseDown: toggle,
83
95
  fg: props.api.theme.current.textMuted,
84
96
  children: ""
85
97
  }
86
98
  );
87
99
  }
88
- function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes) {
100
+ function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, collapsed) {
89
101
  const model = getSubagentSidebarModel(state, sessionID);
90
102
  if (!model) return "";
103
+ const indicator = collapsed ? "\u25B6 " : "\u25BC ";
91
104
  const chunks = [
92
- textChunk(`${model.title}
105
+ textChunk(`${indicator}${model.title}
93
106
  `, api.theme.current.text, titleAttributes),
94
107
  textChunk(`${model.summary}
95
108
  `, api.theme.current.textMuted)
96
109
  ];
97
- for (const [index, row] of model.rows.entries()) {
98
- if (index > 0) chunks.push(textChunk("\n", api.theme.current.textMuted));
99
- chunks.push(textChunk(`${row.title}
110
+ if (!collapsed) {
111
+ for (const [index, row] of model.rows.entries()) {
112
+ if (index > 0) chunks.push(textChunk("\n"));
113
+ const dotColor = row.status === "running" ? api.theme.current.success : row.status === "error" ? api.theme.current.error : api.theme.current.textMuted;
114
+ chunks.push(textChunk("\u2022 ", dotColor));
115
+ chunks.push(textChunk(`${row.title}
100
116
  `, api.theme.current.text));
101
- chunks.push(textChunk(row.subtitle, api.theme.current.textMuted));
117
+ chunks.push(textChunk(row.subtitle, api.theme.current.textMuted));
118
+ }
102
119
  }
103
120
  return new StyledText(chunks);
104
121
  }
@@ -231,4 +248,3 @@ export {
231
248
  id,
232
249
  tui
233
250
  };
234
- //# sourceMappingURL=tui.js.map
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.1",
4
+ "version": "0.1.3",
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",
@@ -21,15 +21,18 @@
21
21
  "exports": {
22
22
  ".": {
23
23
  "types": "./dist/index.d.ts",
24
- "import": "./dist/index.js"
24
+ "import": "./dist/index.js",
25
+ "default": "./dist/index.js"
25
26
  },
26
27
  "./tui": {
27
28
  "types": "./dist/tui.d.ts",
28
- "import": "./dist/tui.js"
29
+ "import": "./dist/tui.js",
30
+ "default": "./dist/tui.js"
29
31
  },
30
32
  "./cli": {
31
33
  "types": "./dist/cli.d.ts",
32
- "import": "./dist/cli.js"
34
+ "import": "./dist/cli.js",
35
+ "default": "./dist/cli.js"
33
36
  }
34
37
  },
35
38
  "files": [
@@ -43,6 +46,7 @@
43
46
  },
44
47
  "scripts": {
45
48
  "build": "tsup",
49
+ "debug": "npm run build && node dist/cli.js debug",
46
50
  "test": "vitest run",
47
51
  "typecheck": "tsc --noEmit",
48
52
  "verify": "npm run typecheck && npm run test && npm run build",
package/DEVELOPMENT.md DELETED
@@ -1,124 +0,0 @@
1
- # Development
2
-
3
- Notes for maintaining and publishing `opencode-insights`.
4
-
5
- ## Local Testing
6
-
7
- From this repo:
8
-
9
- ```bash
10
- cd /Users/zyao/Desktop/opencode-insights
11
- npm install
12
- npm run verify
13
- npm pack
14
- ```
15
-
16
- Install the packed plugin from your OpenCode config/package directory:
17
-
18
- ```bash
19
- cd ~/.config/opencode
20
- npm i /Users/zyao/Desktop/opencode-insights/rejacky-opencode-insights-*.tgz
21
- npx opencode-insights configure
22
- ```
23
-
24
- For local repo development without relying on the installed package binary:
25
-
26
- ```bash
27
- node /Users/zyao/Desktop/opencode-insights/dist/cli.js configure --config-dir ~/.config/opencode
28
- ```
29
-
30
- Restart OpenCode after reinstalling or rebuilding. Existing sessions will not gain missing events retroactively, so create a new session to test capture changes.
31
-
32
- ## Faster Local Iteration
33
-
34
- Link instead of packing each time:
35
-
36
- ```bash
37
- cd /Users/zyao/Desktop/opencode-insights
38
- npm link
39
-
40
- cd ~/.config/opencode
41
- npm link @rejacky/opencode-insights
42
- ```
43
-
44
- After code changes:
45
-
46
- ```bash
47
- cd /Users/zyao/Desktop/opencode-insights
48
- npm run build
49
- node /Users/zyao/Desktop/opencode-insights/dist/cli.js configure --config-dir ~/.config/opencode
50
- ```
51
-
52
- ## Verification
53
-
54
- Run all checks:
55
-
56
- ```bash
57
- npm run verify
58
- ```
59
-
60
- Individual commands:
61
-
62
- ```bash
63
- npm run typecheck
64
- npm test
65
- npm run build
66
- ```
67
-
68
- ## Automated npm Publishing
69
-
70
- The repo publishes from GitHub Actions when `package.json` is pushed to `main` or `master` with a version that is not already on npm.
71
-
72
- One-time npm setup:
73
-
74
- 1. Go to npm package settings for `@rejacky/opencode-insights`.
75
- 2. Open the package publishing / trusted publishing settings.
76
- 3. Add a trusted publisher for repository `Re-Jacky/opencode-insights`.
77
- 4. Use workflow file `.github/workflows/publish.yml`.
78
- 5. Keep the package public.
79
-
80
- No `NPM_TOKEN` secret is needed when npm trusted publishing is configured. The workflow uses GitHub OIDC plus `npm publish --provenance`.
81
-
82
- If GitHub Actions fails with `404 Not Found - PUT https://registry.npmjs.org/@rejacky%2fopencode-insights`, npm found the registry but rejected package write access. Check that the npm package's trusted publisher entry exactly matches the GitHub owner, repo, and workflow file path above. Also confirm the npm account configuring trusted publishing owns or has publish access to `@rejacky/opencode-insights`.
83
-
84
- Release flow:
85
-
86
- ```bash
87
- git status
88
- npm version patch
89
- git push --follow-tags
90
- ```
91
-
92
- Use `npm version minor` or `npm version major` for larger releases. The workflow will run verification first, skip publishing if that exact version already exists, and publish only unpublished versions.
93
-
94
- Manual fallback:
95
-
96
- ```bash
97
- npm run verify
98
- npm pack --dry-run
99
- npm publish --access public --provenance
100
- ```
101
-
102
- ## Manual Publish Checklist
103
-
104
- Review package contents:
105
-
106
- ```bash
107
- npm pack
108
- tar -tf rejacky-opencode-insights-0.1.1.tgz
109
- ```
110
-
111
- Publish manually if GitHub Actions is unavailable:
112
-
113
- ```bash
114
- npm login
115
- npm publish --access public --provenance
116
- ```
117
-
118
- After publishing, users should be able to install from their OpenCode config/package directory with:
119
-
120
- ```bash
121
- cd ~/.config/opencode
122
- npm i @rejacky/opencode-insights
123
- npx opencode-insights configure
124
- ```
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/capture.ts"],"sourcesContent":["import { mkdir, appendFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type CaptureKind =\n | \"chat.message\"\n | \"chat.params\"\n | \"chat.headers\"\n | \"experimental.chat.messages.transform\"\n | \"experimental.chat.system.transform\"\n | \"event\"\n | \"tool.execute.before\"\n | \"tool.execute.after\";\n\nexport type CaptureRecord = {\n id: string;\n kind: CaptureKind;\n timestamp: number;\n sessionID?: string | undefined;\n messageID?: string | undefined;\n providerID?: string | undefined;\n modelID?: string | undefined;\n payload: Record<string, unknown>;\n};\n\nexport type CaptureStore = {\n initialize?(): Promise<void>;\n append(record: CaptureRecord): Promise<void>;\n close?(): Promise<void>;\n};\n\nexport type InsightsOptions = {\n dataDir?: unknown;\n dbPath?: unknown;\n};\n\nlet sequence = 0;\n\nfunction nextID(timestamp: number) {\n sequence += 1;\n return `${timestamp.toString(36)}-${sequence.toString(36)}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction modelIDFrom(input: unknown): string | undefined {\n if (!isRecord(input)) return undefined;\n return optionalString(input.modelID) ?? optionalString(input.id) ?? optionalString(input.name);\n}\n\nfunction providerIDFrom(input: unknown): string | undefined {\n if (!isRecord(input)) return undefined;\n const info = isRecord(input.info) ? input.info : undefined;\n return optionalString(info?.id) ?? optionalString(info?.name) ?? optionalString(input.id);\n}\n\nfunction sessionIDFrom(input: unknown): string | undefined {\n if (!isRecord(input)) return undefined;\n return optionalString(input.sessionID) ?? optionalString(input.sessionId);\n}\n\nfunction messageIDFrom(input: unknown): string | undefined {\n if (!isRecord(input)) return undefined;\n return optionalString(input.messageID) ?? optionalString(input.messageId);\n}\n\nfunction transformedMessages(output: unknown): Record<string, unknown>[] {\n if (!isRecord(output) || !Array.isArray(output.messages)) return [];\n return output.messages.filter((message) => isRecord(message));\n}\n\nfunction infoFromTransformedMessage(message: Record<string, unknown> | undefined): Record<string, unknown> | undefined {\n return isRecord(message?.info) ? message.info : undefined;\n}\n\nfunction latestUserTransformedMessage(output: unknown): Record<string, unknown> | undefined {\n return transformedMessages(output)\n .slice()\n .reverse()\n .find((message) => optionalString(infoFromTransformedMessage(message)?.role) === \"user\");\n}\n\nfunction compactUndefined<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction captureRecord(input: {\n id: string;\n kind: CaptureKind;\n timestamp: number;\n sessionID?: string | undefined;\n messageID?: string | undefined;\n providerID?: string | undefined;\n modelID?: string | undefined;\n payload: Record<string, unknown>;\n}): CaptureRecord {\n return compactUndefined({ ...input }) as CaptureRecord;\n}\n\nexport function defaultDataDir() {\n return join(homedir(), \".opencode-insights\");\n}\n\nexport function resolveCapturePath(options: InsightsOptions = {}) {\n if (typeof options.dbPath === \"string\" && options.dbPath.length > 0) {\n return options.dbPath;\n }\n\n const dataDir =\n typeof options.dataDir === \"string\" && options.dataDir.length > 0\n ? options.dataDir\n : defaultDataDir();\n return join(dataDir, \"insights.sqlite\");\n}\n\nexport function normalizeChatMessageCapture(input: unknown, output: unknown, timestamp = Date.now()): CaptureRecord {\n const inputRecord = isRecord(input) ? input : {};\n const model = isRecord(inputRecord.model) ? inputRecord.model : undefined;\n return captureRecord({\n id: nextID(timestamp),\n kind: \"chat.message\",\n timestamp,\n sessionID: sessionIDFrom(inputRecord),\n messageID: messageIDFrom(inputRecord),\n providerID: model ? optionalString(model.providerID) : undefined,\n modelID: model ? optionalString(model.modelID) : undefined,\n payload: { input, output }\n });\n}\n\nexport function normalizeChatParamsCapture(input: unknown, output: unknown, timestamp = Date.now()): CaptureRecord {\n const inputRecord = isRecord(input) ? input : {};\n return captureRecord({\n id: nextID(timestamp),\n kind: \"chat.params\",\n timestamp,\n sessionID: sessionIDFrom(inputRecord),\n messageID: messageIDFrom(inputRecord.message),\n providerID: providerIDFrom(inputRecord.provider),\n modelID: modelIDFrom(inputRecord.model),\n payload: { input, output }\n });\n}\n\nexport function normalizeChatHeadersCapture(input: unknown, output: unknown, timestamp = Date.now()): CaptureRecord {\n const inputRecord = isRecord(input) ? input : {};\n return captureRecord({\n id: nextID(timestamp),\n kind: \"chat.headers\",\n timestamp,\n sessionID: sessionIDFrom(inputRecord),\n messageID: messageIDFrom(inputRecord.message),\n providerID: providerIDFrom(inputRecord.provider),\n modelID: modelIDFrom(inputRecord.model),\n payload: { input, output }\n });\n}\n\nexport function normalizeExperimentalChatMessagesTransformCapture(input: unknown, output: unknown, timestamp = Date.now()): CaptureRecord {\n const latestUserMessage = latestUserTransformedMessage(output);\n const info = infoFromTransformedMessage(latestUserMessage);\n return captureRecord({\n id: nextID(timestamp),\n kind: \"experimental.chat.messages.transform\",\n timestamp,\n sessionID: sessionIDFrom(info),\n messageID: messageIDFrom(info) ?? optionalString(info?.id),\n payload: { input, output }\n });\n}\n\nexport function normalizeExperimentalChatSystemTransformCapture(input: unknown, output: unknown, timestamp = Date.now()): CaptureRecord {\n const inputRecord = isRecord(input) ? input : {};\n const model = isRecord(inputRecord.model) ? inputRecord.model : undefined;\n return captureRecord({\n id: nextID(timestamp),\n kind: \"experimental.chat.system.transform\",\n timestamp,\n sessionID: sessionIDFrom(inputRecord),\n providerID: model ? optionalString(model.providerID) : undefined,\n modelID: model ? modelIDFrom(model) : undefined,\n payload: { input, output }\n });\n}\n\nexport function normalizeEventCapture(event: unknown, timestamp = Date.now()): CaptureRecord {\n const record = isRecord(event) ? event : {};\n const properties = isRecord(record.properties) ? record.properties : {};\n const info = isRecord(properties.info) ? properties.info : {};\n const part = isRecord(properties.part) ? properties.part : {};\n return captureRecord({\n id: nextID(timestamp),\n kind: \"event\",\n timestamp,\n sessionID: sessionIDFrom(properties) ?? sessionIDFrom(info) ?? sessionIDFrom(part) ?? sessionIDFrom(record),\n messageID: messageIDFrom(properties) ?? messageIDFrom(info) ?? messageIDFrom(part),\n payload: { event }\n });\n}\n\nexport function normalizeToolCapture(\n kind: \"tool.execute.before\" | \"tool.execute.after\",\n input: unknown,\n output: unknown,\n timestamp = Date.now()\n): CaptureRecord {\n const inputRecord = isRecord(input) ? input : {};\n return captureRecord({\n id: nextID(timestamp),\n kind,\n timestamp,\n sessionID: sessionIDFrom(inputRecord),\n payload: { input, output }\n });\n}\n\nexport class JsonlCaptureStore implements CaptureStore {\n constructor(private readonly path: string) {}\n\n async initialize() {\n await mkdir(dirname(this.path), { recursive: true });\n await writeFile(this.path, \"\", { flag: \"a\" });\n }\n\n async append(record: CaptureRecord) {\n await mkdir(dirname(this.path), { recursive: true });\n await appendFile(this.path, `${JSON.stringify(record)}\\n`, \"utf8\");\n }\n}\n\ntype BunSqliteDatabase = {\n run(sql: string, ...params: unknown[]): unknown;\n close?(): void;\n};\n\nexport class SqliteCaptureStore implements CaptureStore {\n private db: BunSqliteDatabase | undefined;\n private unavailable = false;\n\n constructor(private readonly path: string) {}\n\n async initialize() {\n const db = await this.database();\n if (!db) {\n const fallbackPath = this.path.endsWith(\".sqlite\") ? `${this.path}.jsonl` : this.path;\n await new JsonlCaptureStore(fallbackPath).initialize();\n }\n }\n\n async append(record: CaptureRecord) {\n const db = await this.database();\n if (!db) {\n const fallbackPath = this.path.endsWith(\".sqlite\") ? `${this.path}.jsonl` : this.path;\n await new JsonlCaptureStore(fallbackPath).append(record);\n return;\n }\n\n db.run(\n `insert into captures (\n id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json\n ) values (?, ?, ?, ?, ?, ?, ?, ?)`,\n record.id,\n record.kind,\n record.timestamp,\n record.sessionID ?? null,\n record.messageID ?? null,\n record.providerID ?? null,\n record.modelID ?? null,\n JSON.stringify(record.payload)\n );\n }\n\n async close() {\n this.db?.close?.();\n this.db = undefined;\n }\n\n private async database() {\n if (this.db) return this.db;\n if (this.unavailable) return undefined;\n\n try {\n await mkdir(dirname(this.path), { recursive: true });\n const mod = (await import(\"bun:sqlite\").catch(() => undefined)) as\n | { Database: new (path: string) => BunSqliteDatabase }\n | undefined;\n if (!mod) {\n this.unavailable = true;\n return undefined;\n }\n const db = new mod.Database(this.path);\n initializeSchema(db);\n this.db = db;\n return db;\n } catch {\n this.unavailable = true;\n return undefined;\n }\n }\n}\n\nfunction initializeSchema(db: BunSqliteDatabase) {\n db.run(`create table if not exists captures (\n id text primary key,\n kind text not null,\n timestamp integer not null,\n session_id text,\n message_id text,\n provider_id text,\n model_id text,\n payload_json text not null\n )`);\n db.run(`create index if not exists captures_timestamp_idx on captures(timestamp)`);\n db.run(`create index if not exists captures_session_idx on captures(session_id)`);\n}\n\nexport function createCaptureStore(options: InsightsOptions = {}): CaptureStore {\n return new SqliteCaptureStore(resolveCapturePath(options));\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/metrics.ts","../src/subagents.ts"],"sourcesContent":["export type StreamSample = {\n at: number;\n tokens: number;\n};\n\nexport type MessageTiming = {\n sessionID: string;\n requestStartAt: number;\n firstResponseAt?: number | undefined;\n firstTokenAt?: number | undefined;\n lastTokenAt?: number | undefined;\n lastToolCallAt?: number | undefined;\n};\n\nexport type SessionAverage = {\n totalTokens: number;\n totalDurationMs: number;\n totalTtftMs: number;\n messageCount: number;\n};\n\nexport type MetricsState = {\n streamSamplesBySession: Record<string, StreamSample[]>;\n messageTimingByID: Record<string, MessageTiming>;\n sessionAverageByID: Record<string, SessionAverage>;\n};\n\nconst STREAM_WINDOW_MS = 5_000;\nconst LIVE_STALE_MS = 1_500;\nconst SINGLE_SAMPLE_MS = 1_000;\n\nexport function createMetricsState(): MetricsState {\n return {\n streamSamplesBySession: {},\n messageTimingByID: {},\n sessionAverageByID: {}\n };\n}\n\nexport function estimateStreamTokens(delta: string) {\n return Math.max(1, Math.ceil(Buffer.byteLength(delta, \"utf8\") / 5));\n}\n\nexport function recordAssistantMessage(\n state: MetricsState,\n input: {\n sessionID: string;\n messageID: string;\n createdAt: number;\n completedAt?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n finish?: string;\n }\n) {\n if (typeof input.completedAt !== \"number\") {\n const existing = state.messageTimingByID[input.messageID];\n state.messageTimingByID[input.messageID] = {\n sessionID: input.sessionID,\n requestStartAt: input.createdAt,\n firstResponseAt: existing?.firstResponseAt,\n firstTokenAt: existing?.firstTokenAt,\n lastTokenAt: existing?.lastTokenAt,\n lastToolCallAt: existing?.lastToolCallAt\n };\n return;\n }\n\n const timing = state.messageTimingByID[input.messageID];\n if (timing?.sessionID === input.sessionID && typeof timing.firstResponseAt === \"number\") {\n const totalTokens = (input.outputTokens ?? 0) + (input.reasoningTokens ?? 0);\n const endAt = input.finish === \"tool-calls\" ? timing.lastToolCallAt : input.completedAt;\n const durationMs = typeof endAt === \"number\" ? Math.max(endAt - timing.firstResponseAt, 1) : undefined;\n const ttftMs = Math.max(timing.firstResponseAt - timing.requestStartAt, 0);\n if (totalTokens > 0 && durationMs) {\n const totals =\n state.sessionAverageByID[input.sessionID] ??\n {\n totalTokens: 0,\n totalDurationMs: 0,\n totalTtftMs: 0,\n messageCount: 0\n };\n state.sessionAverageByID[input.sessionID] = {\n totalTokens: totals.totalTokens + totalTokens,\n totalDurationMs: totals.totalDurationMs + durationMs,\n totalTtftMs: totals.totalTtftMs + ttftMs,\n messageCount: totals.messageCount + 1\n };\n }\n }\n\n delete state.messageTimingByID[input.messageID];\n pruneSamples(state, input.completedAt);\n}\n\nexport function recordAssistantDelta(\n state: MetricsState,\n input: { sessionID: string; messageID: string; delta: string; at: number }\n) {\n const sample = {\n at: input.at,\n tokens: estimateStreamTokens(input.delta)\n };\n state.streamSamplesBySession[input.sessionID] = [\n ...(state.streamSamplesBySession[input.sessionID] ?? []).filter((item) => input.at - item.at <= STREAM_WINDOW_MS),\n sample\n ];\n\n const timing = state.messageTimingByID[input.messageID];\n if (timing) {\n state.messageTimingByID[input.messageID] = timing.firstTokenAt\n ? { ...timing, lastTokenAt: input.at }\n : {\n ...timing,\n firstResponseAt: timing.firstResponseAt ?? input.at,\n firstTokenAt: input.at,\n lastTokenAt: input.at\n };\n }\n}\n\nexport function recordToolActivity(state: MetricsState, sessionID: string, messageID: string, at = Date.now()) {\n if (state.streamSamplesBySession[sessionID]?.length) {\n delete state.streamSamplesBySession[sessionID];\n }\n const timing = state.messageTimingByID[messageID];\n if (timing) {\n state.messageTimingByID[messageID] = {\n ...timing,\n lastToolCallAt: at,\n firstResponseAt: timing.firstResponseAt ?? at\n };\n }\n}\n\nexport function renderMetricsText(\n state: MetricsState,\n sessionID: string,\n options: { now?: number; idle?: boolean } = {}\n) {\n const live = liveTps(state, sessionID, options) ?? \"-\";\n const avg = sessionAverage(state, sessionID) ?? \"-\";\n const ttft = sessionTtft(state, sessionID) ?? \"-\";\n return `TPS ${live} | AVG ${avg} | TTFT ${ttft}`;\n}\n\nfunction pruneSamples(state: MetricsState, now = Date.now()) {\n for (const [sessionID, samples] of Object.entries(state.streamSamplesBySession)) {\n const next = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);\n if (next.length > 0) state.streamSamplesBySession[sessionID] = next;\n else delete state.streamSamplesBySession[sessionID];\n }\n}\n\nfunction sessionAverage(state: MetricsState, sessionID: string) {\n const totals = state.sessionAverageByID[sessionID];\n if (!totals || totals.totalTokens <= 0 || totals.totalDurationMs <= 0) return undefined;\n return formatRate(totals.totalTokens / (totals.totalDurationMs / 1000), \"AVG\");\n}\n\nfunction sessionTtft(state: MetricsState, sessionID: string) {\n const totals = state.sessionAverageByID[sessionID];\n if (!totals || totals.messageCount <= 0 || totals.totalTtftMs < 0) return undefined;\n return formatTtft(totals.totalTtftMs / totals.messageCount / 1000);\n}\n\nfunction liveTps(\n state: MetricsState,\n sessionID: string,\n options: { now?: number; idle?: boolean } = {}\n) {\n if (options.idle) return undefined;\n const now = options.now ?? Date.now();\n const samples = state.streamSamplesBySession[sessionID] ?? [];\n const relevant = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);\n if (relevant.length === 0) return undefined;\n const lastSample = relevant.at(-1);\n if (!lastSample || now - lastSample.at > LIVE_STALE_MS) return undefined;\n const total = relevant.reduce((sum, sample) => sum + sample.tokens, 0);\n const durationSeconds = activeDurationMs(relevant, now) / 1000;\n if (durationSeconds <= 0) return undefined;\n return formatRate(total / durationSeconds, \"TPS\");\n}\n\nfunction activeDurationMs(samples: StreamSample[], tailAt?: number) {\n if (samples.length === 0) return 0;\n if (samples.length === 1) {\n const tailDuration = tailAt ? Math.max(0, tailAt - samples[0]!.at) : SINGLE_SAMPLE_MS;\n return Math.min(Math.max(tailDuration, 250), SINGLE_SAMPLE_MS);\n }\n\n let duration = 0;\n for (let index = 1; index < samples.length; index++) {\n duration += Math.max(0, samples[index]!.at - samples[index - 1]!.at);\n }\n\n if (tailAt) {\n duration += Math.max(0, tailAt - samples.at(-1)!.at);\n }\n\n return Math.max(duration, SINGLE_SAMPLE_MS);\n}\n\nfunction formatRate(value: number, label: \"TPS\" | \"AVG\") {\n if (!Number.isFinite(value) || value <= 0) return undefined;\n const suffix = label === \"TPS\" ? \" TPS\" : \"\";\n if (value >= 100) return `${Math.round(value)}${suffix}`;\n if (value >= 10) return `${value.toFixed(1)}${suffix}`;\n return `${value.toFixed(2)}${suffix}`;\n}\n\nfunction formatTtft(value: number) {\n if (!Number.isFinite(value) || value < 0) return undefined;\n return `${value.toFixed(1)}s`;\n}\n","export type SubagentStatus = \"running\" | \"done\" | \"error\";\n\nexport type SubagentInfo = {\n id: string;\n parentID: string;\n title: string;\n status: SubagentStatus;\n startedAt: string;\n updatedAt: string;\n endedAt?: string | undefined;\n elapsedMs?: number | undefined;\n tokens?: {\n input?: number | undefined;\n output?: number | undefined;\n total?: number | undefined;\n contextPercent?: number | undefined;\n } | undefined;\n};\n\nexport type SubagentState = {\n children: Record<string, SubagentInfo>;\n totalExecuted: number;\n};\n\nexport type SubagentSidebarRow = {\n id: string;\n title: string;\n subtitle: string;\n status: SubagentStatus;\n};\n\nexport type SubagentSidebarModel = {\n title: string;\n summary: string;\n rows: SubagentSidebarRow[];\n};\n\ntype EventLike = {\n type?: unknown;\n properties?: {\n info?: Record<string, unknown>;\n part?: unknown;\n sessionID?: unknown;\n sessionId?: unknown;\n status?: unknown;\n };\n};\n\nexport function createSubagentState(): SubagentState {\n return { children: {}, totalExecuted: 0 };\n}\n\nexport function applySubagentEvent(state: SubagentState, event: unknown) {\n const created = extractTaskToolSubagent(event) ?? extractSubagent(event) ?? updateExistingSubagent(state, event);\n if (!created) return false;\n\n const previous = state.children[created.id];\n const preserveTerminal = !!previous && isTerminalStatus(previous.status) && created.status === \"running\";\n const status = preserveTerminal ? previous.status : created.status;\n const title = preserveTerminal ? previous.title : created.title;\n const startedAt = preserveTerminal ? previous.startedAt : created.startedAt;\n const endedAt = preserveTerminal ? previous.endedAt : created.endedAt;\n const next: SubagentInfo = {\n ...previous,\n ...created,\n title,\n status,\n startedAt,\n updatedAt: created.updatedAt,\n endedAt,\n elapsedMs: elapsedMs(startedAt, endedAt ?? created.updatedAt),\n tokens: created.tokens ?? previous?.tokens\n };\n\n if (!previous) state.totalExecuted += 1;\n state.children[created.id] = next;\n return true;\n}\n\nfunction isTerminalStatus(status: SubagentStatus) {\n return status === \"done\" || status === \"error\";\n}\n\nexport function renderSubagentStatus(state: SubagentState, options: { now?: number } = {}) {\n const children = getSubagentItems(state);\n const running = children.filter((child) => child.status === \"running\").length;\n const done = children.filter((child) => child.status === \"done\").length;\n const error = children.filter((child) => child.status === \"error\").length;\n const aggregate = `${running} running · ${done} done · ${error} failed · ${state.totalExecuted} total`;\n\n if (children.length === 0) return aggregate;\n\n const details = children\n .map((child) => {\n const nowIso = new Date(options.now ?? Date.now()).toISOString();\n const duration =\n child.status === \"running\" || (child.status === \"error\" && !child.endedAt)\n ? formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso))\n : formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n const context = formatContext(child);\n return [child.title, duration, context].filter(Boolean).join(\" \");\n })\n .join(\" · \");\n\n return `${aggregate} · ${details}`;\n}\n\nexport function getSubagentItems(state: SubagentState, parentID?: string) {\n return Object.values(state.children)\n .filter((child) => !parentID || child.parentID === parentID)\n .sort((a, b) => {\n const statusRank = statusSortRank(a.status) - statusSortRank(b.status);\n if (statusRank !== 0) return statusRank;\n return b.startedAt.localeCompare(a.startedAt);\n });\n}\n\nexport function pruneStaleSubagents(state: SubagentState, options: { now?: number; staleMs?: number } = {}) {\n const now = options.now ?? Date.now();\n const staleMs = options.staleMs ?? 180_000;\n let changed = false;\n\n for (const [id, child] of Object.entries(state.children)) {\n if (child.status === \"running\") continue;\n const lastActivity = Date.parse(child.endedAt ?? child.updatedAt);\n if (!Number.isFinite(lastActivity)) continue;\n if (now - lastActivity <= staleMs) continue;\n delete state.children[id];\n changed = true;\n }\n\n return changed;\n}\n\nexport function getSubagentSidebarModel(\n state: SubagentState,\n parentID: string,\n options: { now?: number; staleMs?: number } = {}\n): SubagentSidebarModel | undefined {\n pruneStaleSubagents(state, options);\n const children = getSubagentItems(state, parentID);\n if (children.length === 0) return undefined;\n\n const running = children.filter((child) => child.status === \"running\").length;\n const done = children.filter((child) => child.status === \"done\").length;\n const error = children.filter((child) => child.status === \"error\").length;\n\n return {\n title: \"Subagents\",\n summary: `${running} running · ${done} done · ${error} error`,\n rows: children.map((child) => ({\n id: child.id,\n title: formatSubagentTitle(child.title),\n subtitle: [formatSubagentDuration(child, options.now), formatUsage(child)].filter(Boolean).join(\" · \"),\n status: child.status\n }))\n };\n}\n\nexport function renderSubagentSidebar(state: SubagentState, parentID: string, options: { now?: number } = {}) {\n const model = getSubagentSidebarModel(state, parentID, options);\n if (!model) return \"\";\n\n return [\n model.title,\n model.summary,\n ...model.rows.flatMap((row) => [row.title, row.subtitle].filter(Boolean))\n ].join(\"\\n\");\n}\n\nexport function renderSubagentFooter(state: SubagentState, parentID: string, options: { now?: number } = {}) {\n const model = getSubagentSidebarModel(state, parentID, options);\n if (!model) return \"\";\n return `Subagents ${model.summary}`;\n}\n\nfunction extractTaskToolSubagent(event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type !== \"message.part.updated\") return undefined;\n\n const part = isRecord(evt.properties?.part) ? evt.properties.part : undefined;\n if (!part || part.type !== \"tool\" || part.tool !== \"task\") return undefined;\n\n const state = isRecord(part.state) ? part.state : undefined;\n if (!state) return undefined;\n\n const metadata = isRecord(state.metadata) ? state.metadata : isRecord(part.metadata) ? part.metadata : undefined;\n const id = asString(metadata?.sessionId) ?? sessionIdFromTaskOutput(asString(state.output));\n const parentID = asString(metadata?.parentSessionId) ?? asString(evt.properties?.sessionID);\n if (!id || !parentID || id === parentID) return undefined;\n\n const status = taskToolStatus(state);\n const startedMs = numberFromPath(state.time, \"start\") ?? numberFromPath(state.time, \"created\") ?? Date.now();\n const endedMs = numberFromPath(state.time, \"end\");\n const updatedMs = endedMs ?? numberFromPath(state.time, \"updated\") ?? startedMs;\n const input = isRecord(state.input) ? state.input : undefined;\n const agent = asString(input?.subagent_type);\n const taskTitle = asString(state.title) ?? asString(input?.description) ?? \"task\";\n\n return {\n id,\n parentID,\n title: agent ? `${agentTitle(agent)}: ${taskTitle}` : taskTitle,\n status,\n startedAt: new Date(startedMs).toISOString(),\n updatedAt: new Date(updatedMs).toISOString(),\n endedAt: status === \"running\" || typeof endedMs !== \"number\" ? undefined : new Date(endedMs).toISOString(),\n elapsedMs: Math.max(0, updatedMs - startedMs),\n tokens: extractTokens(state.tokens)\n };\n}\n\nfunction taskToolStatus(state: Record<string, unknown>): SubagentStatus {\n const status = asString(state.status);\n if (status === \"completed\") return \"done\";\n if (status === \"error\") return \"error\";\n return \"running\";\n}\n\nfunction sessionIdFromTaskOutput(output: string | undefined) {\n return output?.match(/<task\\s+id=\"([^\"]+)\"/)?.[1];\n}\n\nfunction agentTitle(agent: string) {\n return agent\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)\n .join(\" \");\n}\n\nfunction extractSubagent(event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type !== \"session.created\" && evt.type !== \"session.updated\") return undefined;\n\n const info = isRecord(evt.properties?.info) ? evt.properties.info : undefined;\n if (!info) return undefined;\n\n const parentID = asString(info.parentID);\n const id = asString(info.id);\n if (!parentID || !id || id === parentID) return undefined;\n\n const startedMs = numberFromPath(info.time, \"created\") ?? numberFromPath(info.time, \"started\") ?? Date.now();\n const completedMs = numberFromPath(info.time, \"completed\");\n const explicitUpdatedMs = numberFromPath(info.time, \"updated\");\n const updatedMs = completedMs ?? explicitUpdatedMs ?? startedMs;\n const infoStatus = asString(info.status);\n const hasError = info.error !== undefined || infoStatus === \"error\";\n const status: SubagentStatus =\n hasError\n ? \"error\"\n : typeof completedMs === \"number\" || infoStatus === \"idle\"\n ? \"done\"\n : \"running\";\n const terminalMs =\n typeof completedMs === \"number\" || infoStatus === \"idle\"\n ? updatedMs\n : status === \"error\" && typeof explicitUpdatedMs === \"number\"\n ? explicitUpdatedMs\n : undefined;\n const endedAt = typeof terminalMs === \"number\" ? new Date(terminalMs).toISOString() : undefined;\n\n return {\n id,\n parentID,\n title: asString(info.title) ?? asString(info.name) ?? \"subagent\",\n status,\n startedAt: new Date(startedMs).toISOString(),\n updatedAt: new Date(updatedMs).toISOString(),\n endedAt,\n elapsedMs: Math.max(0, updatedMs - startedMs),\n tokens: extractTokens(info.tokens)\n };\n}\n\nfunction updateExistingSubagent(state: SubagentState, event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n const sessionID = asString(evt.properties?.sessionID) ?? asString(evt.properties?.sessionId);\n if (!sessionID) return undefined;\n\n const previous = state.children[sessionID];\n if (!previous) return undefined;\n\n const info = isRecord(evt.properties?.info) ? evt.properties.info : undefined;\n const completedMs = numberFromPath(info?.time, \"completed\");\n const messageCompleted = evt.type === \"message.updated\" && asString(info?.role) === \"assistant\" && typeof completedMs === \"number\";\n const messageFailed = messageCompleted && info?.error !== undefined;\n const status = messageFailed ? \"error\" : messageCompleted ? \"done\" : statusFromEvent(event) ?? previous.status;\n const timestamp = messageCompleted ? new Date(completedMs).toISOString() : new Date().toISOString();\n const done = status === \"done\" || status === \"error\";\n return {\n ...previous,\n status,\n updatedAt: timestamp,\n endedAt: done ? previous.endedAt ?? timestamp : previous.endedAt,\n tokens: extractTokens(info?.tokens) ?? previous.tokens\n };\n}\n\nfunction statusFromEvent(event: unknown): SubagentStatus | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type === \"session.error\") return \"error\";\n if (evt.type === \"session.idle\") return \"done\";\n if (evt.type === \"session.status\" && isRecord(evt.properties?.status)) {\n const statusType = asString(evt.properties.status.type);\n if (statusType === \"busy\" || statusType === \"running\") return \"running\";\n if (statusType === \"idle\") return \"done\";\n if (statusType === \"error\") return \"error\";\n }\n return undefined;\n}\n\nfunction extractTokens(value: unknown): SubagentInfo[\"tokens\"] {\n if (!isRecord(value)) return undefined;\n const input = asNumber(value.input);\n const output = asNumber(value.output);\n const reasoning = asNumber(value.reasoning);\n const cache = isRecord(value.cache) ? value.cache : undefined;\n const cacheRead = asNumber(cache?.read);\n const cacheWrite = asNumber(cache?.write);\n const total =\n asNumber(value.total) ??\n [input, output, reasoning, cacheRead, cacheWrite].reduce<number | undefined>(\n (sum, item) => (item === undefined ? sum : (sum ?? 0) + item),\n undefined\n );\n const contextPercent = asNumber(value.contextPercent);\n if (input === undefined && output === undefined && total === undefined && contextPercent === undefined) return undefined;\n return compactUndefined({ input, output, total, contextPercent });\n}\n\nfunction compactUndefined<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction formatContext(child: SubagentInfo) {\n const total = child.tokens?.total ?? ((child.tokens?.input ?? 0) + (child.tokens?.output ?? 0) || undefined);\n if (typeof total !== \"number\" || !Number.isFinite(total)) return \"\";\n const label = Math.round(total) === 1 ? \"token\" : \"tokens\";\n return `ctx ${Math.max(0, Math.round(total)).toLocaleString(\"en-US\")} ${label}`;\n}\n\nfunction formatUsage(child: SubagentInfo) {\n const context = formatContext(child);\n const percent = child.tokens?.contextPercent;\n if (typeof percent === \"number\") return `${context} ${Math.round(percent)}%`.trim();\n return context;\n}\n\nfunction formatSubagentDuration(child: SubagentInfo, now?: number) {\n const nowIso = new Date(now ?? Date.now()).toISOString();\n if (child.status === \"running\" || (child.status === \"error\" && !child.endedAt)) {\n return formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n }\n return formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n}\n\nfunction formatDuration(ms: number) {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) {\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n }\n return `${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n}\n\nfunction statusSortRank(status: SubagentStatus) {\n if (status === \"running\") return 0;\n if (status === \"error\") return 1;\n return 2;\n}\n\nfunction formatSubagentTitle(title: string) {\n const match = title.match(/^(?:[✓✗!]\\s*)?([A-Za-z][\\w -]*?)\\s+[—-]\\s+(.+)$/u);\n if (!match) return truncateMiddle(title, 36);\n const agent = match[1]?.trim();\n const task = match[2]?.trim();\n if (!agent || !task) return truncateMiddle(title, 36);\n return truncateMiddle(`${agent}: ${task}`, 36);\n}\n\nfunction truncateMiddle(value: string, maxLength: number) {\n if (value.length <= maxLength) return value;\n if (maxLength <= 3) return value.slice(0, maxLength);\n const marker = \"...\";\n const left = Math.ceil((maxLength - marker.length) / 2);\n const right = Math.floor((maxLength - marker.length) / 2);\n return `${value.slice(0, left)}${marker}${value.slice(value.length - right)}`;\n}\n\nfunction elapsedMs(start: string, end: string) {\n const startMs = Date.parse(start);\n const endMs = Date.parse(end);\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return 0;\n return Math.max(0, endMs - startMs);\n}\n\nfunction numberFromPath(value: unknown, key: string) {\n if (!isRecord(value)) return undefined;\n return asNumber(value[key]);\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/inspect.ts","../src/viewer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { execFile } from \"node:child_process\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { pathToFileURL } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport { resolveCapturePath } from \"./capture.js\";\nimport { buildRequestHistory, formatCaptureSummary, readRecentCaptures } from \"./inspect.js\";\nimport { serveViewer } from \"./viewer.js\";\n\nconst execFileAsync = promisify(execFile);\nconst DEFAULT_RECENT_LIMIT = 20;\nconst DEFAULT_HISTORY_LIMIT = 5_000;\nconst SERVER_PLUGIN_SPEC = \"@rejacky/opencode-insights\";\nconst TUI_PLUGIN_SPEC = \"@rejacky/opencode-insights/tui\";\n\ntype CliOptions = {\n dbPath?: string | undefined;\n dataDir?: string | undefined;\n limit: number;\n limitProvided: boolean;\n json: boolean;\n host?: string | undefined;\n port?: number | undefined;\n output?: string | undefined;\n configDir?: string | undefined;\n dryRun: boolean;\n keepData: boolean;\n};\n\nasync function main(argv: string[]) {\n const command = argv[2] ?? \"recent\";\n const options = parseOptions(argv.slice(3));\n const positionals = parsePositionals(argv.slice(3));\n\n if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n process.stdout.write(`${usage()}\\n`);\n return;\n }\n\n if (command === \"recent\") {\n const records = await readRecentCaptures({ ...options, limit: options.limitProvided ? options.limit : DEFAULT_RECENT_LIMIT });\n process.stdout.write(options.json ? `${JSON.stringify(records, null, 2)}\\n` : `${formatCaptureSummary(records)}\\n`);\n return;\n }\n\n if (command === \"history\") {\n const records = await readRecentCaptures(historyReadOptions(options));\n const history = buildRequestHistory(records);\n process.stdout.write(`${JSON.stringify(history, null, 2)}\\n`);\n return;\n }\n\n if (command === \"sessions\") {\n const records = await readRecentCaptures(historyReadOptions(options));\n const history = buildRequestHistory(records);\n const rows = summarizeSessions(history.sessions);\n process.stdout.write(options.json ? `${JSON.stringify(rows, null, 2)}\\n` : `${formatSessionSummary(rows)}\\n`);\n return;\n }\n\n if (command === \"show\") {\n const sessionID = positionals[0];\n if (!sessionID) throw new Error(\"Missing session id. Usage: opencode-insights show <session-id>\");\n const session = await readSession(sessionID, options);\n process.stdout.write(`${JSON.stringify(session, null, 2)}\\n`);\n return;\n }\n\n if (command === \"export\") {\n const sessionID = positionals[0];\n if (!sessionID) throw new Error(\"Missing session id. Usage: opencode-insights export <session-id> [--output PATH]\");\n const session = await readSession(sessionID, options);\n const json = `${JSON.stringify(session, null, 2)}\\n`;\n if (options.output) {\n await mkdir(dirname(options.output), { recursive: true });\n await writeFile(options.output, json, \"utf8\");\n process.stdout.write(`Exported ${sessionID} to ${options.output}\\n`);\n } else {\n process.stdout.write(json);\n }\n return;\n }\n\n if (command === \"serve\") {\n const viewer = await serveViewer({ ...options, limit: options.limitProvided ? options.limit : DEFAULT_HISTORY_LIMIT });\n process.stdout.write(`OpenCode Insights viewer listening at ${viewer.url}\\n`);\n return;\n }\n\n if (command === \"open\") {\n const viewer = await serveViewer({ ...options, limit: options.limitProvided ? options.limit : DEFAULT_HISTORY_LIMIT });\n await openBrowser(viewer.url);\n process.stdout.write(`OpenCode Insights viewer listening at ${viewer.url}\\n`);\n return;\n }\n\n if (command === \"doctor\") {\n process.stdout.write(`${await runDoctor(options)}\\n`);\n return;\n }\n\n if (command === \"vacuum\") {\n process.stdout.write(`${await vacuumDatabase(options)}\\n`);\n return;\n }\n\n if (command === \"configure\") {\n process.stdout.write(`${await configureOpenCode(options)}\\n`);\n return;\n }\n\n if (command === \"uninstall\") {\n process.stdout.write(`${await uninstallOpenCode(options)}\\n`);\n return;\n }\n\n {\n process.stderr.write(`Unknown command: ${command}\\n`);\n process.stderr.write(`${usage()}\\n`);\n process.exitCode = 1;\n }\n}\n\nexport function parseOptions(args: string[]): CliOptions {\n const options: CliOptions = { limit: DEFAULT_RECENT_LIMIT, limitProvided: false, json: false, dryRun: false, keepData: false };\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n if (arg === \"--json\") {\n options.json = true;\n } else if (arg === \"--dry-run\") {\n options.dryRun = true;\n } else if (arg === \"--keep-data\") {\n options.keepData = true;\n } else if (arg === \"--db\") {\n const value = args[index + 1];\n if (value) options.dbPath = value;\n index += 1;\n } else if (arg === \"--data-dir\") {\n const value = args[index + 1];\n if (value) options.dataDir = value;\n index += 1;\n } else if (arg === \"--limit\") {\n options.limit = Number.parseInt(args[index + 1] ?? \"20\", 10);\n options.limitProvided = true;\n index += 1;\n } else if (arg === \"--host\") {\n const value = args[index + 1];\n if (value) options.host = value;\n index += 1;\n } else if (arg === \"--port\") {\n options.port = Number.parseInt(args[index + 1] ?? \"8765\", 10);\n index += 1;\n } else if (arg === \"--output\" || arg === \"-o\") {\n const value = args[index + 1];\n if (value) options.output = value;\n index += 1;\n } else if (arg === \"--config-dir\") {\n const value = args[index + 1];\n if (value) options.configDir = value;\n index += 1;\n }\n }\n if (!Number.isFinite(options.limit) || options.limit < 1) options.limit = DEFAULT_RECENT_LIMIT;\n if (options.port !== undefined && (!Number.isFinite(options.port) || options.port < 1)) options.port = 8765;\n return options;\n}\n\nfunction parsePositionals(args: string[]) {\n const positionals: string[] = [];\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n if (!arg) continue;\n if (arg.startsWith(\"--\")) {\n if ([\"--db\", \"--data-dir\", \"--limit\", \"--host\", \"--port\", \"--output\", \"--config-dir\"].includes(arg)) index += 1;\n continue;\n }\n if (arg === \"-o\") {\n index += 1;\n continue;\n }\n positionals.push(arg);\n }\n return positionals;\n}\n\nfunction historyReadOptions(options: CliOptions) {\n return { ...options, limit: options.limitProvided ? options.limit : DEFAULT_HISTORY_LIMIT };\n}\n\nasync function readSession(sessionID: string, options: CliOptions) {\n const records = await readRecentCaptures(historyReadOptions(options));\n const session = buildRequestHistory(records).sessions.find((item) => item.id === sessionID);\n if (!session) {\n throw new Error(\n `Session not found in the latest ${historyReadOptions(options).limit} capture rows: ${sessionID}. Try --limit 20000 or check opencode-insights sessions.`\n );\n }\n return session;\n}\n\nexport function summarizeSessions(sessions: ReturnType<typeof buildRequestHistory>[\"sessions\"]) {\n return sessions.map((session) => {\n const hookCount = session.requests.length;\n const responseCount = session.messages.filter((message) => message.response).length;\n const updatedAt = session.updatedAt ?? Math.max(0, ...session.messages.map((message) => message.completedAt ?? message.createdAt ?? 0));\n return {\n id: session.id,\n title: session.title ?? \"\",\n updatedAt: updatedAt || undefined,\n messages: session.messages.length,\n hooks: hookCount,\n responses: responseCount\n };\n });\n}\n\nexport function formatSessionSummary(rows: ReturnType<typeof summarizeSessions>) {\n if (rows.length === 0) return \"No sessions found.\";\n const header = [\"updated\".padEnd(24), \"messages\".padStart(8), \"hooks\".padStart(6), \"responses\".padStart(9), \"session\".padEnd(28), \"title\"].join(\n \" \"\n );\n const body = rows.map((row) =>\n [\n (row.updatedAt ? new Date(row.updatedAt).toISOString() : \"-\").padEnd(24),\n String(row.messages).padStart(8),\n String(row.hooks).padStart(6),\n String(row.responses).padStart(9),\n row.id.padEnd(28),\n row.title || \"-\"\n ].join(\" \")\n );\n return [header, ...body].join(\"\\n\");\n}\n\nasync function openBrowser(url: string) {\n const platform = process.platform;\n if (platform === \"darwin\") {\n await execFileAsync(\"open\", [url]);\n return;\n }\n if (platform === \"win32\") {\n await execFileAsync(\"cmd\", [\"/c\", \"start\", \"\", url]);\n return;\n }\n await execFileAsync(\"xdg-open\", [url]);\n}\n\nasync function runDoctor(options: CliOptions) {\n const dbPath = resolveCapturePath(options);\n const jsonlPath = dbPath.endsWith(\".sqlite\") ? `${dbPath}.jsonl` : dbPath;\n const rows = [\n `OpenCode Insights doctor`,\n `DB path: ${dbPath}`,\n `DB exists: ${existsSync(dbPath) ? \"yes\" : \"no\"}`,\n `JSONL fallback exists: ${existsSync(jsonlPath) ? \"yes\" : \"no\"}`\n ];\n\n if (existsSync(dbPath)) {\n rows.push(...(await sqliteDiagnostics(dbPath)));\n } else if (existsSync(jsonlPath)) {\n const records = await readRecentCaptures({ ...options, limit: options.limitProvided ? options.limit : DEFAULT_HISTORY_LIMIT });\n rows.push(`Readable fallback records: ${records.length}`);\n }\n\n return rows.join(\"\\n\");\n}\n\nasync function sqliteDiagnostics(dbPath: string) {\n const diagnostics: string[] = [];\n try {\n const tableRows = await sqliteJsonQuery(dbPath, \"select name from sqlite_master where type='table' order by name;\");\n const tables = tableRows.filter((row) => typeof row.name === \"string\").map((row) => row.name);\n diagnostics.push(`SQLite CLI: yes`);\n diagnostics.push(`Tables: ${tables.join(\", \") || \"-\"}`);\n if (tables.includes(\"captures\")) {\n const captureRows = await sqliteJsonQuery(dbPath, \"select count(*) as captures from captures;\");\n const kindRows = await sqliteJsonQuery(dbPath, \"select kind, count(*) as count from captures group by kind order by kind;\");\n const captureRow = captureRows.find((row) => typeof row.captures === \"number\");\n diagnostics.push(`Capture rows: ${captureRow?.captures ?? \"unknown\"}`);\n diagnostics.push(`Capture kinds: ${kindRows.map((row) => `${row.kind}=${row.count}`).join(\", \") || \"-\"}`);\n } else {\n diagnostics.push(\"Capture rows: unavailable (missing captures table)\");\n }\n const integrityRows = await sqliteJsonQuery(dbPath, \"pragma integrity_check;\");\n const integrity = integrityRows.find((row) => typeof row.integrity_check === \"string\");\n diagnostics.push(`Integrity: ${integrity?.integrity_check ?? \"unknown\"}`);\n } catch (error) {\n diagnostics.push(`SQLite CLI: unavailable or failed (${error instanceof Error ? error.message : String(error)})`);\n const records = await readRecentCaptures({ dbPath, limit: DEFAULT_RECENT_LIMIT });\n diagnostics.push(`Readable records via fallback: ${records.length}`);\n }\n return diagnostics;\n}\n\nasync function sqliteJsonQuery(dbPath: string, sql: string) {\n const { stdout } = await execFileAsync(\"sqlite3\", [\"-json\", dbPath, sql], { maxBuffer: 128 * 1024 * 1024 });\n return stdout.trim() ? (JSON.parse(stdout) as Record<string, unknown>[]) : [];\n}\n\nasync function vacuumDatabase(options: CliOptions) {\n const dbPath = resolveCapturePath(options);\n if (!existsSync(dbPath)) return `No SQLite DB found at ${dbPath}`;\n await execFileAsync(\"sqlite3\", [dbPath, \"vacuum;\"]);\n return `Vacuumed ${dbPath}`;\n}\n\ntype JsonObject = Record<string, unknown>;\n\nexport async function configureOpenCode(options: CliOptions) {\n const configDir = options.configDir ?? defaultOpenCodeConfigDir();\n const opencodePath = resolveOpenCodeConfigPath(configDir);\n const tuiPath = join(configDir, \"tui.json\");\n\n const opencodeConfig = await readJsonConfig(opencodePath, { plugin: [] });\n const tuiConfig = await readJsonConfig(tuiPath, { plugin: [] });\n const opencodeChanged = addUniquePlugin(opencodeConfig, SERVER_PLUGIN_SPEC);\n const tuiChanged = addUniquePlugin(tuiConfig, TUI_PLUGIN_SPEC);\n\n const lines = [\n `OpenCode config: ${opencodePath}`,\n `TUI config: ${tuiPath}`,\n `Server plugin: ${opencodeChanged ? \"added\" : \"already present\"} (${SERVER_PLUGIN_SPEC})`,\n `TUI plugin: ${tuiChanged ? \"added\" : \"already present\"} (${TUI_PLUGIN_SPEC})`\n ];\n\n if (options.dryRun) {\n lines.push(\"Dry run: no files written.\");\n return lines.join(\"\\n\");\n }\n\n await mkdir(configDir, { recursive: true });\n if (opencodeChanged || !existsSync(opencodePath)) await writeJsonConfig(opencodePath, opencodeConfig);\n if (tuiChanged || !existsSync(tuiPath)) await writeJsonConfig(tuiPath, tuiConfig);\n lines.push(\"Configuration written. Restart OpenCode to load the plugin.\");\n return lines.join(\"\\n\");\n}\n\nexport function defaultOpenCodeConfigDir() {\n const override = process.env.OPENCODE_CONFIG_DIR;\n if (override) return override;\n if (process.platform === \"win32\") return join(process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\"), \"opencode\");\n return join(homedir(), \".config\", \"opencode\");\n}\n\nexport function resolveOpenCodeConfigPath(configDir: string) {\n const jsoncPath = join(configDir, \"opencode.jsonc\");\n if (existsSync(jsoncPath)) return jsoncPath;\n const jsonPath = join(configDir, \"opencode.json\");\n if (existsSync(jsonPath)) return jsonPath;\n return jsonPath;\n}\n\nasync function readJsonConfig(path: string, fallback: JsonObject) {\n if (!existsSync(path)) return { ...fallback };\n const content = await readFile(path, \"utf8\");\n const trimmed = content.trim();\n if (!trimmed) return { ...fallback };\n try {\n const parsed = JSON.parse(stripJsonCommentsAndTrailingCommas(trimmed)) as unknown;\n return isJsonObject(parsed) ? parsed : { ...fallback };\n } catch (error) {\n throw new Error(`Could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport function stripJsonCommentsAndTrailingCommas(input: string) {\n let output = \"\";\n let inString = false;\n let quote = \"\";\n let escaped = false;\n for (let index = 0; index < input.length; index += 1) {\n const current = input[index] ?? \"\";\n const next = input[index + 1] ?? \"\";\n if (inString) {\n output += current;\n if (escaped) {\n escaped = false;\n } else if (current === \"\\\\\") {\n escaped = true;\n } else if (current === quote) {\n inString = false;\n }\n continue;\n }\n if (current === '\"' || current === \"'\") {\n inString = true;\n quote = current;\n output += current;\n continue;\n }\n if (current === \"/\" && next === \"/\") {\n while (index < input.length && input[index] !== \"\\n\") index += 1;\n output += \"\\n\";\n continue;\n }\n if (current === \"/\" && next === \"*\") {\n index += 2;\n while (index < input.length && !(input[index] === \"*\" && input[index + 1] === \"/\")) index += 1;\n index += 1;\n continue;\n }\n output += current;\n }\n return output.replace(/,\\s*([}\\]])/g, \"$1\");\n}\n\nfunction isJsonObject(value: unknown): value is JsonObject {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function addUniquePlugin(config: JsonObject, plugin: string) {\n const current = Array.isArray(config.plugin) ? config.plugin : [];\n if (current.includes(plugin)) {\n config.plugin = current;\n return false;\n }\n config.plugin = [...current, plugin];\n return true;\n}\n\nexport function removePlugin(config: JsonObject, plugin: string) {\n const current = Array.isArray(config.plugin) ? config.plugin : [];\n const next = current.filter((entry) => !isPluginEntry(entry, plugin));\n config.plugin = next;\n return next.length !== current.length;\n}\n\nfunction isPluginEntry(entry: unknown, plugin: string) {\n return entry === plugin || (Array.isArray(entry) && entry[0] === plugin);\n}\n\nexport async function uninstallOpenCode(options: CliOptions) {\n const configDir = options.configDir ?? defaultOpenCodeConfigDir();\n const opencodePath = resolveOpenCodeConfigPath(configDir);\n const tuiPath = join(configDir, \"tui.json\");\n const dbPath = resolveCapturePath(options);\n const jsonlPath = dbPath.endsWith(\".sqlite\") ? `${dbPath}.jsonl` : dbPath;\n\n const lines = [\n `OpenCode config: ${opencodePath}`,\n `TUI config: ${tuiPath}`,\n `DB path: ${dbPath}`,\n `JSONL fallback path: ${jsonlPath}`\n ];\n\n const opencodeResult = await removePluginFromConfig(opencodePath, SERVER_PLUGIN_SPEC, options);\n const tuiResult = await removePluginFromConfig(tuiPath, TUI_PLUGIN_SPEC, options);\n lines.push(`Server plugin: ${opencodeResult}`);\n lines.push(`TUI plugin: ${tuiResult}`);\n\n if (options.keepData) {\n lines.push(\"Data cleanup: skipped (--keep-data).\");\n } else {\n const removed = await removeDataFiles([dbPath, jsonlPath], options);\n lines.push(`Data cleanup: ${removed.length ? `removed ${removed.join(\", \")}` : \"no data files found\"}`);\n }\n\n if (options.dryRun) {\n lines.push(\"Dry run: no files changed.\");\n } else {\n lines.push(\"Uninstall cleanup complete. Restart OpenCode to unload the plugin.\");\n }\n\n return lines.join(\"\\n\");\n}\n\nasync function removePluginFromConfig(path: string, plugin: string, options: CliOptions) {\n if (!existsSync(path)) return \"config not found\";\n const config = await readJsonConfig(path, { plugin: [] });\n const changed = removePlugin(config, plugin);\n if (!changed) return `not present (${plugin})`;\n if (options.dryRun) return `would remove (${plugin})`;\n await writeJsonConfig(path, config);\n return `removed (${plugin})`;\n}\n\nasync function removeDataFiles(paths: string[], options: CliOptions) {\n const uniquePaths = [...new Set(paths)];\n const existing = uniquePaths.filter((path) => existsSync(path));\n if (options.dryRun) return existing;\n for (const path of existing) {\n await rm(path, { force: true });\n }\n return existing;\n}\n\nasync function writeJsonConfig(path: string, config: JsonObject) {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n}\n\nfunction usage() {\n return [\n \"Usage:\",\n \" opencode-insights configure [--config-dir DIR] [--dry-run]\",\n \" opencode-insights uninstall [--config-dir DIR] [--db PATH] [--data-dir DIR] [--keep-data] [--dry-run]\",\n \" opencode-insights recent [--db PATH] [--data-dir DIR] [--limit N] [--json]\",\n \" opencode-insights sessions [--db PATH] [--data-dir DIR] [--limit N] [--json]\",\n \" opencode-insights history [--db PATH] [--data-dir DIR] [--limit N]\",\n \" opencode-insights show <session-id> [--db PATH] [--data-dir DIR] [--limit N]\",\n \" opencode-insights export <session-id> [--output PATH] [--db PATH] [--data-dir DIR] [--limit N]\",\n \" opencode-insights serve [--db PATH] [--data-dir DIR] [--limit N] [--host HOST] [--port PORT]\",\n \" opencode-insights open [--db PATH] [--data-dir DIR] [--limit N] [--host HOST] [--port PORT]\",\n \" opencode-insights doctor [--db PATH] [--data-dir DIR]\",\n \" opencode-insights vacuum [--db PATH] [--data-dir DIR]\"\n ].join(\"\\n\");\n}\n\nfunction isDirectRun() {\n const entry = process.argv[1];\n if (!entry) return false;\n try {\n return pathToFileURL(realpathSync(entry)).href === import.meta.url;\n } catch {\n return pathToFileURL(entry).href === import.meta.url;\n }\n}\n\nif (isDirectRun()) {\n main(process.argv).catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n });\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { resolveCapturePath, type CaptureRecord, type InsightsOptions } from \"./capture.js\";\n\nconst execFileAsync = promisify(execFile);\n\ntype SqliteRow = {\n id: string;\n kind: CaptureRecord[\"kind\"];\n timestamp: number;\n session_id: string | null;\n message_id: string | null;\n provider_id: string | null;\n model_id: string | null;\n payload_json: string;\n};\n\ntype BunDatabase = {\n query(sql: string): { all(...params: unknown[]): SqliteRow[] };\n close(): void;\n};\n\nexport type HistoryMessage = {\n id: string;\n sessionID: string;\n role: string;\n createdAt?: number | undefined;\n completedAt?: number | undefined;\n text: string;\n requests: HistoryRequest[];\n response?: HistoryResponse | undefined;\n};\n\nexport type HistoryRequest = {\n id: string;\n sessionID?: string | undefined;\n messageID?: string | undefined;\n timestamp: number;\n agent?: string | undefined;\n purpose: string;\n providerID?: string | undefined;\n modelID?: string | undefined;\n summary: string;\n payload: Record<string, unknown>;\n headers?: HistoryRequestHeaders | undefined;\n response?: HistoryResponse | undefined;\n};\n\nexport type HistoryRequestHeaders = {\n id: string;\n timestamp: number;\n payload: Record<string, unknown>;\n};\n\nexport type HistoryResponse = {\n id: string;\n sessionID: string;\n role: string;\n parentID?: string | undefined;\n createdAt?: number | undefined;\n completedAt?: number | undefined;\n text: string;\n reasoning: string;\n tokens?: unknown;\n cost?: number | undefined;\n finish?: string | undefined;\n events: Record<string, unknown>[];\n};\n\nexport type HistorySession = {\n id: string;\n title?: string | undefined;\n updatedAt?: number | undefined;\n messages: HistoryMessage[];\n requests: HistoryRequest[];\n};\n\nexport type RequestHistory = {\n sessions: HistorySession[];\n requests: HistoryRequest[];\n};\n\nexport function parseJsonlRecords(input: string): CaptureRecord[] {\n return input\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => JSON.parse(line) as CaptureRecord);\n}\n\nexport function formatCaptureSummary(records: CaptureRecord[]): string {\n if (records.length === 0) return \"No capture records found.\";\n\n const rows = records.map((record) => {\n const at = new Date(record.timestamp).toISOString();\n const providerModel = [record.providerID, record.modelID].filter(Boolean).join(\"/\");\n return [\n at,\n record.kind.padEnd(20),\n (record.sessionID ?? \"-\").padEnd(16),\n (record.messageID ?? \"-\").padEnd(16),\n providerModel || \"-\"\n ].join(\" \");\n });\n\n return [\n [\"timestamp\".padEnd(24), \"kind\".padEnd(20), \"session\".padEnd(16), \"message\".padEnd(16), \"provider/model\"].join(\" \"),\n ...rows\n ].join(\"\\n\");\n}\n\nexport async function readRecentCaptures(options: InsightsOptions & { limit?: number } = {}): Promise<CaptureRecord[]> {\n const dbPath = resolveCapturePath(options);\n const sqliteRecords = await readSqliteCaptures(dbPath, options.limit ?? 20);\n if (sqliteRecords) return sqliteRecords;\n\n const jsonlPath = dbPath.endsWith(\".sqlite\") ? `${dbPath}.jsonl` : dbPath;\n if (!existsSync(jsonlPath)) return [];\n\n const records = parseJsonlRecords(await readFile(jsonlPath, \"utf8\"));\n return records.slice(-Math.max(1, options.limit ?? 20)).reverse();\n}\n\nexport function buildRequestHistory(records: CaptureRecord[]): RequestHistory {\n const sessions = new Map<string, HistorySession>();\n const messages = new Map<string, HistoryMessage>();\n const responses = new Map<string, HistoryResponse>();\n const responseByParent = new Map<string, HistoryResponse>();\n const requests: HistoryRequest[] = [];\n\n const getSession = (sessionID: string): HistorySession => {\n const existing = sessions.get(sessionID);\n if (existing) return existing;\n const created = { id: sessionID, messages: [], requests: [] };\n sessions.set(sessionID, created);\n return created;\n };\n\n const getMessage = (sessionID: string, messageID: string, role = \"unknown\"): HistoryMessage => {\n const key = `${sessionID}:${messageID}`;\n const existing = messages.get(key);\n if (existing) {\n if (existing.role === \"unknown\" && role !== \"unknown\") existing.role = role;\n return existing;\n }\n const created: HistoryMessage = { id: messageID, sessionID, role, text: \"\", requests: [] };\n messages.set(key, created);\n getSession(sessionID).messages.push(created);\n return created;\n };\n\n const getResponse = (sessionID: string, messageID: string, role = \"assistant\"): HistoryResponse => {\n const key = `${sessionID}:${messageID}`;\n const existing = responses.get(key);\n if (existing) {\n if (existing.role === \"unknown\" && role !== \"unknown\") existing.role = role;\n return existing;\n }\n const created: HistoryResponse = { id: messageID, sessionID, role, text: \"\", reasoning: \"\", events: [] };\n responses.set(key, created);\n return created;\n };\n\n for (const record of records.slice().sort((a, b) => a.timestamp - b.timestamp)) {\n if (record.sessionID) getSession(record.sessionID);\n\n if (record.kind === \"chat.message\") {\n const message = historyMessageFromChatMessage(record);\n if (message) {\n const existing = getMessage(message.sessionID, message.id, message.role);\n existing.createdAt = message.createdAt ?? existing.createdAt;\n existing.text = message.text || existing.text;\n }\n }\n\n if (record.kind === \"chat.params\" || record.kind === \"experimental.chat.messages.transform\" || record.kind === \"experimental.chat.system.transform\") {\n const request = historyRequestFromCapture(record);\n if (!request.messageID && request.sessionID) {\n request.messageID = latestUserMessageBefore(messages, request.sessionID, request.timestamp)?.id;\n }\n requests.push(request);\n if (request.sessionID) {\n getSession(request.sessionID).requests.push(request);\n if (request.messageID) getMessage(request.sessionID, request.messageID, \"user\").requests.push(request);\n }\n }\n\n if (record.kind === \"chat.headers\") {\n attachHeadersToRequest(requests, record);\n }\n\n if (record.kind !== \"event\") continue;\n const event = record.payload.event;\n if (!isRecord(event)) continue;\n\n const type = optionalString(event.type);\n const properties = isRecord(event.properties) ? event.properties : {};\n\n if (type === \"session.updated\" || type === \"session.created\") {\n const info = isRecord(properties.info) ? properties.info : {};\n const sessionID = optionalString(info.id) ?? optionalString(properties.sessionID);\n if (!sessionID) continue;\n const session = getSession(sessionID);\n session.title = optionalString(info.title) ?? session.title;\n session.updatedAt = numberFromPath(info.time, \"updated\") ?? session.updatedAt;\n continue;\n }\n\n if (type === \"message.updated\") {\n const info = isRecord(properties.info) ? properties.info : {};\n const sessionID = optionalString(info.sessionID) ?? optionalString(properties.sessionID);\n const messageID = optionalString(info.id);\n if (!sessionID || !messageID) continue;\n const role = optionalString(info.role) ?? \"unknown\";\n if (role === \"assistant\") {\n const response = getResponse(sessionID, messageID, role);\n response.createdAt = numberFromPath(info.time, \"created\") ?? response.createdAt;\n response.completedAt = numberFromPath(info.time, \"completed\") ?? response.completedAt;\n response.parentID = optionalString(info.parentID) ?? response.parentID;\n response.tokens = info.tokens ?? response.tokens;\n response.cost = typeof info.cost === \"number\" ? info.cost : response.cost;\n response.finish = optionalString(info.finish) ?? response.finish;\n response.events.push(record.payload);\n if (response.parentID) responseByParent.set(`${sessionID}:${response.parentID}`, response);\n continue;\n }\n const message = getMessage(sessionID, messageID, role);\n message.createdAt = numberFromPath(info.time, \"created\") ?? message.createdAt;\n message.completedAt = numberFromPath(info.time, \"completed\") ?? message.completedAt;\n continue;\n }\n\n if (type === \"message.part.updated\" || type === \"message.part.delta\") {\n const part = isRecord(properties.part) ? properties.part : {};\n const sessionID = optionalString(part.sessionID) ?? optionalString(properties.sessionID);\n const messageID = optionalString(part.messageID) ?? optionalString(properties.messageID);\n if (!sessionID || !messageID) continue;\n const partType = optionalString(part.type);\n const field = optionalString(properties.field);\n const delta = optionalString(properties.delta);\n const text = optionalString(part.text);\n const reasonText = optionalString(part.text) ?? optionalString(part.markdown);\n const targetResponse = responses.get(`${sessionID}:${messageID}`);\n if (targetResponse) {\n targetResponse.events.push(record.payload);\n if (partType === \"text\" && text !== undefined) targetResponse.text = text;\n if (partType === \"reasoning\" && reasonText !== undefined) targetResponse.reasoning = reasonText;\n if (type === \"message.part.delta\" && field === \"text\" && delta !== undefined) targetResponse.text += delta;\n continue;\n }\n if (partType !== \"text\" && partType !== \"reasoning\") continue;\n if (text === undefined) continue;\n const message = getMessage(sessionID, messageID);\n message.text = text;\n }\n }\n\n for (const session of sessions.values()) {\n for (const message of session.messages) {\n message.response = responseByParent.get(`${message.sessionID}:${message.id}`);\n for (const request of message.requests) {\n if (requestShouldOwnAssistantResponse(request)) request.response = message.response;\n }\n }\n session.messages.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0));\n session.requests.sort((a, b) => a.timestamp - b.timestamp);\n }\n\n return {\n sessions: [...sessions.values()].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)),\n requests: requests.sort((a, b) => b.timestamp - a.timestamp)\n };\n}\n\nasync function readSqliteCaptures(path: string, limit: number): Promise<CaptureRecord[] | undefined> {\n if (!existsSync(path)) return undefined;\n\n try {\n const mod = (await import(\"bun:sqlite\").catch(() => undefined)) as\n | { Database: new (path: string, options?: { readonly?: boolean }) => BunDatabase }\n | undefined;\n if (!mod) return readSqliteCapturesWithCli(path, limit);\n\n const db = new mod.Database(path, { readonly: true });\n try {\n const rows = db.query(recentCaptureSql(Math.max(1, limit))).all();\n return dedupeRows(rows).map(rowToCapture);\n } finally {\n db.close();\n }\n } catch {\n return readSqliteCapturesWithCli(path, limit);\n }\n}\n\nasync function readSqliteCapturesWithCli(path: string, limit: number): Promise<CaptureRecord[] | undefined> {\n if (!existsSync(path)) return undefined;\n\n try {\n const { stdout } = await execFileAsync(\"sqlite3\", [\"-json\", path, recentCaptureSql(Math.max(1, Math.trunc(limit)))], {\n maxBuffer: 128 * 1024 * 1024\n });\n if (!stdout.trim()) return [];\n return dedupeRows(JSON.parse(stdout) as SqliteRow[]).map(rowToCapture);\n } catch {\n return undefined;\n }\n}\n\nfunction recentCaptureSql(limit: number) {\n return `select id, kind, timestamp, session_id, message_id, provider_id, model_id, payload_json\n from captures\n where id in (\n select id from captures order by timestamp desc limit ${limit}\n )\n or id in (\n select id from captures\n where kind in (\n 'chat.params',\n 'chat.message',\n 'experimental.chat.messages.transform',\n 'experimental.chat.system.transform'\n )\n order by timestamp desc\n limit ${limit}\n )\n or id in (\n select id from captures\n where kind = 'event'\n and json_extract(payload_json, '$.event.type') in ('message.updated', 'message.part.updated', 'message.part.delta')\n order by timestamp desc\n limit ${limit}\n )\n order by timestamp desc`;\n}\n\nfunction dedupeRows(rows: SqliteRow[]) {\n const seen = new Set<string>();\n return rows.filter((row) => {\n if (seen.has(row.id)) return false;\n seen.add(row.id);\n return true;\n });\n}\n\nfunction rowToCapture(row: SqliteRow): CaptureRecord {\n return {\n id: row.id,\n kind: row.kind,\n timestamp: row.timestamp,\n sessionID: row.session_id ?? undefined,\n messageID: row.message_id ?? undefined,\n providerID: row.provider_id ?? undefined,\n modelID: row.model_id ?? undefined,\n payload: JSON.parse(row.payload_json) as Record<string, unknown>\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction historyRequestFromCapture(record: CaptureRecord): HistoryRequest {\n const input = isRecord(record.payload.input) ? record.payload.input : {};\n const agent = agentFromCapture(record, input);\n return {\n id: record.id,\n sessionID: record.sessionID,\n messageID: messageIDForCapture(record, input),\n timestamp: record.timestamp,\n agent,\n purpose: purposeFromAgent(agent),\n providerID: record.providerID,\n modelID: record.modelID,\n summary: summarizePayload(record.payload),\n payload: record.payload\n };\n}\n\nfunction requestShouldOwnAssistantResponse(request: HistoryRequest) {\n return request.agent !== \"title\";\n}\n\nfunction purposeFromAgent(agent: string | undefined) {\n if (agent === \"title\") return \"Generate or update the session title. This is not the assistant reply shown in the conversation.\";\n if (agent === \"build\") return \"Generate the assistant response for the user message.\";\n if (agent === \"messages.transform\") return \"Capture the final conversation messages OpenCode prepared before model execution.\";\n if (agent === \"system.transform\") return \"Capture the system prompt strings OpenCode prepared before model execution.\";\n if (agent) return `Run the ${agent} agent for this message.`;\n return \"Run an OpenCode model request for this message.\";\n}\n\nfunction agentFromCapture(record: CaptureRecord, input: Record<string, unknown>) {\n if (record.kind === \"experimental.chat.messages.transform\") return \"messages.transform\";\n if (record.kind === \"experimental.chat.system.transform\") return \"system.transform\";\n return optionalString(input.agent);\n}\n\nfunction messageIDForCapture(record: CaptureRecord, input: Record<string, unknown>) {\n if (record.kind === \"experimental.chat.messages.transform\") {\n return latestUserMessageIDFromTransform(record.payload.output) ?? record.messageID;\n }\n return record.messageID ?? messageIDFromPayload(input.message);\n}\n\nfunction latestUserMessageIDFromTransform(output: unknown) {\n if (!isRecord(output) || !Array.isArray(output.messages)) return undefined;\n for (const message of output.messages.slice().reverse()) {\n if (!isRecord(message) || !isRecord(message.info)) continue;\n if (optionalString(message.info.role) === \"user\") return optionalString(message.info.id);\n }\n return undefined;\n}\n\nfunction latestUserMessageBefore(messages: Map<string, HistoryMessage>, sessionID: string, timestamp: number) {\n let latest: HistoryMessage | undefined;\n for (const message of messages.values()) {\n if (message.sessionID !== sessionID || message.role !== \"user\") continue;\n if ((message.createdAt ?? Number.NEGATIVE_INFINITY) > timestamp) continue;\n if (!latest || (message.createdAt ?? 0) > (latest.createdAt ?? 0)) latest = message;\n }\n return latest;\n}\n\nfunction summarizePayload(payload: Record<string, unknown>) {\n const input = isRecord(payload.input) ? payload.input : {};\n const message = isRecord(input.message) ? input.message : {};\n const text = textFromMessagePayload(message) ?? findFirstString(input, [\"prompt\", \"input\"]);\n if (!text) return \"LLM request\";\n return text.replace(/\\s+/g, \" \").trim().slice(0, 160);\n}\n\nfunction historyMessageFromChatMessage(record: CaptureRecord): HistoryMessage | undefined {\n const output = isRecord(record.payload.output) ? record.payload.output : {};\n const message = isRecord(output.message) ? output.message : {};\n const sessionID = record.sessionID ?? optionalString(message.sessionID);\n const messageID = optionalString(message.id);\n if (!sessionID || !messageID) return undefined;\n return {\n id: messageID,\n sessionID,\n role: optionalString(message.role) ?? \"user\",\n createdAt: numberFromPath(message.time, \"created\") ?? record.timestamp,\n completedAt: numberFromPath(message.time, \"completed\"),\n text: textFromChatMessageOutput(output),\n requests: []\n };\n}\n\nfunction attachHeadersToRequest(requests: HistoryRequest[], record: CaptureRecord) {\n const input = isRecord(record.payload.input) ? record.payload.input : {};\n const agent = optionalString(input.agent);\n const messageID = record.messageID ?? messageIDFromPayload(input.message);\n const match = requests\n .slice()\n .reverse()\n .find((request) => {\n return (\n request.sessionID === record.sessionID &&\n request.messageID === messageID &&\n request.agent === agent &&\n request.providerID === record.providerID &&\n request.modelID === record.modelID &&\n request.timestamp <= record.timestamp &&\n !request.headers\n );\n });\n if (!match) return;\n match.headers = { id: record.id, timestamp: record.timestamp, payload: record.payload };\n}\n\nfunction textFromChatMessageOutput(output: Record<string, unknown>) {\n const parts = Array.isArray(output.parts) ? output.parts : [];\n return parts\n .map((part) => (isRecord(part) ? optionalString(part.text) ?? optionalString(part.content) : undefined))\n .filter((text): text is string => !!text)\n .join(\"\\n\");\n}\n\nfunction textFromMessagePayload(message: Record<string, unknown>) {\n const direct = findFirstString(message, [\"text\", \"content\"]);\n if (direct) return direct;\n const parts = Array.isArray(message.parts) ? message.parts : [];\n const joined = parts\n .map((part) => (isRecord(part) ? optionalString(part.text) ?? optionalString(part.content) : undefined))\n .filter((text): text is string => !!text)\n .join(\"\\n\");\n return joined || undefined;\n}\n\nfunction messageIDFromPayload(message: unknown) {\n return isRecord(message) ? optionalString(message.id) ?? optionalString(message.messageID) ?? optionalString(message.messageId) : undefined;\n}\n\nfunction findFirstString(value: Record<string, unknown>, keys: string[]) {\n for (const key of keys) {\n const item = value[key];\n if (typeof item === \"string\" && item.length > 0) return item;\n }\n return undefined;\n}\n\nfunction numberFromPath(value: unknown, key: string) {\n if (!isRecord(value)) return undefined;\n const item = value[key];\n return typeof item === \"number\" && Number.isFinite(item) ? item : undefined;\n}\n","import { createServer, type ServerResponse } from \"node:http\";\nimport { resolveCapturePath, type InsightsOptions } from \"./capture.js\";\nimport { buildRequestHistory, readRecentCaptures, type RequestHistory } from \"./inspect.js\";\n\nexport type ViewerOptions = InsightsOptions & {\n limit?: number | undefined;\n host?: string | undefined;\n port?: number | undefined;\n};\n\nexport async function serveViewer(options: ViewerOptions = {}) {\n const host = options.host ?? \"127.0.0.1\";\n const port = options.port ?? 8765;\n\n const server = createServer(async (request, response) => {\n const url = new URL(request.url ?? \"/\", `http://${host}:${port}`);\n\n if (url.pathname === \"/api/history\") {\n const history = await readHistory(options);\n sendJson(response, history);\n return;\n }\n\n if (url.pathname === \"/\" || url.pathname === \"/index.html\") {\n sendHtml(response, renderViewerHtml(resolveCapturePath(options)));\n return;\n }\n\n response.writeHead(404, { \"content-type\": \"text/plain; charset=utf-8\" });\n response.end(\"Not found\");\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n\n return {\n url: `http://${host}:${port}`,\n close: () => new Promise<void>((resolve) => server.close(() => resolve()))\n };\n}\n\nexport async function readHistory(options: ViewerOptions = {}) {\n const readOptions: InsightsOptions & { limit?: number } = {\n dataDir: options.dataDir,\n dbPath: options.dbPath,\n limit: options.limit ?? 5000\n };\n const records = await readRecentCaptures(readOptions);\n return buildRequestHistory(records);\n}\n\nfunction sendJson(response: ServerResponse, value: unknown) {\n response.writeHead(200, { \"content-type\": \"application/json; charset=utf-8\" });\n response.end(JSON.stringify(value));\n}\n\nfunction sendHtml(response: ServerResponse, html: string) {\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(html);\n}\n\nfunction renderViewerHtml(dbPath: string) {\n const escapedDbPath = escapeHtml(dbPath);\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>OpenCode Insights</title>\n <style>\n :root {\n color-scheme: dark;\n --bg: #101214;\n --panel: #171a1d;\n --panel-2: #20242a;\n --line: #30363d;\n --text: #eef2f5;\n --muted: #9aa6b2;\n --accent: #7dd3fc;\n --ok: #86efac;\n --warn: #fbbf24;\n --bad: #fca5a5;\n }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n min-height: 100vh;\n background: var(--bg);\n color: var(--text);\n font: 13px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace;\n }\n header {\n height: 56px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 18px;\n border-bottom: 1px solid var(--line);\n background: #0d0f11;\n }\n h1 { margin: 0; font-size: 15px; font-weight: 700; }\n main {\n display: grid;\n grid-template-columns: 300px minmax(340px, 0.95fr) minmax(460px, 1.25fr);\n height: calc(100vh - 56px);\n min-height: 540px;\n }\n section { min-width: 0; overflow: auto; border-right: 1px solid var(--line); }\n section:last-child { border-right: 0; }\n .toolbar {\n position: sticky;\n top: 0;\n display: flex;\n gap: 8px;\n padding: 10px;\n background: var(--panel);\n border-bottom: 1px solid var(--line);\n z-index: 2;\n }\n input {\n width: 100%;\n border: 1px solid var(--line);\n background: #0d0f11;\n color: var(--text);\n border-radius: 6px;\n padding: 8px 10px;\n font: inherit;\n }\n button {\n border: 1px solid var(--line);\n background: var(--panel-2);\n color: var(--text);\n border-radius: 6px;\n padding: 8px 10px;\n font: inherit;\n cursor: pointer;\n }\n button:hover { border-color: var(--accent); }\n button:disabled { cursor: wait; opacity: 0.6; }\n .item {\n width: 100%;\n display: block;\n border: 0;\n border-bottom: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n text-align: left;\n padding: 11px 12px;\n }\n .item:hover, .item.active { background: var(--panel-2); }\n .request-item { padding-left: 28px; }\n .title { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .meta {\n margin-top: 4px;\n color: var(--muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .muted { color: var(--muted); }\n .pill {\n color: #0d0f11;\n background: var(--accent);\n border-radius: 999px;\n padding: 1px 6px;\n font-weight: 700;\n }\n .pill.msg { background: var(--ok); }\n .pill.missing { background: var(--warn); }\n .detail { padding: 14px; }\n .tabs { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }\n .tabs button.active { border-color: var(--accent); color: var(--accent); }\n .tabs .json-control { display: none; margin-left: auto; }\n .tabs .json-control + .json-control { margin-left: 0; }\n .tabs.json-mode .json-control { display: inline-block; }\n .panel, pre {\n margin: 0;\n padding: 14px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n background: #0b0d0f;\n border: 1px solid var(--line);\n border-radius: 8px;\n min-height: 220px;\n }\n .empty { color: var(--muted); padding: 16px; }\n .status { color: var(--muted); min-width: 220px; text-align: right; }\n .status.error { color: var(--bad); }\n .kv { display: grid; grid-template-columns: 140px minmax(0, 1fr); gap: 8px 12px; }\n .kv div:nth-child(odd) { color: var(--muted); }\n .explain {\n margin: 0 0 12px;\n color: var(--muted);\n }\n .subhead {\n margin: 16px 0 8px;\n color: var(--text);\n font-weight: 700;\n }\n .json-tree { font-size: 12px; line-height: 1.55; }\n .json-tree details { margin-left: 14px; }\n .json-tree summary { cursor: pointer; color: var(--accent); }\n .json-tree .leaf { margin-left: 14px; }\n .json-key { color: #bae6fd; }\n .json-string { color: #bbf7d0; }\n .json-number { color: #fde68a; }\n .json-boolean { color: #f0abfc; }\n .json-null { color: var(--muted); }\n @media (max-width: 1000px) {\n main { grid-template-columns: 1fr; height: auto; }\n section { min-height: 360px; border-right: 0; border-bottom: 1px solid var(--line); }\n }\n </style>\n</head>\n<body>\n <header>\n <h1>OpenCode Insights</h1>\n <div>\n <div class=\"meta\">${escapedDbPath}</div>\n <div id=\"status\" class=\"status\">Loading history...</div>\n </div>\n </header>\n <main>\n <section>\n <div class=\"toolbar\"><input id=\"session-filter\" placeholder=\"Filter sessions\"></div>\n <div id=\"sessions\"></div>\n </section>\n <section>\n <div class=\"toolbar\"><input id=\"timeline-filter\" placeholder=\"Filter messages and hooks\"></div>\n <div id=\"timeline\"></div>\n </section>\n <section>\n <div class=\"toolbar\">\n <button id=\"copy\">Copy JSON</button>\n <button id=\"refresh\">Refresh</button>\n </div>\n <div class=\"detail\">\n <div class=\"tabs\">\n <button data-tab=\"summary\" class=\"active\">Summary</button>\n <button data-tab=\"request\">Request</button>\n <button data-tab=\"response\">Response</button>\n <button data-tab=\"raw\">Raw</button>\n <button id=\"expand-json\" class=\"json-control\">Expand All</button>\n <button id=\"collapse-json\" class=\"json-control\">Collapse All</button>\n </div>\n <div id=\"detail\" class=\"panel\">Select a message or request.</div>\n </div>\n </section>\n </main>\n <script>\n const state = {\n history: { sessions: [], requests: [] },\n sessionID: null,\n messageID: null,\n requestID: null,\n tab: \"summary\",\n loading: true,\n error: null,\n loadedMs: 0\n };\n\n const qs = (id) => document.getElementById(id);\n const fmt = (ms) => ms ? new Date(ms).toLocaleString() : \"-\";\n const short = (value, size = 90) => {\n const text = String(value || \"\").replace(/\\\\s+/g, \" \").trim();\n return text.length > size ? text.slice(0, size - 1) + \"...\" : text;\n };\n\n async function load() {\n state.loading = true;\n state.error = null;\n renderStatus();\n qs(\"sessions\").innerHTML = '<div class=\"empty\">Loading sessions and hooks...</div>';\n qs(\"timeline\").innerHTML = '<div class=\"empty\">Waiting for history data...</div>';\n try {\n const started = performance.now();\n const res = await fetch(\"/api/history\");\n if (!res.ok) throw new Error(\"HTTP \" + res.status + \" \" + res.statusText);\n state.history = await res.json();\n state.loadedMs = Math.round(performance.now() - started);\n if (!state.sessionID && state.history.sessions[0]) state.sessionID = state.history.sessions[0].id;\n if (state.sessionID && !state.history.sessions.some((session) => session.id === state.sessionID)) {\n state.sessionID = state.history.sessions[0]?.id || null;\n state.messageID = null;\n state.requestID = null;\n }\n const session = selectedSession();\n if (session && !state.messageID && session.messages[0]) state.messageID = session.messages[0].id;\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error);\n } finally {\n state.loading = false;\n render();\n }\n }\n\n function selectedSession() {\n return state.history.sessions.find((session) => session.id === state.sessionID);\n }\n\n function selectedMessage() {\n const session = selectedSession();\n return session?.messages.find((message) => message.id === state.messageID) || null;\n }\n\n function selectedRequest() {\n const message = selectedMessage();\n return message?.requests.find((request) => request.id === state.requestID)\n || state.history.requests.find((request) => request.id === state.requestID)\n || null;\n }\n\n function activeContext() {\n const message = selectedMessage();\n const request = selectedRequest();\n return { message, request: request || message?.requests[0] || null };\n }\n\n function render() {\n renderStatus();\n if (state.error) {\n qs(\"sessions\").innerHTML = '<div class=\"empty\">Failed to load history.</div>';\n qs(\"timeline\").innerHTML = '<div class=\"empty\">' + escapeHtml(state.error) + '</div>';\n qs(\"detail\").textContent = \"Refresh after checking the viewer server.\";\n return;\n }\n renderSessions();\n renderTimeline();\n renderDetail();\n }\n\n function renderStatus() {\n const status = qs(\"status\");\n status.classList.toggle(\"error\", Boolean(state.error));\n if (state.error) status.textContent = \"Load failed\";\n else if (state.loading) status.textContent = \"Loading history...\";\n else status.textContent = state.history.sessions.length + \" sessions · \" + state.history.requests.length + \" hooks · \" + state.loadedMs + \"ms\";\n qs(\"refresh\").disabled = state.loading;\n qs(\"refresh\").textContent = state.loading ? \"Loading...\" : \"Refresh\";\n }\n\n function renderSessions() {\n const filter = qs(\"session-filter\").value.toLowerCase();\n const sessions = state.history.sessions.filter((session) =>\n [session.id, session.title].filter(Boolean).join(\" \").toLowerCase().includes(filter)\n );\n qs(\"sessions\").innerHTML = sessions.length ? sessions.map((session) =>\n '<button class=\"item ' + (session.id === state.sessionID ? \"active\" : \"\") + '\" data-session=\"' + escapeAttr(session.id) + '\">' +\n '<div class=\"title\">' + escapeHtml(session.title || session.id) + '</div>' +\n '<div class=\"meta\">' + session.messages.length + ' messages · ' + session.requests.length + ' hooks · ' + fmt(session.updatedAt) + '</div>' +\n '</button>'\n ).join(\"\") : '<div class=\"empty\">No sessions found.</div>';\n for (const item of document.querySelectorAll(\"[data-session]\")) {\n item.onclick = () => {\n state.sessionID = item.dataset.session;\n const session = selectedSession();\n state.messageID = session?.messages[0]?.id || null;\n state.requestID = null;\n render();\n };\n }\n }\n\n function renderTimeline() {\n const session = selectedSession();\n if (!session) {\n qs(\"timeline\").innerHTML = '<div class=\"empty\">Select a session.</div>';\n return;\n }\n const filter = qs(\"timeline-filter\").value.toLowerCase();\n const blocks = [];\n for (const message of session.messages) {\n const searchable = JSON.stringify(message).toLowerCase();\n if (filter && !searchable.includes(filter)) continue;\n blocks.push(\n '<button class=\"item ' + (message.id === state.messageID && !state.requestID ? \"active\" : \"\") + '\" data-message=\"' + escapeAttr(message.id) + '\">' +\n '<div class=\"title\"><span class=\"pill msg\">MSG</span> ' + escapeHtml(message.role) + ' · ' + escapeHtml(short(message.text || message.id, 110)) + '</div>' +\n '<div class=\"meta\">' + message.requests.length + ' hooks · response ' + (message.response?.text ? \"captured\" : \"missing\") + ' · ' + fmt(message.createdAt) + '</div>' +\n '</button>'\n );\n for (const request of message.requests) {\n blocks.push(\n '<button class=\"item request-item ' + (request.id === state.requestID ? \"active\" : \"\") + '\" data-message=\"' + escapeAttr(message.id) + '\" data-request=\"' + escapeAttr(request.id) + '\">' +\n '<div class=\"title\"><span class=\"pill\">HOOK</span> ' + escapeHtml(request.agent || \"agent\") + ' · ' + escapeHtml([request.providerID, request.modelID].filter(Boolean).join(\"/\") || \"-\") + '</div>' +\n '<div class=\"meta\">' + escapeHtml(short(request.summary, 120)) + ' · ' + (request.headers ? \"headers\" : \"no headers\") + ' · ' + (request.response?.text ? \"response\" : \"no response\") + '</div>' +\n '</button>'\n );\n }\n }\n qs(\"timeline\").innerHTML = blocks.length ? blocks.join(\"\") : '<div class=\"empty\">No messages or hooks found.</div>';\n for (const item of document.querySelectorAll(\"[data-message]\")) {\n item.onclick = () => {\n state.messageID = item.dataset.message;\n state.requestID = item.dataset.request || null;\n render();\n };\n }\n }\n\n function renderDetail() {\n const { message, request } = activeContext();\n document.querySelector(\".tabs\").classList.toggle(\"json-mode\", state.tab !== \"summary\");\n if (!message && !request) {\n qs(\"detail\").textContent = \"Select a message or request.\";\n return;\n }\n\n if (state.tab === \"summary\") {\n const response = request?.response || message?.response;\n qs(\"detail\").innerHTML = '<div class=\"kv\">' +\n kv(\"Message\", message?.id || \"-\") +\n kv(\"Message text\", message?.text || \"(no user text captured)\") +\n kv(\"Selected hook\", request?.id || \"-\") +\n kv(\"Agent\", request?.agent || \"-\") +\n kv(\"Purpose\", request?.purpose || \"-\") +\n kv(\"Provider/model\", [request?.providerID, request?.modelID].filter(Boolean).join(\"/\") || \"-\") +\n kv(\"Request time\", fmt(request?.timestamp)) +\n kv(\"Headers hook\", request?.headers ? \"captured\" : \"empty / not captured\") +\n kv(\"Direct response\", request?.response?.text ? \"captured\" : request ? \"none for this hook\" : \"-\") +\n kv(\"Message response\", response?.id || \"-\") +\n kv(\"Response text\", response?.text || \"(no assistant text captured)\") +\n kv(\"Finish\", response?.finish || \"-\") +\n kv(\"Cost\", response?.cost === undefined ? \"-\" : String(response.cost)) +\n '</div>';\n return;\n }\n\n if (state.tab === \"request\") {\n renderRequestDetail(request);\n return;\n }\n\n if (state.tab === \"response\") {\n const response = state.requestID ? request?.response : message?.response;\n renderJson(response || {\n status: \"missing\",\n note: request?.agent === \"title\"\n ? \"The title request does not produce the assistant reply. Select the build request or MSG row to inspect the conversation response.\"\n : \"No assistant response events were captured for this message yet.\"\n });\n return;\n }\n\n renderJson({ message, request });\n }\n\n function kv(key, value) {\n return '<div>' + escapeHtml(key) + '</div><div>' + escapeHtml(value) + '</div>';\n }\n\n function renderJson(value) {\n qs(\"detail\").innerHTML = '<div class=\"json-tree\">' + jsonNode(value, \"root\", true) + '</div>';\n }\n\n function renderRequestDetail(request) {\n if (!request) {\n renderJson({ status: \"missing\", note: \"Select a hook row to inspect hook details.\" });\n return;\n }\n const payload = request.payload || {};\n const hookInput = payload.input || {};\n const hookOutput = payload.output || {};\n const headerOutput = request.headers?.payload?.output || null;\n qs(\"detail\").innerHTML =\n '<p class=\"explain\">These are OpenCode plugin hook values, not a raw HTTP request. <b>Hook input</b> is the context OpenCode passed to the plugin before the model call. <b>Hook output</b> is the model settings returned by the plugin hook. <b>Headers output</b> is the provider headers hook result.</p>' +\n '<div class=\"kv\">' +\n kv(\"Hook id\", request.id) +\n kv(\"Agent\", request.agent || \"-\") +\n kv(\"Purpose\", request.purpose || \"-\") +\n kv(\"Provider/model\", [request.providerID, request.modelID].filter(Boolean).join(\"/\") || \"-\") +\n kv(\"User message id\", request.messageID || \"-\") +\n kv(\"Time\", fmt(request.timestamp)) +\n '</div>' +\n '<div class=\"subhead\">Hook Input: context OpenCode supplied</div>' +\n '<div class=\"json-tree\">' + jsonNode(summarizeHookInput(hookInput), \"hookInput\", true) + '</div>' +\n '<div class=\"subhead\">Hook Output: model-call settings</div>' +\n '<div class=\"json-tree\">' + jsonNode(hookOutput, \"hookOutput\", true) + '</div>' +\n '<div class=\"subhead\">Headers Hook Output</div>' +\n '<div class=\"json-tree\">' + jsonNode(headerOutput, \"headersOutput\", true) + '</div>' +\n '<div class=\"subhead\">Raw Full-Fidelity Payload</div>' +\n '<div class=\"json-tree\">' + jsonNode({ params: request.payload, headers: request.headers?.payload || null }, \"raw\", false) + '</div>';\n }\n\n function summarizeHookInput(input) {\n return {\n sessionID: input?.sessionID,\n agent: input?.agent,\n model: input?.model ? {\n id: input.model.id,\n providerID: input.model.providerID,\n name: input.model.name,\n family: input.model.family,\n api: input.model.api,\n limit: input.model.limit,\n capabilities: input.model.capabilities,\n cost: input.model.cost\n } : null,\n provider: input?.provider ? {\n id: input.provider.id,\n source: input.provider.source,\n name: input.provider.name,\n env: input.provider.env,\n options: input.provider.options,\n key: input.provider.key\n } : null,\n message: input?.message || null\n };\n }\n\n function setJsonExpanded(expanded) {\n for (const node of qs(\"detail\").querySelectorAll(\"details\")) node.open = expanded;\n }\n\n function jsonNode(value, key, open) {\n if (value === null) return leaf(key, '<span class=\"json-null\">null</span>');\n if (Array.isArray(value)) {\n const children = value.map((item, index) => jsonNode(item, String(index), false)).join(\"\");\n return '<details ' + (open ? \"open\" : \"\") + '><summary>' + label(key) + ' Array(' + value.length + ')</summary>' + children + '</details>';\n }\n if (typeof value === \"object\") {\n const keys = Object.keys(value);\n const children = keys.map((childKey) => jsonNode(value[childKey], childKey, false)).join(\"\");\n return '<details ' + (open ? \"open\" : \"\") + '><summary>' + label(key) + ' Object(' + keys.length + ')</summary>' + children + '</details>';\n }\n if (typeof value === \"string\") return leaf(key, '<span class=\"json-string\">' + escapeHtml(JSON.stringify(value)) + '</span>');\n if (typeof value === \"number\") return leaf(key, '<span class=\"json-number\">' + value + '</span>');\n if (typeof value === \"boolean\") return leaf(key, '<span class=\"json-boolean\">' + value + '</span>');\n return leaf(key, escapeHtml(String(value)));\n }\n\n function label(key) {\n return '<span class=\"json-key\">' + escapeHtml(key) + '</span>: ';\n }\n\n function leaf(key, value) {\n return '<div class=\"leaf\">' + label(key) + value + '</div>';\n }\n\n function escapeHtml(value) {\n return String(value ?? \"\").replace(/[&<>\"']/g, (char) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[char]));\n }\n\n function escapeAttr(value) {\n return escapeHtml(value).replace(new RegExp(String.fromCharCode(96), \"g\"), \"&#96;\");\n }\n\n qs(\"session-filter\").oninput = render;\n qs(\"timeline-filter\").oninput = render;\n qs(\"refresh\").onclick = load;\n qs(\"expand-json\").onclick = () => setJsonExpanded(true);\n qs(\"collapse-json\").onclick = () => setJsonExpanded(false);\n qs(\"copy\").onclick = async () => {\n const { message, request } = activeContext();\n const value = state.tab === \"request\" ? { params: request?.payload || null, headers: request?.headers?.payload || null }\n : state.tab === \"response\" ? (request?.response || message?.response || null)\n : state.tab === \"raw\" ? { message, request }\n : { messageID: message?.id, requestID: request?.id };\n await navigator.clipboard.writeText(JSON.stringify(value, null, 2));\n };\n for (const tab of document.querySelectorAll(\"[data-tab]\")) {\n tab.onclick = () => {\n state.tab = tab.dataset.tab;\n for (const item of document.querySelectorAll(\"[data-tab]\")) item.classList.toggle(\"active\", item === tab);\n renderDetail();\n };\n }\n load();\n </script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(value: string) {\n return value.replace(/[&<>\"']/g, (char) => {\n if (char === \"&\") return \"&amp;\";\n if (char === \"<\") return \"&lt;\";\n if (char === \">\") return \"&gt;\";\n if (char === '\"') return \"&quot;\";\n return \"&#39;\";\n });\n}\n"],"mappings":";;;;;;AACA,SAAS,YAAAA,iBAAgB;AACzB,SAAS,cAAAC,aAAY,oBAAoB;AACzC,SAAS,OAAO,YAAAC,WAAU,IAAI,iBAAiB;AAC/C,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,aAAAC,kBAAiB;;;ACP1B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAG1B,IAAM,gBAAgB,UAAU,QAAQ;AA8EjC,SAAS,kBAAkB,OAAgC;AAChE,SAAO,MACJ,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAkB;AACpD;AAEO,SAAS,qBAAqB,SAAkC;AACrE,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,OAAO,QAAQ,IAAI,CAAC,WAAW;AACnC,UAAM,KAAK,IAAI,KAAK,OAAO,SAAS,EAAE,YAAY;AAClD,UAAM,gBAAgB,CAAC,OAAO,YAAY,OAAO,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAClF,WAAO;AAAA,MACL;AAAA,MACA,OAAO,KAAK,OAAO,EAAE;AAAA,OACpB,OAAO,aAAa,KAAK,OAAO,EAAE;AAAA,OAClC,OAAO,aAAa,KAAK,OAAO,EAAE;AAAA,MACnC,iBAAiB;AAAA,IACnB,EAAE,KAAK,IAAI;AAAA,EACb,CAAC;AAED,SAAO;AAAA,IACL,CAAC,YAAY,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE,GAAG,UAAU,OAAO,EAAE,GAAG,UAAU,OAAO,EAAE,GAAG,gBAAgB,EAAE,KAAK,IAAI;AAAA,IACnH,GAAG;AAAA,EACL,EAAE,KAAK,IAAI;AACb;AAEA,eAAsB,mBAAmB,UAAgD,CAAC,GAA6B;AACrH,QAAM,SAAS,mBAAmB,OAAO;AACzC,QAAM,gBAAgB,MAAM,mBAAmB,QAAQ,QAAQ,SAAS,EAAE;AAC1E,MAAI,cAAe,QAAO;AAE1B,QAAM,YAAY,OAAO,SAAS,SAAS,IAAI,GAAG,MAAM,WAAW;AACnE,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO,CAAC;AAEpC,QAAM,UAAU,kBAAkB,MAAM,SAAS,WAAW,MAAM,CAAC;AACnE,SAAO,QAAQ,MAAM,CAAC,KAAK,IAAI,GAAG,QAAQ,SAAS,EAAE,CAAC,EAAE,QAAQ;AAClE;AAEO,SAAS,oBAAoB,SAA0C;AAC5E,QAAM,WAAW,oBAAI,IAA4B;AACjD,QAAM,WAAW,oBAAI,IAA4B;AACjD,QAAM,YAAY,oBAAI,IAA6B;AACnD,QAAM,mBAAmB,oBAAI,IAA6B;AAC1D,QAAM,WAA6B,CAAC;AAEpC,QAAM,aAAa,CAAC,cAAsC;AACxD,UAAM,WAAW,SAAS,IAAI,SAAS;AACvC,QAAI,SAAU,QAAO;AACrB,UAAM,UAAU,EAAE,IAAI,WAAW,UAAU,CAAC,GAAG,UAAU,CAAC,EAAE;AAC5D,aAAS,IAAI,WAAW,OAAO;AAC/B,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,CAAC,WAAmB,WAAmB,OAAO,cAA8B;AAC7F,UAAM,MAAM,GAAG,SAAS,IAAI,SAAS;AACrC,UAAM,WAAW,SAAS,IAAI,GAAG;AACjC,QAAI,UAAU;AACZ,UAAI,SAAS,SAAS,aAAa,SAAS,UAAW,UAAS,OAAO;AACvE,aAAO;AAAA,IACT;AACA,UAAM,UAA0B,EAAE,IAAI,WAAW,WAAW,MAAM,MAAM,IAAI,UAAU,CAAC,EAAE;AACzF,aAAS,IAAI,KAAK,OAAO;AACzB,eAAW,SAAS,EAAE,SAAS,KAAK,OAAO;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,CAAC,WAAmB,WAAmB,OAAO,gBAAiC;AACjG,UAAM,MAAM,GAAG,SAAS,IAAI,SAAS;AACrC,UAAM,WAAW,UAAU,IAAI,GAAG;AAClC,QAAI,UAAU;AACZ,UAAI,SAAS,SAAS,aAAa,SAAS,UAAW,UAAS,OAAO;AACvE,aAAO;AAAA,IACT;AACA,UAAM,UAA2B,EAAE,IAAI,WAAW,WAAW,MAAM,MAAM,IAAI,WAAW,IAAI,QAAQ,CAAC,EAAE;AACvG,cAAU,IAAI,KAAK,OAAO;AAC1B,WAAO;AAAA,EACT;AAEA,aAAW,UAAU,QAAQ,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,GAAG;AAC9E,QAAI,OAAO,UAAW,YAAW,OAAO,SAAS;AAEjD,QAAI,OAAO,SAAS,gBAAgB;AAClC,YAAM,UAAU,8BAA8B,MAAM;AACpD,UAAI,SAAS;AACX,cAAM,WAAW,WAAW,QAAQ,WAAW,QAAQ,IAAI,QAAQ,IAAI;AACvE,iBAAS,YAAY,QAAQ,aAAa,SAAS;AACnD,iBAAS,OAAO,QAAQ,QAAQ,SAAS;AAAA,MAC3C;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,iBAAiB,OAAO,SAAS,0CAA0C,OAAO,SAAS,sCAAsC;AACnJ,YAAM,UAAU,0BAA0B,MAAM;AAChD,UAAI,CAAC,QAAQ,aAAa,QAAQ,WAAW;AAC3C,gBAAQ,YAAY,wBAAwB,UAAU,QAAQ,WAAW,QAAQ,SAAS,GAAG;AAAA,MAC/F;AACA,eAAS,KAAK,OAAO;AACrB,UAAI,QAAQ,WAAW;AACrB,mBAAW,QAAQ,SAAS,EAAE,SAAS,KAAK,OAAO;AACnD,YAAI,QAAQ,UAAW,YAAW,QAAQ,WAAW,QAAQ,WAAW,MAAM,EAAE,SAAS,KAAK,OAAO;AAAA,MACvG;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,gBAAgB;AAClC,6BAAuB,UAAU,MAAM;AAAA,IACzC;AAEA,QAAI,OAAO,SAAS,QAAS;AAC7B,UAAM,QAAQ,OAAO,QAAQ;AAC7B,QAAI,CAAC,SAAS,KAAK,EAAG;AAEtB,UAAM,OAAO,eAAe,MAAM,IAAI;AACtC,UAAM,aAAa,SAAS,MAAM,UAAU,IAAI,MAAM,aAAa,CAAC;AAEpE,QAAI,SAAS,qBAAqB,SAAS,mBAAmB;AAC5D,YAAM,OAAO,SAAS,WAAW,IAAI,IAAI,WAAW,OAAO,CAAC;AAC5D,YAAM,YAAY,eAAe,KAAK,EAAE,KAAK,eAAe,WAAW,SAAS;AAChF,UAAI,CAAC,UAAW;AAChB,YAAM,UAAU,WAAW,SAAS;AACpC,cAAQ,QAAQ,eAAe,KAAK,KAAK,KAAK,QAAQ;AACtD,cAAQ,YAAY,eAAe,KAAK,MAAM,SAAS,KAAK,QAAQ;AACpE;AAAA,IACF;AAEA,QAAI,SAAS,mBAAmB;AAC9B,YAAM,OAAO,SAAS,WAAW,IAAI,IAAI,WAAW,OAAO,CAAC;AAC5D,YAAM,YAAY,eAAe,KAAK,SAAS,KAAK,eAAe,WAAW,SAAS;AACvF,YAAM,YAAY,eAAe,KAAK,EAAE;AACxC,UAAI,CAAC,aAAa,CAAC,UAAW;AAC9B,YAAM,OAAO,eAAe,KAAK,IAAI,KAAK;AAC1C,UAAI,SAAS,aAAa;AACxB,cAAM,WAAW,YAAY,WAAW,WAAW,IAAI;AACvD,iBAAS,YAAY,eAAe,KAAK,MAAM,SAAS,KAAK,SAAS;AACtE,iBAAS,cAAc,eAAe,KAAK,MAAM,WAAW,KAAK,SAAS;AAC1E,iBAAS,WAAW,eAAe,KAAK,QAAQ,KAAK,SAAS;AAC9D,iBAAS,SAAS,KAAK,UAAU,SAAS;AAC1C,iBAAS,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,SAAS;AACrE,iBAAS,SAAS,eAAe,KAAK,MAAM,KAAK,SAAS;AAC1D,iBAAS,OAAO,KAAK,OAAO,OAAO;AACnC,YAAI,SAAS,SAAU,kBAAiB,IAAI,GAAG,SAAS,IAAI,SAAS,QAAQ,IAAI,QAAQ;AACzF;AAAA,MACF;AACA,YAAM,UAAU,WAAW,WAAW,WAAW,IAAI;AACrD,cAAQ,YAAY,eAAe,KAAK,MAAM,SAAS,KAAK,QAAQ;AACpE,cAAQ,cAAc,eAAe,KAAK,MAAM,WAAW,KAAK,QAAQ;AACxE;AAAA,IACF;AAEA,QAAI,SAAS,0BAA0B,SAAS,sBAAsB;AACpE,YAAM,OAAO,SAAS,WAAW,IAAI,IAAI,WAAW,OAAO,CAAC;AAC5D,YAAM,YAAY,eAAe,KAAK,SAAS,KAAK,eAAe,WAAW,SAAS;AACvF,YAAM,YAAY,eAAe,KAAK,SAAS,KAAK,eAAe,WAAW,SAAS;AACvF,UAAI,CAAC,aAAa,CAAC,UAAW;AAC9B,YAAM,WAAW,eAAe,KAAK,IAAI;AACzC,YAAM,QAAQ,eAAe,WAAW,KAAK;AAC7C,YAAM,QAAQ,eAAe,WAAW,KAAK;AAC7C,YAAM,OAAO,eAAe,KAAK,IAAI;AACrC,YAAM,aAAa,eAAe,KAAK,IAAI,KAAK,eAAe,KAAK,QAAQ;AAC5E,YAAM,iBAAiB,UAAU,IAAI,GAAG,SAAS,IAAI,SAAS,EAAE;AAChE,UAAI,gBAAgB;AAClB,uBAAe,OAAO,KAAK,OAAO,OAAO;AACzC,YAAI,aAAa,UAAU,SAAS,OAAW,gBAAe,OAAO;AACrE,YAAI,aAAa,eAAe,eAAe,OAAW,gBAAe,YAAY;AACrF,YAAI,SAAS,wBAAwB,UAAU,UAAU,UAAU,OAAW,gBAAe,QAAQ;AACrG;AAAA,MACF;AACA,UAAI,aAAa,UAAU,aAAa,YAAa;AACrD,UAAI,SAAS,OAAW;AACxB,YAAM,UAAU,WAAW,WAAW,SAAS;AAC/C,cAAQ,OAAO;AAAA,IACjB;AAAA,EACF;AAEA,aAAW,WAAW,SAAS,OAAO,GAAG;AACvC,eAAW,WAAW,QAAQ,UAAU;AACtC,cAAQ,WAAW,iBAAiB,IAAI,GAAG,QAAQ,SAAS,IAAI,QAAQ,EAAE,EAAE;AAC5E,iBAAW,WAAW,QAAQ,UAAU;AACtC,YAAI,kCAAkC,OAAO,EAAG,SAAQ,WAAW,QAAQ;AAAA,MAC7E;AAAA,IACF;AACA,YAAQ,SAAS,KAAK,CAAC,GAAG,OAAO,EAAE,aAAa,MAAM,EAAE,aAAa,EAAE;AACvE,YAAQ,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,aAAa,MAAM,EAAE,aAAa,EAAE;AAAA,IACvF,UAAU,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,EAC7D;AACF;AAEA,eAAe,mBAAmB,MAAc,OAAqD;AACnG,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAE9B,MAAI;AACF,UAAM,MAAO,MAAM,OAAO,YAAY,EAAE,MAAM,MAAM,MAAS;AAG7D,QAAI,CAAC,IAAK,QAAO,0BAA0B,MAAM,KAAK;AAEtD,UAAM,KAAK,IAAI,IAAI,SAAS,MAAM,EAAE,UAAU,KAAK,CAAC;AACpD,QAAI;AACF,YAAM,OAAO,GAAG,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,IAAI;AAChE,aAAO,WAAW,IAAI,EAAE,IAAI,YAAY;AAAA,IAC1C,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,QAAQ;AACN,WAAO,0BAA0B,MAAM,KAAK;AAAA,EAC9C;AACF;AAEA,eAAe,0BAA0B,MAAc,OAAqD;AAC1G,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAE9B,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc,WAAW,CAAC,SAAS,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG;AAAA,MACnH,WAAW,MAAM,OAAO;AAAA,IAC1B,CAAC;AACD,QAAI,CAAC,OAAO,KAAK,EAAG,QAAO,CAAC;AAC5B,WAAO,WAAW,KAAK,MAAM,MAAM,CAAgB,EAAE,IAAI,YAAY;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,OAAe;AACvC,SAAO;AAAA;AAAA;AAAA,oEAG2D,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAWrD,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAOL,KAAK;AAAA;AAAA;AAGzB;AAEA,SAAS,WAAW,MAAmB;AACrC,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,KAAK,OAAO,CAAC,QAAQ;AAC1B,QAAI,KAAK,IAAI,IAAI,EAAE,EAAG,QAAO;AAC7B,SAAK,IAAI,IAAI,EAAE;AACf,WAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,aAAa,KAA+B;AACnD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,WAAW,IAAI;AAAA,IACf,WAAW,IAAI,cAAc;AAAA,IAC7B,WAAW,IAAI,cAAc;AAAA,IAC7B,YAAY,IAAI,eAAe;AAAA,IAC/B,SAAS,IAAI,YAAY;AAAA,IACzB,SAAS,KAAK,MAAM,IAAI,YAAY;AAAA,EACtC;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AACrE;AAEA,SAAS,eAAe,OAAoC;AAC1D,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEA,SAAS,0BAA0B,QAAuC;AACxE,QAAM,QAAQ,SAAS,OAAO,QAAQ,KAAK,IAAI,OAAO,QAAQ,QAAQ,CAAC;AACvE,QAAM,QAAQ,iBAAiB,QAAQ,KAAK;AAC5C,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,WAAW,OAAO;AAAA,IAClB,WAAW,oBAAoB,QAAQ,KAAK;AAAA,IAC5C,WAAW,OAAO;AAAA,IAClB;AAAA,IACA,SAAS,iBAAiB,KAAK;AAAA,IAC/B,YAAY,OAAO;AAAA,IACnB,SAAS,OAAO;AAAA,IAChB,SAAS,iBAAiB,OAAO,OAAO;AAAA,IACxC,SAAS,OAAO;AAAA,EAClB;AACF;AAEA,SAAS,kCAAkC,SAAyB;AAClE,SAAO,QAAQ,UAAU;AAC3B;AAEA,SAAS,iBAAiB,OAA2B;AACnD,MAAI,UAAU,QAAS,QAAO;AAC9B,MAAI,UAAU,QAAS,QAAO;AAC9B,MAAI,UAAU,qBAAsB,QAAO;AAC3C,MAAI,UAAU,mBAAoB,QAAO;AACzC,MAAI,MAAO,QAAO,WAAW,KAAK;AAClC,SAAO;AACT;AAEA,SAAS,iBAAiB,QAAuB,OAAgC;AAC/E,MAAI,OAAO,SAAS,uCAAwC,QAAO;AACnE,MAAI,OAAO,SAAS,qCAAsC,QAAO;AACjE,SAAO,eAAe,MAAM,KAAK;AACnC;AAEA,SAAS,oBAAoB,QAAuB,OAAgC;AAClF,MAAI,OAAO,SAAS,wCAAwC;AAC1D,WAAO,iCAAiC,OAAO,QAAQ,MAAM,KAAK,OAAO;AAAA,EAC3E;AACA,SAAO,OAAO,aAAa,qBAAqB,MAAM,OAAO;AAC/D;AAEA,SAAS,iCAAiC,QAAiB;AACzD,MAAI,CAAC,SAAS,MAAM,KAAK,CAAC,MAAM,QAAQ,OAAO,QAAQ,EAAG,QAAO;AACjE,aAAW,WAAW,OAAO,SAAS,MAAM,EAAE,QAAQ,GAAG;AACvD,QAAI,CAAC,SAAS,OAAO,KAAK,CAAC,SAAS,QAAQ,IAAI,EAAG;AACnD,QAAI,eAAe,QAAQ,KAAK,IAAI,MAAM,OAAQ,QAAO,eAAe,QAAQ,KAAK,EAAE;AAAA,EACzF;AACA,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAuC,WAAmB,WAAmB;AAC5G,MAAI;AACJ,aAAW,WAAW,SAAS,OAAO,GAAG;AACvC,QAAI,QAAQ,cAAc,aAAa,QAAQ,SAAS,OAAQ;AAChE,SAAK,QAAQ,aAAa,OAAO,qBAAqB,UAAW;AACjE,QAAI,CAAC,WAAW,QAAQ,aAAa,MAAM,OAAO,aAAa,GAAI,UAAS;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAAkC;AAC1D,QAAM,QAAQ,SAAS,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC;AACzD,QAAM,UAAU,SAAS,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC;AAC3D,QAAM,OAAO,uBAAuB,OAAO,KAAK,gBAAgB,OAAO,CAAC,UAAU,OAAO,CAAC;AAC1F,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;AACtD;AAEA,SAAS,8BAA8B,QAAmD;AACxF,QAAM,SAAS,SAAS,OAAO,QAAQ,MAAM,IAAI,OAAO,QAAQ,SAAS,CAAC;AAC1E,QAAM,UAAU,SAAS,OAAO,OAAO,IAAI,OAAO,UAAU,CAAC;AAC7D,QAAM,YAAY,OAAO,aAAa,eAAe,QAAQ,SAAS;AACtE,QAAM,YAAY,eAAe,QAAQ,EAAE;AAC3C,MAAI,CAAC,aAAa,CAAC,UAAW,QAAO;AACrC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,IACA,MAAM,eAAe,QAAQ,IAAI,KAAK;AAAA,IACtC,WAAW,eAAe,QAAQ,MAAM,SAAS,KAAK,OAAO;AAAA,IAC7D,aAAa,eAAe,QAAQ,MAAM,WAAW;AAAA,IACrD,MAAM,0BAA0B,MAAM;AAAA,IACtC,UAAU,CAAC;AAAA,EACb;AACF;AAEA,SAAS,uBAAuB,UAA4B,QAAuB;AACjF,QAAM,QAAQ,SAAS,OAAO,QAAQ,KAAK,IAAI,OAAO,QAAQ,QAAQ,CAAC;AACvE,QAAM,QAAQ,eAAe,MAAM,KAAK;AACxC,QAAM,YAAY,OAAO,aAAa,qBAAqB,MAAM,OAAO;AACxE,QAAM,QAAQ,SACX,MAAM,EACN,QAAQ,EACR,KAAK,CAAC,YAAY;AACjB,WACE,QAAQ,cAAc,OAAO,aAC7B,QAAQ,cAAc,aACtB,QAAQ,UAAU,SAClB,QAAQ,eAAe,OAAO,cAC9B,QAAQ,YAAY,OAAO,WAC3B,QAAQ,aAAa,OAAO,aAC5B,CAAC,QAAQ;AAAA,EAEb,CAAC;AACH,MAAI,CAAC,MAAO;AACZ,QAAM,UAAU,EAAE,IAAI,OAAO,IAAI,WAAW,OAAO,WAAW,SAAS,OAAO,QAAQ;AACxF;AAEA,SAAS,0BAA0B,QAAiC;AAClE,QAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC;AAC5D,SAAO,MACJ,IAAI,CAAC,SAAU,SAAS,IAAI,IAAI,eAAe,KAAK,IAAI,KAAK,eAAe,KAAK,OAAO,IAAI,MAAU,EACtG,OAAO,CAAC,SAAyB,CAAC,CAAC,IAAI,EACvC,KAAK,IAAI;AACd;AAEA,SAAS,uBAAuB,SAAkC;AAChE,QAAM,SAAS,gBAAgB,SAAS,CAAC,QAAQ,SAAS,CAAC;AAC3D,MAAI,OAAQ,QAAO;AACnB,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC9D,QAAM,SAAS,MACZ,IAAI,CAAC,SAAU,SAAS,IAAI,IAAI,eAAe,KAAK,IAAI,KAAK,eAAe,KAAK,OAAO,IAAI,MAAU,EACtG,OAAO,CAAC,SAAyB,CAAC,CAAC,IAAI,EACvC,KAAK,IAAI;AACZ,SAAO,UAAU;AACnB;AAEA,SAAS,qBAAqB,SAAkB;AAC9C,SAAO,SAAS,OAAO,IAAI,eAAe,QAAQ,EAAE,KAAK,eAAe,QAAQ,SAAS,KAAK,eAAe,QAAQ,SAAS,IAAI;AACpI;AAEA,SAAS,gBAAgB,OAAgC,MAAgB;AACvE,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,MAAM,GAAG;AACtB,QAAI,OAAO,SAAS,YAAY,KAAK,SAAS,EAAG,QAAO;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAgB,KAAa;AACnD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,OAAO,MAAM,GAAG;AACtB,SAAO,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,IAAI,OAAO;AACpE;;;AChgBA,SAAS,oBAAyC;AAUlD,eAAsB,YAAY,UAAyB,CAAC,GAAG;AAC7D,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,OAAO,QAAQ,QAAQ;AAE7B,QAAM,SAAS,aAAa,OAAO,SAAS,aAAa;AACvD,UAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,UAAU,IAAI,IAAI,IAAI,EAAE;AAEhE,QAAI,IAAI,aAAa,gBAAgB;AACnC,YAAM,UAAU,MAAM,YAAY,OAAO;AACzC,eAAS,UAAU,OAAO;AAC1B;AAAA,IACF;AAEA,QAAI,IAAI,aAAa,OAAO,IAAI,aAAa,eAAe;AAC1D,eAAS,UAAU,iBAAiB,mBAAmB,OAAO,CAAC,CAAC;AAChE;AAAA,IACF;AAEA,aAAS,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AACvE,aAAS,IAAI,WAAW;AAAA,EAC1B,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,aAAO,IAAI,SAAS,MAAM;AAC1B,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,IAC3B,OAAO,MAAM,IAAI,QAAc,CAAC,YAAY,OAAO,MAAM,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC3E;AACF;AAEA,eAAsB,YAAY,UAAyB,CAAC,GAAG;AAC7D,QAAM,cAAoD;AAAA,IACxD,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,OAAO,QAAQ,SAAS;AAAA,EAC1B;AACA,QAAM,UAAU,MAAM,mBAAmB,WAAW;AACpD,SAAO,oBAAoB,OAAO;AACpC;AAEA,SAAS,SAAS,UAA0B,OAAgB;AAC1D,WAAS,UAAU,KAAK,EAAE,gBAAgB,kCAAkC,CAAC;AAC7E,WAAS,IAAI,KAAK,UAAU,KAAK,CAAC;AACpC;AAEA,SAAS,SAAS,UAA0B,MAAc;AACxD,WAAS,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACtE,WAAS,IAAI,IAAI;AACnB;AAEA,SAAS,iBAAiB,QAAgB;AACxC,QAAM,gBAAgB,WAAW,MAAM;AACvC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BA6JiB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["execFile","existsSync","readFile","promisify","execFileAsync","promisify","execFile","existsSync","readFile"]}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin, PluginModule } from \"@opencode-ai/plugin\";\nimport {\n createCaptureStore,\n normalizeChatHeadersCapture,\n normalizeChatMessageCapture,\n normalizeChatParamsCapture,\n normalizeEventCapture,\n normalizeExperimentalChatMessagesTransformCapture,\n normalizeExperimentalChatSystemTransformCapture,\n normalizeToolCapture,\n type InsightsOptions\n} from \"./capture.js\";\n\nexport const OpenCodeInsights: Plugin = async (_input, options?: InsightsOptions) => {\n const store = createCaptureStore(options);\n\n try {\n await store.initialize?.();\n } catch {\n // Capture is best-effort; plugin startup should not block OpenCode.\n }\n\n async function capture(record: Parameters<typeof store.append>[0]) {\n try {\n await store.append(record);\n } catch {\n // Observability must never interrupt the coding session.\n }\n }\n\n return {\n dispose: async () => {\n await store.close?.();\n },\n event: async ({ event }) => {\n await capture(normalizeEventCapture(event));\n },\n \"chat.message\": async (input, output) => {\n await capture(normalizeChatMessageCapture(input, output));\n },\n \"chat.params\": async (input, output) => {\n await capture(normalizeChatParamsCapture(input, output));\n },\n \"chat.headers\": async (input, output) => {\n await capture(normalizeChatHeadersCapture(input, output));\n },\n \"experimental.chat.messages.transform\": async (input, output) => {\n await capture(normalizeExperimentalChatMessagesTransformCapture(input, output));\n },\n \"experimental.chat.system.transform\": async (input, output) => {\n await capture(normalizeExperimentalChatSystemTransformCapture(input, output));\n },\n \"tool.execute.before\": async (input, output) => {\n await capture(normalizeToolCapture(\"tool.execute.before\", input, output));\n },\n \"tool.execute.after\": async (input, output) => {\n await capture(normalizeToolCapture(\"tool.execute.after\", input, output));\n }\n };\n};\n\nexport const server = OpenCodeInsights;\nexport const id = \"opencode-insights\";\nexport default { id, server } satisfies PluginModule;\nexport * from \"./capture.js\";\nexport * from \"./metrics.js\";\nexport * from \"./subagents.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaO,IAAM,mBAA2B,OAAO,QAAQ,YAA8B;AACnF,QAAM,QAAQ,mBAAmB,OAAO;AAExC,MAAI;AACF,UAAM,MAAM,aAAa;AAAA,EAC3B,QAAQ;AAAA,EAER;AAEA,iBAAe,QAAQ,QAA4C;AACjE,QAAI;AACF,YAAM,MAAM,OAAO,MAAM;AAAA,IAC3B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,YAAY;AACnB,YAAM,MAAM,QAAQ;AAAA,IACtB;AAAA,IACA,OAAO,OAAO,EAAE,MAAM,MAAM;AAC1B,YAAM,QAAQ,sBAAsB,KAAK,CAAC;AAAA,IAC5C;AAAA,IACA,gBAAgB,OAAO,OAAO,WAAW;AACvC,YAAM,QAAQ,4BAA4B,OAAO,MAAM,CAAC;AAAA,IAC1D;AAAA,IACA,eAAe,OAAO,OAAO,WAAW;AACtC,YAAM,QAAQ,2BAA2B,OAAO,MAAM,CAAC;AAAA,IACzD;AAAA,IACA,gBAAgB,OAAO,OAAO,WAAW;AACvC,YAAM,QAAQ,4BAA4B,OAAO,MAAM,CAAC;AAAA,IAC1D;AAAA,IACA,wCAAwC,OAAO,OAAO,WAAW;AAC/D,YAAM,QAAQ,kDAAkD,OAAO,MAAM,CAAC;AAAA,IAChF;AAAA,IACA,sCAAsC,OAAO,OAAO,WAAW;AAC7D,YAAM,QAAQ,gDAAgD,OAAO,MAAM,CAAC;AAAA,IAC9E;AAAA,IACA,uBAAuB,OAAO,OAAO,WAAW;AAC9C,YAAM,QAAQ,qBAAqB,uBAAuB,OAAO,MAAM,CAAC;AAAA,IAC1E;AAAA,IACA,sBAAsB,OAAO,OAAO,WAAW;AAC7C,YAAM,QAAQ,qBAAqB,sBAAsB,OAAO,MAAM,CAAC;AAAA,IACzE;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AACf,IAAM,KAAK;AAClB,IAAO,cAAQ,EAAE,IAAI,OAAO;","names":[]}
package/dist/tui.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tui.tsx"],"sourcesContent":["/** @jsxImportSource @opentui/solid */\nimport { createTextAttributes, StyledText, type TextChunk, type TextRenderable } from \"@opentui/core\";\nimport type { TuiPlugin, TuiPluginApi, TuiPluginModule } from \"@opencode-ai/plugin/tui\";\nimport { onCleanup } from \"solid-js\";\nimport {\n createMetricsState,\n recordAssistantDelta,\n recordAssistantMessage,\n recordToolActivity,\n renderMetricsText\n} from \"./metrics.js\";\nimport {\n applySubagentEvent,\n createSubagentState,\n getSubagentSidebarModel,\n renderSubagentFooter,\n type SubagentState\n} from \"./subagents.js\";\n\ntype Listener = () => void;\n\nfunction isSessionID(value: string | undefined): value is string {\n return typeof value === \"string\" && value.startsWith(\"ses\");\n}\n\nfunction PromptRight(props: {\n api: TuiPluginApi;\n sessionID: string;\n text: () => string;\n subscribe: (listener: Listener) => () => void;\n}) {\n let text: TextRenderable | undefined;\n\n const sync = () => {\n if (!text) return;\n text.content = props.text();\n props.api.renderer.requestRender();\n };\n\n const unsubscribe = props.subscribe(sync);\n onCleanup(unsubscribe);\n\n return (\n <text\n ref={(ref: TextRenderable) => {\n text = ref;\n sync();\n }}\n fg={props.api.theme.current.textMuted}\n >\n {props.text()}\n </text>\n );\n}\n\nfunction ReactiveText(props: {\n api: TuiPluginApi;\n text: () => string;\n subscribe: (listener: Listener) => () => void;\n}) {\n let text: TextRenderable | undefined;\n\n const sync = () => {\n if (!text) return;\n text.content = props.text();\n props.api.renderer.requestRender();\n };\n\n const unsubscribe = props.subscribe(sync);\n onCleanup(unsubscribe);\n\n return (\n <text\n ref={(ref: TextRenderable) => {\n text = ref;\n sync();\n }}\n fg={props.api.theme.current.textMuted}\n >\n {props.text()}\n </text>\n );\n}\n\nfunction SubagentSidebar(props: {\n api: TuiPluginApi;\n sessionID: string;\n state: SubagentState;\n subscribe: (listener: Listener) => () => void;\n}) {\n let text: TextRenderable | undefined;\n const titleAttributes = createTextAttributes({ bold: true });\n\n const sync = () => {\n if (!text) return;\n text.content = renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes);\n props.api.renderer.requestRender();\n };\n\n const unsubscribe = props.subscribe(sync);\n const timer = setInterval(sync, 1_000);\n onCleanup(() => {\n unsubscribe();\n clearInterval(timer);\n });\n\n return (\n <text\n ref={(ref: TextRenderable) => {\n text = ref;\n sync();\n }}\n fg={props.api.theme.current.textMuted}\n >\n {\"\"}\n </text>\n );\n}\n\nfunction renderSubagentStyledSidebar(\n state: SubagentState,\n sessionID: string,\n api: TuiPluginApi,\n titleAttributes: number\n) {\n const model = getSubagentSidebarModel(state, sessionID);\n if (!model) return \"\";\n\n const chunks: TextChunk[] = [\n textChunk(`${model.title}\\n`, api.theme.current.text, titleAttributes),\n textChunk(`${model.summary}\\n`, api.theme.current.textMuted)\n ];\n\n for (const [index, row] of model.rows.entries()) {\n if (index > 0) chunks.push(textChunk(\"\\n\", api.theme.current.textMuted));\n chunks.push(textChunk(`${row.title}\\n`, api.theme.current.text));\n chunks.push(textChunk(row.subtitle, api.theme.current.textMuted));\n }\n\n return new StyledText(chunks);\n}\n\nfunction textChunk(text: string, fg?: TextChunk[\"fg\"], attributes?: number): TextChunk {\n return {\n __isChunk: true,\n text,\n ...(fg === undefined ? {} : { fg }),\n ...(attributes === undefined ? {} : { attributes })\n };\n}\n\nconst tui: TuiPlugin = async (api) => {\n const metrics = createMetricsState();\n const subagents = createSubagentState();\n const listeners = new Set<Listener>();\n const bump = () => {\n for (const listener of listeners) listener();\n };\n const subscribe = (listener: Listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n };\n\n const offDelta = api.event.on(\"message.part.delta\", (evt) => {\n if (evt.properties.field !== \"text\") return;\n recordAssistantDelta(metrics, {\n sessionID: evt.properties.sessionID,\n messageID: evt.properties.messageID,\n delta: evt.properties.delta,\n at: Date.now()\n });\n bump();\n });\n\n const offMessage = api.event.on(\"message.updated\", (evt) => {\n const info = evt.properties.info;\n if (applySubagentEvent(subagents, evt)) bump();\n if (info.role !== \"assistant\") return;\n\n const messageInput: {\n sessionID: string;\n messageID: string;\n createdAt: number;\n completedAt?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n finish?: string;\n } = {\n sessionID: info.sessionID ?? evt.properties.sessionID,\n messageID: info.id,\n createdAt: info.time.created,\n outputTokens: info.tokens.output,\n reasoningTokens: info.tokens.reasoning\n };\n if (typeof info.time.completed === \"number\") messageInput.completedAt = info.time.completed;\n if (typeof info.finish === \"string\") messageInput.finish = info.finish;\n recordAssistantMessage(metrics, messageInput);\n bump();\n });\n\n const offPart = api.event.on(\"message.part.updated\", (evt) => {\n const part = evt.properties.part;\n if (part.type === \"tool\") {\n recordToolActivity(metrics, part.sessionID ?? evt.properties.sessionID, part.messageID, Date.now());\n }\n applySubagentEvent(subagents, evt);\n bump();\n });\n\n const offSessionCreated = api.event.on(\"session.created\", (evt) => {\n if (applySubagentEvent(subagents, evt)) bump();\n });\n\n const offSessionUpdated = api.event.on(\"session.updated\", (evt) => {\n if (applySubagentEvent(subagents, evt)) bump();\n });\n\n const offSessionStatus = api.event.on(\"session.status\", (evt) => {\n if (applySubagentEvent(subagents, evt)) bump();\n });\n\n const offSessionIdle = api.event.on(\"session.idle\", (evt) => {\n if (applySubagentEvent(subagents, evt)) bump();\n });\n\n const offSessionError = api.event.on(\"session.error\", (evt) => {\n if (applySubagentEvent(subagents, evt)) bump();\n });\n\n const offSlots = api.slots.register({\n slots: {\n session_prompt_right: (_ctx, props) => (\n <PromptRight\n api={api}\n sessionID={props.session_id}\n subscribe={subscribe}\n text={() => {\n if (!isSessionID(props.session_id)) return \"\";\n const status = api.state.session.status(props.session_id);\n return renderMetricsText(metrics, props.session_id, { idle: status?.type === \"idle\" });\n }}\n />\n ),\n home_prompt_right: () => (\n <PromptRight\n api={api}\n sessionID=\"\"\n subscribe={subscribe}\n text={() => \"\"}\n />\n ),\n sidebar_content: (_ctx, props) => (\n <SubagentSidebar\n api={api}\n sessionID={props.session_id}\n state={subagents}\n subscribe={subscribe}\n />\n ),\n sidebar_footer: (_ctx, props) => (\n <ReactiveText\n api={api}\n subscribe={subscribe}\n text={() => renderSubagentFooter(subagents, props.session_id)}\n />\n )\n }\n });\n\n api.lifecycle.onDispose(() => {\n offDelta();\n offMessage();\n offPart();\n offSessionCreated();\n offSessionUpdated();\n offSessionStatus();\n offSessionIdle();\n offSessionError();\n });\n};\n\nconst id = \"opencode-insights-tui\";\n\nexport { id, tui };\nexport default { id, tui } satisfies TuiPluginModule;\n"],"mappings":"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","names":[]}