@slkiser/opencode-quota 3.11.2 → 4.0.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.
Files changed (196) hide show
  1. package/README.md +90 -62
  2. package/dist/bin/opencode-quota.d.ts.map +1 -1
  3. package/dist/bin/opencode-quota.js +16 -7
  4. package/dist/bin/opencode-quota.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/cli-show.d.ts.map +1 -1
  10. package/dist/lib/cli-show.js +11 -7
  11. package/dist/lib/cli-show.js.map +1 -1
  12. package/dist/lib/command-surfaces.d.ts +10 -0
  13. package/dist/lib/command-surfaces.d.ts.map +1 -0
  14. package/dist/lib/command-surfaces.js +9 -0
  15. package/dist/lib/command-surfaces.js.map +1 -0
  16. package/dist/lib/config-file-utils.d.ts +5 -0
  17. package/dist/lib/config-file-utils.d.ts.map +1 -1
  18. package/dist/lib/config-file-utils.js +20 -3
  19. package/dist/lib/config-file-utils.js.map +1 -1
  20. package/dist/lib/config.d.ts +5 -3
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/config.js +124 -32
  23. package/dist/lib/config.js.map +1 -1
  24. package/dist/lib/copilot.d.ts +19 -12
  25. package/dist/lib/copilot.d.ts.map +1 -1
  26. package/dist/lib/copilot.js +462 -566
  27. package/dist/lib/copilot.js.map +1 -1
  28. package/dist/lib/display-sanitize.d.ts.map +1 -1
  29. package/dist/lib/display-sanitize.js +13 -0
  30. package/dist/lib/display-sanitize.js.map +1 -1
  31. package/dist/lib/entries.d.ts +53 -4
  32. package/dist/lib/entries.d.ts.map +1 -1
  33. package/dist/lib/entries.js.map +1 -1
  34. package/dist/lib/init-installer.d.ts +11 -1
  35. package/dist/lib/init-installer.d.ts.map +1 -1
  36. package/dist/lib/init-installer.js +524 -158
  37. package/dist/lib/init-installer.js.map +1 -1
  38. package/dist/lib/opencode-config-editor.d.ts +53 -0
  39. package/dist/lib/opencode-config-editor.d.ts.map +1 -0
  40. package/dist/lib/opencode-config-editor.js +274 -0
  41. package/dist/lib/opencode-config-editor.js.map +1 -0
  42. package/dist/lib/opencode-config-providers.d.ts +18 -0
  43. package/dist/lib/opencode-config-providers.d.ts.map +1 -1
  44. package/dist/lib/opencode-config-providers.js +75 -4
  45. package/dist/lib/opencode-config-providers.js.map +1 -1
  46. package/dist/lib/opencode-runtime-paths.d.ts.map +1 -1
  47. package/dist/lib/opencode-runtime-paths.js +10 -3
  48. package/dist/lib/opencode-runtime-paths.js.map +1 -1
  49. package/dist/lib/opencode-storage.d.ts +10 -0
  50. package/dist/lib/opencode-storage.d.ts.map +1 -1
  51. package/dist/lib/opencode-storage.js +75 -0
  52. package/dist/lib/opencode-storage.js.map +1 -1
  53. package/dist/lib/provider-add-command.d.ts +35 -0
  54. package/dist/lib/provider-add-command.d.ts.map +1 -0
  55. package/dist/lib/provider-add-command.js +185 -0
  56. package/dist/lib/provider-add-command.js.map +1 -0
  57. package/dist/lib/provider-add.d.ts +21 -0
  58. package/dist/lib/provider-add.d.ts.map +1 -0
  59. package/dist/lib/provider-add.js +210 -0
  60. package/dist/lib/provider-add.js.map +1 -0
  61. package/dist/lib/provider-metadata.d.ts +1 -1
  62. package/dist/lib/provider-metadata.d.ts.map +1 -1
  63. package/dist/lib/provider-metadata.js +11 -5
  64. package/dist/lib/provider-metadata.js.map +1 -1
  65. package/dist/lib/quota-command-format.d.ts +2 -0
  66. package/dist/lib/quota-command-format.d.ts.map +1 -1
  67. package/dist/lib/quota-command-format.js +94 -26
  68. package/dist/lib/quota-command-format.js.map +1 -1
  69. package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
  70. package/dist/lib/quota-dialog-commands.js +8 -4
  71. package/dist/lib/quota-dialog-commands.js.map +1 -1
  72. package/dist/lib/quota-export-types.d.ts +48 -27
  73. package/dist/lib/quota-export-types.d.ts.map +1 -1
  74. package/dist/lib/quota-export-types.js +0 -7
  75. package/dist/lib/quota-export-types.js.map +1 -1
  76. package/dist/lib/quota-export.d.ts.map +1 -1
  77. package/dist/lib/quota-export.js +63 -16
  78. package/dist/lib/quota-export.js.map +1 -1
  79. package/dist/lib/quota-providers-local.d.ts +55 -0
  80. package/dist/lib/quota-providers-local.d.ts.map +1 -0
  81. package/dist/lib/quota-providers-local.js +313 -0
  82. package/dist/lib/quota-providers-local.js.map +1 -0
  83. package/dist/lib/quota-providers-remote.d.ts +23 -0
  84. package/dist/lib/quota-providers-remote.d.ts.map +1 -0
  85. package/dist/lib/quota-providers-remote.js +318 -0
  86. package/dist/lib/quota-providers-remote.js.map +1 -0
  87. package/dist/lib/quota-providers.d.ts +77 -0
  88. package/dist/lib/quota-providers.d.ts.map +1 -0
  89. package/dist/lib/quota-providers.js +630 -0
  90. package/dist/lib/quota-providers.js.map +1 -0
  91. package/dist/lib/quota-render-data.d.ts +1 -0
  92. package/dist/lib/quota-render-data.d.ts.map +1 -1
  93. package/dist/lib/quota-render-data.js +35 -16
  94. package/dist/lib/quota-render-data.js.map +1 -1
  95. package/dist/lib/quota-runtime-context.d.ts.map +1 -1
  96. package/dist/lib/quota-runtime-context.js +3 -4
  97. package/dist/lib/quota-runtime-context.js.map +1 -1
  98. package/dist/lib/quota-state.d.ts +5 -2
  99. package/dist/lib/quota-state.d.ts.map +1 -1
  100. package/dist/lib/quota-state.js +268 -51
  101. package/dist/lib/quota-state.js.map +1 -1
  102. package/dist/lib/quota-status.d.ts +2 -1
  103. package/dist/lib/quota-status.d.ts.map +1 -1
  104. package/dist/lib/quota-status.js +247 -69
  105. package/dist/lib/quota-status.js.map +1 -1
  106. package/dist/lib/qwen-local-quota.d.ts +12 -11
  107. package/dist/lib/qwen-local-quota.d.ts.map +1 -1
  108. package/dist/lib/qwen-local-quota.js +82 -87
  109. package/dist/lib/qwen-local-quota.js.map +1 -1
  110. package/dist/lib/scoped-update.d.ts.map +1 -1
  111. package/dist/lib/scoped-update.js +32 -22
  112. package/dist/lib/scoped-update.js.map +1 -1
  113. package/dist/lib/tui-runtime.d.ts +2 -0
  114. package/dist/lib/tui-runtime.d.ts.map +1 -1
  115. package/dist/lib/tui-runtime.js +33 -9
  116. package/dist/lib/tui-runtime.js.map +1 -1
  117. package/dist/lib/types.d.ts +51 -15
  118. package/dist/lib/types.d.ts.map +1 -1
  119. package/dist/lib/types.js +2 -1
  120. package/dist/lib/types.js.map +1 -1
  121. package/dist/plugin.d.ts.map +1 -1
  122. package/dist/plugin.js +75 -100
  123. package/dist/plugin.js.map +1 -1
  124. package/dist/providers/alibaba-coding-plan.d.ts.map +1 -1
  125. package/dist/providers/alibaba-coding-plan.js +35 -4
  126. package/dist/providers/alibaba-coding-plan.js.map +1 -1
  127. package/dist/providers/anthropic.d.ts.map +1 -1
  128. package/dist/providers/anthropic.js +13 -1
  129. package/dist/providers/anthropic.js.map +1 -1
  130. package/dist/providers/chutes.d.ts.map +1 -1
  131. package/dist/providers/chutes.js +6 -0
  132. package/dist/providers/chutes.js.map +1 -1
  133. package/dist/providers/copilot.d.ts +2 -1
  134. package/dist/providers/copilot.d.ts.map +1 -1
  135. package/dist/providers/copilot.js +143 -60
  136. package/dist/providers/copilot.js.map +1 -1
  137. package/dist/providers/cursor.d.ts.map +1 -1
  138. package/dist/providers/cursor.js +30 -1
  139. package/dist/providers/cursor.js.map +1 -1
  140. package/dist/providers/deepseek.d.ts.map +1 -1
  141. package/dist/providers/deepseek.js +13 -1
  142. package/dist/providers/deepseek.js.map +1 -1
  143. package/dist/providers/google-agy.d.ts.map +1 -1
  144. package/dist/providers/google-agy.js +7 -1
  145. package/dist/providers/google-agy.js.map +1 -1
  146. package/dist/providers/google-antigravity.d.ts.map +1 -1
  147. package/dist/providers/google-antigravity.js +10 -2
  148. package/dist/providers/google-antigravity.js.map +1 -1
  149. package/dist/providers/google-gemini-cli.d.ts.map +1 -1
  150. package/dist/providers/google-gemini-cli.js +8 -2
  151. package/dist/providers/google-gemini-cli.js.map +1 -1
  152. package/dist/providers/kimi-code.d.ts.map +1 -1
  153. package/dist/providers/kimi-code.js +6 -0
  154. package/dist/providers/kimi-code.js.map +1 -1
  155. package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
  156. package/dist/providers/minimax-coding-plan.js +12 -4
  157. package/dist/providers/minimax-coding-plan.js.map +1 -1
  158. package/dist/providers/nanogpt.d.ts.map +1 -1
  159. package/dist/providers/nanogpt.js +19 -1
  160. package/dist/providers/nanogpt.js.map +1 -1
  161. package/dist/providers/ollama-cloud.d.ts.map +1 -1
  162. package/dist/providers/ollama-cloud.js +12 -0
  163. package/dist/providers/ollama-cloud.js.map +1 -1
  164. package/dist/providers/openai.d.ts.map +1 -1
  165. package/dist/providers/openai.js +6 -0
  166. package/dist/providers/openai.js.map +1 -1
  167. package/dist/providers/opencode-go.d.ts.map +1 -1
  168. package/dist/providers/opencode-go.js +9 -1
  169. package/dist/providers/opencode-go.js.map +1 -1
  170. package/dist/providers/quota-providers.d.ts +6 -0
  171. package/dist/providers/quota-providers.d.ts.map +1 -0
  172. package/dist/providers/quota-providers.js +276 -0
  173. package/dist/providers/quota-providers.js.map +1 -0
  174. package/dist/providers/qwen-code.d.ts.map +1 -1
  175. package/dist/providers/qwen-code.js +29 -3
  176. package/dist/providers/qwen-code.js.map +1 -1
  177. package/dist/providers/registry.d.ts.map +1 -1
  178. package/dist/providers/registry.js +2 -0
  179. package/dist/providers/registry.js.map +1 -1
  180. package/dist/providers/result-helpers.d.ts +2 -1
  181. package/dist/providers/result-helpers.d.ts.map +1 -1
  182. package/dist/providers/result-helpers.js +6 -1
  183. package/dist/providers/result-helpers.js.map +1 -1
  184. package/dist/providers/synthetic.d.ts.map +1 -1
  185. package/dist/providers/synthetic.js +7 -1
  186. package/dist/providers/synthetic.js.map +1 -1
  187. package/dist/providers/zai.d.ts.map +1 -1
  188. package/dist/providers/zai.js +7 -1
  189. package/dist/providers/zai.js.map +1 -1
  190. package/dist/providers/zhipu.d.ts.map +1 -1
  191. package/dist/providers/zhipu.js +7 -1
  192. package/dist/providers/zhipu.js.map +1 -1
  193. package/dist/tui.d.ts.map +1 -1
  194. package/dist/tui.js +345 -41
  195. package/dist/tui.tsx +267 -18
  196. package/package.json +28 -21
