@thelioo/opencode-balancer 0.1.3 → 0.1.4

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 +1 -37
  55. package/dist/index.js +1 -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,100 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { saveAccount } from "../core/accounts";
3
+ import { clearNativeConnectInProgress, markNativeConnectInProgress } from "../core/native-connect";
4
+ import { readNativeAuth } from "../server/auth-watcher";
5
+ import type { AuthInfo } from "../core/types";
6
+
7
+ type NativeAuthReadResult =
8
+ | { ok: true; auth: Record<string, AuthInfo> }
9
+ | { ok: false };
10
+
11
+ type ConnectApi = {
12
+ db?: Database;
13
+ readAuth?: () => NativeAuthReadResult;
14
+ generateAlias?: () => string;
15
+ wait?: (ms: number) => Promise<void>;
16
+ maxWaitMs?: number;
17
+ pollIntervalMs?: number;
18
+ keymap?: {
19
+ dispatchCommand?: (command: string) => unknown;
20
+ };
21
+ ui?: {
22
+ dialog?: { open?: boolean };
23
+ toast?: (input: { variant: "success" | "error"; message: string }) => unknown;
24
+ };
25
+ };
26
+
27
+ const aliasAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
28
+
29
+ function generatedAlias() {
30
+ let alias = "";
31
+ for (let index = 0; index < 5; index++) alias += aliasAlphabet[Math.floor(Math.random() * aliasAlphabet.length)];
32
+ return alias;
33
+ }
34
+
35
+ function authKey(auth: AuthInfo | undefined) {
36
+ return JSON.stringify(auth ?? null);
37
+ }
38
+
39
+ function changedProvider(before: Record<string, AuthInfo>, after: Record<string, AuthInfo>) {
40
+ return Object.entries(after).find(([providerID, auth]) => authKey(before[providerID]) !== authKey(auth));
41
+ }
42
+
43
+ function uniqueAlias(db: Database, providerID: string, generate: () => string) {
44
+ for (let attempt = 0; attempt < 100; attempt++) {
45
+ const alias = generate();
46
+ const existing = db
47
+ .query<{ alias: string }, [string, string]>("SELECT alias FROM accounts WHERE provider_id = ? AND alias = ?")
48
+ .get(providerID, alias);
49
+ if (!existing) return alias;
50
+ }
51
+ throw new Error("Could not generate a unique alias");
52
+ }
53
+
54
+ async function waitForChangedProvider(
55
+ readAuth: () => NativeAuthReadResult,
56
+ before: NativeAuthReadResult,
57
+ options: { wait: (ms: number) => Promise<void>; maxWaitMs: number; pollIntervalMs: number },
58
+ ) {
59
+ const started = Date.now();
60
+ while (true) {
61
+ const after = readAuth();
62
+ if (before.ok && after.ok) {
63
+ const changed = changedProvider(before.auth, after.auth);
64
+ if (changed) return changed;
65
+ }
66
+ if (Date.now() - started >= options.maxWaitMs) return;
67
+ await options.wait(options.pollIntervalMs);
68
+ }
69
+ }
70
+
71
+ export async function openNativeConnect(api: ConnectApi) {
72
+ if (api.keymap?.dispatchCommand) {
73
+ const readAuth = api.readAuth ?? readNativeAuth;
74
+ const before = readAuth();
75
+ if (api.db) markNativeConnectInProgress(api.db);
76
+ await api.keymap.dispatchCommand("provider.connect");
77
+ if (api.db) {
78
+ try {
79
+ const changed = await waitForChangedProvider(readAuth, before, {
80
+ wait: api.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
81
+ maxWaitMs: api.maxWaitMs ?? (api.ui?.dialog ? 10 * 60 * 1000 : 0),
82
+ pollIntervalMs: api.pollIntervalMs ?? 500,
83
+ });
84
+ if (changed) {
85
+ const [providerID, auth] = changed;
86
+ const account = saveAccount(api.db, providerID, uniqueAlias(api.db, providerID, api.generateAlias ?? generatedAlias), auth);
87
+ api.ui?.toast?.({ variant: "success", message: `Saved ${account.providerID}/${account.alias}.` });
88
+ }
89
+ } finally {
90
+ clearNativeConnectInProgress(api.db);
91
+ }
92
+ }
93
+ return;
94
+ }
95
+
96
+ api.ui?.toast?.({
97
+ variant: "error",
98
+ message: "Native provider connect is unavailable in this opencode build.",
99
+ });
100
+ }
@@ -0,0 +1,45 @@
1
+ export type DashboardKey = {
2
+ name: string;
3
+ };
4
+ export type DashboardIntent = {
5
+ type: "move-cursor";
6
+ delta: -1 | 1;
7
+ } | {
8
+ type: "move-header";
9
+ delta: -1 | 1;
10
+ } | {
11
+ type: "primary";
12
+ } | {
13
+ type: "priority";
14
+ } | {
15
+ type: "connect";
16
+ } | {
17
+ type: "alias";
18
+ } | {
19
+ type: "rename";
20
+ } | {
21
+ type: "remove";
22
+ } | {
23
+ type: "confirm";
24
+ } | {
25
+ type: "cancel";
26
+ } | {
27
+ type: "back";
28
+ } | {
29
+ type: "none";
30
+ };
31
+ export type DashboardFocusArea = "header" | "content";
32
+ export declare function moveDashboardFocus(state: {
33
+ area: DashboardFocusArea;
34
+ cursor: number;
35
+ rowCount: number;
36
+ }, delta: -1 | 1): {
37
+ area: DashboardFocusArea;
38
+ cursor: number;
39
+ };
40
+ export declare function dashboardSelectionMarker(input: {
41
+ focusedArea: DashboardFocusArea;
42
+ itemArea: DashboardFocusArea;
43
+ selected: boolean;
44
+ }): " " | "▶";
45
+ export declare function reduceDashboardKey(key: DashboardKey): DashboardIntent;
@@ -0,0 +1,44 @@
1
+ export function moveDashboardFocus(state, delta) {
2
+ if (state.area === "header") {
3
+ return delta > 0 ? { area: "content", cursor: Math.max(0, Math.min(state.cursor, state.rowCount - 1)) } : { area: "header", cursor: state.cursor };
4
+ }
5
+ if (delta < 0 && state.cursor <= 0)
6
+ return { area: "header", cursor: 0 };
7
+ return {
8
+ area: "content",
9
+ cursor: Math.max(0, Math.min(state.cursor + delta, Math.max(0, state.rowCount - 1))),
10
+ };
11
+ }
12
+ export function dashboardSelectionMarker(input) {
13
+ return input.selected && input.focusedArea === input.itemArea ? "▶" : " ";
14
+ }
15
+ export function reduceDashboardKey(key) {
16
+ if (key.name === "up" || key.name === "k")
17
+ return { type: "move-cursor", delta: -1 };
18
+ if (key.name === "down" || key.name === "j")
19
+ return { type: "move-cursor", delta: 1 };
20
+ if (key.name === "left" || key.name === "h")
21
+ return { type: "move-header", delta: -1 };
22
+ if (key.name === "right" || key.name === "l")
23
+ return { type: "move-header", delta: 1 };
24
+ if (key.name === "return" || key.name === "space")
25
+ return { type: "primary" };
26
+ if (key.name === "p")
27
+ return { type: "priority" };
28
+ if (key.name === "c")
29
+ return { type: "connect" };
30
+ if (key.name === "a")
31
+ return { type: "alias" };
32
+ if (key.name === "r")
33
+ return { type: "rename" };
34
+ if (key.name === "d")
35
+ return { type: "remove" };
36
+ if (key.name === "y")
37
+ return { type: "confirm" };
38
+ if (key.name === "n")
39
+ return { type: "cancel" };
40
+ if (key.name === "escape")
41
+ return { type: "back" };
42
+ return { type: "none" };
43
+ }
44
+ //# sourceMappingURL=dashboard-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-keys.js","sourceRoot":"","sources":["../../src/tui/dashboard-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export type DashboardKey = {
2
+ name: string;
3
+ };
4
+
5
+ export type DashboardIntent =
6
+ | { type: "move-cursor"; delta: -1 | 1 }
7
+ | { type: "move-header"; delta: -1 | 1 }
8
+ | { type: "primary" }
9
+ | { type: "priority" }
10
+ | { type: "connect" }
11
+ | { type: "alias" }
12
+ | { type: "rename" }
13
+ | { type: "remove" }
14
+ | { type: "confirm" }
15
+ | { type: "cancel" }
16
+ | { type: "back" }
17
+ | { type: "none" };
18
+
19
+ export type DashboardFocusArea = "header" | "content";
20
+
21
+ export function moveDashboardFocus(
22
+ state: { area: DashboardFocusArea; cursor: number; rowCount: number },
23
+ delta: -1 | 1,
24
+ ): { area: DashboardFocusArea; cursor: number } {
25
+ if (state.area === "header") {
26
+ return delta > 0 ? { area: "content", cursor: Math.max(0, Math.min(state.cursor, state.rowCount - 1)) } : { area: "header", cursor: state.cursor };
27
+ }
28
+
29
+ if (delta < 0 && state.cursor <= 0) return { area: "header", cursor: 0 };
30
+
31
+ return {
32
+ area: "content",
33
+ cursor: Math.max(0, Math.min(state.cursor + delta, Math.max(0, state.rowCount - 1))),
34
+ };
35
+ }
36
+
37
+ export function dashboardSelectionMarker(input: {
38
+ focusedArea: DashboardFocusArea;
39
+ itemArea: DashboardFocusArea;
40
+ selected: boolean;
41
+ }) {
42
+ return input.selected && input.focusedArea === input.itemArea ? "▶" : " ";
43
+ }
44
+
45
+ export function reduceDashboardKey(key: DashboardKey): DashboardIntent {
46
+ if (key.name === "up" || key.name === "k") return { type: "move-cursor", delta: -1 };
47
+ if (key.name === "down" || key.name === "j") return { type: "move-cursor", delta: 1 };
48
+ if (key.name === "left" || key.name === "h") return { type: "move-header", delta: -1 };
49
+ if (key.name === "right" || key.name === "l") return { type: "move-header", delta: 1 };
50
+ if (key.name === "return" || key.name === "space") return { type: "primary" };
51
+ if (key.name === "p") return { type: "priority" };
52
+ if (key.name === "c") return { type: "connect" };
53
+ if (key.name === "a") return { type: "alias" };
54
+ if (key.name === "r") return { type: "rename" };
55
+ if (key.name === "d") return { type: "remove" };
56
+ if (key.name === "y") return { type: "confirm" };
57
+ if (key.name === "n") return { type: "cancel" };
58
+ if (key.name === "escape") return { type: "back" };
59
+ return { type: "none" };
60
+ }
@@ -0,0 +1,21 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ export type NativeModelApplier = (model: {
3
+ providerID: string;
4
+ modelID: string;
5
+ }, title: string) => Promise<boolean>;
6
+ export type NativeModelApplyDeps = {
7
+ dispatchCommand: (command: string) => void;
8
+ isDialogOpen: () => boolean;
9
+ feed: (sequence: string) => boolean;
10
+ wait: (ms: number) => Promise<void>;
11
+ settleMs?: number;
12
+ };
13
+ /**
14
+ * Drive opencode's native model dialog to select `title`:
15
+ * 1. open it via the native `model.list` command
16
+ * 2. type the model title to filter the list down to the target
17
+ * 3. press Enter to select -> opencode runs `local.model.set` and the bar updates
18
+ * 4. dismiss a follow-up variant dialog if one appears
19
+ */
20
+ export declare function applyNativeModelSelection(deps: NativeModelApplyDeps, title: string): Promise<boolean>;
21
+ export declare function createNativeModelApplier(api: TuiPluginApi): NativeModelApplier;
@@ -0,0 +1,53 @@
1
+ const ENTER = "\r";
2
+ const ESCAPE = "\x1B";
3
+ function rendererStdin(api) {
4
+ const stdin = api.renderer?.stdin;
5
+ return stdin && typeof stdin.emit === "function" ? stdin : undefined;
6
+ }
7
+ /**
8
+ * Feed a raw input sequence into opencode's renderer, exactly like a real
9
+ * keypress. opencode's stdin parser dispatches it to the focused element, so
10
+ * this drives opencode's own UI even though the plugin runs in a separate
11
+ * Solid instance and cannot touch opencode's reactive state directly.
12
+ */
13
+ function feed(api, sequence) {
14
+ const stdin = rendererStdin(api);
15
+ if (!stdin)
16
+ return false;
17
+ stdin.emit("data", Buffer.from(sequence));
18
+ return true;
19
+ }
20
+ /**
21
+ * Drive opencode's native model dialog to select `title`:
22
+ * 1. open it via the native `model.list` command
23
+ * 2. type the model title to filter the list down to the target
24
+ * 3. press Enter to select -> opencode runs `local.model.set` and the bar updates
25
+ * 4. dismiss a follow-up variant dialog if one appears
26
+ */
27
+ export async function applyNativeModelSelection(deps, title) {
28
+ const settle = deps.settleMs ?? 90;
29
+ deps.dispatchCommand("model.list");
30
+ await deps.wait(settle);
31
+ if (!deps.isDialogOpen())
32
+ return false;
33
+ if (!deps.feed(title))
34
+ return false;
35
+ await deps.wait(settle);
36
+ if (!deps.feed(ENTER))
37
+ return false;
38
+ await deps.wait(settle);
39
+ // A model with variants opens a follow-up dialog; close it so the bar keeps
40
+ // the model we just set (with its default variant).
41
+ if (deps.isDialogOpen())
42
+ deps.feed(ESCAPE);
43
+ return true;
44
+ }
45
+ export function createNativeModelApplier(api) {
46
+ return (_model, title) => applyNativeModelSelection({
47
+ dispatchCommand: (command) => api.keymap.dispatchCommand(command),
48
+ isDialogOpen: () => api.ui.dialog.open,
49
+ feed: (sequence) => feed(api, sequence),
50
+ wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
51
+ }, title);
52
+ }
53
+ //# sourceMappingURL=native-model-apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-model-apply.js","sourceRoot":"","sources":["../../src/tui/native-model-apply.ts"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAG,IAAI,CAAC;AACnB,MAAM,MAAM,GAAG,MAAM,CAAC;AAItB,SAAS,aAAa,CAAC,GAAiB;IACpC,MAAM,KAAK,GAAI,GAAG,CAAC,QAAyD,EAAE,KAAK,CAAC;IACpF,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,SAAS,IAAI,CAAC,GAAiB,EAAE,QAAgB;IAC7C,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC;AAChB,CAAC;AAYD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,IAA0B,EAAE,KAAa;IACrF,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IAEnC,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IACnC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;QAAE,OAAO,KAAK,CAAC;IAEvC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAExB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAExB,4EAA4E;IAC5E,oDAAoD;IACpD,IAAI,IAAI,CAAC,YAAY,EAAE;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAiB;IACtD,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CACrB,yBAAyB,CACrB;QACI,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC;QACjE,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI;QACtC,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC;QACvC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;KAClE,EACD,KAAK,CACR,CAAC;AACV,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+
3
+ const ENTER = "\r";
4
+ const ESCAPE = "\x1B";
5
+
6
+ type StdinLike = { emit: (event: string, data: unknown) => unknown };
7
+
8
+ function rendererStdin(api: TuiPluginApi): StdinLike | undefined {
9
+ const stdin = (api.renderer as unknown as { stdin?: StdinLike } | undefined)?.stdin;
10
+ return stdin && typeof stdin.emit === "function" ? stdin : undefined;
11
+ }
12
+
13
+ /**
14
+ * Feed a raw input sequence into opencode's renderer, exactly like a real
15
+ * keypress. opencode's stdin parser dispatches it to the focused element, so
16
+ * this drives opencode's own UI even though the plugin runs in a separate
17
+ * Solid instance and cannot touch opencode's reactive state directly.
18
+ */
19
+ function feed(api: TuiPluginApi, sequence: string): boolean {
20
+ const stdin = rendererStdin(api);
21
+ if (!stdin) return false;
22
+ stdin.emit("data", Buffer.from(sequence));
23
+ return true;
24
+ }
25
+
26
+ export type NativeModelApplier = (model: { providerID: string; modelID: string }, title: string) => Promise<boolean>;
27
+
28
+ export type NativeModelApplyDeps = {
29
+ dispatchCommand: (command: string) => void;
30
+ isDialogOpen: () => boolean;
31
+ feed: (sequence: string) => boolean;
32
+ wait: (ms: number) => Promise<void>;
33
+ settleMs?: number;
34
+ };
35
+
36
+ /**
37
+ * Drive opencode's native model dialog to select `title`:
38
+ * 1. open it via the native `model.list` command
39
+ * 2. type the model title to filter the list down to the target
40
+ * 3. press Enter to select -> opencode runs `local.model.set` and the bar updates
41
+ * 4. dismiss a follow-up variant dialog if one appears
42
+ */
43
+ export async function applyNativeModelSelection(deps: NativeModelApplyDeps, title: string): Promise<boolean> {
44
+ const settle = deps.settleMs ?? 90;
45
+
46
+ deps.dispatchCommand("model.list");
47
+ await deps.wait(settle);
48
+ if (!deps.isDialogOpen()) return false;
49
+
50
+ if (!deps.feed(title)) return false;
51
+ await deps.wait(settle);
52
+
53
+ if (!deps.feed(ENTER)) return false;
54
+ await deps.wait(settle);
55
+
56
+ // A model with variants opens a follow-up dialog; close it so the bar keeps
57
+ // the model we just set (with its default variant).
58
+ if (deps.isDialogOpen()) deps.feed(ESCAPE);
59
+ return true;
60
+ }
61
+
62
+ export function createNativeModelApplier(api: TuiPluginApi): NativeModelApplier {
63
+ return (_model, title) =>
64
+ applyNativeModelSelection(
65
+ {
66
+ dispatchCommand: (command) => api.keymap.dispatchCommand(command),
67
+ isDialogOpen: () => api.ui.dialog.open,
68
+ feed: (sequence) => feed(api, sequence),
69
+ wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
70
+ },
71
+ title,
72
+ );
73
+ }
@@ -0,0 +1,40 @@
1
+ export type PriorityKey = {
2
+ name: string;
3
+ shift?: boolean;
4
+ };
5
+ export type PriorityIntent = {
6
+ type: "move-cursor";
7
+ delta: -1 | 1;
8
+ } | {
9
+ type: "reorder";
10
+ direction: -1 | 1;
11
+ } | {
12
+ type: "toggle-enabled";
13
+ } | {
14
+ type: "open-model";
15
+ } | {
16
+ type: "toggle-balancing";
17
+ } | {
18
+ type: "back";
19
+ } | {
20
+ type: "none";
21
+ };
22
+ export type PriorityFocusArea = "header" | "content";
23
+ export declare function movePriorityFocus(state: {
24
+ area: PriorityFocusArea;
25
+ cursor: number;
26
+ rowCount: number;
27
+ }, delta: -1 | 1): {
28
+ area: PriorityFocusArea;
29
+ cursor: number;
30
+ };
31
+ export declare function prioritySelectionMarker(input: {
32
+ focusedArea: PriorityFocusArea;
33
+ itemArea: PriorityFocusArea;
34
+ selected: boolean;
35
+ }): " " | "▶";
36
+ /**
37
+ * Pure keybinding logic for the BIOS-style priority screen. Kept separate from
38
+ * rendering so the whole keyboard contract is testable without a terminal.
39
+ */
40
+ export declare function reducePriorityKey(key: PriorityKey): PriorityIntent;
@@ -0,0 +1,38 @@
1
+ export function movePriorityFocus(state, delta) {
2
+ if (state.area === "header") {
3
+ return delta > 0 ? { area: "content", cursor: Math.max(0, Math.min(state.cursor, state.rowCount - 1)) } : { area: "header", cursor: state.cursor };
4
+ }
5
+ if (delta < 0 && state.cursor <= 0)
6
+ return { area: "header", cursor: 0 };
7
+ return {
8
+ area: "content",
9
+ cursor: Math.max(0, Math.min(state.cursor + delta, Math.max(0, state.rowCount - 1))),
10
+ };
11
+ }
12
+ export function prioritySelectionMarker(input) {
13
+ return input.selected && input.focusedArea === input.itemArea ? "▶" : " ";
14
+ }
15
+ /**
16
+ * Pure keybinding logic for the BIOS-style priority screen. Kept separate from
17
+ * rendering so the whole keyboard contract is testable without a terminal.
18
+ */
19
+ export function reducePriorityKey(key) {
20
+ const name = key.name;
21
+ if ((name === "up" || name === "down") && key.shift) {
22
+ return { type: "reorder", direction: name === "up" ? -1 : 1 };
23
+ }
24
+ if (name === "up" || name === "k")
25
+ return { type: "move-cursor", delta: -1 };
26
+ if (name === "down" || name === "j")
27
+ return { type: "move-cursor", delta: 1 };
28
+ if (name === "space")
29
+ return { type: "toggle-enabled" };
30
+ if (name === "return")
31
+ return { type: "open-model" };
32
+ if (name === "b")
33
+ return { type: "toggle-balancing" };
34
+ if (name === "escape")
35
+ return { type: "back" };
36
+ return { type: "none" };
37
+ }
38
+ //# sourceMappingURL=priority-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priority-keys.js","sourceRoot":"","sources":["../../src/tui/priority-keys.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,iBAAiB,CAC7B,KAAoE,EACpE,KAAa;IAEb,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACvJ,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAEzE,OAAO;QACH,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;KACvF,CAAC;AACN,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAIvC;IACG,OAAO,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAgB;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IAEtB,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IAC7E,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC9E,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACxD,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACrD,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;IACtD,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAE/C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,59 @@
1
+ export type PriorityKey = {
2
+ name: string;
3
+ shift?: boolean;
4
+ };
5
+
6
+ export type PriorityIntent =
7
+ | { type: "move-cursor"; delta: -1 | 1 }
8
+ | { type: "reorder"; direction: -1 | 1 }
9
+ | { type: "toggle-enabled" }
10
+ | { type: "open-model" }
11
+ | { type: "toggle-balancing" }
12
+ | { type: "back" }
13
+ | { type: "none" };
14
+
15
+ export type PriorityFocusArea = "header" | "content";
16
+
17
+ export function movePriorityFocus(
18
+ state: { area: PriorityFocusArea; cursor: number; rowCount: number },
19
+ delta: -1 | 1,
20
+ ): { area: PriorityFocusArea; cursor: number } {
21
+ if (state.area === "header") {
22
+ return delta > 0 ? { area: "content", cursor: Math.max(0, Math.min(state.cursor, state.rowCount - 1)) } : { area: "header", cursor: state.cursor };
23
+ }
24
+
25
+ if (delta < 0 && state.cursor <= 0) return { area: "header", cursor: 0 };
26
+
27
+ return {
28
+ area: "content",
29
+ cursor: Math.max(0, Math.min(state.cursor + delta, Math.max(0, state.rowCount - 1))),
30
+ };
31
+ }
32
+
33
+ export function prioritySelectionMarker(input: {
34
+ focusedArea: PriorityFocusArea;
35
+ itemArea: PriorityFocusArea;
36
+ selected: boolean;
37
+ }) {
38
+ return input.selected && input.focusedArea === input.itemArea ? "▶" : " ";
39
+ }
40
+
41
+ /**
42
+ * Pure keybinding logic for the BIOS-style priority screen. Kept separate from
43
+ * rendering so the whole keyboard contract is testable without a terminal.
44
+ */
45
+ export function reducePriorityKey(key: PriorityKey): PriorityIntent {
46
+ const name = key.name;
47
+
48
+ if ((name === "up" || name === "down") && key.shift) {
49
+ return { type: "reorder", direction: name === "up" ? -1 : 1 };
50
+ }
51
+ if (name === "up" || name === "k") return { type: "move-cursor", delta: -1 };
52
+ if (name === "down" || name === "j") return { type: "move-cursor", delta: 1 };
53
+ if (name === "space") return { type: "toggle-enabled" };
54
+ if (name === "return") return { type: "open-model" };
55
+ if (name === "b") return { type: "toggle-balancing" };
56
+ if (name === "escape") return { type: "back" };
57
+
58
+ return { type: "none" };
59
+ }
@@ -0,0 +1,19 @@
1
+ type ProviderLike = {
2
+ id: string;
3
+ name?: string;
4
+ models?: Record<string, ModelLike>;
5
+ };
6
+ type ModelLike = {
7
+ id?: string;
8
+ name?: string;
9
+ status?: string;
10
+ release_date?: string;
11
+ };
12
+ export type ProviderModelOption = {
13
+ providerID: string;
14
+ providerName: string;
15
+ modelID: string;
16
+ title: string;
17
+ };
18
+ export declare function providerModelOptions(providers: readonly ProviderLike[], providerID: string): ProviderModelOption[];
19
+ export {};
@@ -0,0 +1,17 @@
1
+ export function providerModelOptions(providers, providerID) {
2
+ const provider = providers.find((item) => item.id === providerID);
3
+ if (!provider?.models)
4
+ return [];
5
+ return Object.entries(provider.models)
6
+ .filter(([, model]) => model.status !== "deprecated")
7
+ .map(([modelID, model]) => ({
8
+ providerID: provider.id,
9
+ providerName: provider.name ?? provider.id,
10
+ modelID: model.id ?? modelID,
11
+ title: model.name ?? model.id ?? modelID,
12
+ releaseDate: model.release_date ?? "",
13
+ }))
14
+ .sort((a, b) => b.releaseDate.localeCompare(a.releaseDate) || a.title.localeCompare(b.title))
15
+ .map(({ releaseDate: _releaseDate, ...option }) => option);
16
+ }
17
+ //# sourceMappingURL=provider-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-models.js","sourceRoot":"","sources":["../../src/tui/provider-models.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,oBAAoB,CAAC,SAAkC,EAAE,UAAkB;IACvF,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;IAClE,IAAI,CAAC,QAAQ,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IAEjC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,YAAY,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACxB,UAAU,EAAE,QAAQ,CAAC,EAAE;QACvB,YAAY,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,EAAE;QAC1C,OAAO,EAAE,KAAK,CAAC,EAAE,IAAI,OAAO;QAC5B,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,IAAI,OAAO;QACxC,WAAW,EAAE,KAAK,CAAC,YAAY,IAAI,EAAE;KACxC,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SAC5F,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,36 @@
1
+ type ProviderLike = {
2
+ id: string;
3
+ name?: string;
4
+ models?: Record<string, ModelLike>;
5
+ };
6
+
7
+ type ModelLike = {
8
+ id?: string;
9
+ name?: string;
10
+ status?: string;
11
+ release_date?: string;
12
+ };
13
+
14
+ export type ProviderModelOption = {
15
+ providerID: string;
16
+ providerName: string;
17
+ modelID: string;
18
+ title: string;
19
+ };
20
+
21
+ export function providerModelOptions(providers: readonly ProviderLike[], providerID: string): ProviderModelOption[] {
22
+ const provider = providers.find((item) => item.id === providerID);
23
+ if (!provider?.models) return [];
24
+
25
+ return Object.entries(provider.models)
26
+ .filter(([, model]) => model.status !== "deprecated")
27
+ .map(([modelID, model]) => ({
28
+ providerID: provider.id,
29
+ providerName: provider.name ?? provider.id,
30
+ modelID: model.id ?? modelID,
31
+ title: model.name ?? model.id ?? modelID,
32
+ releaseDate: model.release_date ?? "",
33
+ }))
34
+ .sort((a, b) => b.releaseDate.localeCompare(a.releaseDate) || a.title.localeCompare(b.title))
35
+ .map(({ releaseDate: _releaseDate, ...option }) => option);
36
+ }
@@ -0,0 +1,9 @@
1
+ export type LayoutMode = "compact" | "full";
2
+ export declare function dashboardLayoutMode(size: {
3
+ width?: number;
4
+ height?: number;
5
+ }): LayoutMode;
6
+ export declare function visibleRecentEventLimit(mode: LayoutMode): number;
7
+ export declare function dashboardContentHeight(size: {
8
+ height?: number;
9
+ }): number;
@@ -0,0 +1,13 @@
1
+ export function dashboardLayoutMode(size) {
2
+ const width = size.width ?? 999;
3
+ const height = size.height ?? 999;
4
+ return width < 100 || height < 26 ? "compact" : "full";
5
+ }
6
+ export function visibleRecentEventLimit(mode) {
7
+ return mode === "compact" ? 5 : 10;
8
+ }
9
+ export function dashboardContentHeight(size) {
10
+ const height = size.height ?? 28;
11
+ return Math.max(6, height - 8);
12
+ }
13
+ //# sourceMappingURL=responsive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responsive.js","sourceRoot":"","sources":["../../src/tui/responsive.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,IAAyC;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;IAClC,OAAO,KAAK,GAAG,GAAG,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAgB;IACpD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;AACnC,CAAC"}