opencode-nim-rotator 0.0.1

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/bin/opencode-nim-rotator.ts +10 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +103 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/storage.d.ts +18 -0
  9. package/dist/storage.d.ts.map +1 -0
  10. package/dist/storage.js +129 -0
  11. package/dist/storage.js.map +1 -0
  12. package/dist/themes.d.ts +64 -0
  13. package/dist/themes.d.ts.map +1 -0
  14. package/dist/themes.js +422 -0
  15. package/dist/themes.js.map +1 -0
  16. package/dist/tui/actions.d.ts +3 -0
  17. package/dist/tui/actions.d.ts.map +1 -0
  18. package/dist/tui/actions.js +68 -0
  19. package/dist/tui/actions.js.map +1 -0
  20. package/dist/tui/app.d.ts +2 -0
  21. package/dist/tui/app.d.ts.map +1 -0
  22. package/dist/tui/app.js +141 -0
  23. package/dist/tui/app.js.map +1 -0
  24. package/dist/tui/screens.d.ts +10 -0
  25. package/dist/tui/screens.d.ts.map +1 -0
  26. package/dist/tui/screens.js +316 -0
  27. package/dist/tui/screens.js.map +1 -0
  28. package/dist/tui/state.d.ts +29 -0
  29. package/dist/tui/state.d.ts.map +1 -0
  30. package/dist/tui/state.js +47 -0
  31. package/dist/tui/state.js.map +1 -0
  32. package/dist/tui/types.d.ts +12 -0
  33. package/dist/tui/types.d.ts.map +1 -0
  34. package/dist/tui/types.js +2 -0
  35. package/dist/tui/types.js.map +1 -0
  36. package/dist/tui/ui.d.ts +17 -0
  37. package/dist/tui/ui.d.ts.map +1 -0
  38. package/dist/tui/ui.js +77 -0
  39. package/dist/tui/ui.js.map +1 -0
  40. package/dist/types.d.ts +22 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +2 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +62 -0
  45. package/scripts/postinstall.js +69 -0
  46. package/scripts/uninstall.js +79 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # opencode-nim-rotator
