@thelioo/opencode-balancer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.txt ADDED
@@ -0,0 +1,58 @@
1
+ opencode-balancer installation guide for AI agents
2
+
3
+ Goal
4
+ Install @thelioo/opencode-balancer as an opencode npm plugin without removing or rewriting the user's existing opencode configuration.
5
+
6
+ What this plugin does
7
+ opencode-balancer lets opencode users save multiple authenticated accounts per provider, switch active accounts with /balancer commands, and automatically retry with another saved account when the active account is rate-limited.
8
+
9
+ Important rules
10
+ - Do not run npm install for this plugin. opencode installs npm plugins automatically with Bun at startup.
11
+ - Preserve existing opencode config, plugins, comments, formatting, providers, agents, MCP servers, and permissions whenever possible.
12
+ - Use the singular key "plugin", not "plugins".
13
+ - Add the plugin only if it is not already present.
14
+ - Prefer @thelioo/opencode-balancer@latest for user installs.
15
+
16
+ Steps
17
+ 1. Locate the user's opencode config.
18
+ - Prefer ~/.config/opencode/opencode.jsonc if it exists.
19
+ - Otherwise use ~/.config/opencode/opencode.json if it exists.
20
+ - If neither exists, create ~/.config/opencode/opencode.json with a minimal JSON object.
21
+
22
+ 2. Ensure the config has a top-level plugin array.
23
+ - If plugin already exists and is an array, keep all existing entries.
24
+ - If plugin is missing, create it.
25
+ - If the config is JSONC, keep JSONC syntax valid.
26
+ - If the config is JSON, keep JSON syntax valid.
27
+
28
+ 3. Add this plugin entry:
29
+ @thelioo/opencode-balancer@latest
30
+
31
+ 4. Save the config.
32
+
33
+ 5. Show the user the final plugin array.
34
+
35
+ 6. Tell the user to restart opencode.
36
+
37
+ Expected config shape
38
+ {
39
+ "$schema": "https://opencode.ai/config.json",
40
+ "plugin": ["@thelioo/opencode-balancer@latest"]
41
+ }
42
+
43
+ After restart
44
+ The user can connect and save accounts with:
45
+ /connect anthropic
46
+ /balancer alias work
47
+
48
+ Useful commands
49
+ /balancer help
50
+ /balancer list
51
+ /balancer status
52
+ /balancer use <provider> <alias>
53
+ /balancer remove <provider> <alias>
54
+
55
+ Verification
56
+ - Confirm the opencode config contains @thelioo/opencode-balancer@latest in the plugin array.
57
+ - Confirm no existing plugin entries were removed.
58
+ - Remind the user that opencode must be restarted before /balancer becomes available.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thelioo
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,172 @@
1
+ <div align="center">
2
+
3
+ # opencode-balancer
4
+
5
+ _Use multiple accounts per opencode provider and switch automatically when one hits a rate limit._
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@thelioo/opencode-balancer?style=flat-square)](https://www.npmjs.com/package/@thelioo/opencode-balancer)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
9
+ [![opencode plugin](https://img.shields.io/badge/opencode-plugin-111?style=flat-square)](https://opencode.ai/docs/plugins)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)
11
+
12
+ [Features](#features) | [Installation](#installation) | [Usage](#usage) | [Commands](#commands) | [Troubleshooting](#troubleshooting)
13
+
14
+ </div>
15
+
16
+ `opencode-balancer` is an [opencode](https://opencode.ai/) plugin that lets you save multiple authenticated accounts for the same provider, give each one a friendly alias, and keep working when the active account becomes rate-limited.
17
+
18
+ It works with opencode's existing auth flow: connect a provider, save the detected credentials as an alias, then let the plugin inject the active account credentials into future model requests.
19
+
20
+ > [!NOTE]
21
+ > This plugin manages credentials already configured through opencode. It does not create accounts, bypass provider limits, or modify provider-side quotas.
22
+
23
+ ## Features
24
+
25
+ - **Multiple accounts per provider**: Save aliases like `work`, `personal`, or `backup` for the same provider.
26
+ - **Automatic failover**: Switches to another saved account on retryable rate-limit/server responses.
27
+ - **Native opencode commands**: Adds `/balancer` directly inside opencode.
28
+ - **OAuth-friendly setup**: Adds an optional alias prompt to provider OAuth flows.
29
+ - **Agent tool support**: Exposes a `balancer_command` tool so opencode agents can manage accounts when asked.
30
+ - **Local credential store**: Saves account metadata under your opencode config directory.
31
+
32
+ ## Installation
33
+
34
+ ### Option A: Let an AI Agent Install It
35
+
36
+ Paste this into opencode or another coding agent running on your machine:
37
+
38
+ ```text
39
+ Install and configure @thelioo/opencode-balancer by following this guide:
40
+ https://raw.githubusercontent.com/thelioo/opencode-balancer/refs/heads/main/INSTALL.txt
41
+ ```
42
+
43
+ Or read the local guide: [INSTALL.txt](INSTALL.txt).
44
+
45
+ ### Option B: Manual Setup
46
+
47
+ Add the plugin to your opencode config:
48
+
49
+ ```json
50
+ {
51
+ "$schema": "https://opencode.ai/config.json",
52
+ "plugin": ["@thelioo/opencode-balancer@latest"]
53
+ }
54
+ ```
55
+
56
+ Then restart opencode.
57
+
58
+ > [!TIP]
59
+ > No manual `npm install` is required. opencode automatically installs npm plugins with Bun at startup and caches them locally.
60
+
61
+ ## Usage
62
+
63
+ ### Save Your First Account
64
+
65
+ Connect a provider as usual:
66
+
67
+ ```text
68
+ /connect anthropic
69
+ ```
70
+
71
+ After the connection is detected, save it with an alias:
72
+
73
+ ```text
74
+ /balancer alias work
75
+ ```
76
+
77
+ ### Add Another Account
78
+
79
+ Connect the same provider with a different account, then save it:
80
+
81
+ ```text
82
+ /connect anthropic
83
+ /balancer alias personal
84
+ ```
85
+
86
+ ### Switch Accounts Manually
87
+
88
+ ```text
89
+ /balancer use anthropic work
90
+ ```
91
+
92
+ ### List Saved Accounts
93
+
94
+ ```text
95
+ /balancer list
96
+ ```
97
+
98
+ Example output:
99
+
100
+ ```text
101
+ anthropic:
102
+ * work (oauth, healthy)
103
+ personal (oauth, healthy)
104
+ ```
105
+
106
+ When the active account receives a retryable response such as `429`, `500`, `502`, `503`, `504`, or `529`, the plugin marks it as temporarily rate-limited and retries with another available account for the same provider.
107
+
108
+ ## Commands
109
+
110
+ | Command | Description |
111
+ | --- | --- |
112
+ | `/balancer help` | Show available commands. |
113
+ | `/balancer alias <name>` | Save the last detected connection with an alias. |
114
+ | `/balancer save <provider> <name>` | Save the provider's current native credentials. |
115
+ | `/balancer use <provider> <name>` | Switch the active account for a provider. |
116
+ | `/balancer list` | List saved accounts. |
117
+ | `/balancer status` | Show saved accounts, last capture, and storage path. |
118
+ | `/balancer remove <provider> <name>` | Remove a saved account. |
119
+
120
+ Aliases are normalized to lowercase and may contain letters, numbers, dots, hyphens, and underscores.
121
+
122
+ ## How It Works
123
+
124
+ `opencode-balancer` hooks into opencode's plugin lifecycle and request flow:
125
+
126
+ 1. It watches opencode auth changes and records the last detected provider credentials.
127
+ 2. `/balancer alias <name>` stores those credentials under a provider-specific alias.
128
+ 3. Before model requests, the plugin selects the active account for the request provider.
129
+ 4. If the provider returns a retryable rate-limit or server response, the plugin marks the account as temporarily unavailable and retries with another saved account.
130
+
131
+ Saved account data is written to:
132
+
133
+ ```text
134
+ ~/.config/opencode/balancer-accounts.json
135
+ ```
136
+
137
+ If `OPENCODE_CONFIG_DIR` is set, the plugin uses that directory instead.
138
+
139
+ > [!CAUTION]
140
+ > The account store contains credentials. Keep it private and do not commit it to a repository.
141
+
142
+ ## Local Development
143
+
144
+ ```bash
145
+ npm install
146
+ npm run check
147
+ npm run build
148
+ ```
149
+
150
+ To test a local checkout with opencode, point your config to the package directory:
151
+
152
+ ```json
153
+ {
154
+ "plugin": ["file:///absolute/path/to/opencode-balancer"]
155
+ }
156
+ ```
157
+
158
+ ## Troubleshooting
159
+
160
+ | Problem | What to try |
161
+ | --- | --- |
162
+ | Plugin does not load | Confirm `plugin` is singular, restart opencode, and check that the package name is `@thelioo/opencode-balancer@latest`. |
163
+ | `/balancer` is unavailable | Restart opencode after editing the config. |
164
+ | No connection detected | Run `/connect <provider>` first, then `/balancer alias <name>`. |
165
+ | Account is not switching | Run `/balancer list` and confirm there is another healthy account for the same provider. |
166
+ | Need to inspect storage | Run `/balancer status` to see the account store path. |
167
+
168
+ ## Resources
169
+
170
+ - [opencode plugins documentation](https://opencode.ai/docs/plugins)
171
+ - [opencode configuration](https://opencode.ai/docs/config)
172
+ - [npm package](https://www.npmjs.com/package/@thelioo/opencode-balancer)
@@ -0,0 +1,9 @@
1
+ import type { Account, AuthInfo, ProviderState } from "./types";
2
+ export declare function normalizeAlias(alias: string): string;
3
+ export declare function saveAccount(providerID: string, aliasInput: string, auth: AuthInfo): Promise<Account>;
4
+ export declare function getProviderState(providerID: string): Promise<ProviderState | undefined>;
5
+ export declare function getActiveAccount(providerID: string): Promise<Account | undefined>;
6
+ export declare function listAccountsText(): Promise<string>;
7
+ export declare function nextAvailableAccount(providerID: string, currentAlias?: string): Promise<Account | undefined>;
8
+ export declare function markRateLimited(providerID: string, alias: string, retryAfterMs?: number): Promise<void>;
9
+ export declare function markUsed(providerID: string, alias: string): Promise<void>;
@@ -0,0 +1,102 @@
1
+ import { now } from "./state";
2
+ import { readStore, writeStore } from "./storage";
3
+ export function normalizeAlias(alias) {
4
+ return alias
5
+ .trim()
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9._-]+/g, "-")
8
+ .replace(/^-+|-+$/g, "");
9
+ }
10
+ export async function saveAccount(providerID, aliasInput, auth) {
11
+ const alias = normalizeAlias(aliasInput);
12
+ if (!alias)
13
+ throw new Error("Invalid alias. Use letters, numbers, dots, hyphens, or underscores.");
14
+ const store = await readStore();
15
+ const provider = store.providers[providerID] ?? { accounts: {} };
16
+ const existing = provider.accounts[alias];
17
+ const account = {
18
+ alias,
19
+ providerID,
20
+ auth,
21
+ createdAt: existing?.createdAt ?? now(),
22
+ updatedAt: now(),
23
+ lastUsedAt: existing?.lastUsedAt,
24
+ failures: existing?.failures,
25
+ rateLimitedUntil: existing?.rateLimitedUntil,
26
+ };
27
+ provider.accounts[alias] = account;
28
+ provider.active = alias;
29
+ store.providers[providerID] = provider;
30
+ store.lastCaptured = {
31
+ providerID,
32
+ auth,
33
+ capturedAt: now(),
34
+ source: store.lastCaptured?.source ?? "auth-file",
35
+ };
36
+ await writeStore(store);
37
+ return account;
38
+ }
39
+ export async function getProviderState(providerID) {
40
+ return (await readStore()).providers[providerID];
41
+ }
42
+ export async function getActiveAccount(providerID) {
43
+ const provider = await getProviderState(providerID);
44
+ if (!provider?.active)
45
+ return undefined;
46
+ return provider.accounts[provider.active];
47
+ }
48
+ export async function listAccountsText() {
49
+ const store = await readStore();
50
+ const lines = [];
51
+ for (const [providerID, provider] of Object.entries(store.providers).sort(([a], [b]) => a.localeCompare(b))) {
52
+ lines.push(`${providerID}:`);
53
+ const accounts = Object.values(provider.accounts).sort((a, b) => a.alias.localeCompare(b.alias));
54
+ if (accounts.length === 0) {
55
+ lines.push(" (no accounts)");
56
+ continue;
57
+ }
58
+ for (const account of accounts) {
59
+ const marker = provider.active === account.alias ? "*" : " ";
60
+ const limited = account.rateLimitedUntil && account.rateLimitedUntil > now()
61
+ ? ` rate-limited ${Math.ceil((account.rateLimitedUntil - now()) / 1000)}s`
62
+ : "healthy";
63
+ lines.push(` ${marker} ${account.alias} (${account.auth.type}, ${limited})`);
64
+ }
65
+ }
66
+ if (lines.length === 0)
67
+ return "No accounts saved. Use `/connect <provider>` and then `/balancer alias <name>`.";
68
+ const last = store.lastCaptured;
69
+ if (last)
70
+ lines.push("", `Last detected connection: ${last.providerID} (${last.auth.type})`);
71
+ return lines.join("\n");
72
+ }
73
+ export async function nextAvailableAccount(providerID, currentAlias) {
74
+ const provider = await getProviderState(providerID);
75
+ if (!provider)
76
+ return undefined;
77
+ const accounts = Object.values(provider.accounts);
78
+ return accounts.find((account) => account.alias !== currentAlias &&
79
+ (!account.rateLimitedUntil || account.rateLimitedUntil <= now()));
80
+ }
81
+ export async function markRateLimited(providerID, alias, retryAfterMs = 60_000) {
82
+ const store = await readStore();
83
+ const account = store.providers[providerID]?.accounts[alias];
84
+ if (!account)
85
+ return;
86
+ account.failures = (account.failures ?? 0) + 1;
87
+ account.rateLimitedUntil = now() + retryAfterMs;
88
+ account.updatedAt = now();
89
+ await writeStore(store);
90
+ }
91
+ export async function markUsed(providerID, alias) {
92
+ const store = await readStore();
93
+ const account = store.providers[providerID]?.accounts[alias];
94
+ if (!account)
95
+ return;
96
+ account.lastUsedAt = now();
97
+ account.updatedAt = now();
98
+ if (account.rateLimitedUntil && account.rateLimitedUntil <= now())
99
+ delete account.rateLimitedUntil;
100
+ await writeStore(store);
101
+ }
102
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/balancer/accounts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function startAuthWatcher(client: any): void;
@@ -0,0 +1,30 @@
1
+ import { authPath, captureAuth, isAuthInfo, now, readNativeAuth, showToast, state, } from "./core";
2
+ export function startAuthWatcher(client) {
3
+ if (state.watchStarted)
4
+ return;
5
+ state.watchStarted = true;
6
+ void readNativeAuth().then((auth) => {
7
+ state.lastAuthSnapshot = auth;
8
+ });
9
+ const path = authPath();
10
+ const handleChange = async () => {
11
+ const next = await readNativeAuth();
12
+ for (const [providerID, auth] of Object.entries(next)) {
13
+ if (!isAuthInfo(auth))
14
+ continue;
15
+ const suppressedUntil = state.suppressAuthCaptureUntil.get(providerID) ?? 0;
16
+ if (suppressedUntil > now())
17
+ continue;
18
+ const previous = JSON.stringify(state.lastAuthSnapshot[providerID] ?? null);
19
+ const current = JSON.stringify(auth);
20
+ if (previous === current)
21
+ continue;
22
+ await captureAuth(providerID, auth, "auth-file");
23
+ await showToast(client, `Balancer: new ${providerID} connection detected. Use /balancer alias <name>.`, "info");
24
+ }
25
+ state.lastAuthSnapshot = next;
26
+ };
27
+ const timer = setInterval(() => void handleChange(), 1_000);
28
+ timer.unref?.();
29
+ }
30
+ //# sourceMappingURL=auth-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-watcher.js","sourceRoot":"","sources":["../../src/balancer/auth-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,QAAQ,EACR,WAAW,EACX,UAAU,EACV,GAAG,EACH,cAAc,EACd,SAAS,EACT,KAAK,GACR,MAAM,QAAQ,CAAC;AAEhB,MAAM,UAAU,gBAAgB,CAAC,MAAW;IACxC,IAAI,KAAK,CAAC,YAAY;QAAE,OAAO;IAC/B,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC1B,KAAK,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QAChC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,cAAc,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChC,MAAM,eAAe,GACjB,KAAK,CAAC,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACxD,IAAI,eAAe,GAAG,GAAG,EAAE;gBAAE,SAAS;YACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAC3B,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,IAAI,CAC7C,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,QAAQ,KAAK,OAAO;gBAAE,SAAS;YACnC,MAAM,WAAW,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;YACjD,MAAM,SAAS,CACX,MAAM,EACN,iBAAiB,UAAU,mDAAmD,EAC9E,MAAM,CACT,CAAC;QACN,CAAC;QACD,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAClC,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,YAAY,EAAE,EAAE,KAAK,CAAC,CAAC;IAC3D,KAAgC,CAAC,KAAK,EAAE,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runBalancerCommand(client: any, raw: string): Promise<string>;
@@ -0,0 +1,94 @@
1
+ import { isAuthInfo, listAccountsText, normalizeAlias, readNativeAuth, readStore, saveAccount, setNativeAuth, storePath, writeStore, } from "./core";
2
+ function parseArgs(input) {
3
+ return (input
4
+ .match(/(?:[^\s"]+|"[^"]*")+/g)
5
+ ?.map((x) => x.replace(/^"|"$/g, "")) ?? []);
6
+ }
7
+ export async function runBalancerCommand(client, raw) {
8
+ const [action = "help", ...args] = parseArgs(raw);
9
+ const store = await readStore();
10
+ if (["help", "--help", "-h"].includes(action)) {
11
+ return [
12
+ "Balancer commands:",
13
+ "",
14
+ " /balancer alias <name>",
15
+ " saves the last detected connection",
16
+ " with an alias",
17
+ "",
18
+ " /balancer save <provider> <name>",
19
+ " saves the provider's current credentials",
20
+ "",
21
+ " /balancer use <provider> <name>",
22
+ " switches the active account",
23
+ "",
24
+ " /balancer list",
25
+ " lists accounts",
26
+ "",
27
+ " /balancer status",
28
+ " shows current state",
29
+ "",
30
+ " /balancer remove <provider> <name>",
31
+ " removes an account",
32
+ ].join("\n");
33
+ }
34
+ if (action === "alias") {
35
+ const alias = args[0];
36
+ if (!alias)
37
+ return "Usage: /balancer alias <name>";
38
+ const captured = store.lastCaptured;
39
+ if (!captured)
40
+ return "No connection detected yet. Run /connect <provider> first.";
41
+ const account = await saveAccount(captured.providerID, alias, captured.auth);
42
+ await setNativeAuth(client, captured.providerID, captured.auth);
43
+ return `Account saved and activated: ${captured.providerID}/${account.alias}`;
44
+ }
45
+ if (action === "save") {
46
+ const [providerID, alias] = args;
47
+ if (!providerID || !alias)
48
+ return "Usage: /balancer save <provider> <name>";
49
+ const auth = (await readNativeAuth())[providerID];
50
+ if (!isAuthInfo(auth))
51
+ return `No native credentials found for ${providerID}. Run /connect ${providerID} first.`;
52
+ const account = await saveAccount(providerID, alias, auth);
53
+ return `Account saved and activated: ${providerID}/${account.alias}`;
54
+ }
55
+ if (action === "use") {
56
+ const [providerID, aliasInput] = args;
57
+ const alias = normalizeAlias(aliasInput ?? "");
58
+ if (!providerID || !alias)
59
+ return "Usage: /balancer use <provider> <name>";
60
+ const account = store.providers[providerID]?.accounts[alias];
61
+ if (!account)
62
+ return `Account not found: ${providerID}/${alias}`;
63
+ store.providers[providerID].active = alias;
64
+ await writeStore(store);
65
+ await setNativeAuth(client, providerID, account.auth);
66
+ return `Active account changed to ${providerID}/${alias}`;
67
+ }
68
+ if (action === "list" || action === "accounts")
69
+ return listAccountsText();
70
+ if (action === "status") {
71
+ const last = store.lastCaptured;
72
+ const lines = [await listAccountsText()];
73
+ if (last)
74
+ lines.push("", `Last capture: ${last.providerID} via ${last.source} at ${new Date(last.capturedAt).toLocaleString()}`);
75
+ lines.push("", `Storage: ${storePath()}`);
76
+ return lines.join("\n");
77
+ }
78
+ if (action === "remove") {
79
+ const [providerID, aliasInput] = args;
80
+ const alias = normalizeAlias(aliasInput ?? "");
81
+ if (!providerID || !alias)
82
+ return "Usage: /balancer remove <provider> <name>";
83
+ const provider = store.providers[providerID];
84
+ if (!provider?.accounts[alias])
85
+ return `Account not found: ${providerID}/${alias}`;
86
+ delete provider.accounts[alias];
87
+ if (provider.active === alias)
88
+ provider.active = Object.keys(provider.accounts)[0];
89
+ await writeStore(store);
90
+ return `Account removed: ${providerID}/${alias}`;
91
+ }
92
+ return `Unknown command: ${action}. Use /balancer help.`;
93
+ }
94
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/balancer/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from "./accounts";
2
+ export * from "./http";
3
+ export * from "./native";
4
+ export * from "./state";
5
+ export * from "./storage";
6
+ export * from "./types";
@@ -0,0 +1,7 @@
1
+ export * from "./accounts";
2
+ export * from "./http";
3
+ export * from "./native";
4
+ export * from "./state";
5
+ export * from "./storage";
6
+ export * from "./types";
7
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/balancer/core.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function installFetchPatch(client: any): Promise<void>;
@@ -0,0 +1,110 @@
1
+ import { BALANCER_ALIAS_INPUT, applyAuthToHeaders, captureAuth, cloneRequestInput, fakeCommandResponse, headersFrom, INTERNAL_REQUEST_HEADER, isAuthInfo, isInternalUrl, markRateLimited, markUsed, maybeInjectAliasPrompt, nextAvailableAccount, normalizeAlias, now, parseAuthSetProvider, parseJsonBody, parseProviderAuthorize, parseProviderCallback, parseSessionCommand, parseUrl, readNativeAuth, readStore, RETRYABLE_STATUS, retryAfterMs, saveAccount, setNativeAuth, showToast, state, writeStore, } from "./core";
2
+ import { runBalancerCommand } from "./commands";
3
+ export async function installFetchPatch(client) {
4
+ if (state.fetchPatched)
5
+ return;
6
+ state.fetchPatched = true;
7
+ const originalFetch = globalThis.fetch.bind(globalThis);
8
+ globalThis.fetch = (async (input, init) => {
9
+ const url = parseUrl(input);
10
+ const commandSessionID = parseSessionCommand(url);
11
+ if (commandSessionID &&
12
+ (init?.method ?? "GET").toUpperCase() === "POST") {
13
+ const body = parseJsonBody(init);
14
+ if (body?.command === "balancer") {
15
+ const result = await runBalancerCommand(client, typeof body.arguments === "string" ? body.arguments : "");
16
+ await showToast(client, result.split("\n")[0] ?? result, "info");
17
+ return new Response(JSON.stringify(fakeCommandResponse(commandSessionID, result)), {
18
+ status: 200,
19
+ headers: { "content-type": "application/json" },
20
+ });
21
+ }
22
+ }
23
+ if (isInternalUrl(url) && url?.pathname === "/provider/auth") {
24
+ return maybeInjectAliasPrompt(await originalFetch(input, init));
25
+ }
26
+ const authorizeProvider = parseProviderAuthorize(url);
27
+ if (authorizeProvider &&
28
+ (init?.method ?? "GET").toUpperCase() === "POST") {
29
+ const body = parseJsonBody(init);
30
+ const alias = typeof body?.inputs?.[BALANCER_ALIAS_INPUT] === "string"
31
+ ? normalizeAlias(body.inputs[BALANCER_ALIAS_INPUT])
32
+ : "";
33
+ if (alias)
34
+ state.pendingOauthAlias.set(authorizeProvider, alias);
35
+ if (body?.inputs && BALANCER_ALIAS_INPUT in body.inputs) {
36
+ delete body.inputs[BALANCER_ALIAS_INPUT];
37
+ init = { ...init, body: JSON.stringify(body) };
38
+ }
39
+ return originalFetch(input, init);
40
+ }
41
+ const callbackProvider = parseProviderCallback(url);
42
+ if (callbackProvider &&
43
+ (init?.method ?? "GET").toUpperCase() === "POST") {
44
+ const response = await originalFetch(input, init);
45
+ const alias = state.pendingOauthAlias.get(callbackProvider);
46
+ if (response.ok && alias) {
47
+ setTimeout(async () => {
48
+ const auth = (await readNativeAuth())[callbackProvider];
49
+ if (isAuthInfo(auth)) {
50
+ await saveAccount(callbackProvider, alias, auth);
51
+ await showToast(client, `Balancer: account ${callbackProvider}/${alias} saved.`, "success");
52
+ }
53
+ state.pendingOauthAlias.delete(callbackProvider);
54
+ }, 250);
55
+ }
56
+ return response;
57
+ }
58
+ const authSetProvider = parseAuthSetProvider(url);
59
+ if (authSetProvider &&
60
+ (init?.method ?? "GET").toUpperCase() === "PUT") {
61
+ const body = parseJsonBody(init);
62
+ const response = await originalFetch(input, init);
63
+ if (response.ok && isAuthInfo(body)) {
64
+ const suppressedUntil = state.suppressAuthCaptureUntil.get(authSetProvider) ?? 0;
65
+ if (suppressedUntil <= now()) {
66
+ await captureAuth(authSetProvider, body, "http");
67
+ await showToast(client, `Balancer: ${authSetProvider} connection detected. Use /balancer alias <name>.`, "info");
68
+ }
69
+ }
70
+ return response;
71
+ }
72
+ const headers = headersFrom(input, init);
73
+ const requestID = headers.get(INTERNAL_REQUEST_HEADER);
74
+ if (!requestID)
75
+ return originalFetch(input, init);
76
+ const pending = state.pendingRequests.get(requestID);
77
+ headers.delete(INTERNAL_REQUEST_HEADER);
78
+ if (!pending?.account) {
79
+ const [nextInput, nextInit] = cloneRequestInput(input, init, headers);
80
+ return originalFetch(nextInput, nextInit);
81
+ }
82
+ let account = pending.account;
83
+ let attempt = 0;
84
+ while (true) {
85
+ attempt++;
86
+ const attemptHeaders = new Headers(headers);
87
+ applyAuthToHeaders(attemptHeaders, account);
88
+ const [nextInput, nextInit] = cloneRequestInput(input, init, attemptHeaders);
89
+ const response = await originalFetch(nextInput, nextInit);
90
+ if (!RETRYABLE_STATUS.has(response.status)) {
91
+ await markUsed(account.providerID, account.alias);
92
+ return response;
93
+ }
94
+ await markRateLimited(account.providerID, account.alias, retryAfterMs(response));
95
+ const next = await nextAvailableAccount(account.providerID, account.alias);
96
+ if (!next || attempt >= 3)
97
+ return response;
98
+ account = next;
99
+ const store = await readStore();
100
+ const provider = store.providers[account.providerID];
101
+ if (provider) {
102
+ provider.active = account.alias;
103
+ await writeStore(store);
104
+ }
105
+ await setNativeAuth(client, account.providerID, account.auth);
106
+ await showToast(client, `Balancer: ${pending.providerID}/${pending.account.alias} is rate limited. Switching to ${account.alias}.`, "warning");
107
+ }
108
+ });
109
+ }
110
+ //# sourceMappingURL=fetch-patch.js.map