@thelioo/opencode-balancer 0.1.3 → 0.1.5

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 (167) hide show
  1. package/INSTALL.txt +16 -7
  2. package/README.md +24 -45
  3. package/dist/core/accounts.d.ts +14 -0
  4. package/dist/core/accounts.js +260 -0
  5. package/dist/core/accounts.js.map +1 -0
  6. package/dist/core/database.d.ts +4 -0
  7. package/dist/core/database.js +69 -0
  8. package/dist/core/database.js.map +1 -0
  9. package/dist/core/events.d.ts +18 -0
  10. package/dist/core/events.js +39 -0
  11. package/dist/core/events.js.map +1 -0
  12. package/dist/core/native-auth-suppression.d.ts +3 -0
  13. package/dist/core/native-auth-suppression.js +19 -0
  14. package/dist/core/native-auth-suppression.js.map +1 -0
  15. package/dist/core/native-connect.d.ts +4 -0
  16. package/dist/core/native-connect.js +19 -0
  17. package/dist/core/native-connect.js.map +1 -0
  18. package/dist/core/path.d.ts +4 -0
  19. package/dist/core/path.js +26 -0
  20. package/dist/core/path.js.map +1 -0
  21. package/dist/core/pending.d.ts +9 -0
  22. package/dist/core/pending.js +237 -0
  23. package/dist/core/pending.js.map +1 -0
  24. package/dist/core/priority.d.ts +20 -0
  25. package/dist/core/priority.js +120 -0
  26. package/dist/core/priority.js.map +1 -0
  27. package/dist/core/schema.d.ts +2 -0
  28. package/dist/core/schema.js +265 -0
  29. package/dist/core/schema.js.map +1 -0
  30. package/dist/core/time.d.ts +1 -0
  31. package/dist/core/time.js +4 -0
  32. package/dist/core/time.js.map +1 -0
  33. package/dist/core/types.d.ts +59 -0
  34. package/dist/core/types.js +2 -0
  35. package/dist/core/types.js.map +1 -0
  36. package/dist/core/usage/index.d.ts +4 -0
  37. package/dist/core/usage/index.js +16 -0
  38. package/dist/core/usage/index.js.map +1 -0
  39. package/dist/core/usage/providers/copilot.d.ts +2 -0
  40. package/dist/core/usage/providers/copilot.js +169 -0
  41. package/dist/core/usage/providers/copilot.js.map +1 -0
  42. package/dist/core/usage/providers/openai.d.ts +2 -0
  43. package/dist/core/usage/providers/openai.js +133 -0
  44. package/dist/core/usage/providers/openai.js.map +1 -0
  45. package/dist/core/usage/redact.d.ts +3 -0
  46. package/dist/core/usage/redact.js +67 -0
  47. package/dist/core/usage/redact.js.map +1 -0
  48. package/dist/core/usage/store.d.ts +4 -0
  49. package/dist/core/usage/store.js +31 -0
  50. package/dist/core/usage/store.js.map +1 -0
  51. package/dist/core/usage/types.d.ts +21 -0
  52. package/dist/core/usage/types.js +2 -0
  53. package/dist/core/usage/types.js.map +1 -0
  54. package/dist/index.d.ts +4 -36
  55. package/dist/index.js +3 -60
  56. package/dist/index.js.map +1 -1
  57. package/dist/server/auth-watcher.d.ts +31 -0
  58. package/dist/server/auth-watcher.js +227 -0
  59. package/dist/server/auth-watcher.js.map +1 -0
  60. package/dist/server/commands.d.ts +2 -0
  61. package/dist/server/commands.js +46 -0
  62. package/dist/server/commands.js.map +1 -0
  63. package/dist/server/fetch-patch.d.ts +3 -0
  64. package/dist/server/fetch-patch.js +118 -0
  65. package/dist/server/fetch-patch.js.map +1 -0
  66. package/dist/server/index.d.ts +8 -0
  67. package/dist/server/index.js +94 -0
  68. package/dist/server/index.js.map +1 -0
  69. package/dist/server/native.d.ts +6 -0
  70. package/dist/server/native.js +35 -0
  71. package/dist/server/native.js.map +1 -0
  72. package/dist/server/request-balancer.d.ts +16 -0
  73. package/dist/server/request-balancer.js +43 -0
  74. package/dist/server/request-balancer.js.map +1 -0
  75. package/dist/tui/actions.d.ts +41 -0
  76. package/dist/tui/actions.js +88 -0
  77. package/dist/tui/actions.js.map +1 -0
  78. package/dist/tui/actions.ts +140 -0
  79. package/dist/tui/balancer-bar-sync.d.ts +19 -0
  80. package/dist/tui/balancer-bar-sync.js +45 -0
  81. package/dist/tui/balancer-bar-sync.js.map +1 -0
  82. package/dist/tui/balancer-bar-sync.ts +56 -0
  83. package/dist/tui/components/alias-dialog.d.ts +4 -0
  84. package/dist/tui/components/alias-dialog.js +57 -0
  85. package/dist/tui/components/alias-dialog.js.map +1 -0
  86. package/dist/tui/components/alias-dialog.tsx +72 -0
  87. package/dist/tui/components/dashboard.d.ts +12 -0
  88. package/dist/tui/components/dashboard.js +201 -0
  89. package/dist/tui/components/dashboard.js.map +1 -0
  90. package/dist/tui/components/dashboard.tsx +393 -0
  91. package/dist/tui/components/priority-screen.d.ts +9 -0
  92. package/dist/tui/components/priority-screen.js +120 -0
  93. package/dist/tui/components/priority-screen.js.map +1 -0
  94. package/dist/tui/components/priority-screen.tsx +302 -0
  95. package/dist/tui/components/provider-model-dialog.d.ts +13 -0
  96. package/dist/tui/components/provider-model-dialog.js +45 -0
  97. package/dist/tui/components/provider-model-dialog.js.map +1 -0
  98. package/dist/tui/components/provider-model-dialog.tsx +71 -0
  99. package/dist/tui/components/rename-dialog.d.ts +4 -0
  100. package/dist/tui/components/rename-dialog.js +24 -0
  101. package/dist/tui/components/rename-dialog.js.map +1 -0
  102. package/dist/tui/components/rename-dialog.tsx +40 -0
  103. package/dist/tui/components/sidebar.d.ts +10 -0
  104. package/dist/tui/components/sidebar.js +27 -0
  105. package/dist/tui/components/sidebar.js.map +1 -0
  106. package/dist/tui/components/sidebar.tsx +97 -0
  107. package/dist/tui/components/status-indicator.d.ts +9 -0
  108. package/dist/tui/components/status-indicator.js +59 -0
  109. package/dist/tui/components/status-indicator.js.map +1 -0
  110. package/dist/tui/components/status-indicator.tsx +78 -0
  111. package/dist/tui/components/usage-bar.d.ts +8 -0
  112. package/dist/tui/components/usage-bar.js +7 -0
  113. package/dist/tui/components/usage-bar.js.map +1 -0
  114. package/dist/tui/components/usage-bar.tsx +13 -0
  115. package/dist/tui/components/usage-display.d.ts +10 -0
  116. package/dist/tui/components/usage-display.js +32 -0
  117. package/dist/tui/components/usage-display.js.map +1 -0
  118. package/dist/tui/components/usage-display.tsx +29 -0
  119. package/dist/tui/connect.d.ts +30 -0
  120. package/dist/tui/connect.js +73 -0
  121. package/dist/tui/connect.js.map +1 -0
  122. package/dist/tui/connect.ts +100 -0
  123. package/dist/tui/dashboard-keys.d.ts +45 -0
  124. package/dist/tui/dashboard-keys.js +44 -0
  125. package/dist/tui/dashboard-keys.js.map +1 -0
  126. package/dist/tui/dashboard-keys.ts +60 -0
  127. package/dist/tui/native-model-apply.d.ts +21 -0
  128. package/dist/tui/native-model-apply.js +53 -0
  129. package/dist/tui/native-model-apply.js.map +1 -0
  130. package/dist/tui/native-model-apply.ts +73 -0
  131. package/dist/tui/priority-keys.d.ts +40 -0
  132. package/dist/tui/priority-keys.js +38 -0
  133. package/dist/tui/priority-keys.js.map +1 -0
  134. package/dist/tui/priority-keys.ts +59 -0
  135. package/dist/tui/provider-models.d.ts +19 -0
  136. package/dist/tui/provider-models.js +17 -0
  137. package/dist/tui/provider-models.js.map +1 -0
  138. package/dist/tui/provider-models.ts +36 -0
  139. package/dist/tui/responsive.d.ts +9 -0
  140. package/dist/tui/responsive.js +13 -0
  141. package/dist/tui/responsive.js.map +1 -0
  142. package/dist/tui/responsive.ts +16 -0
  143. package/dist/tui/selection-colors.d.ts +10 -0
  144. package/dist/tui/selection-colors.js +38 -0
  145. package/dist/tui/selection-colors.js.map +1 -0
  146. package/dist/tui/selection-colors.ts +45 -0
  147. package/dist/tui/state.d.ts +14 -0
  148. package/dist/tui/state.js +46 -0
  149. package/dist/tui/state.js.map +1 -0
  150. package/dist/tui/state.ts +65 -0
  151. package/dist/tui/status-format.d.ts +15 -0
  152. package/dist/tui/status-format.js +17 -0
  153. package/dist/tui/status-format.js.map +1 -0
  154. package/dist/tui/status-format.ts +29 -0
  155. package/dist/tui/tui.d.ts +7 -0
  156. package/dist/tui/tui.js +120 -0
  157. package/dist/tui/tui.js.map +1 -0
  158. package/dist/tui/tui.tsx +142 -0
  159. package/dist/tui/usage-auto-refresh.d.ts +16 -0
  160. package/dist/tui/usage-auto-refresh.js +46 -0
  161. package/dist/tui/usage-auto-refresh.js.map +1 -0
  162. package/dist/tui/usage-auto-refresh.ts +64 -0
  163. package/dist/tui/usage-format.d.ts +2 -0
  164. package/dist/tui/usage-format.js +17 -0
  165. package/dist/tui/usage-format.js.map +1 -0
  166. package/dist/tui/usage-format.ts +14 -0
  167. package/package.json +10 -3
