@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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/README.md +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { confirm, isCancel, log, note, select } from "@clack/prompts";
|
|
2
|
+
import {
|
|
3
|
+
type AccountSelectionStrategy,
|
|
4
|
+
getConfigPath,
|
|
5
|
+
loadConfig,
|
|
6
|
+
saveConfig,
|
|
7
|
+
VALID_STRATEGIES,
|
|
8
|
+
} from "../../config.js";
|
|
9
|
+
import { createDefaultStats, getStoragePath, loadAccounts, saveAccounts, type AccountMetadata } from "../../storage.js";
|
|
10
|
+
import { c, pad, shortPath } from "../formatting.js";
|
|
11
|
+
import { cmdStats } from "./auth.js";
|
|
12
|
+
|
|
13
|
+
type ManageAction = "switch" | "enable" | "disable" | "remove" | "reset" | "strategy" | "quit";
|
|
14
|
+
|
|
15
|
+
const STRATEGY_DESCRIPTIONS: Record<AccountSelectionStrategy, string> = {
|
|
16
|
+
sticky: "Stay on one account until it fails or is rate-limited",
|
|
17
|
+
"round-robin": "Rotate through accounts on every request",
|
|
18
|
+
hybrid: "Prefer healthy accounts, rotate when degraded",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function cmdGroupHelp(group: "usage" | "config" | "manage") {
|
|
22
|
+
const bin = "opencode-anthropic-auth";
|
|
23
|
+
|
|
24
|
+
switch (group) {
|
|
25
|
+
case "usage":
|
|
26
|
+
console.log(`
|
|
27
|
+
${c.bold("Usage Commands")}
|
|
28
|
+
|
|
29
|
+
${pad(c.cyan("stats"), 20)}Show per-account usage statistics
|
|
30
|
+
${pad(c.cyan("reset-stats") + " [N|all]", 20)}Reset usage statistics
|
|
31
|
+
${pad(c.cyan("status"), 20)}Compact one-liner for scripts/prompts (alias: st)
|
|
32
|
+
|
|
33
|
+
${c.dim("Examples:")}
|
|
34
|
+
${bin} usage stats
|
|
35
|
+
${bin} usage status
|
|
36
|
+
`);
|
|
37
|
+
return 0;
|
|
38
|
+
case "config":
|
|
39
|
+
console.log(`
|
|
40
|
+
${c.bold("Config Commands")}
|
|
41
|
+
|
|
42
|
+
${pad(c.cyan("show"), 20)}Show current configuration and file paths (alias: cfg)
|
|
43
|
+
${pad(c.cyan("strategy") + " [name]", 20)}Show or change selection strategy (alias: strat)
|
|
44
|
+
|
|
45
|
+
${c.dim("Examples:")}
|
|
46
|
+
${bin} config show
|
|
47
|
+
${bin} config strategy round-robin
|
|
48
|
+
`);
|
|
49
|
+
return 0;
|
|
50
|
+
case "manage":
|
|
51
|
+
console.log(`
|
|
52
|
+
${c.bold("Manage Command")}
|
|
53
|
+
|
|
54
|
+
${pad(c.cyan("manage"), 20)}Interactive account management menu (alias: mg)
|
|
55
|
+
|
|
56
|
+
${c.dim("Examples:")}
|
|
57
|
+
${bin} manage
|
|
58
|
+
`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderManageAccounts(
|
|
64
|
+
accounts: AccountMetadata[],
|
|
65
|
+
activeIndex: number,
|
|
66
|
+
currentStrategy: AccountSelectionStrategy,
|
|
67
|
+
) {
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(c.bold(`${accounts.length} account(s):`));
|
|
70
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
71
|
+
const num = i + 1;
|
|
72
|
+
const label = accounts[i].email || `Account ${num}`;
|
|
73
|
+
const active = i === activeIndex ? c.green(" (active)") : "";
|
|
74
|
+
const disabled = !accounts[i].enabled ? c.yellow(" [disabled]") : "";
|
|
75
|
+
console.log(` ${c.bold(String(num))}. ${label}${active}${disabled}`);
|
|
76
|
+
}
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(c.dim(`Strategy: ${currentStrategy}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildManageTargetOptions(accounts: AccountMetadata[], activeIndex: number) {
|
|
82
|
+
return accounts.map((account, index) => {
|
|
83
|
+
const num = index + 1;
|
|
84
|
+
const label = account.email || `Account ${num}`;
|
|
85
|
+
const statuses = [index === activeIndex ? "active" : null, !account.enabled ? "disabled" : null].filter(
|
|
86
|
+
Boolean,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
value: String(index),
|
|
91
|
+
label: `#${num} ${label}`,
|
|
92
|
+
hint: statuses.length > 0 ? statuses.join(", ") : undefined,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function cmdConfig() {
|
|
98
|
+
const config = loadConfig();
|
|
99
|
+
const stored = await loadAccounts();
|
|
100
|
+
|
|
101
|
+
log.info(c.bold("Anthropic Auth Configuration"));
|
|
102
|
+
|
|
103
|
+
const generalLines = [
|
|
104
|
+
`Strategy: ${c.cyan(config.account_selection_strategy)}`,
|
|
105
|
+
`Failure TTL: ${config.failure_ttl_seconds}s`,
|
|
106
|
+
`Debug: ${config.debug ? c.yellow("on") : "off"}`,
|
|
107
|
+
];
|
|
108
|
+
note(generalLines.join("\n"), "General");
|
|
109
|
+
|
|
110
|
+
const healthLines = [
|
|
111
|
+
`Initial: ${config.health_score.initial}`,
|
|
112
|
+
`Success reward: +${config.health_score.success_reward}`,
|
|
113
|
+
`Rate limit: ${config.health_score.rate_limit_penalty}`,
|
|
114
|
+
`Failure: ${config.health_score.failure_penalty}`,
|
|
115
|
+
`Recovery/hour: +${config.health_score.recovery_rate_per_hour}`,
|
|
116
|
+
`Min usable: ${config.health_score.min_usable}`,
|
|
117
|
+
];
|
|
118
|
+
note(healthLines.join("\n"), "Health Score");
|
|
119
|
+
|
|
120
|
+
const bucketLines = [
|
|
121
|
+
`Max tokens: ${config.token_bucket.max_tokens}`,
|
|
122
|
+
`Regen/min: ${config.token_bucket.regeneration_rate_per_minute}`,
|
|
123
|
+
`Initial: ${config.token_bucket.initial_tokens}`,
|
|
124
|
+
];
|
|
125
|
+
note(bucketLines.join("\n"), "Token Bucket");
|
|
126
|
+
|
|
127
|
+
const fileLines = [
|
|
128
|
+
`Config: ${shortPath(getConfigPath())}`,
|
|
129
|
+
`Accounts: ${shortPath(getStoragePath())}`,
|
|
130
|
+
];
|
|
131
|
+
if (stored) {
|
|
132
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
133
|
+
fileLines.push(`Accounts total: ${stored.accounts.length} (${enabled} enabled)`);
|
|
134
|
+
} else {
|
|
135
|
+
fileLines.push("Accounts total: none");
|
|
136
|
+
}
|
|
137
|
+
note(fileLines.join("\n"), "Files");
|
|
138
|
+
|
|
139
|
+
const envOverrides: string[] = [];
|
|
140
|
+
if (process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
141
|
+
envOverrides.push(`OPENCODE_ANTHROPIC_STRATEGY=${process.env.OPENCODE_ANTHROPIC_STRATEGY}`);
|
|
142
|
+
}
|
|
143
|
+
if (process.env.OPENCODE_ANTHROPIC_DEBUG) {
|
|
144
|
+
envOverrides.push(`OPENCODE_ANTHROPIC_DEBUG=${process.env.OPENCODE_ANTHROPIC_DEBUG}`);
|
|
145
|
+
}
|
|
146
|
+
if (envOverrides.length > 0) {
|
|
147
|
+
log.warn("Environment overrides:\n" + envOverrides.map((override) => ` ${c.yellow(override)}`).join("\n"));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function cmdStrategy(arg?: string) {
|
|
154
|
+
const config = loadConfig();
|
|
155
|
+
|
|
156
|
+
if (!arg) {
|
|
157
|
+
log.info(c.bold("Account Selection Strategy"));
|
|
158
|
+
|
|
159
|
+
const lines = VALID_STRATEGIES.map((strategy) => {
|
|
160
|
+
const current = strategy === config.account_selection_strategy;
|
|
161
|
+
const marker = current ? c.green("▸ ") : " ";
|
|
162
|
+
const name = current ? c.bold(c.cyan(strategy)) : c.dim(strategy);
|
|
163
|
+
const description = current ? STRATEGY_DESCRIPTIONS[strategy] : c.dim(STRATEGY_DESCRIPTIONS[strategy]);
|
|
164
|
+
return `${marker}${pad(name, 16)}${description}`;
|
|
165
|
+
});
|
|
166
|
+
log.message(lines.join("\n"));
|
|
167
|
+
|
|
168
|
+
log.message(c.dim(`Change with: opencode-anthropic-auth strategy <${VALID_STRATEGIES.join("|")}>`));
|
|
169
|
+
|
|
170
|
+
if (process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
171
|
+
log.warn(
|
|
172
|
+
`OPENCODE_ANTHROPIC_STRATEGY=${process.env.OPENCODE_ANTHROPIC_STRATEGY} overrides config file at runtime.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const normalized = arg.toLowerCase().trim();
|
|
180
|
+
if (!VALID_STRATEGIES.includes(normalized as AccountSelectionStrategy)) {
|
|
181
|
+
log.error(`Invalid strategy '${arg}'. Valid strategies: ${VALID_STRATEGIES.join(", ")}`);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (normalized === config.account_selection_strategy && !process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
186
|
+
log.message(c.dim(`Strategy is already '${normalized}'.`));
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
saveConfig({ account_selection_strategy: normalized });
|
|
191
|
+
log.success(`Strategy changed to '${normalized}'.`);
|
|
192
|
+
|
|
193
|
+
if (process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
194
|
+
log.warn(
|
|
195
|
+
`OPENCODE_ANTHROPIC_STRATEGY=${process.env.OPENCODE_ANTHROPIC_STRATEGY} will override this at runtime.`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function cmdStatus() {
|
|
203
|
+
const stored = await loadAccounts();
|
|
204
|
+
if (!stored || stored.accounts.length === 0) {
|
|
205
|
+
console.log("anthropic: no accounts configured");
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const config = loadConfig();
|
|
210
|
+
const total = stored.accounts.length;
|
|
211
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
|
|
214
|
+
let rateLimited = 0;
|
|
215
|
+
for (const account of stored.accounts) {
|
|
216
|
+
if (!account.enabled) continue;
|
|
217
|
+
const resetTimes = account.rateLimitResetTimes || {};
|
|
218
|
+
const maxReset = Math.max(0, ...Object.values(resetTimes));
|
|
219
|
+
if (maxReset > now) rateLimited++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let line = `anthropic: ${total} account${total !== 1 ? "s" : ""} (${enabled} active)`;
|
|
223
|
+
line += `, strategy: ${config.account_selection_strategy}`;
|
|
224
|
+
line += `, next: #${stored.activeIndex + 1}`;
|
|
225
|
+
if (rateLimited > 0) {
|
|
226
|
+
line += `, ${rateLimited} rate-limited`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(line);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function cmdResetStats(arg?: string) {
|
|
234
|
+
const stored = await loadAccounts();
|
|
235
|
+
if (!stored || stored.accounts.length === 0) {
|
|
236
|
+
log.warn("No accounts configured.");
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
|
|
242
|
+
if (!arg || arg === "all") {
|
|
243
|
+
for (const account of stored.accounts) {
|
|
244
|
+
account.stats = createDefaultStats(now);
|
|
245
|
+
}
|
|
246
|
+
await saveAccounts(stored);
|
|
247
|
+
log.success("Reset usage statistics for all accounts.");
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const idx = parseInt(arg, 10) - 1;
|
|
252
|
+
if (isNaN(idx) || idx < 0 || idx >= stored.accounts.length) {
|
|
253
|
+
log.error(`Invalid account number. Use 1-${stored.accounts.length} or 'all'.`);
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
stored.accounts[idx].stats = createDefaultStats(now);
|
|
258
|
+
await saveAccounts(stored);
|
|
259
|
+
const name = stored.accounts[idx].email || `Account ${idx + 1}`;
|
|
260
|
+
log.success(`Reset usage statistics for ${name}.`);
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function cmdManage() {
|
|
265
|
+
let stored = await loadAccounts();
|
|
266
|
+
if (!stored || stored.accounts.length === 0) {
|
|
267
|
+
console.log(c.yellow("No accounts configured."));
|
|
268
|
+
console.log(c.dim("Run 'opencode auth login' and select 'Claude Pro/Max' to add accounts."));
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!process.stdin.isTTY) {
|
|
273
|
+
console.error(c.red("Error: 'manage' requires an interactive terminal."));
|
|
274
|
+
console.error(c.dim("Use 'enable', 'disable', 'remove', 'switch' for non-interactive use."));
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
while (true) {
|
|
279
|
+
stored = await loadAccounts();
|
|
280
|
+
if (!stored || stored.accounts.length === 0) {
|
|
281
|
+
console.log(c.dim("No accounts remaining."));
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const accounts = stored.accounts;
|
|
286
|
+
const activeIndex = stored.activeIndex;
|
|
287
|
+
const currentStrategy = loadConfig().account_selection_strategy;
|
|
288
|
+
|
|
289
|
+
renderManageAccounts(accounts, activeIndex, currentStrategy);
|
|
290
|
+
|
|
291
|
+
const action = await select<ManageAction>({
|
|
292
|
+
message: "Choose an action.",
|
|
293
|
+
options: [
|
|
294
|
+
{ value: "switch", label: "Switch", hint: "set the active account" },
|
|
295
|
+
{ value: "enable", label: "Enable", hint: "re-enable a disabled account" },
|
|
296
|
+
{ value: "disable", label: "Disable", hint: "skip an account in rotation" },
|
|
297
|
+
{ value: "remove", label: "Remove", hint: "delete an account from storage" },
|
|
298
|
+
{ value: "reset", label: "Reset", hint: "clear rate-limit and failure tracking" },
|
|
299
|
+
{ value: "strategy", label: "Strategy", hint: currentStrategy },
|
|
300
|
+
{ value: "quit", label: "Quit", hint: "exit manage" },
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
if (isCancel(action) || action === "quit") break;
|
|
304
|
+
|
|
305
|
+
if (action === "strategy") {
|
|
306
|
+
const strategy = await select<AccountSelectionStrategy>({
|
|
307
|
+
message: "Choose an account selection strategy.",
|
|
308
|
+
initialValue: currentStrategy,
|
|
309
|
+
options: VALID_STRATEGIES.map((value) => ({
|
|
310
|
+
value,
|
|
311
|
+
label: value,
|
|
312
|
+
hint: value === currentStrategy ? "current" : undefined,
|
|
313
|
+
})),
|
|
314
|
+
});
|
|
315
|
+
if (isCancel(strategy)) break;
|
|
316
|
+
saveConfig({ account_selection_strategy: strategy });
|
|
317
|
+
console.log(c.green(`Strategy changed to '${strategy}'.`));
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const target = await select<string>({
|
|
322
|
+
message: "Choose an account.",
|
|
323
|
+
initialValue: String(activeIndex),
|
|
324
|
+
options: buildManageTargetOptions(accounts, activeIndex),
|
|
325
|
+
});
|
|
326
|
+
if (isCancel(target)) break;
|
|
327
|
+
|
|
328
|
+
const idx = Number.parseInt(target, 10);
|
|
329
|
+
const num = idx + 1;
|
|
330
|
+
|
|
331
|
+
switch (action) {
|
|
332
|
+
case "switch": {
|
|
333
|
+
if (!accounts[idx].enabled) {
|
|
334
|
+
console.log(c.yellow(`Account ${num} is disabled. Enable it first.`));
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
stored.activeIndex = idx;
|
|
338
|
+
await saveAccounts(stored);
|
|
339
|
+
const switchLabel = accounts[idx].email || `Account ${num}`;
|
|
340
|
+
console.log(c.green(`Switched to #${num} (${switchLabel}).`));
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case "enable": {
|
|
344
|
+
if (accounts[idx].enabled) {
|
|
345
|
+
console.log(c.dim(`Account ${num} is already enabled.`));
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
stored.accounts[idx].enabled = true;
|
|
349
|
+
await saveAccounts(stored);
|
|
350
|
+
console.log(c.green(`Enabled account #${num}.`));
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "disable": {
|
|
354
|
+
if (!accounts[idx].enabled) {
|
|
355
|
+
console.log(c.dim(`Account ${num} is already disabled.`));
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
const enabledCount = accounts.filter((account) => account.enabled).length;
|
|
359
|
+
if (enabledCount <= 1) {
|
|
360
|
+
console.log(c.red("Cannot disable the last enabled account."));
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
stored.accounts[idx].enabled = false;
|
|
364
|
+
if (idx === stored.activeIndex) {
|
|
365
|
+
const nextEnabled = accounts.findIndex(
|
|
366
|
+
(account, accountIndex) => accountIndex !== idx && account.enabled,
|
|
367
|
+
);
|
|
368
|
+
if (nextEnabled >= 0) stored.activeIndex = nextEnabled;
|
|
369
|
+
}
|
|
370
|
+
await saveAccounts(stored);
|
|
371
|
+
console.log(c.yellow(`Disabled account #${num}.`));
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case "remove": {
|
|
375
|
+
const removeLabel = accounts[idx].email || `Account ${num}`;
|
|
376
|
+
const removeConfirm = await confirm({
|
|
377
|
+
message: `Remove #${num} (${removeLabel})?`,
|
|
378
|
+
});
|
|
379
|
+
if (isCancel(removeConfirm)) break;
|
|
380
|
+
if (removeConfirm) {
|
|
381
|
+
stored.accounts.splice(idx, 1);
|
|
382
|
+
if (stored.accounts.length === 0) {
|
|
383
|
+
stored.activeIndex = 0;
|
|
384
|
+
} else if (stored.activeIndex >= stored.accounts.length) {
|
|
385
|
+
stored.activeIndex = stored.accounts.length - 1;
|
|
386
|
+
} else if (stored.activeIndex > idx) {
|
|
387
|
+
stored.activeIndex--;
|
|
388
|
+
}
|
|
389
|
+
await saveAccounts(stored);
|
|
390
|
+
console.log(c.green(`Removed account #${num}.`));
|
|
391
|
+
} else {
|
|
392
|
+
console.log(c.dim("Cancelled."));
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case "reset": {
|
|
397
|
+
stored.accounts[idx].rateLimitResetTimes = {};
|
|
398
|
+
stored.accounts[idx].consecutiveFailures = 0;
|
|
399
|
+
stored.accounts[idx].lastFailureTime = null;
|
|
400
|
+
await saveAccounts(stored);
|
|
401
|
+
console.log(c.green(`Reset tracking for account #${num}.`));
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function cmdHelp() {
|
|
411
|
+
const bin = "opencode-anthropic-auth";
|
|
412
|
+
console.log(`
|
|
413
|
+
${c.bold("Anthropic Multi-Account Auth CLI")}
|
|
414
|
+
|
|
415
|
+
${c.dim("Usage:")}
|
|
416
|
+
${bin} [group] [command] [args]
|
|
417
|
+
${bin} [command] [args] ${c.dim("(legacy format, still supported)")}
|
|
418
|
+
oaa [group] [command] [args] ${c.dim("(short alias)")}
|
|
419
|
+
|
|
420
|
+
${c.dim("Command Groups:")}
|
|
421
|
+
${pad(c.cyan("auth"), 22)}Authentication: login, logout, reauth, refresh
|
|
422
|
+
${pad(c.cyan("account"), 22)}Account management: list, switch, enable, disable, remove, reset
|
|
423
|
+
${pad(c.cyan("usage"), 22)}Usage statistics: stats, reset-stats, status
|
|
424
|
+
${pad(c.cyan("config"), 22)}Configuration: show, strategy
|
|
425
|
+
${pad(c.cyan("manage"), 22)}Interactive account management menu
|
|
426
|
+
|
|
427
|
+
${c.dim("Auth Commands:")}
|
|
428
|
+
${pad(c.cyan("login"), 22)}Add a new account via browser OAuth (alias: ln)
|
|
429
|
+
${pad(c.cyan("logout") + " <N>", 22)}Revoke tokens and remove account N (alias: lo)
|
|
430
|
+
${pad(c.cyan("logout") + " --all", 22)}Revoke all tokens and clear all accounts
|
|
431
|
+
${pad(c.cyan("reauth") + " <N>", 22)}Re-authenticate account N (alias: ra)
|
|
432
|
+
${pad(c.cyan("refresh") + " <N>", 22)}Attempt token refresh (alias: rf)
|
|
433
|
+
|
|
434
|
+
${c.dim("Account Commands:")}
|
|
435
|
+
${pad(c.cyan("list"), 22)}Show all accounts with status ${c.dim("(default, alias: ls)")}
|
|
436
|
+
${pad(c.cyan("switch") + " <N>", 22)}Set account N as active (alias: sw)
|
|
437
|
+
${pad(c.cyan("enable") + " <N>", 22)}Enable a disabled account (alias: en)
|
|
438
|
+
${pad(c.cyan("disable") + " <N>", 22)}Disable an account (alias: dis)
|
|
439
|
+
${pad(c.cyan("remove") + " <N>", 22)}Remove an account permanently (alias: rm)
|
|
440
|
+
${pad(c.cyan("reset") + " <N|all>", 22)}Clear rate-limit / failure tracking
|
|
441
|
+
|
|
442
|
+
${c.dim("Usage Commands:")}
|
|
443
|
+
${pad(c.cyan("stats"), 22)}Show per-account usage statistics
|
|
444
|
+
${pad(c.cyan("reset-stats") + " [N|all]", 22)}Reset usage statistics
|
|
445
|
+
${pad(c.cyan("status"), 22)}Compact one-liner for scripts/prompts (alias: st)
|
|
446
|
+
|
|
447
|
+
${c.dim("Config Commands:")}
|
|
448
|
+
${pad(c.cyan("config"), 22)}Show configuration and file paths (alias: cfg)
|
|
449
|
+
${pad(c.cyan("strategy") + " [name]", 22)}Show or change selection strategy (alias: strat)
|
|
450
|
+
|
|
451
|
+
${c.dim("Manage Commands:")}
|
|
452
|
+
${pad(c.cyan("manage"), 22)}Interactive account management menu (alias: mg)
|
|
453
|
+
${pad(c.cyan("help"), 22)}Show this help message
|
|
454
|
+
|
|
455
|
+
${c.dim("Group Help:")}
|
|
456
|
+
${bin} auth help ${c.dim("# Show auth commands")}
|
|
457
|
+
${bin} account help ${c.dim("# Show account commands")}
|
|
458
|
+
${bin} usage help ${c.dim("# Show usage commands")}
|
|
459
|
+
${bin} config help ${c.dim("# Show config commands")}
|
|
460
|
+
|
|
461
|
+
${c.dim("Options:")}
|
|
462
|
+
--force Skip confirmation prompts
|
|
463
|
+
--all Target all accounts (for logout)
|
|
464
|
+
--no-color Disable colored output
|
|
465
|
+
|
|
466
|
+
${c.dim("Examples:")}
|
|
467
|
+
${bin} login ${c.dim("# Add a new account via browser")}
|
|
468
|
+
${bin} auth login ${c.dim("# Same as above (group format)")}
|
|
469
|
+
oaa login ${c.dim("# Same as above (short alias)")}
|
|
470
|
+
${bin} logout 2 ${c.dim("# Revoke tokens & remove account 2")}
|
|
471
|
+
${bin} auth logout 2 ${c.dim("# Same as above (group format)")}
|
|
472
|
+
${bin} list ${c.dim("# Show all accounts (default)")}
|
|
473
|
+
${bin} account list ${c.dim("# Same as above (group format)")}
|
|
474
|
+
${bin} switch 2 ${c.dim("# Make account 2 active")}
|
|
475
|
+
${bin} account switch 2 ${c.dim("# Same as above (group format)")}
|
|
476
|
+
${bin} stats ${c.dim("# Show token usage per account")}
|
|
477
|
+
${bin} usage stats ${c.dim("# Same as above (group format)")}
|
|
478
|
+
|
|
479
|
+
${c.dim("Files:")}
|
|
480
|
+
Config: ${shortPath(getConfigPath())}
|
|
481
|
+
Accounts: ${shortPath(getStoragePath())}
|
|
482
|
+
`);
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export async function dispatchUsageCommands(args: string[]) {
|
|
487
|
+
const subcommand = args[0] || "stats";
|
|
488
|
+
const arg = args[1];
|
|
489
|
+
|
|
490
|
+
switch (subcommand) {
|
|
491
|
+
case "stats":
|
|
492
|
+
return cmdStats();
|
|
493
|
+
case "reset-stats":
|
|
494
|
+
return cmdResetStats(arg);
|
|
495
|
+
case "status":
|
|
496
|
+
case "st":
|
|
497
|
+
return cmdStatus();
|
|
498
|
+
case "help":
|
|
499
|
+
case "-h":
|
|
500
|
+
case "--help":
|
|
501
|
+
return cmdGroupHelp("usage");
|
|
502
|
+
default:
|
|
503
|
+
console.error(c.red(`Unknown usage command: ${subcommand}`));
|
|
504
|
+
console.error(c.dim("Run 'opencode-anthropic-auth usage help' for usage."));
|
|
505
|
+
return 1;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export async function dispatchConfigCommands(args: string[]) {
|
|
510
|
+
const subcommand = args[0] || "show";
|
|
511
|
+
const arg = args[1];
|
|
512
|
+
|
|
513
|
+
switch (subcommand) {
|
|
514
|
+
case "show":
|
|
515
|
+
case "cfg":
|
|
516
|
+
return cmdConfig();
|
|
517
|
+
case "strategy":
|
|
518
|
+
case "strat":
|
|
519
|
+
return cmdStrategy(arg);
|
|
520
|
+
case "help":
|
|
521
|
+
case "-h":
|
|
522
|
+
case "--help":
|
|
523
|
+
return cmdGroupHelp("config");
|
|
524
|
+
default:
|
|
525
|
+
console.error(c.red(`Unknown config command: ${subcommand}`));
|
|
526
|
+
console.error(c.dim("Run 'opencode-anthropic-auth config help' for usage."));
|
|
527
|
+
return 1;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export async function dispatchManageCommands(args: string[]) {
|
|
532
|
+
const subcommand = args[0] || "manage";
|
|
533
|
+
|
|
534
|
+
switch (subcommand) {
|
|
535
|
+
case "manage":
|
|
536
|
+
case "mg":
|
|
537
|
+
return cmdManage();
|
|
538
|
+
case "help":
|
|
539
|
+
case "-h":
|
|
540
|
+
case "--help":
|
|
541
|
+
return cmdGroupHelp("manage");
|
|
542
|
+
default:
|
|
543
|
+
console.error(c.red(`Unknown manage command: ${subcommand}`));
|
|
544
|
+
console.error(c.dim("Run 'opencode-anthropic-auth manage help' for usage."));
|
|
545
|
+
return 1;
|
|
546
|
+
}
|
|
547
|
+
}
|