2
+
3
+ An [OpenCode](https://opencode.ai) plugin for managing and rotating multiple NVIDIA NIM API keys.
4
+
5
+ ## How It Works
6
+
7
+ The plugin uses OpenCode's `auth` hook with a custom `fetch` function to intercept every NVIDIA NIM API call and inject a rotated API key into the `Authorization` header. This is the same pattern used by the codex and github-copilot plugins in production.
8
+
9
+ **Key rotation happens per-request**: each LLM call uses the next key in the rotation. If a key returns 401/403, it automatically increments the failure count and retries with the next key.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install -g opencode-nim-rotator
15
+ ```
16
+
17
+ The postinstall script automatically adds the plugin to your `~/.config/opencode/opencode.json`.
18
+
19
+ ## Setup
20
+
21
+ ### 1. Add API keys
22
+
23
+ Run the TUI manager:
24
+
25
+ ```bash
26
+ bun opencode-nim-rotator
27
+ ```
28
+
29
+ Or manually — add at least one key via OpenCode's auth system:
30
+
31
+ ```bash
32
+ opencode /connect nvidia
33
+ ```
34
+
35
+ Select "Enter NVIDIA NIM API Key" and paste your key.
36
+
37
+ ### 2. Add more keys via the TUI
38
+
39
+ ```bash
40
+ bun opencode-nim-rotator
41
+ ```
42
+
43
+ The TUI lets you:
44
+
45
+ - **Add** keys with a friendly name
46
+ - **Rename** keys
47
+ - **Delete** keys
48
+ - **Toggle** keys on/off
49
+ - **Reset** failure counts
50
+ - **Switch** rotation strategy (round-robin or least-failures)
51
+
52
+ ### 3. Restart OpenCode
53
+
54
+ After adding keys, restart opencode. The plugin's `auth` loader will fire on startup and provide the custom `fetch` that rotates keys.
55
+
56
+ ## Configuration
57
+
58
+ ### Environment Variables
59
+
60
+ | Variable | Description | Default |
61
+ | -------------------------- | ----------------------------------- | ------------------------------------------ |
62
+ | `NIM_ROTATOR_STORE_PATH` | Path to key store JSON file | `~/.config/opencode/nim-rotator-keys.json` |
63
+ | `NIM_ROTATOR_MAX_FAILURES` | Max failures before disabling a key | `5` |
64
+
65
+ ### opencode.json Options
66
+
67
+ ```json
68
+ {
69
+ "plugin": [
70
+ [
71
+ "opencode-nim-rotator",
72
+ {
73
+ "rotationStrategy": "round-robin",
74
+ "storePath": "/custom/path/to/keys.json"
75
+ }
76
+ ]
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ### Rotation Strategies
82
+
83
+ - **`round-robin`** (default): Cycles through keys in order
84
+ - **`least-failures`**: Always uses the key with the fewest failures
85
+
86
+ ## How Key Rotation Works
87
+
88
+ 1. On startup, the `auth` hook `loader` registers a custom `fetch` for the `nvidia` provider
89
+ 2. Every NVIDIA API call goes through this custom fetch
90
+ 3. The fetch selects the next key based on the rotation strategy
91
+ 4. The `Authorization: Bearer <key>` header is replaced with the rotated key
92
+ 5. If the request returns 401/403, the failure count is incremented and the next key is tried
93
+ 6. Keys that exceed `NIM_ROTATOR_MAX_FAILURES` are automatically skipped
94
+ 7. Successful requests reset the key's failure count to 0
95
+
96
+ ## Key Store Format
97
+
98
+ Keys are stored in `~/.config/opencode/nim-rotator-keys.json` with file mode `0600`:
99
+
100
+ ```json
101
+ {
102
+ "keys": [
103
+ {
104
+ "id": "uuid",
105
+ "name": "work-key",
106
+ "key": "nvapi-...",
107
+ "createdAt": 1700000000000,
108
+ "lastUsedAt": 1700000100000,
109
+ "failureCount": 0,
110
+ "enabled": true
111
+ }
112
+ ],
113
+ "currentIndex": 0,
114
+ "rotationStrategy": "round-robin",
115
+ "updatedAt": 1700000000000
116
+ }
117
+ ```
118
+
119
+ ## Themes
120
+
121
+ The TUI supports multiple color themes that match OpenCode's built-in themes. By default, the rotator **syncs with your `opencode.json` theme setting** — when you change your theme in OpenCode, the rotator picks it up automatically.
122
+
123
+ ### Available Themes
124
+
125
+ | ID | Name |
126
+ | ------------ | ------------------ |
127
+ | `opencode` | OpenCode (default) |
128
+ | `catppuccin` | Catppuccin Mocha |
129
+ | `dracula` | Dracula |
130
+ | `gruvbox` | Gruvbox |
131
+ | `kanagawa` | Kanagawa |
132
+ | `nord` | Nord |
133
+ | `one-dark` | One Dark |
134
+ | `rosepine` | Rose Pine |
135
+ | `solarized` | Solarized |
136
+ | `tokyonight` | Tokyonight |
137
+
138
+ ### Setting a Theme
139
+
140
+ From the TUI main menu, select **Theme** to pick a theme or switch back to syncing with `opencode.json`.
141
+
142
+ To override via `opencode.json`:
143
+
144
+ ```json
145
+ {
146
+ "theme": "dracula"
147
+ }
148
+ ```
149
+
150
+ To override independently (saved in the key store):
151
+
152
+ ```json
153
+ {
154
+ "theme": "kanagawa",
155
+ "keys": [...]
156
+ }
157
+ ```
158
+
159
+ Set the store `theme` field to `""` or remove it to revert to syncing with `opencode.json`.
160
+
161
+ ## TUI
162
+
163
+ The TUI is built with [OpenTUI](https://opentui.com) and provides a menu-driven interface:
164
+
165
+ - Main menu with Add, Manage, Strategy, and Theme options
166
+ - Key selector showing name, masked key, failure count, last used
167
+ - Key actions: toggle, rename, delete
168
+ - Add key flow: enter name, then enter key
169
+ - Theme selector with sync-to-opencode option
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ # Install dependencies
175
+ bun install
176
+
177
+ # Run TUI locally
178
+ bun run tui
179
+
180
+ # Build TypeScript
181
+ bun run build
182
+ ```
183
+
184
+ ## Uninstall
185
+
186
+ To remove the plugin and clean up all associated data:
187
+
188
+ ```bash
189
+ npm uninstall -g opencode-nim-rotator
190
+ ```
191
+
192
+ The uninstaller will automatically:
193
+
194
+ 1. Remove `opencode-nim-rotator` from your `~/.config/opencode/opencode.json` plugin list
195
+ 2. Delete your key store file at `~/.config/opencode/nim-rotator-keys.json`
196
+
197
+ **Note:** This will permanently delete all stored API keys, so back them up first if needed. After uninstalling, restart opencode to apply the changes.
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createCliRenderer } from "@opentui/core";
4
+ import { state } from "../src/tui/state.js";
5
+ import { initApp } from "../src/tui/app.js";
6
+
7
+ const renderer = await createCliRenderer({ exitOnCtrlC: false });
8
+
9
+ state.renderer = renderer;
10
+ initApp();
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const NvidiaNimKeyRotator: Plugin;
3
+ export default NvidiaNimKeyRotator;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;AAkCtE,eAAO,MAAM,mBAAmB,EAAE,MAyFjC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ import { loadStore, saveStore, addKey, getNextKey, recordFailure, getActiveKeys, } from "./storage.js";
2
+ const PROVIDER_ID = "nvidia";
3
+ const NIM_BASE_URL = "https://integrate.api.nvidia.com";
4
+ const VALID_STRATEGIES = ["round-robin", "least-failures"];
5
+ function isValidStrategy(val) {
6
+ return typeof val === "string" && VALID_STRATEGIES.includes(val);
7
+ }
8
+ function isAuthError(obj) {
9
+ if (typeof obj === "object" && obj !== null) {
10
+ const code = obj.code;
11
+ const status = obj.status;
12
+ if (code === 401 || code === 403 || status === 401 || status === 403)
13
+ return true;
14
+ }
15
+ const msg = String(obj).toLowerCase();
16
+ return (msg.includes("401") || msg.includes("403") || msg.includes("unauthorized"));
17
+ }
18
+ export const NvidiaNimKeyRotator = async (input, options) => {
19
+ const config = {
20
+ storePath: options?.storePath,
21
+ rotationStrategy: isValidStrategy(options?.rotationStrategy)
22
+ ? options.rotationStrategy
23
+ : "round-robin",
24
+ };
25
+ const store = loadStore(config);
26
+ const activeKeys = getActiveKeys(store, config);
27
+ // Seed an env key if the store is empty
28
+ if (activeKeys.length === 0) {
29
+ const envKey = process.env.NVIDIA_API_KEY;
30
+ if (envKey) {
31
+ const existing = store.keys.find((k) => k.name === "env-default");
32
+ if (!existing) {
33
+ addKey(store, "env-default", envKey);
34
+ saveStore(store, config);
35
+ }
36
+ }
37
+ }
38
+ const hooks = {
39
+ auth: {
40
+ provider: PROVIDER_ID,
41
+ // We deliberately omit auth.loader. The fetch we used
42
+ // to return was never called by opencode; it only uses
43
+ // the apiKey string from the authorize step. Rotation is
44
+ // handled in chat.headers instead.
45
+ methods: [
46
+ {
47
+ type: "api",
48
+ label: "Enter NVIDIA NIM API Key",
49
+ async authorize(inputs) {
50
+ const key = inputs?.["apiKey"];
51
+ if (!key)
52
+ return { type: "failed" };
53
+ try {
54
+ const res = await fetch(`${NIM_BASE_URL}/v1/models`, {
55
+ headers: { Authorization: `Bearer ${key}` },
56
+ });
57
+ if (!res.ok)
58
+ return { type: "failed" };
59
+ }
60
+ catch {
61
+ return { type: "failed" };
62
+ }
63
+ return {
64
+ type: "success",
65
+ key,
66
+ provider: PROVIDER_ID,
67
+ };
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ // Rotate the API key on every outgoing request by mutating the
73
+ // Authorization header. This is the hook that actually runs
74
+ // before each LLM call, unlike auth.loader which only runs once.
75
+ "chat.headers": async (_input, _output) => {
76
+ const next = getNextKey(store, config);
77
+ if (next) {
78
+ _output.headers["Authorization"] = `Bearer ${next.key.key}`;
79
+ saveStore(store, config);
80
+ }
81
+ },
82
+ "shell.env": async (_input, output) => {
83
+ if (output.env.NVIDIA_API_KEY !== undefined) {
84
+ const next = getNextKey(store, config);
85
+ if (next) {
86
+ output.env.NVIDIA_API_KEY = next.key.key;
87
+ saveStore(store, config);
88
+ }
89
+ }
90
+ },
91
+ event: async ({ event }) => {
92
+ if (event.type === "session.error" && isAuthError(event.error)) {
93
+ if (store.lastUsedKeyId) {
94
+ recordFailure(store, store.lastUsedKeyId);
95
+ saveStore(store, config);
96
+ }
97
+ }
98
+ },
99
+ };
100
+ return hooks;
101
+ };
102
+ export default NvidiaNimKeyRotator;
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { ApiKeyEntry, KeyStore, KeyStoreConfig } from "./types.js";
2
+ export declare const DEFAULT_MAX_FAILURES = 5;
3
+ export declare function resolveStorePath(config?: KeyStoreConfig): string;
4
+ export declare function loadStore(config?: KeyStoreConfig): KeyStore;
5
+ export declare function saveStore(store: KeyStore, config?: KeyStoreConfig): void;
6
+ export declare function addKey(store: KeyStore, name: string, key: string): void;
7
+ export declare function removeKey(store: KeyStore, id: string): void;
8
+ export declare function renameKey(store: KeyStore, id: string, newName: string): void;
9
+ export declare function toggleKey(store: KeyStore, id: string, enabled?: boolean): void;
10
+ export declare function getMaxFailures(config?: KeyStoreConfig): number;
11
+ export declare function getActiveKeys(store: KeyStore, config?: KeyStoreConfig): ApiKeyEntry[];
12
+ export declare function getNextKey(store: KeyStore, config?: KeyStoreConfig): {
13
+ key: ApiKeyEntry;
14
+ index: number;
15
+ } | null;
16
+ export declare function recordFailure(store: KeyStore, keyId: string): void;
17
+ export declare function resetFailures(store: KeyStore, keyId?: string): void;
18
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAQxE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAWtC,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CAMhE;AAED,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,CAwB3D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAUxE;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAO3D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAG5E;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,QAAQ,EACf,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,GAChB,IAAI,CAGN;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CAK9D;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EACf,MAAM,CAAC,EAAE,cAAc,GACtB,WAAW,EAAE,CAGf;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,QAAQ,EACf,MAAM,CAAC,EAAE,cAAc,GACtB;IAAE,GAAG,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAyB5C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAOnE"}
@@ -0,0 +1,129 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { homedir } from "os";
4
+ const DEFAULT_STORE_PATH = join(homedir(), ".config", "opencode", "nim-rotator-keys.json");
5
+ export const DEFAULT_MAX_FAILURES = 5;
6
+ function getDefaultStore() {
7
+ return {
8
+ keys: [],
9
+ currentIndex: 0,
10
+ rotationStrategy: "round-robin",
11
+ updatedAt: Date.now(),
12
+ };
13
+ }
14
+ export function resolveStorePath(config) {
15
+ return (config?.storePath ??
16
+ process.env.NIM_ROTATOR_STORE_PATH ??
17
+ DEFAULT_STORE_PATH);
18
+ }
19
+ export function loadStore(config) {
20
+ const storePath = resolveStorePath(config);
21
+ try {
22
+ if (existsSync(storePath)) {
23
+ const raw = readFileSync(storePath, "utf-8");
24
+ const data = JSON.parse(raw);
25
+ if (!data.keys || !Array.isArray(data.keys)) {
26
+ console.warn(`[nim-rotator] Store at "${storePath}" has invalid keys format, using defaults`);
27
+ return getDefaultStore();
28
+ }
29
+ return {
30
+ ...getDefaultStore(),
31
+ ...data,
32
+ };
33
+ }
34
+ }
35
+ catch (err) {
36
+ console.error(`[nim-rotator] Failed to load store at "${storePath}":`, err);
37
+ console.warn("[nim-rotator] Starting with a fresh store. Your existing keys are preserved on disk.");
38
+ }
39
+ return getDefaultStore();
40
+ }
41
+ export function saveStore(store, config) {
42
+ const storePath = resolveStorePath(config);
43
+ const dir = dirname(storePath);
44
+ if (!existsSync(dir)) {
45
+ mkdirSync(dir, { recursive: true });
46
+ }
47
+ store.updatedAt = Date.now();
48
+ writeFileSync(storePath, JSON.stringify(store, null, 2) + "\n", {
49
+ mode: 0o600,
50
+ });
51
+ }
52
+ export function addKey(store, name, key) {
53
+ const entry = {
54
+ id: crypto.randomUUID(),
55
+ name,
56
+ key,
57
+ createdAt: Date.now(),
58
+ failureCount: 0,
59
+ enabled: true,
60
+ };
61
+ store.keys.push(entry);
62
+ }
63
+ export function removeKey(store, id) {
64
+ const index = store.keys.findIndex((k) => k.id === id);
65
+ if (index === -1)
66
+ return;
67
+ store.keys.splice(index, 1);
68
+ if (store.currentIndex >= store.keys.length) {
69
+ store.currentIndex = 0;
70
+ }
71
+ }
72
+ export function renameKey(store, id, newName) {
73
+ const entry = store.keys.find((k) => k.id === id);
74
+ if (entry)
75
+ entry.name = newName;
76
+ }
77
+ export function toggleKey(store, id, enabled) {
78
+ const entry = store.keys.find((k) => k.id === id);
79
+ if (entry)
80
+ entry.enabled = enabled ?? !entry.enabled;
81
+ }
82
+ export function getMaxFailures(config) {
83
+ return (config?.maxFailuresBeforeDisable ??
84
+ (Number(process.env.NIM_ROTATOR_MAX_FAILURES) || DEFAULT_MAX_FAILURES));
85
+ }
86
+ export function getActiveKeys(store, config) {
87
+ const maxFailures = getMaxFailures(config);
88
+ return store.keys.filter((k) => k.enabled && k.failureCount < maxFailures);
89
+ }
90
+ export function getNextKey(store, config) {
91
+ const active = getActiveKeys(store, config);
92
+ if (active.length === 0)
93
+ return null;
94
+ const strategy = config?.rotationStrategy ?? store.rotationStrategy ?? "round-robin";
95
+ if (strategy === "least-failures") {
96
+ const sorted = [...active].sort((a, b) => a.failureCount - b.failureCount);
97
+ const best = sorted[0];
98
+ const idx = store.keys.indexOf(best);
99
+ store.currentIndex = idx;
100
+ store.lastUsedKeyId = best.id;
101
+ best.lastUsedAt = Date.now();
102
+ return { key: best, index: idx };
103
+ }
104
+ // round-robin
105
+ let idx = store.currentIndex % active.length;
106
+ const selected = active[idx];
107
+ const realIdx = store.keys.indexOf(selected);
108
+ store.currentIndex = (idx + 1) % active.length;
109
+ store.lastUsedKeyId = selected.id;
110
+ selected.lastUsedAt = Date.now();
111
+ return { key: selected, index: realIdx };
112
+ }
113
+ export function recordFailure(store, keyId) {
114
+ const entry = store.keys.find((k) => k.id === keyId);
115
+ if (entry)
116
+ entry.failureCount++;
117
+ }
118
+ export function resetFailures(store, keyId) {
119
+ if (keyId) {
120
+ const entry = store.keys.find((k) => k.id === keyId);
121
+ if (entry)
122
+ entry.failureCount = 0;
123
+ }
124
+ else {
125
+ for (const k of store.keys)
126
+ k.failureCount = 0;
127
+ }
128
+ }
129
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export interface SelectThemeColors {
2
+ backgroundColor: string;
3
+ focusedBackgroundColor: string;
4
+ focusedTextColor: string;
5
+ selectedBackgroundColor: string;
6
+ selectedTextColor: string;
7
+ textColor: string;
8
+ descriptionColor: string;
9
+ selectedDescriptionColor: string;
10
+ }
11
+ export interface InputThemeColors {
12
+ backgroundColor: string;
13
+ focusedBackgroundColor: string;
14
+ textColor: string;
15
+ cursorColor: string;
16
+ }
17
+ export declare function selectColors(theme: RotatorTheme): SelectThemeColors;
18
+ export declare function inputColors(theme: RotatorTheme): InputThemeColors;
19
+ export declare function dangerSelectColors(theme: RotatorTheme): SelectThemeColors;
20
+ export declare function applySelectColors(target: {
21
+ backgroundColor: string;
22
+ focusedBackgroundColor: string;
23
+ focusedTextColor: string;
24
+ selectedBackgroundColor: string;
25
+ selectedTextColor: string;
26
+ textColor: string;
27
+ descriptionColor: string;
28
+ selectedDescriptionColor: string;
29
+ }, colors: SelectThemeColors): void;
30
+ export interface RotatorTheme {
31
+ id: string;
32
+ name: string;
33
+ background: string;
34
+ backgroundPanel: string;
35
+ backgroundElement: string;
36
+ text: string;
37
+ textMuted: string;
38
+ primary: string;
39
+ primaryMuted: string;
40
+ accent: string;
41
+ error: string;
42
+ errorBg: string;
43
+ success: string;
44
+ warning: string;
45
+ selectedBg: string;
46
+ selectedText: string;
47
+ border: string;
48
+ borderActive: string;
49
+ inputBg: string;
50
+ inputFocusedBg: string;
51
+ cursor: string;
52
+ description: string;
53
+ selectedDescription: string;
54
+ }
55
+ export declare function getTheme(id: string): RotatorTheme;
56
+ export declare function listThemes(): RotatorTheme[];
57
+ export declare function getThemeIdFromOpenCodeConfig(): string | null;
58
+ export declare function getResolvedTheme(): RotatorTheme;
59
+ export declare function getThemeOverride(): string | null;
60
+ export declare function saveThemeOverride(themeId: string): void;
61
+ export declare function setPreviewTheme(themeId: string | null): void;
62
+ export declare function getPreviewTheme(): string | null;
63
+ export declare function getActiveTheme(): RotatorTheme;
64
+ //# sourceMappingURL=themes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../src/themes.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,CAWnE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAOjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,CAWzE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE;IACN,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,CAAC;CAClC,EACD,MAAM,EAAE,iBAAiB,GACxB,IAAI,CASN;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AA+PD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAEjD;AAED,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAE3C;AAED,wBAAgB,4BAA4B,IAAI,MAAM,GAAG,IAAI,CAG5D;AAED,wBAAgB,gBAAgB,IAAI,YAAY,CAM/C;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAShD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAmBvD;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE5D;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,cAAc,IAAI,YAAY,CAS7C"}