@@ -0,0 +1,16 @@
1
+ export type LayoutMode = "compact" | "full";
2
+
3
+ export function dashboardLayoutMode(size: { width?: number; height?: number }): LayoutMode {
4
+ const width = size.width ?? 999;
5
+ const height = size.height ?? 999;
6
+ return width < 100 || height < 26 ? "compact" : "full";
7
+ }
8
+
9
+ export function visibleRecentEventLimit(mode: LayoutMode): number {
10
+ return mode === "compact" ? 5 : 10;
11
+ }
12
+
13
+ export function dashboardContentHeight(size: { height?: number }): number {
14
+ const height = size.height ?? 28;
15
+ return Math.max(6, height - 8);
16
+ }
@@ -0,0 +1,10 @@
1
+ export declare function selectedRowColors<TColor>(theme: {
2
+ text: TColor;
3
+ textMuted?: TColor;
4
+ backgroundElement: TColor;
5
+ background?: TColor;
6
+ accent?: TColor;
7
+ }): {
8
+ fg: TColor | undefined;
9
+ bg: TColor;
10
+ };
@@ -0,0 +1,38 @@
1
+ export function selectedRowColors(theme) {
2
+ const bg = theme.backgroundElement;
3
+ const textContrast = contrast(theme.text, bg);
4
+ const backgroundContrast = theme.background === undefined ? undefined : contrast(theme.background, bg);
5
+ const fg = backgroundContrast !== undefined && backgroundContrast > textContrast ? theme.background : theme.text;
6
+ return { fg, bg };
7
+ }
8
+ function contrast(a, b) {
9
+ const ca = channels(a);
10
+ const cb = channels(b);
11
+ if (!ca || !cb)
12
+ return 0;
13
+ const la = luminance(ca);
14
+ const lb = luminance(cb);
15
+ const lighter = Math.max(la, lb);
16
+ const darker = Math.min(la, lb);
17
+ return (lighter + 0.05) / (darker + 0.05);
18
+ }
19
+ function channels(value) {
20
+ if (!value || typeof value !== "object")
21
+ return undefined;
22
+ const color = value;
23
+ const r = numberChannel(color.r ?? color.red);
24
+ const g = numberChannel(color.g ?? color.green);
25
+ const b = numberChannel(color.b ?? color.blue);
26
+ return r === undefined || g === undefined || b === undefined ? undefined : { r, g, b };
27
+ }
28
+ function numberChannel(value) {
29
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(255, value)) : undefined;
30
+ }
31
+ function luminance(color) {
32
+ const [r, g, b] = [color.r, color.g, color.b].map((channel) => {
33
+ const value = channel / 255;
34
+ return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
35
+ });
36
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
37
+ }
38
+ //# sourceMappingURL=selection-colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-colors.js","sourceRoot":"","sources":["../../src/tui/selection-colors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export function selectedRowColors<TColor>(theme: {
2
+ text: TColor;
3
+ textMuted?: TColor;
4
+ backgroundElement: TColor;
5
+ background?: TColor;
6
+ accent?: TColor;
7
+ }) {
8
+ const bg = theme.backgroundElement;
9
+ const textContrast = contrast(theme.text, bg);
10
+ const backgroundContrast = theme.background === undefined ? undefined : contrast(theme.background, bg);
11
+ const fg = backgroundContrast !== undefined && backgroundContrast > textContrast ? theme.background : theme.text;
12
+ return { fg, bg };
13
+ }
14
+
15
+ function contrast<TColor>(a: TColor, b: TColor) {
16
+ const ca = channels(a);
17
+ const cb = channels(b);
18
+ if (!ca || !cb) return 0;
19
+ const la = luminance(ca);
20
+ const lb = luminance(cb);
21
+ const lighter = Math.max(la, lb);
22
+ const darker = Math.min(la, lb);
23
+ return (lighter + 0.05) / (darker + 0.05);
24
+ }
25
+
26
+ function channels(value: unknown) {
27
+ if (!value || typeof value !== "object") return undefined;
28
+ const color = value as Record<string, unknown>;
29
+ const r = numberChannel(color.r ?? color.red);
30
+ const g = numberChannel(color.g ?? color.green);
31
+ const b = numberChannel(color.b ?? color.blue);
32
+ return r === undefined || g === undefined || b === undefined ? undefined : { r, g, b };
33
+ }
34
+
35
+ function numberChannel(value: unknown) {
36
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(255, value)) : undefined;
37
+ }
38
+
39
+ function luminance(color: { r: number; g: number; b: number }) {
40
+ const [r, g, b] = [color.r, color.g, color.b].map((channel) => {
41
+ const value = channel / 255;
42
+ return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
43
+ });
44
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
45
+ }
@@ -0,0 +1,14 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { Account, BalancerEvent, PendingConnection } from "../core/types";
3
+ export type BalancerTuiState = {
4
+ db: Database;
5
+ version: () => number;
6
+ refresh: () => void;
7
+ accounts: () => Account[];
8
+ pending: () => PendingConnection[];
9
+ events: () => BalancerEvent[];
10
+ removeAccountView: (providerID: string, alias: string) => void;
11
+ removePendingView: (pendingID: string) => void;
12
+ dispose: () => void;
13
+ };
14
+ export declare function createBalancerTuiState(): BalancerTuiState;
@@ -0,0 +1,46 @@
1
+ import { createSignal, onCleanup } from "solid-js";
2
+ import { listAccounts } from "../core/accounts";
3
+ import { closeBalancerDatabase, openBalancerDatabase } from "../core/database";
4
+ import { listEvents } from "../core/events";
5
+ import { listPendingConnections } from "../core/pending";
6
+ import { storePath } from "../core/path";
7
+ import { migrate } from "../core/schema";
8
+ export function createBalancerTuiState() {
9
+ const dbPath = storePath();
10
+ const db = openBalancerDatabase(dbPath);
11
+ migrate(db);
12
+ const [version, setVersion] = createSignal(0);
13
+ const [accounts, setAccounts] = createSignal([]);
14
+ const [pending, setPending] = createSignal([]);
15
+ const [events, setEvents] = createSignal([]);
16
+ const refresh = () => {
17
+ setAccounts(listAccounts(db));
18
+ setPending(listPendingConnections(db));
19
+ setEvents(listEvents(db, 10));
20
+ setVersion((current) => current + 1);
21
+ };
22
+ const removeAccountView = (providerID, alias) => {
23
+ setAccounts((current) => current.filter((account) => account.providerID !== providerID || account.alias !== alias));
24
+ setVersion((current) => current + 1);
25
+ };
26
+ const removePendingView = (pendingID) => {
27
+ setPending((current) => current.filter((pending) => pending.id !== pendingID));
28
+ setVersion((current) => current + 1);
29
+ };
30
+ let interval;
31
+ let disposed = false;
32
+ const dispose = () => {
33
+ if (disposed)
34
+ return;
35
+ disposed = true;
36
+ if (interval)
37
+ clearInterval(interval);
38
+ interval = undefined;
39
+ closeBalancerDatabase(dbPath);
40
+ };
41
+ refresh();
42
+ interval = setInterval(refresh, 1000);
43
+ onCleanup(dispose);
44
+ return { db, version, refresh, accounts, pending, events, removeAccountView, removePendingView, dispose };
45
+ }
46
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/tui/state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAezC,MAAM,UAAU,sBAAsB;IAClC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,CAAC;IAEZ,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,YAAY,CAAY,EAAE,CAAC,CAAC;IAC5D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,YAAY,CAAsB,EAAE,CAAC,CAAC;IACpE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,YAAY,CAAkB,EAAE,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,GAAG,EAAE;QACjB,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9B,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,CAAC;QACvC,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9B,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC;IACF,MAAM,iBAAiB,GAAG,CAAC,UAAkB,EAAE,KAAa,EAAE,EAAE;QAC5D,WAAW,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC;QACpH,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC;IACF,MAAM,iBAAiB,GAAG,CAAC,SAAiB,EAAE,EAAE;QAC5C,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC;QAC/E,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC;IAEF,IAAI,QAAoD,CAAC;IACzD,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,OAAO,GAAG,GAAG,EAAE;QACjB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,QAAQ;YAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;QACtC,QAAQ,GAAG,SAAS,CAAC;QACrB,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC;IAEF,OAAO,EAAE,CAAC;IAEV,QAAQ,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,OAAO,CAAC,CAAC;IAEnB,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC;AAC9G,CAAC"}
@@ -0,0 +1,65 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { createSignal, onCleanup } from "solid-js";
3
+ import { listAccounts } from "../core/accounts";
4
+ import { closeBalancerDatabase, openBalancerDatabase } from "../core/database";
5
+ import { listEvents } from "../core/events";
6
+ import { listPendingConnections } from "../core/pending";
7
+ import { storePath } from "../core/path";
8
+ import { migrate } from "../core/schema";
9
+ import type { Account, BalancerEvent, PendingConnection } from "../core/types";
10
+
11
+ export type BalancerTuiState = {
12
+ db: Database;
13
+ version: () => number;
14
+ refresh: () => void;
15
+ accounts: () => Account[];
16
+ pending: () => PendingConnection[];
17
+ events: () => BalancerEvent[];
18
+ removeAccountView: (providerID: string, alias: string) => void;
19
+ removePendingView: (pendingID: string) => void;
20
+ dispose: () => void;
21
+ };
22
+
23
+ export function createBalancerTuiState(): BalancerTuiState {
24
+ const dbPath = storePath();
25
+ const db = openBalancerDatabase(dbPath);
26
+ migrate(db);
27
+
28
+ const [version, setVersion] = createSignal(0);
29
+ const [accounts, setAccounts] = createSignal<Account[]>([]);
30
+ const [pending, setPending] = createSignal<PendingConnection[]>([]);
31
+ const [events, setEvents] = createSignal<BalancerEvent[]>([]);
32
+
33
+ const refresh = () => {
34
+ setAccounts(listAccounts(db));
35
+ setPending(listPendingConnections(db));
36
+ setEvents(listEvents(db, 10));
37
+ setVersion((current) => current + 1);
38
+ };
39
+ const removeAccountView = (providerID: string, alias: string) => {
40
+ setAccounts((current) => current.filter((account) => account.providerID !== providerID || account.alias !== alias));
41
+ setVersion((current) => current + 1);
42
+ };
43
+ const removePendingView = (pendingID: string) => {
44
+ setPending((current) => current.filter((pending) => pending.id !== pendingID));
45
+ setVersion((current) => current + 1);
46
+ };
47
+
48
+ let interval: ReturnType<typeof setInterval> | undefined;
49
+ let disposed = false;
50
+
51
+ const dispose = () => {
52
+ if (disposed) return;
53
+ disposed = true;
54
+ if (interval) clearInterval(interval);
55
+ interval = undefined;
56
+ closeBalancerDatabase(dbPath);
57
+ };
58
+
59
+ refresh();
60
+
61
+ interval = setInterval(refresh, 1000);
62
+ onCleanup(dispose);
63
+
64
+ return { db, version, refresh, accounts, pending, events, removeAccountView, removePendingView, dispose };
65
+ }
@@ -0,0 +1,15 @@
1
+ export type StatusAccount = {
2
+ providerID: string;
3
+ alias: string;
4
+ };
5
+ export declare function formatBalancerStatus(input: {
6
+ selected?: StatusAccount;
7
+ sessionActive?: StatusAccount;
8
+ sessionProviderID?: string;
9
+ balancing?: {
10
+ providerID: string;
11
+ alias?: string;
12
+ modelID: string;
13
+ };
14
+ usage?: string;
15
+ }): string;
@@ -0,0 +1,17 @@
1
+ export function formatBalancerStatus(input) {
2
+ const withUsage = (value) => (input.usage ? `${value} · ${input.usage}` : value);
3
+ if (input.balancing) {
4
+ const account = input.balancing.alias
5
+ ? `${input.balancing.providerID}/${input.balancing.alias}`
6
+ : input.balancing.providerID;
7
+ return withUsage(account);
8
+ }
9
+ if (!input.selected) {
10
+ if (input.sessionProviderID)
11
+ return withUsage(`${input.sessionProviderID}/${input.sessionActive?.alias ?? "none"}`);
12
+ return withUsage("balancer");
13
+ }
14
+ const selected = `${input.selected.providerID}/${input.selected.alias}`;
15
+ return withUsage(selected);
16
+ }
17
+ //# sourceMappingURL=status-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-format.js","sourceRoot":"","sources":["../../src/tui/status-format.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,oBAAoB,CAAC,KAMpC;IACG,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAEzF,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK;YACjC,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE;YAC1D,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC;QACjC,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,iBAAiB;YAAE,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,aAAa,EAAE,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;QACpH,OAAO,SAAS,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxE,OAAO,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,29 @@
1
+ export type StatusAccount = {
2
+ providerID: string;
3
+ alias: string;
4
+ };
5
+
6
+ export function formatBalancerStatus(input: {
7
+ selected?: StatusAccount;
8
+ sessionActive?: StatusAccount;
9
+ sessionProviderID?: string;
10
+ balancing?: { providerID: string; alias?: string; modelID: string };
11
+ usage?: string;
12
+ }) {
13
+ const withUsage = (value: string) => (input.usage ? `${value} · ${input.usage}` : value);
14
+
15
+ if (input.balancing) {
16
+ const account = input.balancing.alias
17
+ ? `${input.balancing.providerID}/${input.balancing.alias}`
18
+ : input.balancing.providerID;
19
+ return withUsage(account);
20
+ }
21
+
22
+ if (!input.selected) {
23
+ if (input.sessionProviderID) return withUsage(`${input.sessionProviderID}/${input.sessionActive?.alias ?? "none"}`);
24
+ return withUsage("balancer");
25
+ }
26
+
27
+ const selected = `${input.selected.providerID}/${input.selected.alias}`;
28
+ return withUsage(selected);
29
+ }
@@ -0,0 +1,7 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPlugin } from "@opencode-ai/plugin/tui";
3
+ declare const _default: {
4
+ id: string;
5
+ tui: TuiPlugin;
6
+ };
7
+ export default _default;
@@ -0,0 +1,120 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import { createComponent } from "solid-js";
3
+ import { setProviderModel } from "../core/priority";
4
+ import { activateAccount, removeAccountFromTui } from "./actions";
5
+ import { createTuiBalancerBarSync } from "./balancer-bar-sync";
6
+ import { openNativeConnect } from "./connect";
7
+ import { createBalancerTuiState } from "./state";
8
+ import { createUsageAutoRefresh } from "./usage-auto-refresh";
9
+ function inferProviderID(session) {
10
+ const providerID = session?.model?.providerID;
11
+ return typeof providerID === "string" && providerID.length > 0 ? providerID : undefined;
12
+ }
13
+ function copyRoute(route) {
14
+ return "params" in route && route.params
15
+ ? { name: route.name, params: { ...route.params } }
16
+ : { name: route.name };
17
+ }
18
+ const tui = async (api) => {
19
+ await import("@opentui/solid/runtime-plugin" + "-support");
20
+ const [dashboardModule, priorityScreenModule, providerModelDialogModule, renameDialogModule, sidebarModule, statusIndicatorModule] = await Promise.all([
21
+ import("./components/dashboard" + ".tsx"),
22
+ import("./components/priority-screen" + ".tsx"),
23
+ import("./components/provider-model-dialog" + ".tsx"),
24
+ import("./components/rename-dialog" + ".tsx"),
25
+ import("./components/sidebar" + ".tsx"),
26
+ import("./components/status-indicator" + ".tsx"),
27
+ ]);
28
+ const state = createBalancerTuiState();
29
+ const usageAutoRefresh = createUsageAutoRefresh(api, state);
30
+ const balancerBarSync = createTuiBalancerBarSync(api, state);
31
+ let dashboardReturnRoute;
32
+ api.lifecycle.onDispose(() => {
33
+ usageAutoRefresh.dispose();
34
+ state.dispose();
35
+ });
36
+ const openDashboard = () => {
37
+ if (api.route.current.name !== "balancer.dashboard")
38
+ dashboardReturnRoute = copyRoute(api.route.current);
39
+ api.route.navigate("balancer.dashboard");
40
+ };
41
+ const openPriority = () => {
42
+ api.route.navigate("balancer.priority");
43
+ };
44
+ const backFromDashboard = () => {
45
+ const route = dashboardReturnRoute;
46
+ dashboardReturnRoute = undefined;
47
+ if (route)
48
+ api.route.navigate(route.name, "params" in route ? route.params : undefined);
49
+ else
50
+ api.route.navigate("home");
51
+ };
52
+ const unregisterDashboard = api.route.register([
53
+ {
54
+ name: "balancer.dashboard",
55
+ render: () => createComponent(dashboardModule.Dashboard, {
56
+ api,
57
+ state,
58
+ onBack: backFromDashboard,
59
+ openPriority,
60
+ openConnect: () => openNativeConnect({ ...api, db: state.db }),
61
+ renameAccount: (providerID, alias) => renameDialogModule.openRenameDialog(api, state, providerID, alias),
62
+ removeAccount: (providerID, alias) => removeAccountFromTui(api, state, providerID, alias),
63
+ }),
64
+ },
65
+ {
66
+ name: "balancer.priority",
67
+ render: () => createComponent(priorityScreenModule.PriorityScreen, {
68
+ api,
69
+ state,
70
+ onBack: () => api.route.navigate("balancer.dashboard"),
71
+ openModelPicker: (providerID) => providerModelDialogModule.openProviderModelDialog(api, state, providerID, {
72
+ onSelected: (model) => setProviderModel(state.db, model.providerID, model.modelID),
73
+ }),
74
+ }),
75
+ },
76
+ ]);
77
+ api.lifecycle.onDispose(unregisterDashboard);
78
+ const unregisterKeymap = api.keymap.registerLayer({
79
+ commands: [
80
+ {
81
+ name: "balancer.dashboard.open",
82
+ title: "Open Balancer Dashboard",
83
+ category: "Plugin",
84
+ namespace: "palette",
85
+ slashName: "balancer",
86
+ run() {
87
+ openDashboard();
88
+ },
89
+ },
90
+ ],
91
+ bindings: [{ key: "ctrl+b", cmd: "balancer.dashboard.open" }],
92
+ });
93
+ api.lifecycle.onDispose(unregisterKeymap);
94
+ api.slots.register({
95
+ slots: {
96
+ session_prompt_right(_ctx, value) {
97
+ void usageAutoRefresh.refreshForPrompt();
98
+ void balancerBarSync.maybeSync();
99
+ return createComponent(statusIndicatorModule.BalancerStatusIndicator, {
100
+ api,
101
+ state,
102
+ providerID: () => inferProviderID(api.state.session.get(value.session_id)),
103
+ });
104
+ },
105
+ sidebar_content(_ctx, value) {
106
+ return createComponent(sidebarModule.BalancerSidebar, {
107
+ api,
108
+ state,
109
+ openDashboard,
110
+ activateAccount: (providerID, alias) => activateAccount(api, state, providerID, alias, {
111
+ sessionProviderID: inferProviderID(api.state.session.get(value.session_id)),
112
+ openProviderModelPicker: (targetProviderID) => providerModelDialogModule.openProviderModelDialog(api, state, targetProviderID),
113
+ }),
114
+ });
115
+ },
116
+ },
117
+ });
118
+ };
119
+ export default { id: "opencode-balancer", tui };
120
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../../src/tui/tui.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+
3
+ import type { TuiPlugin, TuiPluginModule, TuiRouteCurrent } from "@opencode-ai/plugin/tui";
4
+ import { createComponent } from "solid-js";
5
+ import { setProviderModel } from "../core/priority";
6
+ import { activateAccount, removeAccountFromTui } from "./actions";
7
+ import { createTuiBalancerBarSync } from "./balancer-bar-sync";
8
+ import { openNativeConnect } from "./connect";
9
+ import { createBalancerTuiState } from "./state";
10
+ import { createUsageAutoRefresh } from "./usage-auto-refresh";
11
+
12
+ type DashboardModule = typeof import("./components/dashboard");
13
+ type PriorityScreenModule = typeof import("./components/priority-screen");
14
+ type ProviderModelDialogModule = typeof import("./components/provider-model-dialog");
15
+ type RenameDialogModule = typeof import("./components/rename-dialog");
16
+ type SidebarModule = typeof import("./components/sidebar");
17
+ type StatusIndicatorModule = typeof import("./components/status-indicator");
18
+
19
+ function inferProviderID(session: unknown) {
20
+ const providerID = (session as { model?: { providerID?: unknown } } | undefined)?.model?.providerID;
21
+ return typeof providerID === "string" && providerID.length > 0 ? providerID : undefined;
22
+ }
23
+
24
+ function copyRoute(route: TuiRouteCurrent): TuiRouteCurrent {
25
+ return "params" in route && route.params
26
+ ? { name: route.name, params: { ...route.params } }
27
+ : { name: route.name };
28
+ }
29
+
30
+ const tui: TuiPlugin = async (api) => {
31
+ await import("@opentui/solid/runtime-plugin" + "-support");
32
+
33
+ const [dashboardModule, priorityScreenModule, providerModelDialogModule, renameDialogModule, sidebarModule, statusIndicatorModule] = await Promise.all([
34
+ import("./components/dashboard" + ".tsx") as Promise<DashboardModule>,
35
+ import("./components/priority-screen" + ".tsx") as Promise<PriorityScreenModule>,
36
+ import("./components/provider-model-dialog" + ".tsx") as Promise<ProviderModelDialogModule>,
37
+ import("./components/rename-dialog" + ".tsx") as Promise<RenameDialogModule>,
38
+ import("./components/sidebar" + ".tsx") as Promise<SidebarModule>,
39
+ import("./components/status-indicator" + ".tsx") as Promise<StatusIndicatorModule>,
40
+ ]);
41
+ const state = createBalancerTuiState();
42
+ const usageAutoRefresh = createUsageAutoRefresh(api, state);
43
+ const balancerBarSync = createTuiBalancerBarSync(api, state);
44
+ let dashboardReturnRoute: TuiRouteCurrent | undefined;
45
+
46
+ api.lifecycle.onDispose(() => {
47
+ usageAutoRefresh.dispose();
48
+ state.dispose();
49
+ });
50
+
51
+ const openDashboard = () => {
52
+ if (api.route.current.name !== "balancer.dashboard") dashboardReturnRoute = copyRoute(api.route.current);
53
+ api.route.navigate("balancer.dashboard");
54
+ };
55
+
56
+ const openPriority = () => {
57
+ api.route.navigate("balancer.priority");
58
+ };
59
+
60
+ const backFromDashboard = () => {
61
+ const route = dashboardReturnRoute;
62
+ dashboardReturnRoute = undefined;
63
+ if (route) api.route.navigate(route.name, "params" in route ? route.params : undefined);
64
+ else api.route.navigate("home");
65
+ };
66
+
67
+ const unregisterDashboard = api.route.register([
68
+ {
69
+ name: "balancer.dashboard",
70
+ render: () =>
71
+ createComponent(dashboardModule.Dashboard, {
72
+ api,
73
+ state,
74
+ onBack: backFromDashboard,
75
+ openPriority,
76
+ openConnect: () => openNativeConnect({ ...api, db: state.db }),
77
+ renameAccount: (providerID, alias) => renameDialogModule.openRenameDialog(api, state, providerID, alias),
78
+ removeAccount: (providerID, alias) => removeAccountFromTui(api, state, providerID, alias),
79
+ }),
80
+ },
81
+ {
82
+ name: "balancer.priority",
83
+ render: () =>
84
+ createComponent(priorityScreenModule.PriorityScreen, {
85
+ api,
86
+ state,
87
+ onBack: () => api.route.navigate("balancer.dashboard"),
88
+ openModelPicker: (providerID) =>
89
+ providerModelDialogModule.openProviderModelDialog(api, state, providerID, {
90
+ onSelected: (model) => setProviderModel(state.db, model.providerID, model.modelID),
91
+ }),
92
+ }),
93
+ },
94
+ ]);
95
+ api.lifecycle.onDispose(unregisterDashboard);
96
+
97
+ const unregisterKeymap = api.keymap.registerLayer({
98
+ commands: [
99
+ {
100
+ name: "balancer.dashboard.open",
101
+ title: "Open Balancer Dashboard",
102
+ category: "Plugin",
103
+ namespace: "palette",
104
+ slashName: "balancer",
105
+ run() {
106
+ openDashboard();
107
+ },
108
+ },
109
+ ],
110
+ bindings: [{ key: "ctrl+b", cmd: "balancer.dashboard.open" }],
111
+ });
112
+ api.lifecycle.onDispose(unregisterKeymap);
113
+
114
+ api.slots.register({
115
+ slots: {
116
+ session_prompt_right(_ctx, value) {
117
+ void usageAutoRefresh.refreshForPrompt();
118
+ void balancerBarSync.maybeSync();
119
+ return createComponent(statusIndicatorModule.BalancerStatusIndicator, {
120
+ api,
121
+ state,
122
+ providerID: () => inferProviderID(api.state.session.get(value.session_id)),
123
+ });
124
+ },
125
+ sidebar_content(_ctx, value) {
126
+ return createComponent(sidebarModule.BalancerSidebar, {
127
+ api,
128
+ state,
129
+ openDashboard,
130
+ activateAccount: (providerID, alias) =>
131
+ activateAccount(api, state, providerID, alias, {
132
+ sessionProviderID: inferProviderID(api.state.session.get(value.session_id)),
133
+ openProviderModelPicker: (targetProviderID) =>
134
+ providerModelDialogModule.openProviderModelDialog(api, state, targetProviderID),
135
+ }),
136
+ });
137
+ },
138
+ },
139
+ });
140
+ };
141
+
142
+ export default { id: "opencode-balancer", tui } satisfies TuiPluginModule;
@@ -0,0 +1,16 @@
1
+ import { refreshAccountUsage } from "../core/usage";
2
+ import { refreshUsageForAccount } from "./actions";
3
+ import type { BalancerTuiState } from "./state";
4
+ type ToastApi = Parameters<typeof refreshUsageForAccount>[0];
5
+ type UsageAutoRefreshOptions = {
6
+ intervalMs?: number;
7
+ promptDebounceMs?: number;
8
+ refreshUsage?: typeof refreshAccountUsage;
9
+ now?: () => number;
10
+ };
11
+ export declare function createUsageAutoRefresh(api: ToastApi, state: BalancerTuiState, options?: UsageAutoRefreshOptions): {
12
+ refreshNow: () => Promise<void>;
13
+ refreshForPrompt: () => Promise<void>;
14
+ dispose(): void;
15
+ };
16
+ export {};