@chenlongapps/opencode-token-usage 0.3.3 → 0.4.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.
package/dist/tui.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { memo as _$memo } from "@opentui/solid";
2
+ import { use as _$use } from "@opentui/solid";
1
3
  import { createComponent as _$createComponent } from "@opentui/solid";
2
4
  import { effect as _$effect } from "@opentui/solid";
3
5
  import { insert as _$insert } from "@opentui/solid";
@@ -6,11 +8,13 @@ import { insertNode as _$insertNode } from "@opentui/solid";
6
8
  import { setProp as _$setProp } from "@opentui/solid";
7
9
  import { createElement as _$createElement } from "@opentui/solid";
8
10
  import { Plugin } from "@opencode/plugin/tui";
11
+ import { useTerminalDimensions } from "@opentui/solid";
9
12
  import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";
10
13
  import { UsageController } from "./controller.js";
11
14
  import { PerformanceMonitor } from "./performance.js";
15
+ import { breakdownRows } from "./context-sources.js";
12
16
  import { createSource } from "./source.js";
13
- import { usageRows } from "./usage.js";
17
+ import { bar, countLabel, formatCompact, formatEstimatedCost, formatTokens, requestRows, summaryRows, usageRows } from "./usage.js";
14
18
  function UsagePanel(props) {
15
19
  const [state, setState] = createSignal({
16
20
  status: "loading"
@@ -87,6 +91,573 @@ function UsagePanel(props) {
87
91
  return _el$;
88
92
  })();
89
93
  }
94
+ function UsageDialog(props) {
95
+ const [state, setState] = createSignal({
96
+ status: "loading"
97
+ });
98
+ const [detailed, setDetailed] = createSignal(false);
99
+ const dimensions = useTerminalDimensions();
100
+ const controller = new UsageController(createSource(props.context.client), listener => props.context.data.listen(({
101
+ details
102
+ }) => listener(details)), setState, 80, 3_000, 100, props.performance, true);
103
+ const unregister = props.register(controller);
104
+ controller.select(props.sessionID);
105
+ onCleanup(() => {
106
+ controller.dispose();
107
+ unregister();
108
+ });
109
+ let scroll;
110
+ props.context.keymap.layer(() => ({
111
+ mode: "modal",
112
+ commands: [{
113
+ bind: "up",
114
+ title: "Scroll usage up",
115
+ group: "Dialog",
116
+ run: () => scroll?.scrollBy(-1)
117
+ }, {
118
+ bind: "down",
119
+ title: "Scroll usage down",
120
+ group: "Dialog",
121
+ run: () => scroll?.scrollBy(1)
122
+ }, {
123
+ bind: "pageup",
124
+ title: "Previous usage page",
125
+ group: "Dialog",
126
+ run: () => scroll?.scrollBy(-10)
127
+ }, {
128
+ bind: "pagedown",
129
+ title: "Next usage page",
130
+ group: "Dialog",
131
+ run: () => scroll?.scrollBy(10)
132
+ }, {
133
+ bind: "home",
134
+ title: "First usage row",
135
+ group: "Dialog",
136
+ run: () => scroll?.scrollTo(0)
137
+ }, {
138
+ bind: "end",
139
+ title: "Last usage row",
140
+ group: "Dialog",
141
+ run: () => scroll?.scrollTo(Infinity)
142
+ }, {
143
+ bind: "d",
144
+ title: "Toggle usage details",
145
+ group: "Dialog",
146
+ run: () => {
147
+ setDetailed(value => !value);
148
+ }
149
+ }]
150
+ }));
151
+ const theme = () => props.context.theme.text;
152
+ const context = createMemo(() => state().details?.context);
153
+ const models = createMemo(() => state().details?.models ?? []);
154
+ const sources = createMemo(() => state().details?.sources);
155
+ // The host gives this dialog a fixed width; keep every row inside that content box.
156
+ const width = createMemo(() => Math.max(24, Math.min(88, dimensions().width - 2) - 4));
157
+ const windowBar = createMemo(() => Math.max(8, Math.min(48, width() - 16)));
158
+ const breakdownBar = createMemo(() => Math.max(6, Math.min(20, width() - 46)));
159
+ const pairColumns = createMemo(() => width() >= 44);
160
+ const compact = () => !detailed();
161
+ const number = value => compact() ? formatCompact(value) : formatTokens(value);
162
+ const status = () => ({
163
+ loading: "Loading…",
164
+ ready: "",
165
+ stale: "Not updated · retrying…",
166
+ unavailable: "Unavailable · retrying…"
167
+ })[state().status];
168
+ // The body never repeats the model name: one identity line carries the session and models.
169
+ const identity = createMemo(() => {
170
+ const title = state().details?.sessionTitle;
171
+ const active = models().length > 1 ? `${models().length} models` : state().model;
172
+ return [title, active].filter(Boolean).join(" · ");
173
+ });
174
+ const requestRowsList = rows => {
175
+ const half = Math.ceil(rows.length / 2);
176
+ const columns = [rows.slice(0, half), rows.slice(half)];
177
+ const cell = ([label, value]) => (() => {
178
+ var _el$9 = _$createElement("box"),
179
+ _el$0 = _$createElement("text"),
180
+ _el$1 = _$createElement("text");
181
+ _$insertNode(_el$9, _el$0);
182
+ _$insertNode(_el$9, _el$1);
183
+ _$setProp(_el$9, "flexDirection", "row");
184
+ _$setProp(_el$9, "justifyContent", "space-between");
185
+ _$setProp(_el$9, "gap", 2);
186
+ _$setProp(_el$0, "flexShrink", 0);
187
+ _$insert(_el$0, label);
188
+ _$insert(_el$1, value);
189
+ _$effect(_p$ => {
190
+ var _v$4 = theme().muted,
191
+ _v$5 = theme().base;
192
+ _v$4 !== _p$.e && (_p$.e = _$setProp(_el$0, "fg", _v$4, _p$.e));
193
+ _v$5 !== _p$.t && (_p$.t = _$setProp(_el$1, "fg", _v$5, _p$.t));
194
+ return _p$;
195
+ }, {
196
+ e: undefined,
197
+ t: undefined
198
+ });
199
+ return _el$9;
200
+ })();
201
+ return pairColumns() ? (() => {
202
+ var _el$10 = _$createElement("box");
203
+ _$setProp(_el$10, "flexDirection", "row");
204
+ _$setProp(_el$10, "gap", 2);
205
+ _$insert(_el$10, () => columns.map(column => (() => {
206
+ var _el$11 = _$createElement("box");
207
+ _$setProp(_el$11, "flexDirection", "column");
208
+ _$setProp(_el$11, "flexGrow", 1);
209
+ _$setProp(_el$11, "minWidth", 0);
210
+ _$insert(_el$11, () => column.map(cell));
211
+ return _el$11;
212
+ })()));
213
+ return _el$10;
214
+ })() : (() => {
215
+ var _el$12 = _$createElement("box");
216
+ _$setProp(_el$12, "flexDirection", "column");
217
+ _$insert(_el$12, () => rows.map(cell));
218
+ return _el$12;
219
+ })();
220
+ };
221
+ const barRow = (percent, size) => {
222
+ const cells = bar(percent, size);
223
+ return (() => {
224
+ var _el$13 = _$createElement("box"),
225
+ _el$14 = _$createElement("text"),
226
+ _el$15 = _$createElement("text");
227
+ _$insertNode(_el$13, _el$14);
228
+ _$insertNode(_el$13, _el$15);
229
+ _$setProp(_el$13, "flexDirection", "row");
230
+ _$insert(_el$14, () => "█".repeat(cells.filled));
231
+ _$insert(_el$15, () => "░".repeat(cells.empty));
232
+ _$effect(_p$ => {
233
+ var _v$6 = theme().base,
234
+ _v$7 = theme().muted;
235
+ _v$6 !== _p$.e && (_p$.e = _$setProp(_el$14, "fg", _v$6, _p$.e));
236
+ _v$7 !== _p$.t && (_p$.t = _$setProp(_el$15, "fg", _v$7, _p$.t));
237
+ return _p$;
238
+ }, {
239
+ e: undefined,
240
+ t: undefined
241
+ });
242
+ return _el$13;
243
+ })();
244
+ };
245
+ return (() => {
246
+ var _el$16 = _$createElement("box"),
247
+ _el$17 = _$createElement("box"),
248
+ _el$18 = _$createElement("text"),
249
+ _el$19 = _$createElement("b"),
250
+ _el$21 = _$createElement("text"),
251
+ _el$48 = _$createElement("text");
252
+ _$insertNode(_el$16, _el$17);
253
+ _$insertNode(_el$16, _el$48);
254
+ _$setProp(_el$16, "paddingLeft", 2);
255
+ _$setProp(_el$16, "paddingRight", 2);
256
+ _$setProp(_el$16, "paddingBottom", 1);
257
+ _$setProp(_el$16, "gap", 1);
258
+ _$insertNode(_el$17, _el$18);
259
+ _$insertNode(_el$17, _el$21);
260
+ _$setProp(_el$17, "flexDirection", "row");
261
+ _$setProp(_el$17, "justifyContent", "space-between");
262
+ _$setProp(_el$17, "gap", 2);
263
+ _$insertNode(_el$18, _el$19);
264
+ _$insertNode(_el$19, _$createTextNode(`Token Usage`));
265
+ _$insertNode(_el$21, _$createTextNode(`esc`));
266
+ _$setProp(_el$21, "onMouseUp", () => props.context.ui.dialog.clear());
267
+ _$insert(_el$16, _$createComponent(Show, {
268
+ get when() {
269
+ return identity();
270
+ },
271
+ get children() {
272
+ var _el$23 = _$createElement("text");
273
+ _$setProp(_el$23, "wrapMode", "word");
274
+ _$insert(_el$23, identity);
275
+ _$effect(_$p => _$setProp(_el$23, "fg", theme().muted, _$p));
276
+ return _el$23;
277
+ }
278
+ }), _el$48);
279
+ _$insert(_el$16, _$createComponent(Show, {
280
+ get when() {
281
+ return state().status !== "ready";
282
+ },
283
+ get children() {
284
+ var _el$24 = _$createElement("text");
285
+ _$insert(_el$24, status);
286
+ _$effect(_$p => _$setProp(_el$24, "fg", theme().muted, _$p));
287
+ return _el$24;
288
+ }
289
+ }), _el$48);
290
+ _$insert(_el$16, _$createComponent(Show, {
291
+ get when() {
292
+ return state().summary;
293
+ },
294
+ get children() {
295
+ var _el$25 = _$createElement("scrollbox"),
296
+ _el$26 = _$createElement("box"),
297
+ _el$27 = _$createElement("box"),
298
+ _el$28 = _$createElement("text"),
299
+ _el$29 = _$createElement("b"),
300
+ _el$31 = _$createElement("box"),
301
+ _el$32 = _$createElement("box"),
302
+ _el$33 = _$createElement("text"),
303
+ _el$34 = _$createElement("b"),
304
+ _el$36 = _$createElement("box"),
305
+ _el$37 = _$createElement("text"),
306
+ _el$38 = _$createElement("b"),
307
+ _el$40 = _$createElement("box"),
308
+ _el$41 = _$createElement("text"),
309
+ _el$42 = _$createElement("b"),
310
+ _el$44 = _$createElement("box"),
311
+ _el$45 = _$createElement("text"),
312
+ _el$46 = _$createElement("b");
313
+ _$insertNode(_el$25, _el$26);
314
+ _$use(value => {
315
+ scroll = value;
316
+ }, _el$25);
317
+ _$setProp(_el$25, "contentOptions", {
318
+ minHeight: 0
319
+ });
320
+ _$setProp(_el$25, "scrollbarOptions", {
321
+ visible: false
322
+ });
323
+ _$insertNode(_el$26, _el$27);
324
+ _$insertNode(_el$26, _el$31);
325
+ _$insertNode(_el$26, _el$36);
326
+ _$insertNode(_el$26, _el$40);
327
+ _$insertNode(_el$26, _el$44);
328
+ _$setProp(_el$26, "gap", 1);
329
+ _$insertNode(_el$27, _el$28);
330
+ _$insertNode(_el$28, _el$29);
331
+ _$insertNode(_el$29, _$createTextNode(`Context Window`));
332
+ _$insert(_el$27, _$createComponent(Show, {
333
+ get when() {
334
+ return context();
335
+ },
336
+ get fallback() {
337
+ return (() => {
338
+ var _el$50 = _$createElement("text");
339
+ _$insertNode(_el$50, _$createTextNode(`Context usage unavailable`));
340
+ _$effect(_$p => _$setProp(_el$50, "fg", theme().muted, _$p));
341
+ return _el$50;
342
+ })();
343
+ },
344
+ children: details => (() => {
345
+ var _el$52 = _$createElement("box"),
346
+ _el$53 = _$createElement("box"),
347
+ _el$54 = _$createElement("text"),
348
+ _el$56 = _$createElement("text"),
349
+ _el$57 = _$createTextNode(` / `),
350
+ _el$58 = _$createTextNode(` (`),
351
+ _el$59 = _$createTextNode(`%)`);
352
+ _$insertNode(_el$52, _el$53);
353
+ _$insertNode(_el$53, _el$54);
354
+ _$insertNode(_el$53, _el$56);
355
+ _$setProp(_el$53, "flexDirection", "row");
356
+ _$setProp(_el$53, "justifyContent", "space-between");
357
+ _$setProp(_el$53, "gap", 2);
358
+ _$insertNode(_el$54, _$createTextNode(`Used / Limit`));
359
+ _$setProp(_el$54, "flexShrink", 0);
360
+ _$insertNode(_el$56, _el$57);
361
+ _$insertNode(_el$56, _el$58);
362
+ _$insertNode(_el$56, _el$59);
363
+ _$setProp(_el$56, "wrapMode", "word");
364
+ _$setProp(_el$56, "minWidth", 0);
365
+ _$insert(_el$56, () => number(details().usage.used), _el$57);
366
+ _$insert(_el$56, () => number(details().usage.limit), _el$58);
367
+ _$insert(_el$56, () => details().usage.percent.toFixed(1), _el$59);
368
+ _$insert(_el$52, () => barRow(details().usage.percent, windowBar()), null);
369
+ _$effect(_p$ => {
370
+ var _v$15 = theme().muted,
371
+ _v$16 = theme().base;
372
+ _v$15 !== _p$.e && (_p$.e = _$setProp(_el$54, "fg", _v$15, _p$.e));
373
+ _v$16 !== _p$.t && (_p$.t = _$setProp(_el$56, "fg", _v$16, _p$.t));
374
+ return _p$;
375
+ }, {
376
+ e: undefined,
377
+ t: undefined
378
+ });
379
+ return _el$52;
380
+ })()
381
+ }), null);
382
+ _$insertNode(_el$31, _el$32);
383
+ _$insertNode(_el$32, _el$33);
384
+ _$setProp(_el$32, "flexDirection", "row");
385
+ _$setProp(_el$32, "justifyContent", "space-between");
386
+ _$setProp(_el$32, "gap", 2);
387
+ _$insertNode(_el$33, _el$34);
388
+ _$insertNode(_el$34, _$createTextNode(`Last Request`));
389
+ _$insert(_el$32, _$createComponent(Show, {
390
+ get when() {
391
+ return sources();
392
+ },
393
+ children: captured => (() => {
394
+ var _el$60 = _$createElement("text");
395
+ _$insert(_el$60, () => new Date(captured().capturedAt).toLocaleTimeString());
396
+ _$effect(_$p => _$setProp(_el$60, "fg", theme().muted, _$p));
397
+ return _el$60;
398
+ })()
399
+ }), null);
400
+ _$insert(_el$31, _$createComponent(Show, {
401
+ get when() {
402
+ return context();
403
+ },
404
+ get fallback() {
405
+ return (() => {
406
+ var _el$61 = _$createElement("text");
407
+ _$insertNode(_el$61, _$createTextNode(`Request composition unavailable`));
408
+ _$effect(_$p => _$setProp(_el$61, "fg", theme().muted, _$p));
409
+ return _el$61;
410
+ })();
411
+ },
412
+ children: details => (() => {
413
+ var _el$63 = _$createElement("box");
414
+ _$insert(_el$63, () => requestRowsList(requestRows(details(), compact())));
415
+ return _el$63;
416
+ })()
417
+ }), null);
418
+ _$insertNode(_el$36, _el$37);
419
+ _$insertNode(_el$37, _el$38);
420
+ _$insertNode(_el$38, _$createTextNode(`Context Breakdown`));
421
+ _$insert(_el$36, _$createComponent(Show, {
422
+ get when() {
423
+ return sources();
424
+ },
425
+ get fallback() {
426
+ return (() => {
427
+ var _el$64 = _$createElement("text");
428
+ _$insertNode(_el$64, _$createTextNode(`Source estimates unavailable (no request snapshot or RPC)`));
429
+ _$effect(_$p => _$setProp(_el$64, "fg", theme().muted, _$p));
430
+ return _el$64;
431
+ })();
432
+ },
433
+ children: captured => _$createComponent(For, {
434
+ get each() {
435
+ return breakdownRows(captured(), detailed());
436
+ },
437
+ children: row => (() => {
438
+ var _el$66 = _$createElement("box"),
439
+ _el$67 = _$createElement("box"),
440
+ _el$68 = _$createElement("text"),
441
+ _el$69 = _$createElement("box"),
442
+ _el$70 = _$createElement("text"),
443
+ _el$71 = _$createElement("text"),
444
+ _el$72 = _$createTextNode(`%`);
445
+ _$insertNode(_el$66, _el$67);
446
+ _$insertNode(_el$66, _el$69);
447
+ _$insertNode(_el$66, _el$70);
448
+ _$insertNode(_el$66, _el$71);
449
+ _$setProp(_el$66, "flexDirection", "row");
450
+ _$setProp(_el$66, "gap", 1);
451
+ _$insertNode(_el$67, _el$68);
452
+ _$setProp(_el$67, "width", 14);
453
+ _$setProp(_el$67, "flexShrink", 0);
454
+ _$insert(_el$68, () => row.label);
455
+ _$setProp(_el$69, "flexShrink", 0);
456
+ _$insert(_el$69, () => barRow(row.percent, breakdownBar()));
457
+ _$setProp(_el$70, "flexShrink", 0);
458
+ _$insert(_el$70, () => number(row.tokens));
459
+ _$insertNode(_el$71, _el$72);
460
+ _$setProp(_el$71, "flexShrink", 0);
461
+ _$insert(_el$71, () => row.percent.toFixed(1), _el$72);
462
+ _$effect(_p$ => {
463
+ var _v$17 = theme().muted,
464
+ _v$18 = breakdownBar(),
465
+ _v$19 = theme().base,
466
+ _v$20 = theme().muted;
467
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$68, "fg", _v$17, _p$.e));
468
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$69, "width", _v$18, _p$.t));
469
+ _v$19 !== _p$.a && (_p$.a = _$setProp(_el$70, "fg", _v$19, _p$.a));
470
+ _v$20 !== _p$.o && (_p$.o = _$setProp(_el$71, "fg", _v$20, _p$.o));
471
+ return _p$;
472
+ }, {
473
+ e: undefined,
474
+ t: undefined,
475
+ a: undefined,
476
+ o: undefined
477
+ });
478
+ return _el$66;
479
+ })()
480
+ })
481
+ }), null);
482
+ _$insertNode(_el$40, _el$41);
483
+ _$insertNode(_el$41, _el$42);
484
+ _$insertNode(_el$42, _$createTextNode(`Session`));
485
+ _$insert(_el$40, _$createComponent(Show, {
486
+ get when() {
487
+ return state().summary;
488
+ },
489
+ get fallback() {
490
+ return (() => {
491
+ var _el$73 = _$createElement("text");
492
+ _$insertNode(_el$73, _$createTextNode(`Session usage unavailable`));
493
+ _$effect(_$p => _$setProp(_el$73, "fg", theme().muted, _$p));
494
+ return _el$73;
495
+ })();
496
+ },
497
+ children: summary => _$createComponent(Show, {
498
+ get when() {
499
+ return detailed();
500
+ },
501
+ get fallback() {
502
+ return (() => {
503
+ var _el$75 = _$createElement("text"),
504
+ _el$76 = _$createTextNode(` · `),
505
+ _el$77 = _$createTextNode(` · `),
506
+ _el$78 = _$createTextNode(` tokens · `),
507
+ _el$79 = _$createTextNode(`% cached`);
508
+ _$insertNode(_el$75, _el$76);
509
+ _$insertNode(_el$75, _el$77);
510
+ _$insertNode(_el$75, _el$78);
511
+ _$insertNode(_el$75, _el$79);
512
+ _$setProp(_el$75, "wrapMode", "word");
513
+ _$insert(_el$75, () => countLabel(summary().steps, "step"), _el$76);
514
+ _$insert(_el$75, () => countLabel(summary().calls, "call"), _el$77);
515
+ _$insert(_el$75, () => formatCompact(summary().total), _el$78);
516
+ _$insert(_el$75, () => (summary().cacheRate * 100).toFixed(1), _el$79);
517
+ _$insert(_el$75, (() => {
518
+ var _c$ = _$memo(() => !!formatEstimatedCost(summary().cost, summary().costStatus));
519
+ return () => _c$() ? ` · ${formatEstimatedCost(summary().cost, summary().costStatus)}` : "";
520
+ })(), null);
521
+ _$effect(_$p => _$setProp(_el$75, "fg", theme().base, _$p));
522
+ return _el$75;
523
+ })();
524
+ },
525
+ get children() {
526
+ return _$createComponent(For, {
527
+ get each() {
528
+ return summaryRows(summary());
529
+ },
530
+ children: ([label, value]) => (() => {
531
+ var _el$80 = _$createElement("box"),
532
+ _el$81 = _$createElement("text"),
533
+ _el$82 = _$createElement("text");
534
+ _$insertNode(_el$80, _el$81);
535
+ _$insertNode(_el$80, _el$82);
536
+ _$setProp(_el$80, "flexDirection", "row");
537
+ _$setProp(_el$80, "justifyContent", "space-between");
538
+ _$setProp(_el$80, "gap", 2);
539
+ _$setProp(_el$81, "flexShrink", 0);
540
+ _$insert(_el$81, label);
541
+ _$insert(_el$82, value);
542
+ _$effect(_p$ => {
543
+ var _v$21 = theme().muted,
544
+ _v$22 = theme().base;
545
+ _v$21 !== _p$.e && (_p$.e = _$setProp(_el$81, "fg", _v$21, _p$.e));
546
+ _v$22 !== _p$.t && (_p$.t = _$setProp(_el$82, "fg", _v$22, _p$.t));
547
+ return _p$;
548
+ }, {
549
+ e: undefined,
550
+ t: undefined
551
+ });
552
+ return _el$80;
553
+ })()
554
+ });
555
+ }
556
+ })
557
+ }), null);
558
+ _$insertNode(_el$44, _el$45);
559
+ _$insertNode(_el$45, _el$46);
560
+ _$insertNode(_el$46, _$createTextNode(`By Model`));
561
+ _$insert(_el$44, _$createComponent(Show, {
562
+ get when() {
563
+ return models().length;
564
+ },
565
+ get fallback() {
566
+ return (() => {
567
+ var _el$83 = _$createElement("text");
568
+ _$insertNode(_el$83, _$createTextNode(`No model usage yet`));
569
+ _$effect(_$p => _$setProp(_el$83, "fg", theme().muted, _$p));
570
+ return _el$83;
571
+ })();
572
+ },
573
+ get children() {
574
+ return _$createComponent(For, {
575
+ get each() {
576
+ return models();
577
+ },
578
+ children: model => (() => {
579
+ var _el$85 = _$createElement("box"),
580
+ _el$86 = _$createElement("box"),
581
+ _el$87 = _$createElement("text"),
582
+ _el$88 = _$createElement("text"),
583
+ _el$89 = _$createElement("text"),
584
+ _el$90 = _$createTextNode(` tokens · `);
585
+ _$insertNode(_el$85, _el$86);
586
+ _$insertNode(_el$85, _el$89);
587
+ _$insertNode(_el$86, _el$87);
588
+ _$insertNode(_el$86, _el$88);
589
+ _$setProp(_el$86, "flexDirection", "row");
590
+ _$setProp(_el$86, "justifyContent", "space-between");
591
+ _$setProp(_el$86, "gap", 2);
592
+ _$setProp(_el$87, "wrapMode", "word");
593
+ _$setProp(_el$87, "minWidth", 0);
594
+ _$setProp(_el$87, "flexGrow", 1);
595
+ _$insert(_el$87, () => model.model);
596
+ _$setProp(_el$88, "flexShrink", 0);
597
+ _$insert(_el$88, () => formatEstimatedCost(model.cost, model.costStatus) ?? "");
598
+ _$insertNode(_el$89, _el$90);
599
+ _$insert(_el$89, () => number(model.tokens), _el$90);
600
+ _$insert(_el$89, () => countLabel(model.calls, "call"), null);
601
+ _$effect(_p$ => {
602
+ var _v$23 = theme().base,
603
+ _v$24 = theme().base,
604
+ _v$25 = theme().muted;
605
+ _v$23 !== _p$.e && (_p$.e = _$setProp(_el$87, "fg", _v$23, _p$.e));
606
+ _v$24 !== _p$.t && (_p$.t = _$setProp(_el$88, "fg", _v$24, _p$.t));
607
+ _v$25 !== _p$.a && (_p$.a = _$setProp(_el$89, "fg", _v$25, _p$.a));
608
+ return _p$;
609
+ }, {
610
+ e: undefined,
611
+ t: undefined,
612
+ a: undefined
613
+ });
614
+ return _el$85;
615
+ })()
616
+ });
617
+ }
618
+ }), null);
619
+ _$effect(_p$ => {
620
+ var _v$8 = Math.max(3, dimensions().height - Math.floor(dimensions().height / 4) - 8),
621
+ _v$9 = theme().base,
622
+ _v$0 = theme().base,
623
+ _v$1 = theme().base,
624
+ _v$10 = theme().base,
625
+ _v$11 = theme().base;
626
+ _v$8 !== _p$.e && (_p$.e = _$setProp(_el$25, "maxHeight", _v$8, _p$.e));
627
+ _v$9 !== _p$.t && (_p$.t = _$setProp(_el$28, "fg", _v$9, _p$.t));
628
+ _v$0 !== _p$.a && (_p$.a = _$setProp(_el$33, "fg", _v$0, _p$.a));
629
+ _v$1 !== _p$.o && (_p$.o = _$setProp(_el$37, "fg", _v$1, _p$.o));
630
+ _v$10 !== _p$.i && (_p$.i = _$setProp(_el$41, "fg", _v$10, _p$.i));
631
+ _v$11 !== _p$.n && (_p$.n = _$setProp(_el$45, "fg", _v$11, _p$.n));
632
+ return _p$;
633
+ }, {
634
+ e: undefined,
635
+ t: undefined,
636
+ a: undefined,
637
+ o: undefined,
638
+ i: undefined,
639
+ n: undefined
640
+ });
641
+ return _el$25;
642
+ }
643
+ }), _el$48);
644
+ _$insertNode(_el$48, _$createTextNode(`↑/↓ scroll · d details · esc close`));
645
+ _$effect(_p$ => {
646
+ var _v$12 = theme().base,
647
+ _v$13 = theme().muted,
648
+ _v$14 = theme().muted;
649
+ _v$12 !== _p$.e && (_p$.e = _$setProp(_el$18, "fg", _v$12, _p$.e));
650
+ _v$13 !== _p$.t && (_p$.t = _$setProp(_el$21, "fg", _v$13, _p$.t));
651
+ _v$14 !== _p$.a && (_p$.a = _$setProp(_el$48, "fg", _v$14, _p$.a));
652
+ return _p$;
653
+ }, {
654
+ e: undefined,
655
+ t: undefined,
656
+ a: undefined
657
+ });
658
+ return _el$16;
659
+ })();
660
+ }
90
661
  export default Plugin.define({
91
662
  id: "opencode-token-usage.tui",
92
663
  setup(context) {
@@ -120,17 +691,17 @@ export default Plugin.define({
120
691
  return context.data.session.get(props.sessionID)?.parentID;
121
692
  },
122
693
  get children() {
123
- var _el$9 = _$createElement("box"),
124
- _el$0 = _$createElement("box");
125
- _$insertNode(_el$9, _el$0);
126
- _$setProp(_el$9, "flexDirection", "row");
127
- _$setProp(_el$9, "justifyContent", "flex-end");
128
- _$setProp(_el$9, "paddingRight", 2);
129
- _$setProp(_el$9, "flexShrink", 0);
130
- _$setProp(_el$0, "width", 36);
131
- _$setProp(_el$0, "maxWidth", "100%");
132
- _$setProp(_el$0, "flexDirection", "column");
133
- _$insert(_el$0, _$createComponent(UsagePanel, {
694
+ var _el$91 = _$createElement("box"),
695
+ _el$92 = _$createElement("box");
696
+ _$insertNode(_el$91, _el$92);
697
+ _$setProp(_el$91, "flexDirection", "row");
698
+ _$setProp(_el$91, "justifyContent", "flex-end");
699
+ _$setProp(_el$91, "paddingRight", 2);
700
+ _$setProp(_el$91, "flexShrink", 0);
701
+ _$setProp(_el$92, "width", 36);
702
+ _$setProp(_el$92, "maxWidth", "100%");
703
+ _$setProp(_el$92, "flexDirection", "column");
704
+ _$insert(_el$92, _$createComponent(UsagePanel, {
134
705
  context: context,
135
706
  get sessionID() {
136
707
  return props.sessionID;
@@ -138,15 +709,51 @@ export default Plugin.define({
138
709
  performance: performance,
139
710
  register: register
140
711
  }));
141
- return _el$9;
712
+ return _el$91;
142
713
  }
143
714
  })
144
715
  });
716
+ const removeCommand = context.ui.slot({
717
+ append: "app",
718
+ render: () => {
719
+ context.keymap.layer(() => ({
720
+ mode: "global",
721
+ commands: [{
722
+ id: "opencode-token-usage.show",
723
+ title: "Token usage",
724
+ group: "Session",
725
+ palette: true,
726
+ slash: {
727
+ name: "usage"
728
+ },
729
+ enabled: () => context.ui.router.current().type === "session",
730
+ run: () => {
731
+ const route = context.ui.router.current();
732
+ if (route.type !== "session") return;
733
+ context.ui.dialog.show(() => _$createComponent(UsageDialog, {
734
+ context: context,
735
+ get sessionID() {
736
+ return route.sessionID;
737
+ },
738
+ performance: performance,
739
+ register: register
740
+ }));
741
+ context.ui.dialog.set({
742
+ size: "large",
743
+ centered: true
744
+ });
745
+ }
746
+ }]
747
+ }));
748
+ return null;
749
+ }
750
+ });
145
751
  return () => {
146
752
  for (const controller of controllers) controller.dispose();
147
753
  controllers.clear();
148
754
  remove();
149
755
  removeChild();
756
+ removeCommand();
150
757
  performance.dispose();
151
758
  };
152
759
  }