@rejacky/opencode-insights 0.1.2 → 0.1.4
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 +25 -41
- package/dist/{capture-gQauLsdn.d.ts → capture-DkASdFpu.d.ts} +3 -0
- package/dist/{chunk-N3MIWWYE.js → chunk-36ZNCLI3.js} +5 -11
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +41 -17
- package/dist/index.d.ts +6 -2
- package/dist/index.js +19 -11
- package/dist/tui.js +28 -50
- package/package.json +2 -1
- package/DEVELOPMENT.md +0 -124
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
|
|
7
|
+
Install globally with OpenCode's plugin manager:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -83,10 +47,11 @@ npm rm @rejacky/opencode-insights
|
|
|
83
47
|
|
|
84
48
|
## What You Get
|
|
85
49
|
|
|
86
|
-
- Live TPS, average TPS, and average TTFT in the OpenCode prompt zone.
|
|
87
|
-
-
|
|
50
|
+
- Live TPS, average TPS, and average TTFT in the OpenCode session prompt zone.
|
|
51
|
+
- Subagent status (running, done, failed, elapsed time, and token/context usage) in the sidebar.
|
|
88
52
|
- Local capture of OpenCode hook/event data without redaction.
|
|
89
53
|
- A local web viewer for sessions, messages, hooks, request context, system/messages transforms, and assistant responses.
|
|
54
|
+
- Native OpenCode footer components (project directory and version) remain visible — the plugin does not override `sidebar_footer` or `home_prompt_right` slots.
|
|
90
55
|
|
|
91
56
|
## Open The Viewer
|
|
92
57
|
|
|
@@ -200,6 +165,25 @@ This plugin intentionally does not redact anything. It stores data locally exact
|
|
|
200
165
|
|
|
201
166
|
Captured data can include prompts, system messages, provider metadata, API keys exposed inside hook payloads, tool arguments, headers, reasoning text, and response events. Use it only on machines where local full-fidelity capture is acceptable.
|
|
202
167
|
|
|
168
|
+
## Experimental Features
|
|
169
|
+
|
|
170
|
+
The request-interception hooks (`chat.headers`, `experimental.chat.messages.transform`, `experimental.chat.system.transform`) are disabled by default. They capture additional request context (HTTP headers, transformed messages, system prompts) but are not yet mature.
|
|
171
|
+
|
|
172
|
+
To enable them, pass `experimental: true` in the plugin options:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"plugin": [
|
|
177
|
+
[
|
|
178
|
+
"@rejacky/opencode-insights",
|
|
179
|
+
{
|
|
180
|
+
"experimental": true
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
203
187
|
## Captured Hooks
|
|
204
188
|
|
|
205
189
|
The viewer labels OpenCode hook records as `HOOK` because they are not raw HTTP requests.
|
|
@@ -17,6 +17,9 @@ type CaptureStore = {
|
|
|
17
17
|
type InsightsOptions = {
|
|
18
18
|
dataDir?: unknown;
|
|
19
19
|
dbPath?: unknown;
|
|
20
|
+
/** Enable experimental request capture (chat.headers, messages/system transforms).
|
|
21
|
+
* Disabled by default — these hooks intercept request data and are not yet mature. */
|
|
22
|
+
experimental?: boolean;
|
|
20
23
|
};
|
|
21
24
|
declare function defaultDataDir(): string;
|
|
22
25
|
declare function resolveCapturePath(options?: InsightsOptions): string;
|
|
@@ -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
|
|
303
|
-
const
|
|
304
|
-
const
|
|
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
|
|
328
|
-
const
|
|
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;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as CaptureRecord } from './capture-
|
|
2
|
+
import { C as CaptureRecord } from './capture-DkASdFpu.js';
|
|
3
3
|
|
|
4
4
|
type HistoryMessage = {
|
|
5
5
|
id: string;
|
|
@@ -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
|
|
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,
|
|
92
|
+
export { addUniquePlugin, configureOpenCodeDebug, defaultOpenCodeConfigDir, formatSessionSummary, parseOptions, removePlugin, resolveOpenCodeConfigPath, stripJsonCommentsAndTrailingCommas, summarizeSessions, uninstallOpenCode };
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
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((
|
|
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
|
-
|
|
405
|
+
resolve2();
|
|
406
406
|
});
|
|
407
407
|
});
|
|
408
408
|
return {
|
|
409
409
|
url: `http://${host}:${port}`,
|
|
410
|
-
close: () => new Promise((
|
|
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 =
|
|
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 === "
|
|
1038
|
-
process.stdout.write(`${await
|
|
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
|
|
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
|
-
|
|
1232
|
-
|
|
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
|
-
`
|
|
1237
|
-
`TUI plugin: ${
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
lines.push("
|
|
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
|
|
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
|
-
|
|
1445
|
+
configureOpenCodeDebug,
|
|
1422
1446
|
defaultOpenCodeConfigDir,
|
|
1423
1447
|
formatSessionSummary,
|
|
1424
1448
|
parseOptions,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
|
|
2
|
+
import { TuiPlugin } from '@opencode-ai/plugin/tui';
|
|
3
|
+
export { a as CaptureKind, C as CaptureRecord, b as CaptureStore, I as InsightsOptions, J as JsonlCaptureStore, S as SqliteCaptureStore, c as 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-DkASdFpu.js';
|
|
3
4
|
|
|
4
5
|
type StreamSample = {
|
|
5
6
|
at: number;
|
|
@@ -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-
|
|
16
|
+
} from "./chunk-36ZNCLI3.js";
|
|
17
17
|
import {
|
|
18
18
|
JsonlCaptureStore,
|
|
19
19
|
SqliteCaptureStore,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
var OpenCodeInsights = async (_input, options) => {
|
|
34
34
|
const store = createCaptureStore(options);
|
|
35
|
+
const experimental = options?.experimental ?? false;
|
|
35
36
|
try {
|
|
36
37
|
await store.initialize?.();
|
|
37
38
|
} catch {
|
|
@@ -55,14 +56,16 @@ var OpenCodeInsights = async (_input, options) => {
|
|
|
55
56
|
"chat.params": async (input, output) => {
|
|
56
57
|
await capture(normalizeChatParamsCapture(input, output));
|
|
57
58
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
...experimental && {
|
|
60
|
+
"chat.headers": async (input, output) => {
|
|
61
|
+
await capture(normalizeChatHeadersCapture(input, output));
|
|
62
|
+
},
|
|
63
|
+
"experimental.chat.messages.transform": async (input, output) => {
|
|
64
|
+
await capture(normalizeExperimentalChatMessagesTransformCapture(input, output));
|
|
65
|
+
},
|
|
66
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
67
|
+
await capture(normalizeExperimentalChatSystemTransformCapture(input, output));
|
|
68
|
+
}
|
|
66
69
|
},
|
|
67
70
|
"tool.execute.before": async (input, output) => {
|
|
68
71
|
await capture(normalizeToolCapture("tool.execute.before", input, output));
|
|
@@ -73,8 +76,12 @@ var OpenCodeInsights = async (_input, options) => {
|
|
|
73
76
|
};
|
|
74
77
|
};
|
|
75
78
|
var server = OpenCodeInsights;
|
|
79
|
+
var rootTui = async (...args) => {
|
|
80
|
+
const mod = await import("./tui.js");
|
|
81
|
+
return mod.tui(...args);
|
|
82
|
+
};
|
|
76
83
|
var id = "opencode-insights";
|
|
77
|
-
var src_default = { id, server };
|
|
84
|
+
var src_default = { id, server, tui: rootTui };
|
|
78
85
|
export {
|
|
79
86
|
JsonlCaptureStore,
|
|
80
87
|
OpenCodeInsights,
|
|
@@ -105,5 +112,6 @@ export {
|
|
|
105
112
|
renderSubagentSidebar,
|
|
106
113
|
renderSubagentStatus,
|
|
107
114
|
resolveCapturePath,
|
|
108
|
-
server
|
|
115
|
+
server,
|
|
116
|
+
rootTui as tui
|
|
109
117
|
};
|
package/dist/tui.js
CHANGED
|
@@ -6,13 +6,12 @@ import {
|
|
|
6
6
|
recordAssistantDelta,
|
|
7
7
|
recordAssistantMessage,
|
|
8
8
|
recordToolActivity,
|
|
9
|
-
renderMetricsText
|
|
10
|
-
|
|
11
|
-
} from "./chunk-N3MIWWYE.js";
|
|
9
|
+
renderMetricsText
|
|
10
|
+
} from "./chunk-36ZNCLI3.js";
|
|
12
11
|
|
|
13
12
|
// src/tui.tsx
|
|
14
13
|
import { createTextAttributes, StyledText } from "@opentui/core";
|
|
15
|
-
import { onCleanup } from "solid-js";
|
|
14
|
+
import { createSignal, onCleanup } from "solid-js";
|
|
16
15
|
import { jsx } from "@opentui/solid/jsx-runtime";
|
|
17
16
|
function isSessionID(value) {
|
|
18
17
|
return typeof value === "string" && value.startsWith("ses");
|
|
@@ -21,28 +20,10 @@ function PromptRight(props) {
|
|
|
21
20
|
let text;
|
|
22
21
|
const sync = () => {
|
|
23
22
|
if (!text) return;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
onCleanup(unsubscribe);
|
|
29
|
-
return /* @__PURE__ */ jsx(
|
|
30
|
-
"text",
|
|
31
|
-
{
|
|
32
|
-
ref: (ref) => {
|
|
33
|
-
text = ref;
|
|
34
|
-
sync();
|
|
35
|
-
},
|
|
36
|
-
fg: props.api.theme.current.textMuted,
|
|
37
|
-
children: props.text()
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
function ReactiveText(props) {
|
|
42
|
-
let text;
|
|
43
|
-
const sync = () => {
|
|
44
|
-
if (!text) return;
|
|
45
|
-
text.content = props.text();
|
|
23
|
+
const content = props.text();
|
|
24
|
+
text.content = content;
|
|
25
|
+
text.visible = content.length > 0;
|
|
26
|
+
text.height = content.length > 0 ? "auto" : 0;
|
|
46
27
|
props.api.renderer.requestRender();
|
|
47
28
|
};
|
|
48
29
|
const unsubscribe = props.subscribe(sync);
|
|
@@ -61,10 +42,18 @@ function ReactiveText(props) {
|
|
|
61
42
|
}
|
|
62
43
|
function SubagentSidebar(props) {
|
|
63
44
|
let text;
|
|
45
|
+
const [collapsed, setCollapsed] = createSignal(false);
|
|
64
46
|
const titleAttributes = createTextAttributes({ bold: true });
|
|
47
|
+
const toggle = () => {
|
|
48
|
+
setCollapsed((prev) => !prev);
|
|
49
|
+
props.api.renderer.requestRender();
|
|
50
|
+
};
|
|
65
51
|
const sync = () => {
|
|
66
52
|
if (!text) return;
|
|
67
|
-
|
|
53
|
+
const model = getSubagentSidebarModel(props.state, props.sessionID);
|
|
54
|
+
text.visible = !!model;
|
|
55
|
+
text.height = model ? "auto" : 0;
|
|
56
|
+
text.content = model ? renderSubagentStyledSidebar(props.state, props.sessionID, props.api, titleAttributes, collapsed()) : "";
|
|
68
57
|
props.api.renderer.requestRender();
|
|
69
58
|
};
|
|
70
59
|
const unsubscribe = props.subscribe(sync);
|
|
@@ -80,25 +69,31 @@ function SubagentSidebar(props) {
|
|
|
80
69
|
text = ref;
|
|
81
70
|
sync();
|
|
82
71
|
},
|
|
72
|
+
onMouseDown: toggle,
|
|
83
73
|
fg: props.api.theme.current.textMuted,
|
|
84
74
|
children: ""
|
|
85
75
|
}
|
|
86
76
|
);
|
|
87
77
|
}
|
|
88
|
-
function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes) {
|
|
78
|
+
function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, collapsed) {
|
|
89
79
|
const model = getSubagentSidebarModel(state, sessionID);
|
|
90
80
|
if (!model) return "";
|
|
81
|
+
const indicator = collapsed ? "\u25B6 " : "\u25BC ";
|
|
91
82
|
const chunks = [
|
|
92
|
-
textChunk(`${model.title}
|
|
83
|
+
textChunk(`${indicator}${model.title}
|
|
93
84
|
`, api.theme.current.text, titleAttributes),
|
|
94
85
|
textChunk(`${model.summary}
|
|
95
86
|
`, api.theme.current.textMuted)
|
|
96
87
|
];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
if (!collapsed) {
|
|
89
|
+
for (const [index, row] of model.rows.entries()) {
|
|
90
|
+
if (index > 0) chunks.push(textChunk("\n"));
|
|
91
|
+
const dotColor = row.status === "running" ? api.theme.current.success : row.status === "error" ? api.theme.current.error : api.theme.current.textMuted;
|
|
92
|
+
chunks.push(textChunk("\u2022 ", dotColor));
|
|
93
|
+
chunks.push(textChunk(`${row.title}
|
|
100
94
|
`, api.theme.current.text));
|
|
101
|
-
|
|
95
|
+
chunks.push(textChunk(row.subtitle, api.theme.current.textMuted));
|
|
96
|
+
}
|
|
102
97
|
}
|
|
103
98
|
return new StyledText(chunks);
|
|
104
99
|
}
|
|
@@ -185,15 +180,6 @@ var tui = async (api) => {
|
|
|
185
180
|
}
|
|
186
181
|
}
|
|
187
182
|
),
|
|
188
|
-
home_prompt_right: () => /* @__PURE__ */ jsx(
|
|
189
|
-
PromptRight,
|
|
190
|
-
{
|
|
191
|
-
api,
|
|
192
|
-
sessionID: "",
|
|
193
|
-
subscribe,
|
|
194
|
-
text: () => ""
|
|
195
|
-
}
|
|
196
|
-
),
|
|
197
183
|
sidebar_content: (_ctx, props) => /* @__PURE__ */ jsx(
|
|
198
184
|
SubagentSidebar,
|
|
199
185
|
{
|
|
@@ -202,14 +188,6 @@ var tui = async (api) => {
|
|
|
202
188
|
state: subagents,
|
|
203
189
|
subscribe
|
|
204
190
|
}
|
|
205
|
-
),
|
|
206
|
-
sidebar_footer: (_ctx, props) => /* @__PURE__ */ jsx(
|
|
207
|
-
ReactiveText,
|
|
208
|
-
{
|
|
209
|
-
api,
|
|
210
|
-
subscribe,
|
|
211
|
-
text: () => renderSubagentFooter(subagents, props.session_id)
|
|
212
|
-
}
|
|
213
191
|
)
|
|
214
192
|
}
|
|
215
193
|
});
|
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.
|
|
4
|
+
"version": "0.1.4",
|
|
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",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsup",
|
|
49
|
+
"debug": "npm run build && node dist/cli.js debug",
|
|
49
50
|
"test": "vitest run",
|
|
50
51
|
"typecheck": "tsc --noEmit",
|
|
51
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
|
-
```
|