@rejacky/opencode-insights 0.1.0

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.
@@ -0,0 +1 @@
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 } from \"node:fs\";\nimport { mkdir, readFile, 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};\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 {\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 };\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 === \"--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\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 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 return !!entry && pathToFileURL(entry).href === import.meta.url;\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":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;A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","names":["execFile","existsSync","readFile","promisify","execFileAsync","promisify","execFile","existsSync","readFile"]}
@@ -0,0 +1,111 @@
1
+ import { Plugin } from '@opencode-ai/plugin';
2
+ 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
+ type StreamSample = {
5
+ at: number;
6
+ tokens: number;
7
+ };
8
+ type MessageTiming = {
9
+ sessionID: string;
10
+ requestStartAt: number;
11
+ firstResponseAt?: number | undefined;
12
+ firstTokenAt?: number | undefined;
13
+ lastTokenAt?: number | undefined;
14
+ lastToolCallAt?: number | undefined;
15
+ };
16
+ type SessionAverage = {
17
+ totalTokens: number;
18
+ totalDurationMs: number;
19
+ totalTtftMs: number;
20
+ messageCount: number;
21
+ };
22
+ type MetricsState = {
23
+ streamSamplesBySession: Record<string, StreamSample[]>;
24
+ messageTimingByID: Record<string, MessageTiming>;
25
+ sessionAverageByID: Record<string, SessionAverage>;
26
+ };
27
+ declare function createMetricsState(): MetricsState;
28
+ declare function estimateStreamTokens(delta: string): number;
29
+ declare function recordAssistantMessage(state: MetricsState, input: {
30
+ sessionID: string;
31
+ messageID: string;
32
+ createdAt: number;
33
+ completedAt?: number;
34
+ outputTokens?: number;
35
+ reasoningTokens?: number;
36
+ finish?: string;
37
+ }): void;
38
+ declare function recordAssistantDelta(state: MetricsState, input: {
39
+ sessionID: string;
40
+ messageID: string;
41
+ delta: string;
42
+ at: number;
43
+ }): void;
44
+ declare function recordToolActivity(state: MetricsState, sessionID: string, messageID: string, at?: number): void;
45
+ declare function renderMetricsText(state: MetricsState, sessionID: string, options?: {
46
+ now?: number;
47
+ idle?: boolean;
48
+ }): string;
49
+
50
+ type SubagentStatus = "running" | "done" | "error";
51
+ type SubagentInfo = {
52
+ id: string;
53
+ parentID: string;
54
+ title: string;
55
+ status: SubagentStatus;
56
+ startedAt: string;
57
+ updatedAt: string;
58
+ endedAt?: string | undefined;
59
+ elapsedMs?: number | undefined;
60
+ tokens?: {
61
+ input?: number | undefined;
62
+ output?: number | undefined;
63
+ total?: number | undefined;
64
+ contextPercent?: number | undefined;
65
+ } | undefined;
66
+ };
67
+ type SubagentState = {
68
+ children: Record<string, SubagentInfo>;
69
+ totalExecuted: number;
70
+ };
71
+ type SubagentSidebarRow = {
72
+ id: string;
73
+ title: string;
74
+ subtitle: string;
75
+ status: SubagentStatus;
76
+ };
77
+ type SubagentSidebarModel = {
78
+ title: string;
79
+ summary: string;
80
+ rows: SubagentSidebarRow[];
81
+ };
82
+ declare function createSubagentState(): SubagentState;
83
+ declare function applySubagentEvent(state: SubagentState, event: unknown): boolean;
84
+ declare function renderSubagentStatus(state: SubagentState, options?: {
85
+ now?: number;
86
+ }): string;
87
+ declare function getSubagentItems(state: SubagentState, parentID?: string): SubagentInfo[];
88
+ declare function pruneStaleSubagents(state: SubagentState, options?: {
89
+ now?: number;
90
+ staleMs?: number;
91
+ }): boolean;
92
+ declare function getSubagentSidebarModel(state: SubagentState, parentID: string, options?: {
93
+ now?: number;
94
+ staleMs?: number;
95
+ }): SubagentSidebarModel | undefined;
96
+ declare function renderSubagentSidebar(state: SubagentState, parentID: string, options?: {
97
+ now?: number;
98
+ }): string;
99
+ declare function renderSubagentFooter(state: SubagentState, parentID: string, options?: {
100
+ now?: number;
101
+ }): string;
102
+
103
+ declare const OpenCodeInsights: Plugin;
104
+ declare const server: Plugin;
105
+ declare const id = "opencode-insights";
106
+ declare const _default: {
107
+ id: string;
108
+ server: Plugin;
109
+ };
110
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ import {
2
+ applySubagentEvent,
3
+ createMetricsState,
4
+ createSubagentState,
5
+ estimateStreamTokens,
6
+ getSubagentItems,
7
+ getSubagentSidebarModel,
8
+ pruneStaleSubagents,
9
+ recordAssistantDelta,
10
+ recordAssistantMessage,
11
+ recordToolActivity,
12
+ renderMetricsText,
13
+ renderSubagentFooter,
14
+ renderSubagentSidebar,
15
+ renderSubagentStatus
16
+ } from "./chunk-WF6BN2SB.js";
17
+ import {
18
+ JsonlCaptureStore,
19
+ SqliteCaptureStore,
20
+ createCaptureStore,
21
+ defaultDataDir,
22
+ normalizeChatHeadersCapture,
23
+ normalizeChatMessageCapture,
24
+ normalizeChatParamsCapture,
25
+ normalizeEventCapture,
26
+ normalizeExperimentalChatMessagesTransformCapture,
27
+ normalizeExperimentalChatSystemTransformCapture,
28
+ normalizeToolCapture,
29
+ resolveCapturePath
30
+ } from "./chunk-G7E4J6OW.js";
31
+
32
+ // src/index.ts
33
+ var OpenCodeInsights = async (_input, options) => {
34
+ const store = createCaptureStore(options);
35
+ try {
36
+ await store.initialize?.();
37
+ } catch {
38
+ }
39
+ async function capture(record) {
40
+ try {
41
+ await store.append(record);
42
+ } catch {
43
+ }
44
+ }
45
+ return {
46
+ dispose: async () => {
47
+ await store.close?.();
48
+ },
49
+ event: async ({ event }) => {
50
+ await capture(normalizeEventCapture(event));
51
+ },
52
+ "chat.message": async (input, output) => {
53
+ await capture(normalizeChatMessageCapture(input, output));
54
+ },
55
+ "chat.params": async (input, output) => {
56
+ await capture(normalizeChatParamsCapture(input, output));
57
+ },
58
+ "chat.headers": async (input, output) => {
59
+ await capture(normalizeChatHeadersCapture(input, output));
60
+ },
61
+ "experimental.chat.messages.transform": async (input, output) => {
62
+ await capture(normalizeExperimentalChatMessagesTransformCapture(input, output));
63
+ },
64
+ "experimental.chat.system.transform": async (input, output) => {
65
+ await capture(normalizeExperimentalChatSystemTransformCapture(input, output));
66
+ },
67
+ "tool.execute.before": async (input, output) => {
68
+ await capture(normalizeToolCapture("tool.execute.before", input, output));
69
+ },
70
+ "tool.execute.after": async (input, output) => {
71
+ await capture(normalizeToolCapture("tool.execute.after", input, output));
72
+ }
73
+ };
74
+ };
75
+ var server = OpenCodeInsights;
76
+ var id = "opencode-insights";
77
+ var src_default = { id, server };
78
+ export {
79
+ JsonlCaptureStore,
80
+ OpenCodeInsights,
81
+ SqliteCaptureStore,
82
+ applySubagentEvent,
83
+ createCaptureStore,
84
+ createMetricsState,
85
+ createSubagentState,
86
+ src_default as default,
87
+ defaultDataDir,
88
+ estimateStreamTokens,
89
+ getSubagentItems,
90
+ getSubagentSidebarModel,
91
+ id,
92
+ normalizeChatHeadersCapture,
93
+ normalizeChatMessageCapture,
94
+ normalizeChatParamsCapture,
95
+ normalizeEventCapture,
96
+ normalizeExperimentalChatMessagesTransformCapture,
97
+ normalizeExperimentalChatSystemTransformCapture,
98
+ normalizeToolCapture,
99
+ pruneStaleSubagents,
100
+ recordAssistantDelta,
101
+ recordAssistantMessage,
102
+ recordToolActivity,
103
+ renderMetricsText,
104
+ renderSubagentFooter,
105
+ renderSubagentSidebar,
106
+ renderSubagentStatus,
107
+ resolveCapturePath,
108
+ server
109
+ };
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
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.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { TuiPlugin } from '@opencode-ai/plugin/tui';
2
+
3
+ declare const tui: TuiPlugin;
4
+ declare const id = "opencode-insights-tui";
5
+
6
+ declare const _default: {
7
+ id: string;
8
+ tui: TuiPlugin;
9
+ };
10
+
11
+ export { _default as default, id, tui };