package/dist/tui.tsx CHANGED
@@ -8,6 +8,8 @@ import type {
8
8
  } from "@opencode-ai/plugin/tui";
9
9
  import { Show, createEffect, createSignal, onCleanup } from "solid-js";
10
10
 
11
+ import type { SessionTokenError } from "./lib/quota-status.js";
12
+ import type { TuiCommandDisplay } from "./lib/types.js";
11
13
  import type {
12
14
  CompactStatusState,
13
15
  HomeBottomState,
@@ -25,11 +27,21 @@ import {
25
27
  } from "./lib/tui-panel-state.js";
26
28
  import { getSidebarBodyLineColor } from "./lib/tui-line-style.js";
27
29
  import {
30
+ createTuiQuotaClient,
31
+ getTuiRuntimeRootHints,
32
+ getTuiSessionModelMeta,
28
33
  loadTuiHomeBottomStatus,
29
34
  loadTuiSessionQuotaSurfaces,
35
+ normalizeTuiSessionID,
30
36
  resolveTuiSurfaceRegistration,
31
37
  writeTuiQuotaExportIfEnabled,
32
38
  } from "./lib/tui-runtime.js";
39
+ import {
40
+ QUOTA_DIALOG_COMMANDS,
41
+ buildQuotaDialogCommandOutput,
42
+ type QuotaDialogCommandId,
43
+ type QuotaDialogCommandSpec,
44
+ } from "./lib/quota-dialog-commands.js";
33
45
 
34
46
  const id = "@slkiser/opencode-quota";
35
47
  // Place Quota near the top so variable-height built-in sections
@@ -41,6 +53,11 @@ const EVENT_REFRESH_DELAYS_MS = [150, 600] as const;
41
53
  const MOUNT_RECOVERY_DELAYS_MS = [500, 1_500, 4_000] as const;
42
54
 
43
55
  type TuiPromptRefCallback = (ref: TuiPromptRef | undefined) => void;
56
+ type DialogSize = "medium" | "large" | "xlarge";
57
+
58
+ type QuotaDialogCommandState = {
59
+ lastSessionTokenError?: SessionTokenError;
60
+ };
44
61
  type SessionQuotaResource = {
45
62
  sessionID: string;
46
63
  sidebar: () => SidebarPanelState;
@@ -197,10 +214,13 @@ function acquireSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sess
197
214
  return next;
198
215
  }
199
216
 
200
- function createHomeBottomResource(api: TuiPluginApi): HomeBottomResource {
217
+ function createHomeBottomResource(
218
+ api: TuiPluginApi,
219
+ compactHomeBottomEnabled: boolean,
220
+ ): HomeBottomResource {
201
221
  const [bottom, setBottom] = createSignal<HomeBottomState>({
202
222
  status: "loading",
203
- compact: { status: "loading" },
223
+ compact: compactHomeBottomEnabled ? { status: "loading" } : { status: "disabled" },
204
224
  });
205
225
 
206
226
  let refCount = 0;
@@ -295,11 +315,14 @@ function createHomeBottomResource(api: TuiPluginApi): HomeBottomResource {
295
315
  return resource;
296
316
  }
297
317
 
298
- function acquireHomeBottomResource(api: TuiPluginApi): HomeBottomResource {
318
+ function acquireHomeBottomResource(
319
+ api: TuiPluginApi,
320
+ compactHomeBottomEnabled: boolean,
321
+ ): HomeBottomResource {
299
322
  const existing = homeResources.get(api);
300
323
  if (existing) return existing.retain();
301
324
 
302
- const next = createHomeBottomResource(api).retain();
325
+ const next = createHomeBottomResource(api, compactHomeBottomEnabled).retain();
303
326
  homeResources.set(api, next);
304
327
  return next;
305
328
  }
@@ -435,30 +458,251 @@ function SessionPromptWithCompactStatus(props: {
435
458
  );
436
459
  }
437
460
 
438
- function HomeBottomView(props: { api: TuiPluginApi }) {
439
- const resource = acquireHomeBottomResource(props.api);
461
+ function HomeBottomView(props: { api: TuiPluginApi; compactHomeBottomEnabled: boolean }) {
462
+ const resource = acquireHomeBottomResource(props.api, props.compactHomeBottomEnabled);
440
463
  onCleanup(() => resource.release());
441
464
 
442
465
  const announcement = () => getHomeBottomAnnouncementText(resource.bottom());
443
466
  const compact = () => resource.bottom().compact;
467
+ const visible = () => shouldRenderHomeBottom(resource.bottom());
444
468
 
445
469
  return (
446
- <Show when={shouldRenderHomeBottom(resource.bottom())}>
447
- <box gap={0}>
470
+ <box gap={0}>
471
+ <Show when={visible()}>
448
472
  <text> </text>
449
- <Show when={announcement()}>
450
- <box flexDirection="row" justifyContent="center">
451
- <text fg={props.api.theme.current.textMuted} wrapMode="none">
452
- {announcement()}
453
- </text>
454
- </box>
455
- </Show>
473
+ </Show>
474
+ <Show when={visible() && announcement()}>
475
+ <box flexDirection="row" justifyContent="center">
476
+ <text fg={props.api.theme.current.textMuted} wrapMode="none">
477
+ {announcement()}
478
+ </text>
479
+ </box>
480
+ </Show>
481
+ <Show when={visible()}>
456
482
  <CompactStatusLine api={props.api} panel={compact} justifyContent="center" />
457
- </box>
458
- </Show>
483
+ </Show>
484
+ </box>
459
485
  );
460
486
  }
461
487
 
488
+ function getActiveTuiSessionID(api: TuiPluginApi): string | undefined {
489
+ if (api.route.current.name !== "session") return undefined;
490
+ return normalizeTuiSessionID(api.route.current.params?.sessionID);
491
+ }
492
+
493
+ function getTuiCommandArguments(input: unknown): string | undefined {
494
+ if (!input || typeof input !== "object") return undefined;
495
+ const record = input as Record<string, unknown>;
496
+ for (const key of ["arguments", "args", "query"] as const) {
497
+ const value = record[key];
498
+ if (typeof value === "string" && value.trim()) return value.trim();
499
+ }
500
+ return undefined;
501
+ }
502
+
503
+ function CommandLoadingDialog(props: { api: TuiPluginApi; title: string }) {
504
+ return (
505
+ <box gap={1} paddingLeft={2} paddingRight={2} paddingBottom={1}>
506
+ <text fg={props.api.theme.current.text}>
507
+ <b>{props.title}</b>
508
+ </text>
509
+ <text fg={props.api.theme.current.textMuted}>Loading deterministic local output…</text>
510
+ </box>
511
+ );
512
+ }
513
+
514
+ function CommandOutputDialog(props: { api: TuiPluginApi; title: string; output: string }) {
515
+ const lines = () => props.output.split("\n");
516
+ const bodyHeight = () => Math.min(28, Math.max(6, lines().length));
517
+ return (
518
+ <box gap={1} width="100%" flexGrow={1} paddingLeft={2} paddingRight={2} paddingBottom={1}>
519
+ <text fg={props.api.theme.current.text}>
520
+ <b>{props.title}</b>
521
+ </text>
522
+ <scrollbox width="100%" flexGrow={1} minHeight={bodyHeight()} maxHeight={28}>
523
+ <box gap={0} width="100%" minWidth={0}>
524
+ {lines().map((line) => (
525
+ <text fg={props.api.theme.current.text} wrapMode="word" width="100%">
526
+ {line || " "}
527
+ </text>
528
+ ))}
529
+ </box>
530
+ </scrollbox>
531
+ <text fg={props.api.theme.current.textMuted}>esc closes</text>
532
+ </box>
533
+ );
534
+ }
535
+
536
+ function CommandErrorDialog(props: { api: TuiPluginApi; title: string; error: unknown }) {
537
+ const message = props.error instanceof Error ? props.error.message : String(props.error);
538
+ return (
539
+ <box gap={1} paddingLeft={2} paddingRight={2} paddingBottom={1}>
540
+ <text fg={props.api.theme.current.text}>
541
+ <b>{props.title}</b>
542
+ </text>
543
+ <text fg={props.api.theme.current.text}>OpenCode Quota command failed.</text>
544
+ <text fg={props.api.theme.current.textMuted} wrapMode="none">
545
+ {message || "Unknown error"}
546
+ </text>
547
+ <text fg={props.api.theme.current.textMuted}>esc closes</text>
548
+ </box>
549
+ );
550
+ }
551
+
552
+ function getCommandPromptCopy(spec: QuotaDialogCommandSpec): {
553
+ title: string;
554
+ placeholder: string;
555
+ description: string;
556
+ } {
557
+ switch (spec.id) {
558
+ case "tokens_between":
559
+ return {
560
+ title: "OpenCode Quota Token Range",
561
+ placeholder: "YYYY-MM-DD YYYY-MM-DD",
562
+ description: "Enter start and end dates, for example: 2026-01-01 2026-01-15",
563
+ };
564
+ case "quota_status":
565
+ return {
566
+ title: "OpenCode Quota Status Options",
567
+ placeholder: 'Optional JSON, e.g. {"refreshGoogleTokens":true}',
568
+ description: "Leave blank for normal diagnostics, or enter one JSON options object.",
569
+ };
570
+ default:
571
+ return {
572
+ title: spec.title,
573
+ placeholder: "Optional arguments",
574
+ description: "Leave blank to run with no arguments.",
575
+ };
576
+ }
577
+ }
578
+
579
+ function replaceDialog(api: TuiPluginApi, size: DialogSize, render: () => JSX.Element): void {
580
+ api.ui.dialog.replace(render);
581
+ // OpenCode dialog.replace() resets size to medium.
582
+ api.ui.dialog.setSize(size);
583
+ }
584
+
585
+ async function runQuotaDialogCommandAsync(
586
+ api: TuiPluginApi,
587
+ command: QuotaDialogCommandId,
588
+ commandDisplay: TuiCommandDisplay,
589
+ rawInput?: unknown,
590
+ state?: QuotaDialogCommandState,
591
+ ): Promise<void> {
592
+ const spec = QUOTA_DIALOG_COMMANDS.find((item) => item.id === command)!;
593
+ const argumentsText = getTuiCommandArguments(rawInput);
594
+ const sessionID = getActiveTuiSessionID(api);
595
+
596
+ if (spec.acceptsArguments && rawInput === undefined) {
597
+ const prompt = getCommandPromptCopy(spec);
598
+ replaceDialog(api, "medium", () => (
599
+ <api.ui.DialogPrompt
600
+ title={prompt.title}
601
+ placeholder={prompt.placeholder}
602
+ description={() => (
603
+ <text fg={api.theme.current.textMuted} wrapMode="word">
604
+ {prompt.description}
605
+ </text>
606
+ )}
607
+ onCancel={() => api.ui.dialog.clear()}
608
+ onConfirm={(value) => {
609
+ void runQuotaDialogCommandAsync(
610
+ api,
611
+ command,
612
+ commandDisplay,
613
+ { arguments: value.trim() },
614
+ state,
615
+ );
616
+ }}
617
+ />
618
+ ));
619
+ return;
620
+ }
621
+
622
+ const destination =
623
+ commandDisplay === "inline" && sessionID
624
+ ? { type: "inline" as const, sessionID }
625
+ : { type: "dialog" as const };
626
+ if (destination.type === "dialog") {
627
+ replaceDialog(api, spec.dialogSize, () => (
628
+ <CommandLoadingDialog api={api} title={spec.title} />
629
+ ));
630
+ }
631
+
632
+ try {
633
+ const result = await buildQuotaDialogCommandOutput({
634
+ command,
635
+ arguments: argumentsText,
636
+ client: createTuiQuotaClient(api),
637
+ roots: getTuiRuntimeRootHints(api),
638
+ sessionID,
639
+ resolveSessionMeta: (id) => getTuiSessionModelMeta(api, id),
640
+ lastSessionTokenError: state?.lastSessionTokenError,
641
+ setLastSessionTokenError: state
642
+ ? (error) => {
643
+ state.lastSessionTokenError = error;
644
+ }
645
+ : undefined,
646
+ log: async (message, extra) => {
647
+ await api.client.app.log({
648
+ body: {
649
+ service: "quota-toast",
650
+ level: "debug",
651
+ message,
652
+ extra,
653
+ },
654
+ });
655
+ },
656
+ });
657
+
658
+ if (result.state === "noop") {
659
+ if (destination.type === "dialog") api.ui.dialog.clear();
660
+ return;
661
+ }
662
+
663
+ if (destination.type === "inline") {
664
+ await api.client.session.prompt({
665
+ sessionID: destination.sessionID,
666
+ noReply: true,
667
+ parts: [{ type: "text", text: result.output, ignored: true }],
668
+ });
669
+ return;
670
+ }
671
+
672
+ replaceDialog(api, result.dialogSize, () => (
673
+ <CommandOutputDialog api={api} title={result.title} output={result.output} />
674
+ ));
675
+ } catch (error) {
676
+ replaceDialog(api, "large", () => (
677
+ <CommandErrorDialog api={api} title={spec.title} error={error} />
678
+ ));
679
+ api.ui.toast({
680
+ variant: "error",
681
+ message: "OpenCode Quota command failed",
682
+ });
683
+ }
684
+ }
685
+
686
+ function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiCommandDisplay): void {
687
+ const commandState: QuotaDialogCommandState = {};
688
+ const dispose = api.keymap.registerLayer({
689
+ commands: QUOTA_DIALOG_COMMANDS.map((spec) => ({
690
+ namespace: "palette",
691
+ name: `opencode-quota.${spec.id}`,
692
+ title: spec.title,
693
+ desc: spec.description,
694
+ category: "OpenCode Quota",
695
+ slashName: spec.slashName,
696
+ run(input?: unknown) {
697
+ void runQuotaDialogCommandAsync(api, spec.id, commandDisplay, input, commandState);
698
+ },
699
+ })),
700
+ bindings: [],
701
+ });
702
+
703
+ api.lifecycle.onDispose(dispose);
704
+ }
705
+
462
706
  function registerSidebarSlots(api: TuiPluginApi): void {
463
707
  api.slots.register({
464
708
  order: SIDEBAR_ORDER,
@@ -475,10 +719,13 @@ const tui: TuiPlugin = async (api) => {
475
719
  try {
476
720
  surfaceRegistration = await resolveTuiSurfaceRegistration(api);
477
721
  } catch {
722
+ registerQuotaDialogCommands(api, "inline");
478
723
  registerSidebarSlots(api);
479
724
  return;
480
725
  }
481
726
 
727
+ registerQuotaDialogCommands(api, surfaceRegistration.commandDisplay);
728
+
482
729
  if (surfaceRegistration.sidebar.enabled) {
483
730
  registerSidebarSlots(api);
484
731
  }
@@ -511,7 +758,9 @@ const tui: TuiPlugin = async (api) => {
511
758
  }
512
759
 
513
760
  if (surfaceRegistration.homeBottom) {
514
- compactSlots.home_bottom = () => <HomeBottomView api={api} />;
761
+ compactSlots.home_bottom = () => (
762
+ <HomeBottomView api={api} compactHomeBottomEnabled={surfaceRegistration.compact.homeBottom} />
763
+ );
515
764
  }
516
765
 
517
766
  if (Object.keys(compactSlots).length > 0) {
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@slkiser/opencode-quota",
3
- "version": "3.11.2",
4
- "packageManager": "pnpm@11.0.0",
3
+ "version": "4.0.0",
5
4
  "description": "OpenCode quota & tokens usage with zero context window pollution. Supports GitHub Copilot, OpenAI (Plus/Pro), Qwen Code, Chutes AI, Synthetic, Google Antigravity, Z.ai coding plan and more.",
6
5
  "type": "module",
7
6
  "main": "./dist/index.js",
@@ -32,21 +31,6 @@
32
31
  "README.md",
33
32
  "LICENSE"
34
33
  ],
35
- "scripts": {
36
- "build": "node scripts/clean-dist.mjs && tsc && node scripts/copy-data.mjs && node scripts/prepare-tui-dist.mjs",
37
- "build:check": "pnpm run build && pnpm pack --dry-run",
38
- "pricing:refresh": "node scripts/refresh-modelsdev-pricing.mjs",
39
- "pricing:refresh:if-stale": "node scripts/refresh-modelsdev-pricing-if-stale.mjs",
40
- "upstream:check": "node scripts/check-upstream-plugin-updates.mjs",
41
- "upstream:prepare-review": "node scripts/prepare-upstream-plugin-review.mjs",
42
- "upstream:sync": "node scripts/sync-upstream-plugin-references.mjs",
43
- "verify:release-version": "node scripts/verify-release-version.mjs",
44
- "typecheck": "tsc --noEmit",
45
- "test": "vitest run",
46
- "test:watch": "vitest",
47
- "prepublishOnly": "pnpm run build",
48
- "prepare": "husky"
49
- },
50
34
  "keywords": [
51
35
  "opencode",
52
36
  "plugin",
@@ -72,14 +56,15 @@
72
56
  "devDependencies": {
73
57
  "@babel/core": "^7.28.0",
74
58
  "@babel/preset-typescript": "^7.27.1",
75
- "@opencode-ai/plugin": "^1.4.3",
59
+ "@opencode-ai/plugin": "1.18.1",
76
60
  "@types/node": "^22.0.0",
77
61
  "babel-preset-solid": "^1.9.12",
78
62
  "husky": "^9.1.7",
79
63
  "lint-staged": "^16.2.7",
80
64
  "prettier": "^3.8.0",
81
65
  "typescript": "^5.9.3",
82
- "vitest": "^4.0.17"
66
+ "vitest": "^4.0.17",
67
+ "yaml": "^2.8.3"
83
68
  },
84
69
  "dependencies": {
85
70
  "@clack/prompts": "^1.2.0",
@@ -97,6 +82,28 @@
97
82
  "@opencode-ai/plugin": "^1.4.3"
98
83
  },
99
84
  "engines": {
100
- "node": ">=20.0.0"
85
+ "node": ">=22.0.0"
86
+ },
87
+ "scripts": {
88
+ "build": "node scripts/clean-dist.mjs && tsc && node scripts/copy-data.mjs && node scripts/prepare-tui-dist.mjs",
89
+ "build:check": "pnpm run build && pnpm pack --dry-run",
90
+ "pack:release-package": "node scripts/pack-release-package.mjs",
91
+ "format:check": "node scripts/verify-v4-formatting.mjs",
92
+ "pricing:refresh": "node scripts/refresh-modelsdev-pricing.mjs",
93
+ "pricing:refresh:if-stale": "node scripts/refresh-modelsdev-pricing-if-stale.mjs",
94
+ "upstream:check": "node scripts/check-upstream-plugin-updates.mjs",
95
+ "upstream:prepare-review": "node scripts/prepare-upstream-plugin-review.mjs",
96
+ "upstream:sync": "node scripts/sync-upstream-plugin-references.mjs",
97
+ "release:check": "pnpm run format:check && pnpm run verify:typescript-version && pnpm run verify:v4-history && pnpm run typecheck && pnpm run build && pnpm test && pnpm run test:four-surfaces && pnpm run verify:release-package && pnpm run verify:release-version",
98
+ "smoke:package": "node scripts/smoke-packed-package.mjs",
99
+ "test:four-surfaces": "vitest run tests/v4-phase5-cross-surface.test.ts",
100
+ "verify:package-contents": "node scripts/verify-package-contents.mjs",
101
+ "verify:release-package": "node scripts/verify-release-package.mjs",
102
+ "verify:release-version": "node scripts/verify-release-version.mjs",
103
+ "verify:typescript-version": "node scripts/verify-typescript-version.mjs",
104
+ "verify:v4-history": "node scripts/verify-v4-history.mjs",
105
+ "typecheck": "tsc --noEmit",
106
+ "test": "vitest run",
107
+ "test:watch": "vitest"
101
108
  }
102
- }
109
+ }