@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.
Files changed (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. 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
+ }