opencode-usage-stats 0.3.0 → 0.3.1
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/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +91 -28
- package/dist/tui.js.map +2 -2
- package/package.json +1 -1
package/dist/tui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/D;AAmCD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,GAC/B,mBAAmB,CA0CrB;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/D;AAmCD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,GAC/B,mBAAmB,CA0CrB;AAgED,QAAA,MAAM,MAAM,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAmG3C,CAAA;eAEc,MAAM"}
|
package/dist/tui.js
CHANGED
|
@@ -58,7 +58,7 @@ function aggregateCurrentSessionMessages(messages) {
|
|
|
58
58
|
stats.models = Object.entries(modelMap).sort(([, a], [, b]) => b.messages - a.messages).map(([key, usage]) => ({ key, ...usage }));
|
|
59
59
|
return stats;
|
|
60
60
|
}
|
|
61
|
-
function renderStatsPanel(stats, theme) {
|
|
61
|
+
function renderStatsPanel(stats, theme, error) {
|
|
62
62
|
const totalTokens = stats.inputTokens + stats.outputTokens + stats.reasoningTokens + stats.cacheRead + stats.cacheWrite;
|
|
63
63
|
const lines = [
|
|
64
64
|
{ label: "Messages", value: stats.messages.toLocaleString() },
|
|
@@ -66,7 +66,22 @@ function renderStatsPanel(stats, theme) {
|
|
|
66
66
|
{ label: "Tokens", value: formatNumber(totalTokens) }
|
|
67
67
|
];
|
|
68
68
|
if (stats.models.length > 0) {
|
|
69
|
-
lines.push({
|
|
69
|
+
lines.push({
|
|
70
|
+
label: "Model",
|
|
71
|
+
value: stats.models[0].key.split("/").pop() ?? stats.models[0].key
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const children = [text({ fg: theme.text, bold: true }, ["Usage"])];
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
children.push(
|
|
77
|
+
box({ width: "100%", flexDirection: "row" }, [
|
|
78
|
+
text({ fg: theme.textMuted }, [`${line.label}:`]),
|
|
79
|
+
text({ fg: line.accent ? theme.accent : theme.text }, [line.value])
|
|
80
|
+
])
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (error) {
|
|
84
|
+
children.push(text({ fg: theme.accent, wrap: "truncate" }, [error]));
|
|
70
85
|
}
|
|
71
86
|
return box(
|
|
72
87
|
{
|
|
@@ -77,20 +92,24 @@ function renderStatsPanel(stats, theme) {
|
|
|
77
92
|
paddingLeft: 1,
|
|
78
93
|
paddingRight: 1
|
|
79
94
|
},
|
|
80
|
-
|
|
81
|
-
text({ fg: theme.text, bold: true }, ["Usage"]),
|
|
82
|
-
...lines.map(
|
|
83
|
-
(line) => box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
|
|
84
|
-
text({ fg: theme.textMuted }, [`${line.label}:`]),
|
|
85
|
-
text({ fg: line.accent ? theme.accent : theme.text }, [line.value])
|
|
86
|
-
])
|
|
87
|
-
)
|
|
88
|
-
]
|
|
95
|
+
children
|
|
89
96
|
);
|
|
90
97
|
}
|
|
98
|
+
function log(api, level, message, extra) {
|
|
99
|
+
try {
|
|
100
|
+
void api.client.app.log({
|
|
101
|
+
service: "opencode-usage-stats:tui",
|
|
102
|
+
level,
|
|
103
|
+
message,
|
|
104
|
+
extra
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
91
109
|
var plugin = {
|
|
92
110
|
id: "opencode-usage-stats:tui",
|
|
93
111
|
tui: async (api) => {
|
|
112
|
+
log(api, "info", "TUI plugin initializing");
|
|
94
113
|
const [getStats, setStats] = createSignal({
|
|
95
114
|
messages: 0,
|
|
96
115
|
cost: 0,
|
|
@@ -101,37 +120,81 @@ var plugin = {
|
|
|
101
120
|
cacheWrite: 0,
|
|
102
121
|
models: []
|
|
103
122
|
});
|
|
123
|
+
const [getError, setError] = createSignal(void 0);
|
|
104
124
|
const [getSessionID, setSessionID] = createSignal("");
|
|
105
125
|
const refresh = () => {
|
|
126
|
+
if (!api.state.ready) {
|
|
127
|
+
setError("state not ready");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
106
130
|
const sessionID = getSessionID();
|
|
107
131
|
if (!sessionID) return;
|
|
108
132
|
try {
|
|
109
133
|
const messages = api.state.session.messages(sessionID);
|
|
110
134
|
const stats = aggregateCurrentSessionMessages(messages);
|
|
111
135
|
setStats(stats);
|
|
136
|
+
setError(void 0);
|
|
137
|
+
log(api, "debug", "Refreshed usage stats", {
|
|
138
|
+
messages: stats.messages,
|
|
139
|
+
cost: stats.cost,
|
|
140
|
+
sessionID
|
|
141
|
+
});
|
|
112
142
|
} catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
setError(message);
|
|
145
|
+
log(api, "error", "Failed to refresh usage stats", { error: message, sessionID });
|
|
113
146
|
}
|
|
114
147
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
const unsubscribers = [];
|
|
149
|
+
try {
|
|
150
|
+
unsubscribers.push(api.event.on("message.updated", refresh));
|
|
151
|
+
unsubscribers.push(api.event.on("message.part.updated", refresh));
|
|
152
|
+
unsubscribers.push(api.event.on("session.status", refresh));
|
|
153
|
+
log(api, "info", "Subscribed to usage events");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
log(api, "error", "Failed to subscribe to events", {
|
|
156
|
+
error: error instanceof Error ? error.message : String(error)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
api.slots.register({
|
|
161
|
+
order: 100,
|
|
162
|
+
slots: {
|
|
163
|
+
sidebar_content(_ctx, data) {
|
|
164
|
+
try {
|
|
165
|
+
if (data.session_id !== getSessionID()) {
|
|
166
|
+
setSessionID(data.session_id);
|
|
167
|
+
log(api, "debug", "Session changed", { session_id: data.session_id });
|
|
168
|
+
refresh();
|
|
169
|
+
}
|
|
170
|
+
const theme = api.theme.current;
|
|
171
|
+
const stats = getStats();
|
|
172
|
+
const error = getError();
|
|
173
|
+
return renderStatsPanel(stats, {
|
|
174
|
+
text: theme.text,
|
|
175
|
+
textMuted: theme.textMuted,
|
|
176
|
+
accent: theme.accent
|
|
177
|
+
}, error);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
log(api, "error", "Failed to render sidebar_content", { error: message });
|
|
181
|
+
return text({ fg: api.theme.current.error }, ["usage stats error"]);
|
|
182
|
+
}
|
|
125
183
|
}
|
|
126
|
-
const theme = api.theme.current;
|
|
127
|
-
const stats = getStats();
|
|
128
|
-
return renderStatsPanel(stats, {
|
|
129
|
-
text: theme.text,
|
|
130
|
-
textMuted: theme.textMuted,
|
|
131
|
-
accent: theme.accent
|
|
132
|
-
});
|
|
133
184
|
}
|
|
185
|
+
});
|
|
186
|
+
log(api, "info", "Registered sidebar_content slot");
|
|
187
|
+
api.renderer.requestRender();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
log(api, "error", "Failed to register sidebar_content slot", {
|
|
190
|
+
error: error instanceof Error ? error.message : String(error)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
api.lifecycle.onDispose(() => {
|
|
194
|
+
for (const unsubscribe of unsubscribers) {
|
|
195
|
+
unsubscribe();
|
|
134
196
|
}
|
|
197
|
+
log(api, "info", "TUI plugin disposed");
|
|
135
198
|
});
|
|
136
199
|
}
|
|
137
200
|
};
|
package/dist/tui.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/tui.ts"],
|
|
4
|
-
"sourcesContent": ["import type { TuiPluginApi, TuiPluginModule } from \"@opencode-ai/plugin/tui\"\nimport type { Message } from \"@opencode-ai/sdk/v2\"\nimport type { JSX } from \"@opentui/solid\"\nimport { createElement, insert, setProp } from \"@opentui/solid\"\nimport { createSignal } from \"solid-js\"\n\nexport interface CurrentSessionStats {\n messages: number\n cost: number\n inputTokens: number\n outputTokens: number\n reasoningTokens: number\n cacheRead: number\n cacheWrite: number\n models: Array<{ key: string; messages: number; cost: number }>\n}\n\ntype Child = JSX.Element | string | number | null | undefined | false\n\nfunction element(\n tag: string,\n props: Record<string, unknown>,\n children: Child[] = [],\n): JSX.Element {\n const node = createElement(tag)\n for (const [key, value] of Object.entries(props)) {\n if (value !== undefined) setProp(node, key, value)\n }\n for (const child of children) {\n if (child !== null && child !== undefined && child !== false) {\n insert(node, child)\n }\n }\n return node as JSX.Element\n}\n\nfunction box(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"box\", props, children)\n}\n\nfunction text(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"text\", props, children)\n}\n\nfunction formatNumber(num: number): string {\n if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`\n if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`\n return num.toLocaleString()\n}\n\nexport function aggregateCurrentSessionMessages(\n messages: ReadonlyArray<Message>,\n): CurrentSessionStats {\n const stats: CurrentSessionStats = {\n messages: 0,\n cost: 0,\n inputTokens: 0,\n outputTokens: 0,\n reasoningTokens: 0,\n cacheRead: 0,\n cacheWrite: 0,\n models: [],\n }\n\n const modelMap: Record<string, { messages: number; cost: number }> = {}\n\n for (const message of messages) {\n if (message.role !== \"assistant\") continue\n\n stats.messages += 1\n stats.cost += message.cost ?? 0\n\n const tokens = message.tokens\n if (tokens) {\n stats.inputTokens += tokens.input ?? 0\n stats.outputTokens += tokens.output ?? 0\n stats.reasoningTokens += tokens.reasoning ?? 0\n stats.cacheRead += tokens.cache?.read ?? 0\n stats.cacheWrite += tokens.cache?.write ?? 0\n }\n\n const key = `${message.providerID}/${message.modelID}`\n if (!modelMap[key]) {\n modelMap[key] = { messages: 0, cost: 0 }\n }\n modelMap[key].messages += 1\n modelMap[key].cost += message.cost ?? 0\n }\n\n stats.models = Object.entries(modelMap)\n .sort(([, a], [, b]) => b.messages - a.messages)\n .map(([key, usage]) => ({ key, ...usage }))\n\n return stats\n}\n\nfunction renderStatsPanel(\n stats: CurrentSessionStats,\n theme: { text: unknown; textMuted: unknown; accent: unknown },\n): JSX.Element {\n const totalTokens =\n stats.inputTokens + stats.outputTokens + stats.reasoningTokens + stats.cacheRead + stats.cacheWrite\n\n const lines: Array<{ label: string; value: string; accent?: boolean }> = [\n { label: \"Messages\", value: stats.messages.toLocaleString() },\n { label: \"Cost\", value: `$${stats.cost.toFixed(4)}` },\n { label: \"Tokens\", value: formatNumber(totalTokens) },\n ]\n\n if (stats.models.length > 0) {\n lines.push({
|
|
5
|
-
"mappings": ";AAGA,SAAS,eAAe,QAAQ,eAAe;AAC/C,SAAS,oBAAoB;AAe7B,SAAS,QACP,KACA,OACA,WAAoB,CAAC,GACR;AACb,QAAM,OAAO,cAAc,GAAG;AAC9B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,OAAW,SAAQ,MAAM,KAAK,KAAK;AAAA,EACnD;AACA,aAAW,SAAS,UAAU;AAC5B,QAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,OAAO;AAC5D,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,IAAI,OAAgC,WAAoB,CAAC,GAAgB;AAChF,SAAO,QAAQ,OAAO,OAAO,QAAQ;AACvC;AAEA,SAAS,KAAK,OAAgC,WAAoB,CAAC,GAAgB;AACjF,SAAO,QAAQ,QAAQ,OAAO,QAAQ;AACxC;AAEA,SAAS,aAAa,KAAqB;AACzC,MAAI,OAAO,IAAW,QAAO,IAAI,MAAM,KAAW,QAAQ,CAAC,CAAC;AAC5D,MAAI,OAAO,IAAO,QAAO,IAAI,MAAM,KAAO,QAAQ,CAAC,CAAC;AACpD,SAAO,IAAI,eAAe;AAC5B;AAEO,SAAS,gCACd,UACqB;AACrB,QAAM,QAA6B;AAAA,IACjC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,QAAQ,CAAC;AAAA,EACX;AAEA,QAAM,WAA+D,CAAC;AAEtE,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,YAAa;AAElC,UAAM,YAAY;AAClB,UAAM,QAAQ,QAAQ,QAAQ;AAE9B,UAAM,SAAS,QAAQ;AACvB,QAAI,QAAQ;AACV,YAAM,eAAe,OAAO,SAAS;AACrC,YAAM,gBAAgB,OAAO,UAAU;AACvC,YAAM,mBAAmB,OAAO,aAAa;AAC7C,YAAM,aAAa,OAAO,OAAO,QAAQ;AACzC,YAAM,cAAc,OAAO,OAAO,SAAS;AAAA,IAC7C;AAEA,UAAM,MAAM,GAAG,QAAQ,UAAU,IAAI,QAAQ,OAAO;AACpD,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB,eAAS,GAAG,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE;AAAA,IACzC;AACA,aAAS,GAAG,EAAE,YAAY;AAC1B,aAAS,GAAG,EAAE,QAAQ,QAAQ,QAAQ;AAAA,EACxC;AAEA,QAAM,SAAS,OAAO,QAAQ,QAAQ,EACnC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAC9C,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,GAAG,MAAM,EAAE;AAE5C,SAAO;AACT;AAEA,SAAS,iBACP,OACA,OACa;AACb,QAAM,cACJ,MAAM,cAAc,MAAM,eAAe,MAAM,kBAAkB,MAAM,YAAY,MAAM;AAE3F,QAAM,QAAmE;AAAA,IACvE,EAAE,OAAO,YAAY,OAAO,MAAM,SAAS,eAAe,EAAE;AAAA,IAC5D,EAAE,OAAO,QAAQ,OAAO,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC,GAAG;AAAA,IACpD,EAAE,OAAO,UAAU,OAAO,aAAa,WAAW,EAAE;AAAA,EACtD;AAEA,MAAI,MAAM,OAAO,SAAS,GAAG;AAC3B,UAAM,KAAK,
|
|
4
|
+
"sourcesContent": ["import type { TuiPluginApi, TuiPluginModule } from \"@opencode-ai/plugin/tui\"\nimport type { Message } from \"@opencode-ai/sdk/v2\"\nimport type { JSX } from \"@opentui/solid\"\nimport { createElement, insert, setProp } from \"@opentui/solid\"\nimport { createSignal } from \"solid-js\"\n\nexport interface CurrentSessionStats {\n messages: number\n cost: number\n inputTokens: number\n outputTokens: number\n reasoningTokens: number\n cacheRead: number\n cacheWrite: number\n models: Array<{ key: string; messages: number; cost: number }>\n}\n\ntype Child = JSX.Element | string | number | null | undefined | false\n\nfunction element(\n tag: string,\n props: Record<string, unknown>,\n children: Child[] = [],\n): JSX.Element {\n const node = createElement(tag)\n for (const [key, value] of Object.entries(props)) {\n if (value !== undefined) setProp(node, key, value)\n }\n for (const child of children) {\n if (child !== null && child !== undefined && child !== false) {\n insert(node, child)\n }\n }\n return node as JSX.Element\n}\n\nfunction box(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"box\", props, children)\n}\n\nfunction text(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"text\", props, children)\n}\n\nfunction formatNumber(num: number): string {\n if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`\n if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`\n return num.toLocaleString()\n}\n\nexport function aggregateCurrentSessionMessages(\n messages: ReadonlyArray<Message>,\n): CurrentSessionStats {\n const stats: CurrentSessionStats = {\n messages: 0,\n cost: 0,\n inputTokens: 0,\n outputTokens: 0,\n reasoningTokens: 0,\n cacheRead: 0,\n cacheWrite: 0,\n models: [],\n }\n\n const modelMap: Record<string, { messages: number; cost: number }> = {}\n\n for (const message of messages) {\n if (message.role !== \"assistant\") continue\n\n stats.messages += 1\n stats.cost += message.cost ?? 0\n\n const tokens = message.tokens\n if (tokens) {\n stats.inputTokens += tokens.input ?? 0\n stats.outputTokens += tokens.output ?? 0\n stats.reasoningTokens += tokens.reasoning ?? 0\n stats.cacheRead += tokens.cache?.read ?? 0\n stats.cacheWrite += tokens.cache?.write ?? 0\n }\n\n const key = `${message.providerID}/${message.modelID}`\n if (!modelMap[key]) {\n modelMap[key] = { messages: 0, cost: 0 }\n }\n modelMap[key].messages += 1\n modelMap[key].cost += message.cost ?? 0\n }\n\n stats.models = Object.entries(modelMap)\n .sort(([, a], [, b]) => b.messages - a.messages)\n .map(([key, usage]) => ({ key, ...usage }))\n\n return stats\n}\n\nfunction renderStatsPanel(\n stats: CurrentSessionStats,\n theme: { text: unknown; textMuted: unknown; accent: unknown },\n error?: string,\n): JSX.Element {\n const totalTokens =\n stats.inputTokens + stats.outputTokens + stats.reasoningTokens + stats.cacheRead + stats.cacheWrite\n\n const lines: Array<{ label: string; value: string; accent?: boolean }> = [\n { label: \"Messages\", value: stats.messages.toLocaleString() },\n { label: \"Cost\", value: `$${stats.cost.toFixed(4)}` },\n { label: \"Tokens\", value: formatNumber(totalTokens) },\n ]\n\n if (stats.models.length > 0) {\n lines.push({\n label: \"Model\",\n value: stats.models[0].key.split(\"/\").pop() ?? stats.models[0].key,\n })\n }\n\n const children: Child[] = [text({ fg: theme.text, bold: true }, [\"Usage\"])]\n\n for (const line of lines) {\n children.push(\n box({ width: \"100%\", flexDirection: \"row\" }, [\n text({ fg: theme.textMuted }, [`${line.label}:`]),\n text({ fg: line.accent ? theme.accent : theme.text }, [line.value]),\n ]),\n )\n }\n\n if (error) {\n children.push(text({ fg: theme.accent, wrap: \"truncate\" }, [error]))\n }\n\n return box(\n {\n width: \"100%\",\n flexDirection: \"column\",\n paddingTop: 1,\n paddingBottom: 1,\n paddingLeft: 1,\n paddingRight: 1,\n },\n children,\n )\n}\n\nfunction log(api: TuiPluginApi, level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string, extra?: Record<string, unknown>) {\n try {\n void api.client.app.log({\n service: \"opencode-usage-stats:tui\",\n level,\n message,\n extra,\n })\n } catch {\n // Logging is best-effort; ignore failures.\n }\n}\n\nconst plugin: TuiPluginModule & { id: string } = {\n id: \"opencode-usage-stats:tui\",\n tui: async (api) => {\n log(api, \"info\", \"TUI plugin initializing\")\n\n const [getStats, setStats] = createSignal<CurrentSessionStats>({\n messages: 0,\n cost: 0,\n inputTokens: 0,\n outputTokens: 0,\n reasoningTokens: 0,\n cacheRead: 0,\n cacheWrite: 0,\n models: [],\n })\n\n const [getError, setError] = createSignal<string | undefined>(undefined)\n const [getSessionID, setSessionID] = createSignal<string>(\"\")\n\n const refresh = (): void => {\n if (!api.state.ready) {\n setError(\"state not ready\")\n return\n }\n const sessionID = getSessionID()\n if (!sessionID) return\n try {\n const messages = api.state.session.messages(sessionID)\n const stats = aggregateCurrentSessionMessages(messages)\n setStats(stats)\n setError(undefined)\n log(api, \"debug\", \"Refreshed usage stats\", {\n messages: stats.messages,\n cost: stats.cost,\n sessionID,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n setError(message)\n log(api, \"error\", \"Failed to refresh usage stats\", { error: message, sessionID })\n }\n }\n\n const unsubscribers: Array<() => void> = []\n try {\n unsubscribers.push(api.event.on(\"message.updated\", refresh))\n unsubscribers.push(api.event.on(\"message.part.updated\", refresh))\n unsubscribers.push(api.event.on(\"session.status\", refresh))\n log(api, \"info\", \"Subscribed to usage events\")\n } catch (error) {\n log(api, \"error\", \"Failed to subscribe to events\", {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n try {\n api.slots.register({\n order: 100,\n slots: {\n sidebar_content(_ctx, data: { session_id: string }) {\n try {\n if (data.session_id !== getSessionID()) {\n setSessionID(data.session_id)\n log(api, \"debug\", \"Session changed\", { session_id: data.session_id })\n refresh()\n }\n\n const theme = api.theme.current\n const stats = getStats()\n const error = getError()\n\n return renderStatsPanel(stats, {\n text: theme.text,\n textMuted: theme.textMuted,\n accent: theme.accent,\n }, error)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n log(api, \"error\", \"Failed to render sidebar_content\", { error: message })\n return text({ fg: api.theme.current.error }, [\"usage stats error\"])\n }\n },\n },\n })\n log(api, \"info\", \"Registered sidebar_content slot\")\n api.renderer.requestRender()\n } catch (error) {\n log(api, \"error\", \"Failed to register sidebar_content slot\", {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n api.lifecycle.onDispose(() => {\n for (const unsubscribe of unsubscribers) {\n unsubscribe()\n }\n log(api, \"info\", \"TUI plugin disposed\")\n })\n },\n}\n\nexport default plugin\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED