opencode-usage-total 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +1,36 @@
1
1
  // usage-total.ts
2
2
  var UsageTotalPlugin = async ({ client }) => {
3
- await client.app.log({
4
- body: {
5
- service: "usage-total",
6
- level: "info",
7
- message: "usage-total plugin loaded"
8
- }
9
- });
3
+ try {
4
+ await client.app.log({
5
+ body: {
6
+ service: "usage-total",
7
+ level: "info",
8
+ message: "usage-total plugin loaded"
9
+ }
10
+ });
11
+ } catch {
12
+ }
10
13
  return {
11
14
  event: async ({ event }) => {
12
15
  if (event.type === "session.created") {
13
- void client.app.log({
16
+ client.app.log({
14
17
  body: {
15
18
  service: "usage-total",
16
- level: "info",
19
+ level: "debug",
17
20
  message: `session.created [${event.properties.info.id}]`
18
21
  }
22
+ }).catch(() => {
19
23
  });
20
24
  }
21
25
  if (event.type === "message.updated") {
22
26
  const msg = event.properties.info;
23
- void client.app.log({
27
+ client.app.log({
24
28
  body: {
25
29
  service: "usage-total",
26
- level: "info",
30
+ level: "debug",
27
31
  message: `message.updated [${msg.id}] role=${msg.role}`
28
32
  }
33
+ }).catch(() => {
29
34
  });
30
35
  }
31
36
  }
package/dist/index.js CHANGED
@@ -1,237 +1,317 @@
1
1
  import {
2
2
  UsageTotalPlugin
3
- } from "./chunk-XNCJ32GU.js";
3
+ } from "./chunk-2K4AVHCL.js";
4
4
 
5
5
  // usage-total-tui.tsx
6
6
  import { createRoot, createSignal } from "solid-js";
7
- import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
8
- var DEFAULT_AGENT = "primary";
9
- var UNKNOWN_ID = "?";
10
- function resolveRouteSessionID(api) {
11
- var _a;
12
- return api.route.current.name === "session" && typeof ((_a = api.route.current.params) == null ? void 0 : _a.sessionID) === "string" ? api.route.current.params.sessionID : void 0;
13
- }
14
- function modelTokens(m) {
15
- return m.tokensInput + m.tokensOutput + m.tokensReasoning;
16
- }
7
+
8
+ // package.json
9
+ var version = "0.1.3";
10
+
11
+ // helpers.ts
17
12
  function safeNum(value) {
18
13
  const n = typeof value === "number" ? value : Number(value);
19
14
  return Number.isFinite(n) ? n : 0;
20
15
  }
16
+ function roundCost(n) {
17
+ return safeNum(Number(n.toFixed(6)));
18
+ }
21
19
  function fmtTokens(n) {
20
+ if (n < 0) return "0";
22
21
  if (!Number.isFinite(n) || n === 0) return "0";
23
22
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
24
23
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
25
24
  return String(Math.round(n));
26
25
  }
27
26
  function fmtCost(n) {
27
+ if (n < 0) return "";
28
28
  if (!Number.isFinite(n) || n === 0) return "";
29
29
  if (n < 0.01) return `$${n.toFixed(4)}`;
30
30
  return `$${n.toFixed(2)}`;
31
31
  }
32
+ function modelTokens(m) {
33
+ return m.tokensInput + m.tokensOutput + m.tokensReasoning + m.tokensCacheRead + m.tokensCacheWrite;
34
+ }
35
+
36
+ // usage-total-tui.tsx
37
+ import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
38
+ var DEFAULT_AGENT = "primary";
39
+ var UNKNOWN_ID = "?";
40
+ function resolveRouteSessionID(api) {
41
+ const current = api.route.current;
42
+ if (current.name === "session") {
43
+ const sid = current.params?.sessionID;
44
+ return typeof sid === "string" ? sid : void 0;
45
+ }
46
+ return void 0;
47
+ }
48
+ var initialized = false;
49
+ var lastToastTime = 0;
32
50
  var tui = async (api) => {
33
- api.ui.toast({ message: "usage-total TUI loaded", variant: "info" });
34
- createRoot((dispose) => {
35
- var _a;
36
- const [modelState, setModelState] = createSignal({});
37
- const EXPANDED_KV_KEY = "usage-total.sidebar.expanded";
38
- const [expanded, setExpanded] = createSignal(
39
- api.kv.get(EXPANDED_KV_KEY, true) !== false
40
- );
41
- const TOGGLE_CMD = "usage-total.toggle-section";
42
- const keymapDispose = ((_a = api.keymap) == null ? void 0 : _a.registerLayer) ? api.keymap.registerLayer({
43
- commands: [
44
- {
45
- name: TOGGLE_CMD,
46
- title: "Usage: Toggle models section",
47
- description: "Collapse or expand the models list in the sidebar",
48
- run: () => {
49
- const next = !expanded();
50
- setExpanded(next);
51
- api.kv.set(EXPANDED_KV_KEY, next);
52
- }
53
- }
54
- ],
55
- bindings: [{ key: "alt+m", cmd: TOGGLE_CMD }]
56
- }) : void 0;
57
- const loadedSessions = /* @__PURE__ */ new Set();
58
- function kvKey(sessionID) {
59
- return `usage-total:models:${sessionID}`;
51
+ if (initialized) {
52
+ api.ui?.toast?.({
53
+ message: "usage-total TUI already loaded; skipping re-init",
54
+ variant: "warning"
55
+ });
56
+ return;
57
+ }
58
+ initialized = true;
59
+ api.ui?.toast?.({ message: "usage-total TUI loaded", variant: "info" });
60
+ let unsub;
61
+ let keymapDispose;
62
+ let solidDispose;
63
+ let flushPending;
64
+ const cleanup = () => {
65
+ try {
66
+ flushPending?.();
67
+ } catch {
60
68
  }
61
- function saveSession(sessionID) {
62
- const models = modelState()[sessionID];
63
- if (models && models.length > 0) {
64
- api.kv.set(kvKey(sessionID), models);
65
- }
69
+ try {
70
+ unsub?.();
71
+ } catch {
66
72
  }
67
- function loadSession(sessionID) {
68
- if (loadedSessions.has(sessionID)) return;
69
- loadedSessions.add(sessionID);
70
- const saved = api.kv.get(kvKey(sessionID));
71
- if (saved && saved.length > 0) {
72
- setModelState((current) => ({ ...current, [sessionID]: saved }));
73
- }
73
+ try {
74
+ keymapDispose?.();
75
+ } catch {
74
76
  }
75
- function upsertModel(sessionID, entry, cost, tokens) {
76
- const dedupeKey = `${entry.provider}/${entry.model}/${entry.agent}`;
77
- const current = modelState();
78
- const sessionModels = [...current[sessionID] ?? []];
79
- const existingIdx = sessionModels.findIndex(
80
- (m) => `${m.provider}/${m.model}/${m.agent}` === dedupeKey
77
+ try {
78
+ solidDispose?.();
79
+ } catch {
80
+ }
81
+ initialized = false;
82
+ };
83
+ try {
84
+ if (api.lifecycle?.onDispose) {
85
+ api.lifecycle.onDispose(cleanup);
86
+ }
87
+ createRoot((dispose) => {
88
+ solidDispose = dispose;
89
+ const [modelState, setModelState] = createSignal({});
90
+ const EXPANDED_KV_KEY = "usage-total.sidebar.expanded";
91
+ const [expanded, setExpanded] = createSignal(
92
+ api.kv?.get?.(EXPANDED_KV_KEY, true) !== false
81
93
  );
82
- const safeCost = safeNum(cost);
83
- const safeInput = safeNum(tokens.input);
84
- const safeOutput = safeNum(tokens.output);
85
- const safeReasoning = safeNum(tokens.reasoning);
86
- if (existingIdx >= 0) {
87
- const existing = sessionModels[existingIdx];
88
- sessionModels[existingIdx] = {
89
- ...existing,
90
- cost: safeNum(existing.cost + safeCost),
91
- tokensInput: safeNum(existing.tokensInput + safeInput),
92
- tokensOutput: safeNum(existing.tokensOutput + safeOutput),
93
- tokensReasoning: safeNum(existing.tokensReasoning + safeReasoning),
94
- messageCount: existing.messageCount + 1
95
- };
96
- } else {
97
- sessionModels.push({
98
- ...entry,
99
- cost: safeCost,
100
- tokensInput: safeInput,
101
- tokensOutput: safeOutput,
102
- tokensReasoning: safeReasoning,
103
- messageCount: 1
104
- });
94
+ const TOGGLE_CMD = "usage-total.toggle-section";
95
+ keymapDispose = api.keymap?.registerLayer ? api.keymap.registerLayer({
96
+ commands: [
97
+ {
98
+ name: TOGGLE_CMD,
99
+ title: "Usage: Toggle models section",
100
+ description: "Collapse or expand the models list in the sidebar",
101
+ run: () => {
102
+ const next = !expanded();
103
+ setExpanded(next);
104
+ api.kv?.set?.(EXPANDED_KV_KEY, next);
105
+ }
106
+ }
107
+ ],
108
+ bindings: [{ key: "alt+m", cmd: TOGGLE_CMD }]
109
+ }) : void 0;
110
+ const loadedSessions = /* @__PURE__ */ new Set();
111
+ function kvKey(sessionID) {
112
+ return `usage-total:models:${sessionID}`;
105
113
  }
106
- setModelState({
107
- ...current,
108
- [sessionID]: sessionModels
109
- });
110
- saveSession(sessionID);
111
- return existingIdx < 0;
112
- }
113
- function trackModel(eventSessionID, entry, cost, tokens) {
114
- const isNew = upsertModel(eventSessionID, entry, cost, tokens);
115
- if (isNew) {
116
- api.ui.toast({
117
- message: `${entry.agent}: ${entry.model}`,
118
- variant: "success"
119
- });
114
+ const SAVE_DEBOUNCE_MS = 500;
115
+ const dirtySessions = /* @__PURE__ */ new Set();
116
+ let saveTimer;
117
+ function flushDirtySessions() {
118
+ if (saveTimer) {
119
+ clearTimeout(saveTimer);
120
+ saveTimer = void 0;
121
+ }
122
+ for (const sid of dirtySessions) {
123
+ const models = modelState()[sid];
124
+ if (models && models.length > 0) {
125
+ api.kv?.set?.(kvKey(sid), models);
126
+ }
127
+ }
128
+ dirtySessions.clear();
120
129
  }
121
- const session = api.state.session.get(eventSessionID);
122
- if ((session == null ? void 0 : session.parentID) && session.parentID !== eventSessionID) {
123
- upsertModel(session.parentID, entry, cost, tokens);
130
+ flushPending = flushDirtySessions;
131
+ function scheduleSave(sessionID) {
132
+ dirtySessions.add(sessionID);
133
+ if (saveTimer) clearTimeout(saveTimer);
134
+ saveTimer = setTimeout(flushDirtySessions, SAVE_DEBOUNCE_MS);
124
135
  }
125
- }
126
- const unsub = api.event.on("message.updated", (event) => {
127
- var _a2, _b, _c, _d;
128
- const info = (_a2 = event == null ? void 0 : event.properties) == null ? void 0 : _a2.info;
129
- if (!info) return;
130
- const eventSessionID = info.sessionID;
131
- if (!eventSessionID) return;
132
- let provider;
133
- let model;
134
- let agent;
135
- let cost = 0;
136
- let tokens = {};
137
- if (info.role === "user") {
138
- const mdl = info.model;
139
- provider = (mdl == null ? void 0 : mdl.providerID) ?? UNKNOWN_ID;
140
- model = (mdl == null ? void 0 : mdl.modelID) ?? UNKNOWN_ID;
141
- agent = info.agent ?? DEFAULT_AGENT;
142
- } else if (info.role === "assistant") {
143
- provider = info.providerID ?? UNKNOWN_ID;
144
- model = info.modelID ?? UNKNOWN_ID;
145
- agent = info.agent ?? info.mode ?? DEFAULT_AGENT;
146
- cost = safeNum(info.cost);
147
- tokens = {
148
- input: safeNum((_b = info.tokens) == null ? void 0 : _b.input),
149
- output: safeNum((_c = info.tokens) == null ? void 0 : _c.output),
150
- reasoning: safeNum((_d = info.tokens) == null ? void 0 : _d.reasoning)
151
- };
152
- } else {
153
- return;
136
+ function loadSession(sessionID) {
137
+ if (loadedSessions.has(sessionID)) return;
138
+ loadedSessions.add(sessionID);
139
+ const saved = api.kv?.get?.(kvKey(sessionID));
140
+ if (saved && saved.length > 0) {
141
+ const valid = Array.isArray(saved) && typeof saved[0].provider === "string" && typeof saved[0].model === "string" && typeof saved[0].agent === "string";
142
+ if (!valid) {
143
+ api.ui?.toast?.({
144
+ message: "usage-total: discarded corrupt saved model data",
145
+ variant: "warning"
146
+ });
147
+ api.kv?.set?.(kvKey(sessionID), void 0);
148
+ return;
149
+ }
150
+ setModelState((current) => ({ ...current, [sessionID]: saved }));
151
+ }
154
152
  }
155
- trackModel(eventSessionID, { provider, model, agent }, cost, tokens);
156
- });
157
- api.slots.register({
158
- id: "usage-total",
159
- order: 200,
160
- slots: {
161
- sidebar_content(ctx) {
162
- const sessionID = ctx.session_id ?? resolveRouteSessionID(api) ?? "";
163
- if (sessionID) loadSession(sessionID);
164
- const models = sessionID ? modelState()[sessionID] ?? [] : [];
165
- if (!sessionID || models.length === 0) {
166
- return /* @__PURE__ */ jsxs(
167
- "box",
168
- {
169
- flexDirection: "column",
170
- padding: { left: 1, right: 1, top: 1 },
171
- children: [
172
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, bold: true, children: "\u{1F9E0} Models" }),
173
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: sessionID ? "waiting for messages..." : "open a session to track models" })
174
- ]
175
- }
153
+ function upsertModel(sessionID, entry, cost, tokens) {
154
+ const dedupeKey = `${entry.provider}/${entry.model}/${entry.agent}`;
155
+ const current = modelState();
156
+ const sessionModels = [...current[sessionID] ?? []];
157
+ const existingIdx = sessionModels.findIndex(
158
+ (m) => `${m.provider}/${m.model}/${m.agent}` === dedupeKey
159
+ );
160
+ const safeCost = safeNum(cost);
161
+ const safeInput = safeNum(tokens.input);
162
+ const safeOutput = safeNum(tokens.output);
163
+ const safeReasoning = safeNum(tokens.reasoning);
164
+ const safeCacheRead = safeNum(tokens.cacheRead);
165
+ const safeCacheWrite = safeNum(tokens.cacheWrite);
166
+ if (existingIdx >= 0) {
167
+ const existing = sessionModels[existingIdx];
168
+ sessionModels[existingIdx] = {
169
+ ...existing,
170
+ cost: roundCost(existing.cost + safeCost),
171
+ tokensInput: safeNum(existing.tokensInput + safeInput),
172
+ tokensOutput: safeNum(existing.tokensOutput + safeOutput),
173
+ tokensReasoning: safeNum(existing.tokensReasoning + safeReasoning),
174
+ tokensCacheRead: safeNum(existing.tokensCacheRead + safeCacheRead),
175
+ tokensCacheWrite: safeNum(
176
+ existing.tokensCacheWrite + safeCacheWrite
177
+ ),
178
+ messageCount: existing.messageCount + 1
179
+ };
180
+ } else {
181
+ sessionModels.push({
182
+ ...entry,
183
+ cost: safeCost,
184
+ tokensInput: safeInput,
185
+ tokensOutput: safeOutput,
186
+ tokensReasoning: safeReasoning,
187
+ tokensCacheRead: safeCacheRead,
188
+ tokensCacheWrite: safeCacheWrite,
189
+ messageCount: 1
190
+ });
191
+ }
192
+ setModelState({
193
+ ...current,
194
+ [sessionID]: sessionModels
195
+ });
196
+ scheduleSave(sessionID);
197
+ return existingIdx < 0;
198
+ }
199
+ function trackModel(eventSessionID, entry, cost, tokens) {
200
+ const isNew = upsertModel(eventSessionID, entry, cost, tokens);
201
+ if (isNew && Date.now() - lastToastTime > 2e3) {
202
+ lastToastTime = Date.now();
203
+ api.ui?.toast?.({
204
+ message: `${entry.agent}: ${entry.model}`,
205
+ variant: "success"
206
+ });
207
+ }
208
+ let cursor = eventSessionID;
209
+ const visited = /* @__PURE__ */ new Set();
210
+ while (true) {
211
+ visited.add(cursor);
212
+ const sess = api.state?.session?.get?.(cursor);
213
+ if (!sess?.parentID || sess.parentID === cursor || visited.has(sess.parentID))
214
+ break;
215
+ cursor = sess.parentID;
216
+ }
217
+ if (cursor !== eventSessionID) {
218
+ upsertModel(cursor, entry, cost, tokens);
219
+ }
220
+ }
221
+ unsub = api.event?.on?.("message.updated", (event) => {
222
+ const info = event?.properties?.info;
223
+ if (!info) return;
224
+ const eventSessionID = info.sessionID;
225
+ if (!eventSessionID) return;
226
+ let provider;
227
+ let model;
228
+ let agent;
229
+ let cost = 0;
230
+ let tokens = {};
231
+ if (info.role === "user") {
232
+ const mdl = info.model;
233
+ provider = mdl?.providerID ?? UNKNOWN_ID;
234
+ model = mdl?.modelID ?? UNKNOWN_ID;
235
+ agent = info.agent ?? DEFAULT_AGENT;
236
+ } else if (info.role === "assistant") {
237
+ provider = info.providerID ?? UNKNOWN_ID;
238
+ model = info.modelID ?? UNKNOWN_ID;
239
+ agent = info.agent ?? info.mode ?? DEFAULT_AGENT;
240
+ cost = safeNum(info.cost);
241
+ tokens = {
242
+ input: safeNum(info.tokens?.input),
243
+ output: safeNum(info.tokens?.output),
244
+ reasoning: safeNum(info.tokens?.reasoning),
245
+ cacheRead: safeNum(info.tokens?.cache?.read),
246
+ cacheWrite: safeNum(info.tokens?.cache?.write)
247
+ };
248
+ } else {
249
+ return;
250
+ }
251
+ trackModel(eventSessionID, { provider, model, agent }, cost, tokens);
252
+ });
253
+ api.slots?.register?.({
254
+ order: 200,
255
+ slots: {
256
+ sidebar_content(ctx, props) {
257
+ const sessionID = props.session_id ?? resolveRouteSessionID(api) ?? "";
258
+ if (sessionID) loadSession(sessionID);
259
+ const models = sessionID ? modelState()[sessionID] ?? [] : [];
260
+ if (!sessionID || models.length === 0) {
261
+ return /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
262
+ /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: "\u{1F9E0} Models" }),
263
+ /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: sessionID ? "waiting for messages..." : "open a session to track models" })
264
+ ] });
265
+ }
266
+ const totalCost = models.reduce((sum, m) => sum + m.cost, 0);
267
+ const totalTokens = models.reduce(
268
+ (sum, m) => sum + modelTokens(m),
269
+ 0
176
270
  );
177
- }
178
- const totalCost = models.reduce((sum, m) => sum + m.cost, 0);
179
- const totalTokens = models.reduce(
180
- (sum, m) => sum + modelTokens(m),
181
- 0
182
- );
183
- return /* @__PURE__ */ jsxs(
184
- "box",
185
- {
186
- flexDirection: "column",
187
- padding: { left: 1, right: 1, top: 1 },
188
- children: [
189
- /* @__PURE__ */ jsxs(
190
- "box",
191
- {
192
- flexDirection: "row",
193
- justifyContent: "space-between",
194
- children: [
195
- /* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
196
- /* @__PURE__ */ jsxs(
197
- "text",
198
- {
199
- fg: ctx.theme.current.text,
200
- bold: true,
201
- children: [
202
- expanded() ? "\u25BC" : "\u25B6",
203
- " \u{1F9E0} Models"
204
- ]
205
- }
206
- ),
207
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: " 0.1.0" })
271
+ return /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
272
+ /* @__PURE__ */ jsxs(
273
+ "box",
274
+ {
275
+ flexDirection: "row",
276
+ justifyContent: "space-between",
277
+ children: [
278
+ /* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
279
+ /* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.text, children: [
280
+ expanded() ? "\u25BC" : "\u25B6",
281
+ " \u{1F9E0} Models"
208
282
  ] }),
209
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: totalCost > 0 ? `${fmtCost(totalCost)} \xB7 ${fmtTokens(totalTokens)}` : fmtTokens(totalTokens) })
210
- ]
211
- }
212
- ),
213
- expanded() && models.map((m, i) => /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
214
- /* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.text, children: [
215
- m.agent,
216
- ":"
217
- ] }),
218
- /* @__PURE__ */ jsxs("box", { flexDirection: "row", justifyContent: "space-between", children: [
219
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: " " + m.model }),
220
- /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: m.cost > 0 ? `${fmtCost(m.cost)}${modelTokens(m) > 0 ? ` \xB7 ${fmtTokens(modelTokens(m))}` : ""}` : modelTokens(m) > 0 ? fmtTokens(modelTokens(m)) : "-" })
221
- ] })
222
- ] }, i))
223
- ]
224
- }
225
- );
283
+ /* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.textMuted, children: [
284
+ " ",
285
+ version
286
+ ] })
287
+ ] }),
288
+ /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: totalCost > 0 ? `${fmtCost(totalCost)} \xB7 ${fmtTokens(totalTokens)}` : fmtTokens(totalTokens) })
289
+ ]
290
+ }
291
+ ),
292
+ expanded() && models.map((m) => /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
293
+ /* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.text, children: [
294
+ m.agent,
295
+ ":"
296
+ ] }),
297
+ /* @__PURE__ */ jsxs("box", { flexDirection: "row", justifyContent: "space-between", children: [
298
+ /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: " " + m.model }),
299
+ /* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: m.cost > 0 ? `${fmtCost(m.cost)}${modelTokens(m) > 0 ? ` \xB7 ${fmtTokens(modelTokens(m))}` : ""}` : modelTokens(m) > 0 ? fmtTokens(modelTokens(m)) : "-" })
300
+ ] })
301
+ ] }))
302
+ ] });
303
+ }
226
304
  }
227
- }
305
+ });
228
306
  });
229
- api.lifecycle.onDispose(() => {
230
- unsub();
231
- keymapDispose == null ? void 0 : keymapDispose();
232
- dispose();
307
+ } catch (err) {
308
+ cleanup();
309
+ api.ui?.toast?.({
310
+ message: "usage-total TUI failed to initialize",
311
+ variant: "error"
233
312
  });
234
- });
313
+ throw err;
314
+ }
235
315
  };
236
316
  var usage_total_tui_default = { id: "usage-total", tui };
237
317
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  UsageTotalPlugin
3
- } from "./chunk-XNCJ32GU.js";
3
+ } from "./chunk-2K4AVHCL.js";
4
4
  export {
5
5
  UsageTotalPlugin
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-total",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Track model usage, tokens, and costs per agent in the OpenCode TUI sidebar",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,8 @@
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup",
33
+ "test": "vitest",
34
+ "test:run": "vitest run",
33
35
  "prepublishOnly": "npm run build"
34
36
  },
35
37
  "devDependencies": {
@@ -38,6 +40,7 @@
38
40
  "@opentui/keymap": "^0.4.1",
39
41
  "@opentui/solid": "^0.4.1",
40
42
  "solid-js": "^1.9.12",
41
- "tsup": "^8.5.1"
43
+ "tsup": "^8.5.1",
44
+ "vitest": "^4.1.9"
42
45
  }
43
46
  }