@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
|
@@ -83,9 +83,113 @@ var require_src = __commonJS({
|
|
|
83
83
|
|
|
84
84
|
// src/cli.ts
|
|
85
85
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
86
|
-
import { exec as exec2 } from "node:child_process";
|
|
87
86
|
import { pathToFileURL } from "node:url";
|
|
88
87
|
|
|
88
|
+
// src/cli/formatting.ts
|
|
89
|
+
var USE_COLOR = !process.env.NO_COLOR && process.stdout.isTTY !== false;
|
|
90
|
+
function setUseColor(value) {
|
|
91
|
+
USE_COLOR = value;
|
|
92
|
+
}
|
|
93
|
+
function ansi(code, text) {
|
|
94
|
+
return USE_COLOR ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
95
|
+
}
|
|
96
|
+
var c = {
|
|
97
|
+
bold: (t2) => ansi("1", t2),
|
|
98
|
+
dim: (t2) => ansi("2", t2),
|
|
99
|
+
green: (t2) => ansi("32", t2),
|
|
100
|
+
yellow: (t2) => ansi("33", t2),
|
|
101
|
+
cyan: (t2) => ansi("36", t2),
|
|
102
|
+
red: (t2) => ansi("31", t2),
|
|
103
|
+
gray: (t2) => ansi("90", t2)
|
|
104
|
+
};
|
|
105
|
+
function stripAnsi(str) {
|
|
106
|
+
return str.replace(new RegExp("\x1B\\[[0-9;]*m", "g"), "");
|
|
107
|
+
}
|
|
108
|
+
function formatDuration(ms) {
|
|
109
|
+
if (ms <= 0) return "now";
|
|
110
|
+
const seconds = Math.floor(ms / 1e3);
|
|
111
|
+
if (seconds < 60) return `${seconds}s`;
|
|
112
|
+
const minutes = Math.floor(seconds / 60);
|
|
113
|
+
const remainSec = seconds % 60;
|
|
114
|
+
if (minutes < 60) return remainSec > 0 ? `${minutes}m ${remainSec}s` : `${minutes}m`;
|
|
115
|
+
const hours = Math.floor(minutes / 60);
|
|
116
|
+
const remainMin = minutes % 60;
|
|
117
|
+
if (hours < 24) return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`;
|
|
118
|
+
const days = Math.floor(hours / 24);
|
|
119
|
+
const remainHours = hours % 24;
|
|
120
|
+
return remainHours > 0 ? `${days}d ${remainHours}h` : `${days}d`;
|
|
121
|
+
}
|
|
122
|
+
function formatTimeAgo(timestamp) {
|
|
123
|
+
if (!timestamp || timestamp === 0) return "never";
|
|
124
|
+
const ms = Date.now() - timestamp;
|
|
125
|
+
if (ms < 0) return "just now";
|
|
126
|
+
return `${formatDuration(ms)} ago`;
|
|
127
|
+
}
|
|
128
|
+
function formatResetTime(isoString) {
|
|
129
|
+
const resetMs = new Date(isoString).getTime();
|
|
130
|
+
const remaining = resetMs - Date.now();
|
|
131
|
+
if (remaining <= 0) return "now";
|
|
132
|
+
return formatDuration(remaining);
|
|
133
|
+
}
|
|
134
|
+
function shortPath(p2) {
|
|
135
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
136
|
+
if (home && p2.startsWith(home)) return "~" + p2.slice(home.length);
|
|
137
|
+
return p2;
|
|
138
|
+
}
|
|
139
|
+
function pad(str, width) {
|
|
140
|
+
const diff = width - stripAnsi(str).length;
|
|
141
|
+
return diff > 0 ? str + " ".repeat(diff) : str;
|
|
142
|
+
}
|
|
143
|
+
function rpad(str, width) {
|
|
144
|
+
const diff = width - stripAnsi(str).length;
|
|
145
|
+
return diff > 0 ? " ".repeat(diff) + str : str;
|
|
146
|
+
}
|
|
147
|
+
function renderBar(utilization, width = 10) {
|
|
148
|
+
const pct = Math.max(0, Math.min(100, utilization));
|
|
149
|
+
const filled = Math.round(pct / 100 * width);
|
|
150
|
+
const empty = width - filled;
|
|
151
|
+
let bar;
|
|
152
|
+
if (pct >= 90) {
|
|
153
|
+
bar = c.red("\u2588".repeat(filled)) + c.dim("\u2591".repeat(empty));
|
|
154
|
+
} else if (pct >= 70) {
|
|
155
|
+
bar = c.yellow("\u2588".repeat(filled)) + c.dim("\u2591".repeat(empty));
|
|
156
|
+
} else {
|
|
157
|
+
bar = c.green("\u2588".repeat(filled)) + c.dim("\u2591".repeat(empty));
|
|
158
|
+
}
|
|
159
|
+
return bar;
|
|
160
|
+
}
|
|
161
|
+
var QUOTA_BUCKETS = [
|
|
162
|
+
{ key: "five_hour", label: "5h" },
|
|
163
|
+
{ key: "seven_day", label: "7d" },
|
|
164
|
+
{ key: "seven_day_sonnet", label: "Sonnet 7d" },
|
|
165
|
+
{ key: "seven_day_opus", label: "Opus 7d" },
|
|
166
|
+
{ key: "seven_day_oauth_apps", label: "OAuth Apps 7d" },
|
|
167
|
+
{ key: "seven_day_cowork", label: "Cowork 7d" }
|
|
168
|
+
];
|
|
169
|
+
var USAGE_INDENT = " ";
|
|
170
|
+
var USAGE_LABEL_WIDTH = 13;
|
|
171
|
+
function renderUsageLines(usage) {
|
|
172
|
+
const lines = [];
|
|
173
|
+
for (const { key, label } of QUOTA_BUCKETS) {
|
|
174
|
+
const bucket = usage[key];
|
|
175
|
+
if (!bucket || bucket.utilization == null) continue;
|
|
176
|
+
const pct = bucket.utilization;
|
|
177
|
+
const bar = renderBar(pct);
|
|
178
|
+
const pctStr = pad(String(Math.round(pct)) + "%", 4);
|
|
179
|
+
const reset = bucket.resets_at ? c.dim(`resets in ${formatResetTime(bucket.resets_at)}`) : "";
|
|
180
|
+
lines.push(`${USAGE_INDENT}${pad(label, USAGE_LABEL_WIDTH)} ${bar} ${pctStr}${reset ? ` ${reset}` : ""}`);
|
|
181
|
+
}
|
|
182
|
+
return lines;
|
|
183
|
+
}
|
|
184
|
+
function fmtTokens(n) {
|
|
185
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
186
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
187
|
+
return String(n);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/cli/commands/auth.ts
|
|
191
|
+
import { exec as exec2 } from "node:child_process";
|
|
192
|
+
|
|
89
193
|
// src/account-identity.ts
|
|
90
194
|
function isCCAccountSource(source) {
|
|
91
195
|
return source === "cc-keychain" || source === "cc-file";
|
|
@@ -270,7 +374,12 @@ function validateConfig(raw) {
|
|
|
270
374
|
config.health_score = {
|
|
271
375
|
initial: clampNumber(hs.initial, 0, 100, DEFAULT_CONFIG.health_score.initial),
|
|
272
376
|
success_reward: clampNumber(hs.success_reward, 0, 10, DEFAULT_CONFIG.health_score.success_reward),
|
|
273
|
-
rate_limit_penalty: clampNumber(
|
|
377
|
+
rate_limit_penalty: clampNumber(
|
|
378
|
+
hs.rate_limit_penalty,
|
|
379
|
+
-50,
|
|
380
|
+
0,
|
|
381
|
+
DEFAULT_CONFIG.health_score.rate_limit_penalty
|
|
382
|
+
),
|
|
274
383
|
failure_penalty: clampNumber(hs.failure_penalty, -100, 0, DEFAULT_CONFIG.health_score.failure_penalty),
|
|
275
384
|
recovery_rate_per_hour: clampNumber(
|
|
276
385
|
hs.recovery_rate_per_hour,
|
|
@@ -754,7 +863,7 @@ function mergeAccountWithFresherAuth(account, diskMatch) {
|
|
|
754
863
|
token_updated_at: diskTokenUpdatedAt
|
|
755
864
|
};
|
|
756
865
|
}
|
|
757
|
-
function unionAccountsWithDisk(storage, disk) {
|
|
866
|
+
function unionAccountsWithDisk(storage, disk, options = {}) {
|
|
758
867
|
if (!disk || storage.accounts.length === 0) {
|
|
759
868
|
return {
|
|
760
869
|
...storage,
|
|
@@ -770,7 +879,10 @@ function unionAccountsWithDisk(storage, disk) {
|
|
|
770
879
|
}
|
|
771
880
|
return mergeAccountWithFresherAuth(account, diskMatch);
|
|
772
881
|
});
|
|
773
|
-
const
|
|
882
|
+
const droppedIds = options.droppedIds;
|
|
883
|
+
const diskOnlyAccounts = disk.accounts.filter(
|
|
884
|
+
(account) => !matchedDiskAccounts.has(account) && !(droppedIds && droppedIds.has(account.id))
|
|
885
|
+
);
|
|
774
886
|
const accounts = [...mergedAccounts, ...diskOnlyAccounts];
|
|
775
887
|
const activeIndex = activeAccountId ? accounts.findIndex((account) => account.id === activeAccountId) : -1;
|
|
776
888
|
return {
|
|
@@ -812,7 +924,7 @@ async function loadAccounts() {
|
|
|
812
924
|
return null;
|
|
813
925
|
}
|
|
814
926
|
}
|
|
815
|
-
async function saveAccounts(storage) {
|
|
927
|
+
async function saveAccounts(storage, options = {}) {
|
|
816
928
|
const storagePath = getStoragePath();
|
|
817
929
|
const configDir = dirname2(storagePath);
|
|
818
930
|
await fs.mkdir(configDir, { recursive: true });
|
|
@@ -823,7 +935,7 @@ async function saveAccounts(storage) {
|
|
|
823
935
|
};
|
|
824
936
|
try {
|
|
825
937
|
const disk = await loadAccounts();
|
|
826
|
-
storageToWrite = unionAccountsWithDisk(storageToWrite, disk);
|
|
938
|
+
storageToWrite = unionAccountsWithDisk(storageToWrite, disk, { droppedIds: options.droppedIds });
|
|
827
939
|
} catch {
|
|
828
940
|
}
|
|
829
941
|
const tempPath = `${storagePath}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
@@ -840,6 +952,250 @@ async function saveAccounts(storage) {
|
|
|
840
952
|
}
|
|
841
953
|
}
|
|
842
954
|
|
|
955
|
+
// src/cc-credentials.ts
|
|
956
|
+
import { execSync } from "node:child_process";
|
|
957
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
958
|
+
import { homedir as homedir2 } from "node:os";
|
|
959
|
+
import { join as join3 } from "node:path";
|
|
960
|
+
var SECURITY_TIMEOUT_MS = 5e3;
|
|
961
|
+
var SECURITY_HANDLED_EXIT_CODES = /* @__PURE__ */ new Set([36, 44, 128]);
|
|
962
|
+
var CLAUDE_CODE_SERVICE_PATTERN = /"svce"<blob>="(Claude Code-credentials[^"]*)"/g;
|
|
963
|
+
function isRecord(value) {
|
|
964
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
965
|
+
}
|
|
966
|
+
function isValidCredentialShape(value) {
|
|
967
|
+
if (!isRecord(value)) return false;
|
|
968
|
+
if (typeof value.accessToken !== "string" || value.accessToken.length === 0) return false;
|
|
969
|
+
if (typeof value.refreshToken !== "string" || value.refreshToken.length === 0) return false;
|
|
970
|
+
if (typeof value.expiresAt !== "number" || !Number.isFinite(value.expiresAt)) return false;
|
|
971
|
+
if (value.subscriptionType !== void 0 && typeof value.subscriptionType !== "string") return false;
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
function parseCCCredentialWithMeta(raw, meta) {
|
|
975
|
+
try {
|
|
976
|
+
const parsed = JSON.parse(raw);
|
|
977
|
+
if (!isRecord(parsed)) return null;
|
|
978
|
+
const wrapped = parsed.claudeAiOauth;
|
|
979
|
+
const candidate = isValidCredentialShape(wrapped) ? wrapped : isValidCredentialShape(parsed) ? parsed : parsed.mcpOAuth !== void 0 && parsed.accessToken === void 0 ? null : null;
|
|
980
|
+
if (!candidate) return null;
|
|
981
|
+
return {
|
|
982
|
+
accessToken: candidate.accessToken,
|
|
983
|
+
refreshToken: candidate.refreshToken,
|
|
984
|
+
expiresAt: candidate.expiresAt,
|
|
985
|
+
subscriptionType: candidate.subscriptionType,
|
|
986
|
+
source: meta.source,
|
|
987
|
+
label: meta.label
|
|
988
|
+
};
|
|
989
|
+
} catch {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function extractClaudeCodeServices(raw) {
|
|
994
|
+
const services = /* @__PURE__ */ new Set();
|
|
995
|
+
for (const match of raw.matchAll(CLAUDE_CODE_SERVICE_PATTERN)) {
|
|
996
|
+
const service = match[1]?.trim();
|
|
997
|
+
if (service) services.add(service);
|
|
998
|
+
}
|
|
999
|
+
return Array.from(services);
|
|
1000
|
+
}
|
|
1001
|
+
function shellQuote(value) {
|
|
1002
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1003
|
+
}
|
|
1004
|
+
function runSecurityCommand(command) {
|
|
1005
|
+
try {
|
|
1006
|
+
return execSync(command, {
|
|
1007
|
+
encoding: "utf-8",
|
|
1008
|
+
timeout: SECURITY_TIMEOUT_MS
|
|
1009
|
+
});
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
const securityError = error;
|
|
1012
|
+
if (typeof securityError.status === "number" && SECURITY_HANDLED_EXIT_CODES.has(securityError.status)) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
if (securityError.code === "ETIMEDOUT" || securityError.signal === "SIGTERM") {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function readCCCredentialsFromKeychain() {
|
|
1022
|
+
if (process.platform !== "darwin") return null;
|
|
1023
|
+
const dumpOutput = runSecurityCommand("security dump-keychain");
|
|
1024
|
+
if (!dumpOutput) return null;
|
|
1025
|
+
const services = extractClaudeCodeServices(dumpOutput);
|
|
1026
|
+
if (services.length === 0) return null;
|
|
1027
|
+
const credentials = [];
|
|
1028
|
+
for (const service of services) {
|
|
1029
|
+
const rawCredential = runSecurityCommand(`security find-generic-password -s ${shellQuote(service)} -w`);
|
|
1030
|
+
if (!rawCredential) return null;
|
|
1031
|
+
const credential = parseCCCredentialWithMeta(rawCredential, {
|
|
1032
|
+
source: "cc-keychain",
|
|
1033
|
+
label: service
|
|
1034
|
+
});
|
|
1035
|
+
if (credential) credentials.push(credential);
|
|
1036
|
+
}
|
|
1037
|
+
return credentials.length > 0 ? credentials : null;
|
|
1038
|
+
}
|
|
1039
|
+
function readCCCredentialsFromFile() {
|
|
1040
|
+
const credentialsPath = join3(homedir2(), ".claude", ".credentials.json");
|
|
1041
|
+
try {
|
|
1042
|
+
const raw = readFileSync3(credentialsPath, "utf-8");
|
|
1043
|
+
return parseCCCredentialWithMeta(raw, {
|
|
1044
|
+
source: "cc-file",
|
|
1045
|
+
label: credentialsPath
|
|
1046
|
+
});
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function readCCCredentials() {
|
|
1052
|
+
const credentials = [];
|
|
1053
|
+
if (process.platform === "darwin") {
|
|
1054
|
+
const keychainCredentials = readCCCredentialsFromKeychain();
|
|
1055
|
+
if (keychainCredentials) credentials.push(...keychainCredentials);
|
|
1056
|
+
}
|
|
1057
|
+
const fileCredential = readCCCredentialsFromFile();
|
|
1058
|
+
if (fileCredential) credentials.push(fileCredential);
|
|
1059
|
+
return credentials;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/accounts/repair.ts
|
|
1063
|
+
var CC_ID_PATTERN = /^cc-(cc-keychain|cc-file)-\d+:/;
|
|
1064
|
+
function inferCCSourceFromId(id) {
|
|
1065
|
+
const match = CC_ID_PATTERN.exec(id);
|
|
1066
|
+
if (!match) return null;
|
|
1067
|
+
const source = match[1];
|
|
1068
|
+
return source === "cc-keychain" || source === "cc-file" ? source : null;
|
|
1069
|
+
}
|
|
1070
|
+
function isCCIdentity(identity) {
|
|
1071
|
+
return identity?.kind === "cc";
|
|
1072
|
+
}
|
|
1073
|
+
function looksCorruptedStored(account, inferredSource) {
|
|
1074
|
+
if (account.source !== inferredSource) return true;
|
|
1075
|
+
if (!isCCIdentity(account.identity)) return true;
|
|
1076
|
+
if (!account.label) return true;
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
function findMatchingCredentialStored(account, inferredSource, ccCredentials) {
|
|
1080
|
+
const sameSource = ccCredentials.filter((credential) => credential.source === inferredSource);
|
|
1081
|
+
if (sameSource.length === 0) return null;
|
|
1082
|
+
const byRefresh = sameSource.find((credential) => credential.refreshToken === account.refreshToken);
|
|
1083
|
+
if (byRefresh) return byRefresh;
|
|
1084
|
+
if (account.access) {
|
|
1085
|
+
const byAccess = sameSource.find((credential) => credential.accessToken === account.access);
|
|
1086
|
+
if (byAccess) return byAccess;
|
|
1087
|
+
}
|
|
1088
|
+
if (sameSource.length === 1) return sameSource[0];
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
function repairStoredAccount(account, inferredSource, ccCredentials) {
|
|
1092
|
+
const matched = findMatchingCredentialStored(account, inferredSource, ccCredentials);
|
|
1093
|
+
if (matched) {
|
|
1094
|
+
account.source = matched.source;
|
|
1095
|
+
account.label = matched.label;
|
|
1096
|
+
account.identity = {
|
|
1097
|
+
kind: "cc",
|
|
1098
|
+
source: matched.source,
|
|
1099
|
+
label: matched.label
|
|
1100
|
+
};
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
let mutated = false;
|
|
1104
|
+
if (account.source !== inferredSource) {
|
|
1105
|
+
account.source = inferredSource;
|
|
1106
|
+
mutated = true;
|
|
1107
|
+
}
|
|
1108
|
+
if (account.identity?.kind === "legacy") {
|
|
1109
|
+
account.identity = void 0;
|
|
1110
|
+
mutated = true;
|
|
1111
|
+
}
|
|
1112
|
+
return mutated;
|
|
1113
|
+
}
|
|
1114
|
+
function mergeStoredDuplicate(keep, loser) {
|
|
1115
|
+
const keepTokenTs = keep.token_updated_at ?? 0;
|
|
1116
|
+
const loserTokenTs = loser.token_updated_at ?? 0;
|
|
1117
|
+
if (loserTokenTs > keepTokenTs) {
|
|
1118
|
+
keep.refreshToken = loser.refreshToken;
|
|
1119
|
+
keep.access = loser.access;
|
|
1120
|
+
keep.expires = loser.expires;
|
|
1121
|
+
keep.token_updated_at = loserTokenTs;
|
|
1122
|
+
}
|
|
1123
|
+
keep.lastUsed = Math.max(keep.lastUsed, loser.lastUsed);
|
|
1124
|
+
keep.enabled = keep.enabled || loser.enabled;
|
|
1125
|
+
keep.addedAt = Math.min(keep.addedAt, loser.addedAt);
|
|
1126
|
+
const keepIsLegacy = keep.identity?.kind === "legacy" || keep.identity === void 0;
|
|
1127
|
+
const loserIsRicher = loser.identity !== void 0 && loser.identity.kind !== "legacy";
|
|
1128
|
+
if (keepIsLegacy && loserIsRicher) {
|
|
1129
|
+
keep.identity = loser.identity;
|
|
1130
|
+
if (loser.source) keep.source = loser.source;
|
|
1131
|
+
if (loser.label !== void 0) keep.label = loser.label;
|
|
1132
|
+
if (loser.email !== void 0) keep.email = loser.email;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function collapseStoredDuplicates(accounts) {
|
|
1136
|
+
const kept = [];
|
|
1137
|
+
const droppedIds = [];
|
|
1138
|
+
let collapsed = 0;
|
|
1139
|
+
for (const account of accounts) {
|
|
1140
|
+
const identity = resolveIdentity(account);
|
|
1141
|
+
if (identity.kind === "legacy") {
|
|
1142
|
+
kept.push(account);
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
const duplicate = kept.find((existing) => identitiesMatch(resolveIdentity(existing), identity));
|
|
1146
|
+
if (duplicate) {
|
|
1147
|
+
mergeStoredDuplicate(duplicate, account);
|
|
1148
|
+
droppedIds.push(account.id);
|
|
1149
|
+
collapsed += 1;
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
kept.push(account);
|
|
1153
|
+
}
|
|
1154
|
+
return { kept, collapsed, droppedIds };
|
|
1155
|
+
}
|
|
1156
|
+
function repairStoredCCAccounts(accounts, ccCredentials) {
|
|
1157
|
+
let repaired = 0;
|
|
1158
|
+
for (const account of accounts) {
|
|
1159
|
+
const inferredSource = inferCCSourceFromId(account.id);
|
|
1160
|
+
if (!inferredSource) continue;
|
|
1161
|
+
if (!looksCorruptedStored(account, inferredSource)) continue;
|
|
1162
|
+
if (repairStoredAccount(account, inferredSource, ccCredentials)) {
|
|
1163
|
+
repaired += 1;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const { kept, collapsed, droppedIds } = collapseStoredDuplicates(accounts);
|
|
1167
|
+
return {
|
|
1168
|
+
accounts: kept,
|
|
1169
|
+
result: { repaired, collapsed },
|
|
1170
|
+
droppedIds
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
async function loadAccountsWithRepair() {
|
|
1174
|
+
const stored = await loadAccounts();
|
|
1175
|
+
if (!stored) return stored;
|
|
1176
|
+
let ccCredentials;
|
|
1177
|
+
try {
|
|
1178
|
+
ccCredentials = readCCCredentials();
|
|
1179
|
+
} catch {
|
|
1180
|
+
ccCredentials = [];
|
|
1181
|
+
}
|
|
1182
|
+
const { accounts, result, droppedIds } = repairStoredCCAccounts(stored.accounts, ccCredentials);
|
|
1183
|
+
if (result.repaired === 0 && result.collapsed === 0) {
|
|
1184
|
+
return stored;
|
|
1185
|
+
}
|
|
1186
|
+
const healed = {
|
|
1187
|
+
...stored,
|
|
1188
|
+
accounts,
|
|
1189
|
+
activeIndex: accounts.length === 0 ? 0 : Math.max(0, Math.min(stored.activeIndex, accounts.length - 1))
|
|
1190
|
+
};
|
|
1191
|
+
try {
|
|
1192
|
+
await saveAccounts(healed, { droppedIds: new Set(droppedIds) });
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
console.error("[opencode-anthropic-auth] loadAccountsWithRepair: save failed:", err.message);
|
|
1195
|
+
}
|
|
1196
|
+
return healed;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
843
1199
|
// node_modules/@clack/core/dist/index.mjs
|
|
844
1200
|
import { styleText as y } from "node:util";
|
|
845
1201
|
import { stdout as S, stdin as $ } from "node:process";
|
|
@@ -1775,54 +2131,7 @@ ${c3}
|
|
|
1775
2131
|
}
|
|
1776
2132
|
} }).prompt();
|
|
1777
2133
|
|
|
1778
|
-
// src/cli.ts
|
|
1779
|
-
var USE_COLOR = !process.env.NO_COLOR && process.stdout.isTTY !== false;
|
|
1780
|
-
var ansi = (code, text) => USE_COLOR ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
1781
|
-
var c2 = {
|
|
1782
|
-
bold: (t2) => ansi("1", t2),
|
|
1783
|
-
dim: (t2) => ansi("2", t2),
|
|
1784
|
-
green: (t2) => ansi("32", t2),
|
|
1785
|
-
yellow: (t2) => ansi("33", t2),
|
|
1786
|
-
cyan: (t2) => ansi("36", t2),
|
|
1787
|
-
red: (t2) => ansi("31", t2),
|
|
1788
|
-
gray: (t2) => ansi("90", t2)
|
|
1789
|
-
};
|
|
1790
|
-
function formatDuration(ms) {
|
|
1791
|
-
if (ms <= 0) return "now";
|
|
1792
|
-
const seconds = Math.floor(ms / 1e3);
|
|
1793
|
-
if (seconds < 60) return `${seconds}s`;
|
|
1794
|
-
const minutes = Math.floor(seconds / 60);
|
|
1795
|
-
const remainSec = seconds % 60;
|
|
1796
|
-
if (minutes < 60) return remainSec > 0 ? `${minutes}m ${remainSec}s` : `${minutes}m`;
|
|
1797
|
-
const hours = Math.floor(minutes / 60);
|
|
1798
|
-
const remainMin = minutes % 60;
|
|
1799
|
-
if (hours < 24) return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`;
|
|
1800
|
-
const days = Math.floor(hours / 24);
|
|
1801
|
-
const remainHours = hours % 24;
|
|
1802
|
-
return remainHours > 0 ? `${days}d ${remainHours}h` : `${days}d`;
|
|
1803
|
-
}
|
|
1804
|
-
function formatTimeAgo(timestamp) {
|
|
1805
|
-
if (!timestamp || timestamp === 0) return "never";
|
|
1806
|
-
const ms = Date.now() - timestamp;
|
|
1807
|
-
if (ms < 0) return "just now";
|
|
1808
|
-
return `${formatDuration(ms)} ago`;
|
|
1809
|
-
}
|
|
1810
|
-
function shortPath(p2) {
|
|
1811
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1812
|
-
if (home && p2.startsWith(home)) return "~" + p2.slice(home.length);
|
|
1813
|
-
return p2;
|
|
1814
|
-
}
|
|
1815
|
-
function stripAnsi(str) {
|
|
1816
|
-
return str.replace(new RegExp("\x1B\\[[0-9;]*m", "g"), "");
|
|
1817
|
-
}
|
|
1818
|
-
function pad(str, width) {
|
|
1819
|
-
const diff = width - stripAnsi(str).length;
|
|
1820
|
-
return diff > 0 ? str + " ".repeat(diff) : str;
|
|
1821
|
-
}
|
|
1822
|
-
function rpad(str, width) {
|
|
1823
|
-
const diff = width - stripAnsi(str).length;
|
|
1824
|
-
return diff > 0 ? " ".repeat(diff) + str : str;
|
|
1825
|
-
}
|
|
2134
|
+
// src/cli/commands/auth.ts
|
|
1826
2135
|
async function refreshAccessToken(account) {
|
|
1827
2136
|
try {
|
|
1828
2137
|
const resp = await fetch("https://platform.claude.com/v1/oauth/token", {
|
|
@@ -1867,56 +2176,14 @@ async function ensureTokenAndFetchUsage(account) {
|
|
|
1867
2176
|
let token = account.access;
|
|
1868
2177
|
let tokenRefreshed = false;
|
|
1869
2178
|
if (!token || !account.expires || account.expires < Date.now()) {
|
|
1870
|
-
|
|
1871
|
-
tokenRefreshed = !!
|
|
1872
|
-
if (!
|
|
2179
|
+
const refreshedToken = await refreshAccessToken(account);
|
|
2180
|
+
tokenRefreshed = !!refreshedToken;
|
|
2181
|
+
if (!refreshedToken) return { usage: null, tokenRefreshed: false };
|
|
2182
|
+
token = refreshedToken;
|
|
1873
2183
|
}
|
|
1874
2184
|
const usage = await fetchUsage(token);
|
|
1875
2185
|
return { usage, tokenRefreshed };
|
|
1876
2186
|
}
|
|
1877
|
-
function renderBar(utilization, width = 10) {
|
|
1878
|
-
const pct = Math.max(0, Math.min(100, utilization));
|
|
1879
|
-
const filled = Math.round(pct / 100 * width);
|
|
1880
|
-
const empty = width - filled;
|
|
1881
|
-
let bar;
|
|
1882
|
-
if (pct >= 90) {
|
|
1883
|
-
bar = c2.red("\u2588".repeat(filled)) + c2.dim("\u2591".repeat(empty));
|
|
1884
|
-
} else if (pct >= 70) {
|
|
1885
|
-
bar = c2.yellow("\u2588".repeat(filled)) + c2.dim("\u2591".repeat(empty));
|
|
1886
|
-
} else {
|
|
1887
|
-
bar = c2.green("\u2588".repeat(filled)) + c2.dim("\u2591".repeat(empty));
|
|
1888
|
-
}
|
|
1889
|
-
return bar;
|
|
1890
|
-
}
|
|
1891
|
-
function formatResetTime(isoString) {
|
|
1892
|
-
const resetMs = new Date(isoString).getTime();
|
|
1893
|
-
const remaining = resetMs - Date.now();
|
|
1894
|
-
if (remaining <= 0) return "now";
|
|
1895
|
-
return formatDuration(remaining);
|
|
1896
|
-
}
|
|
1897
|
-
var QUOTA_BUCKETS = [
|
|
1898
|
-
{ key: "five_hour", label: "5h" },
|
|
1899
|
-
{ key: "seven_day", label: "7d" },
|
|
1900
|
-
{ key: "seven_day_sonnet", label: "Sonnet 7d" },
|
|
1901
|
-
{ key: "seven_day_opus", label: "Opus 7d" },
|
|
1902
|
-
{ key: "seven_day_oauth_apps", label: "OAuth Apps 7d" },
|
|
1903
|
-
{ key: "seven_day_cowork", label: "Cowork 7d" }
|
|
1904
|
-
];
|
|
1905
|
-
var USAGE_INDENT = " ";
|
|
1906
|
-
var USAGE_LABEL_WIDTH = 13;
|
|
1907
|
-
function renderUsageLines(usage) {
|
|
1908
|
-
const lines = [];
|
|
1909
|
-
for (const { key, label } of QUOTA_BUCKETS) {
|
|
1910
|
-
const bucket = usage[key];
|
|
1911
|
-
if (!bucket || bucket.utilization == null) continue;
|
|
1912
|
-
const pct = bucket.utilization;
|
|
1913
|
-
const bar = renderBar(pct);
|
|
1914
|
-
const pctStr = pad(String(Math.round(pct)) + "%", 4);
|
|
1915
|
-
const reset = bucket.resets_at ? c2.dim(`resets in ${formatResetTime(bucket.resets_at)}`) : "";
|
|
1916
|
-
lines.push(`${USAGE_INDENT}${pad(label, USAGE_LABEL_WIDTH)} ${bar} ${pctStr}${reset ? ` ${reset}` : ""}`);
|
|
1917
|
-
}
|
|
1918
|
-
return lines;
|
|
1919
|
-
}
|
|
1920
2187
|
function openBrowser(url) {
|
|
1921
2188
|
if (process.platform === "win32") {
|
|
1922
2189
|
exec2(`cmd /c start "" ${JSON.stringify(url)}`);
|
|
@@ -1979,17 +2246,20 @@ async function cmdLogin() {
|
|
|
1979
2246
|
if (!credentials) return 1;
|
|
1980
2247
|
const storage = stored || { version: 1, accounts: [], activeIndex: 0 };
|
|
1981
2248
|
const identity = resolveIdentityFromOAuthExchange(credentials);
|
|
1982
|
-
const existing = findByIdentity(storage.accounts, identity) || storage.accounts.find((
|
|
2249
|
+
const existing = findByIdentity(storage.accounts, identity) || storage.accounts.find((account) => account.refreshToken === credentials.refresh);
|
|
1983
2250
|
if (existing) {
|
|
1984
2251
|
const existingIdx = storage.accounts.indexOf(existing);
|
|
2252
|
+
const existingIsCC = existing.source === "cc-keychain" || existing.source === "cc-file";
|
|
1985
2253
|
existing.refreshToken = credentials.refresh;
|
|
1986
2254
|
existing.access = credentials.access;
|
|
1987
2255
|
existing.expires = credentials.expires;
|
|
1988
2256
|
existing.token_updated_at = Date.now();
|
|
1989
|
-
if (credentials.email) existing.email = credentials.email;
|
|
1990
|
-
existing.identity = identity;
|
|
1991
|
-
existing.source = existing.source ?? "oauth";
|
|
1992
2257
|
existing.enabled = true;
|
|
2258
|
+
if (!existingIsCC) {
|
|
2259
|
+
if (credentials.email) existing.email = credentials.email;
|
|
2260
|
+
existing.identity = identity;
|
|
2261
|
+
existing.source = existing.source ?? "oauth";
|
|
2262
|
+
}
|
|
1993
2263
|
await saveAccounts(storage);
|
|
1994
2264
|
const label2 = credentials.email || existing.email || `Account ${existingIdx + 1}`;
|
|
1995
2265
|
O2.success(`Updated existing account #${existingIdx + 1} (${label2}).`);
|
|
@@ -2099,8 +2369,8 @@ async function cmdLogoutAll(opts = {}) {
|
|
|
2099
2369
|
return 0;
|
|
2100
2370
|
}
|
|
2101
2371
|
}
|
|
2102
|
-
const results = await Promise.allSettled(stored.accounts.map((
|
|
2103
|
-
const revokedCount = results.filter((
|
|
2372
|
+
const results = await Promise.allSettled(stored.accounts.map((account) => revoke(account.refreshToken)));
|
|
2373
|
+
const revokedCount = results.filter((result) => result.status === "fulfilled" && result.value === true).length;
|
|
2104
2374
|
if (revokedCount > 0) {
|
|
2105
2375
|
O2.info(`Revoked ${revokedCount} of ${count} token(s) server-side.`);
|
|
2106
2376
|
}
|
|
@@ -2129,6 +2399,7 @@ async function cmdReauth(arg) {
|
|
|
2129
2399
|
return 1;
|
|
2130
2400
|
}
|
|
2131
2401
|
const existing = stored.accounts[idx];
|
|
2402
|
+
const existingIsCC = existing.source === "cc-keychain" || existing.source === "cc-file";
|
|
2132
2403
|
const wasDisabled = !existing.enabled;
|
|
2133
2404
|
const oldLabel = existing.email || `Account ${n}`;
|
|
2134
2405
|
O2.info(`Re-authenticating account #${n} (${oldLabel})...`);
|
|
@@ -2137,11 +2408,16 @@ async function cmdReauth(arg) {
|
|
|
2137
2408
|
existing.refreshToken = credentials.refresh;
|
|
2138
2409
|
existing.access = credentials.access;
|
|
2139
2410
|
existing.expires = credentials.expires;
|
|
2140
|
-
|
|
2411
|
+
existing.token_updated_at = Date.now();
|
|
2141
2412
|
existing.enabled = true;
|
|
2142
2413
|
existing.consecutiveFailures = 0;
|
|
2143
2414
|
existing.lastFailureTime = null;
|
|
2144
2415
|
existing.rateLimitResetTimes = {};
|
|
2416
|
+
if (!existingIsCC) {
|
|
2417
|
+
if (credentials.email) existing.email = credentials.email;
|
|
2418
|
+
existing.identity = resolveIdentityFromOAuthExchange(credentials);
|
|
2419
|
+
existing.source = existing.source ?? "oauth";
|
|
2420
|
+
}
|
|
2145
2421
|
await saveAccounts(stored);
|
|
2146
2422
|
const newLabel = credentials.email || `Account ${n}`;
|
|
2147
2423
|
O2.success(`Re-authenticated account #${n} (${newLabel}).`);
|
|
@@ -2193,7 +2469,7 @@ async function cmdRefresh(arg) {
|
|
|
2193
2469
|
return 0;
|
|
2194
2470
|
}
|
|
2195
2471
|
async function cmdList() {
|
|
2196
|
-
const stored = await
|
|
2472
|
+
const stored = await loadAccountsWithRepair();
|
|
2197
2473
|
if (!stored || stored.accounts.length === 0) {
|
|
2198
2474
|
O2.warn("No accounts configured.");
|
|
2199
2475
|
O2.info(`Storage: ${shortPath(getStoragePath())}`);
|
|
@@ -2204,7 +2480,7 @@ async function cmdList() {
|
|
|
2204
2480
|
const now = Date.now();
|
|
2205
2481
|
const s = fe();
|
|
2206
2482
|
s.start("Fetching usage quotas...");
|
|
2207
|
-
const usageResults = await Promise.allSettled(stored.accounts.map((
|
|
2483
|
+
const usageResults = await Promise.allSettled(stored.accounts.map((account) => ensureTokenAndFetchUsage(account)));
|
|
2208
2484
|
s.stop("Usage quotas fetched.");
|
|
2209
2485
|
let anyRefreshed = false;
|
|
2210
2486
|
for (const result of usageResults) {
|
|
@@ -2213,50 +2489,32 @@ async function cmdList() {
|
|
|
2213
2489
|
}
|
|
2214
2490
|
}
|
|
2215
2491
|
if (anyRefreshed) {
|
|
2216
|
-
await saveAccounts(stored).catch((
|
|
2217
|
-
console.error("[opencode-anthropic-auth] failed to persist refreshed tokens:",
|
|
2492
|
+
await saveAccounts(stored).catch((error) => {
|
|
2493
|
+
console.error("[opencode-anthropic-auth] failed to persist refreshed tokens:", error);
|
|
2218
2494
|
});
|
|
2219
2495
|
}
|
|
2220
|
-
O2.message(
|
|
2496
|
+
O2.message(c.bold("Anthropic Multi-Account Status"));
|
|
2221
2497
|
O2.message(
|
|
2222
|
-
" " + pad(
|
|
2498
|
+
" " + pad(c.dim("#"), 5) + pad(c.dim("Account"), 22) + pad(c.dim("Status"), 14) + pad(c.dim("Failures"), 11) + c.dim("Rate Limit")
|
|
2223
2499
|
);
|
|
2224
|
-
O2.message(
|
|
2500
|
+
O2.message(c.dim(" " + "\u2500".repeat(62)));
|
|
2225
2501
|
for (let i = 0; i < stored.accounts.length; i++) {
|
|
2226
|
-
const
|
|
2502
|
+
const account = stored.accounts[i];
|
|
2227
2503
|
const isActive = i === stored.activeIndex;
|
|
2228
|
-
const
|
|
2229
|
-
const
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
status = c2.green("\u25CF active");
|
|
2504
|
+
const label = account.email || `Account ${i + 1}`;
|
|
2505
|
+
const status = !account.enabled ? c.gray("\u25CB disabled") : isActive ? c.green("\u25CF active") : c.cyan("\u25CF ready");
|
|
2506
|
+
const failures = !account.enabled ? c.dim("\u2014") : account.consecutiveFailures > 0 ? c.yellow(String(account.consecutiveFailures)) : c.dim("0");
|
|
2507
|
+
let rateLimit;
|
|
2508
|
+
if (!account.enabled) {
|
|
2509
|
+
rateLimit = c.dim("\u2014");
|
|
2235
2510
|
} else {
|
|
2236
|
-
|
|
2511
|
+
const maxReset = Math.max(0, ...Object.values(account.rateLimitResetTimes || {}));
|
|
2512
|
+
rateLimit = maxReset > now ? c.yellow(`\u26A0 ${formatDuration(maxReset - now)}`) : c.dim("\u2014");
|
|
2237
2513
|
}
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
failures = c2.yellow(String(acc.consecutiveFailures));
|
|
2243
|
-
} else {
|
|
2244
|
-
failures = c2.dim("0");
|
|
2245
|
-
}
|
|
2246
|
-
let rateLimit;
|
|
2247
|
-
if (!acc.enabled) {
|
|
2248
|
-
rateLimit = c2.dim("\u2014");
|
|
2249
|
-
} else {
|
|
2250
|
-
const resetTimes = acc.rateLimitResetTimes || {};
|
|
2251
|
-
const maxReset = Math.max(0, ...Object.values(resetTimes));
|
|
2252
|
-
if (maxReset > now) {
|
|
2253
|
-
rateLimit = c2.yellow(`\u26A0 ${formatDuration(maxReset - now)}`);
|
|
2254
|
-
} else {
|
|
2255
|
-
rateLimit = c2.dim("\u2014");
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
O2.message(" " + pad(c2.bold(num), 5) + pad(label, 22) + pad(status, 14) + pad(failures, 11) + rateLimit);
|
|
2259
|
-
if (acc.enabled) {
|
|
2514
|
+
O2.message(
|
|
2515
|
+
" " + pad(c.bold(String(i + 1)), 5) + pad(label, 22) + pad(status, 14) + pad(failures, 11) + rateLimit
|
|
2516
|
+
);
|
|
2517
|
+
if (account.enabled) {
|
|
2260
2518
|
const result = usageResults[i];
|
|
2261
2519
|
const usage = result.status === "fulfilled" ? result.value.usage : null;
|
|
2262
2520
|
if (usage) {
|
|
@@ -2265,7 +2523,7 @@ async function cmdList() {
|
|
|
2265
2523
|
O2.message(line);
|
|
2266
2524
|
}
|
|
2267
2525
|
} else {
|
|
2268
|
-
O2.message(
|
|
2526
|
+
O2.message(c.dim(`${USAGE_INDENT}quotas: unavailable`));
|
|
2269
2527
|
}
|
|
2270
2528
|
}
|
|
2271
2529
|
if (i < stored.accounts.length - 1) {
|
|
@@ -2273,34 +2531,33 @@ async function cmdList() {
|
|
|
2273
2531
|
}
|
|
2274
2532
|
}
|
|
2275
2533
|
O2.message("");
|
|
2276
|
-
const enabled = stored.accounts.filter((
|
|
2534
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
2277
2535
|
const disabled = stored.accounts.length - enabled;
|
|
2278
2536
|
const parts = [
|
|
2279
|
-
`Strategy: ${
|
|
2280
|
-
`${
|
|
2537
|
+
`Strategy: ${c.cyan(config.account_selection_strategy)}`,
|
|
2538
|
+
`${c.bold(String(enabled))} of ${stored.accounts.length} enabled`
|
|
2281
2539
|
];
|
|
2282
2540
|
if (disabled > 0) {
|
|
2283
|
-
parts.push(`${
|
|
2541
|
+
parts.push(`${c.yellow(String(disabled))} disabled`);
|
|
2284
2542
|
}
|
|
2285
|
-
O2.info(parts.join(
|
|
2543
|
+
O2.info(parts.join(c.dim(" | ")));
|
|
2286
2544
|
O2.info(`Storage: ${shortPath(getStoragePath())}`);
|
|
2287
2545
|
return 0;
|
|
2288
2546
|
}
|
|
2289
2547
|
async function cmdStatus() {
|
|
2290
|
-
const stored = await
|
|
2548
|
+
const stored = await loadAccountsWithRepair();
|
|
2291
2549
|
if (!stored || stored.accounts.length === 0) {
|
|
2292
2550
|
console.log("anthropic: no accounts configured");
|
|
2293
2551
|
return 1;
|
|
2294
2552
|
}
|
|
2295
2553
|
const config = loadConfig();
|
|
2296
2554
|
const total = stored.accounts.length;
|
|
2297
|
-
const enabled = stored.accounts.filter((
|
|
2555
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
2298
2556
|
const now = Date.now();
|
|
2299
2557
|
let rateLimited = 0;
|
|
2300
|
-
for (const
|
|
2301
|
-
if (!
|
|
2302
|
-
const
|
|
2303
|
-
const maxReset = Math.max(0, ...Object.values(resetTimes));
|
|
2558
|
+
for (const account of stored.accounts) {
|
|
2559
|
+
if (!account.enabled) continue;
|
|
2560
|
+
const maxReset = Math.max(0, ...Object.values(account.rateLimitResetTimes || {}));
|
|
2304
2561
|
if (maxReset > now) rateLimited++;
|
|
2305
2562
|
}
|
|
2306
2563
|
let line = `anthropic: ${total} account${total !== 1 ? "s" : ""} (${enabled} active)`;
|
|
@@ -2384,7 +2641,7 @@ async function cmdDisable(arg) {
|
|
|
2384
2641
|
O2.info(`Account ${n} is already disabled.`);
|
|
2385
2642
|
return 0;
|
|
2386
2643
|
}
|
|
2387
|
-
const enabledCount = stored.accounts.filter((
|
|
2644
|
+
const enabledCount = stored.accounts.filter((account) => account.enabled).length;
|
|
2388
2645
|
if (enabledCount <= 1) {
|
|
2389
2646
|
O2.error("Error: cannot disable the last enabled account.");
|
|
2390
2647
|
return 1;
|
|
@@ -2393,7 +2650,7 @@ async function cmdDisable(arg) {
|
|
|
2393
2650
|
const label = stored.accounts[idx].email || `Account ${n}`;
|
|
2394
2651
|
let switchedTo = null;
|
|
2395
2652
|
if (idx === stored.activeIndex) {
|
|
2396
|
-
const nextEnabled = stored.accounts.findIndex((
|
|
2653
|
+
const nextEnabled = stored.accounts.findIndex((account) => account.enabled);
|
|
2397
2654
|
if (nextEnabled >= 0) {
|
|
2398
2655
|
stored.activeIndex = nextEnabled;
|
|
2399
2656
|
switchedTo = nextEnabled;
|
|
@@ -2407,6 +2664,94 @@ async function cmdDisable(arg) {
|
|
|
2407
2664
|
}
|
|
2408
2665
|
return 0;
|
|
2409
2666
|
}
|
|
2667
|
+
async function cmdReset(arg) {
|
|
2668
|
+
if (!arg) {
|
|
2669
|
+
O2.error("Error: provide an account number or 'all' (e.g., 'reset 1' or 'reset all')");
|
|
2670
|
+
return 1;
|
|
2671
|
+
}
|
|
2672
|
+
const stored = await loadAccounts();
|
|
2673
|
+
if (!stored || stored.accounts.length === 0) {
|
|
2674
|
+
O2.error("Error: no accounts configured.");
|
|
2675
|
+
return 1;
|
|
2676
|
+
}
|
|
2677
|
+
if (arg.toLowerCase() === "all") {
|
|
2678
|
+
let count = 0;
|
|
2679
|
+
for (const account of stored.accounts) {
|
|
2680
|
+
account.rateLimitResetTimes = {};
|
|
2681
|
+
account.consecutiveFailures = 0;
|
|
2682
|
+
account.lastFailureTime = null;
|
|
2683
|
+
count++;
|
|
2684
|
+
}
|
|
2685
|
+
await saveAccounts(stored);
|
|
2686
|
+
O2.success(`Reset tracking for all ${count} account(s).`);
|
|
2687
|
+
return 0;
|
|
2688
|
+
}
|
|
2689
|
+
const n = parseInt(arg, 10);
|
|
2690
|
+
if (isNaN(n) || n < 1) {
|
|
2691
|
+
O2.error("Error: provide a valid account number or 'all'.");
|
|
2692
|
+
return 1;
|
|
2693
|
+
}
|
|
2694
|
+
const idx = n - 1;
|
|
2695
|
+
if (idx >= stored.accounts.length) {
|
|
2696
|
+
O2.error(`Error: account ${n} does not exist.`);
|
|
2697
|
+
return 1;
|
|
2698
|
+
}
|
|
2699
|
+
stored.accounts[idx].rateLimitResetTimes = {};
|
|
2700
|
+
stored.accounts[idx].consecutiveFailures = 0;
|
|
2701
|
+
stored.accounts[idx].lastFailureTime = null;
|
|
2702
|
+
await saveAccounts(stored);
|
|
2703
|
+
const label = stored.accounts[idx].email || `Account ${n}`;
|
|
2704
|
+
O2.success(`Reset tracking for account #${n} (${label}).`);
|
|
2705
|
+
return 0;
|
|
2706
|
+
}
|
|
2707
|
+
async function cmdStats() {
|
|
2708
|
+
const stored = await loadAccounts();
|
|
2709
|
+
if (!stored || stored.accounts.length === 0) {
|
|
2710
|
+
O2.warn("No accounts configured.");
|
|
2711
|
+
return 1;
|
|
2712
|
+
}
|
|
2713
|
+
const widths = { num: 4, name: 22, val: 10 };
|
|
2714
|
+
const rule = c.dim(" " + "\u2500".repeat(74));
|
|
2715
|
+
O2.message(c.bold("Anthropic Account Usage"));
|
|
2716
|
+
O2.message(
|
|
2717
|
+
" " + pad(c.dim("#"), widths.num) + pad(c.dim("Account"), widths.name) + rpad(c.dim("Requests"), widths.val) + rpad(c.dim("Input"), widths.val) + rpad(c.dim("Output"), widths.val) + rpad(c.dim("Cache R"), widths.val) + rpad(c.dim("Cache W"), widths.val)
|
|
2718
|
+
);
|
|
2719
|
+
O2.message(rule);
|
|
2720
|
+
let totalRequests = 0;
|
|
2721
|
+
let totalInput = 0;
|
|
2722
|
+
let totalOutput = 0;
|
|
2723
|
+
let totalCacheRead = 0;
|
|
2724
|
+
let totalCacheWrite = 0;
|
|
2725
|
+
let oldestReset = Infinity;
|
|
2726
|
+
for (let i = 0; i < stored.accounts.length; i++) {
|
|
2727
|
+
const account = stored.accounts[i];
|
|
2728
|
+
const stats = account.stats || createDefaultStats();
|
|
2729
|
+
const marker = i === stored.activeIndex ? c.green("\u25CF") : " ";
|
|
2730
|
+
const number = `${marker} ${i + 1}`;
|
|
2731
|
+
const name = account.email || `Account ${i + 1}`;
|
|
2732
|
+
O2.message(
|
|
2733
|
+
" " + pad(number, widths.num) + pad(name, widths.name) + rpad(String(stats.requests), widths.val) + rpad(fmtTokens(stats.inputTokens), widths.val) + rpad(fmtTokens(stats.outputTokens), widths.val) + rpad(fmtTokens(stats.cacheReadTokens), widths.val) + rpad(fmtTokens(stats.cacheWriteTokens), widths.val)
|
|
2734
|
+
);
|
|
2735
|
+
totalRequests += stats.requests;
|
|
2736
|
+
totalInput += stats.inputTokens;
|
|
2737
|
+
totalOutput += stats.outputTokens;
|
|
2738
|
+
totalCacheRead += stats.cacheReadTokens;
|
|
2739
|
+
totalCacheWrite += stats.cacheWriteTokens;
|
|
2740
|
+
if (stats.lastReset < oldestReset) oldestReset = stats.lastReset;
|
|
2741
|
+
}
|
|
2742
|
+
if (stored.accounts.length > 1) {
|
|
2743
|
+
O2.message(rule);
|
|
2744
|
+
O2.message(
|
|
2745
|
+
c.bold(
|
|
2746
|
+
" " + pad("", widths.num) + pad("Total", widths.name) + rpad(String(totalRequests), widths.val) + rpad(fmtTokens(totalInput), widths.val) + rpad(fmtTokens(totalOutput), widths.val) + rpad(fmtTokens(totalCacheRead), widths.val) + rpad(fmtTokens(totalCacheWrite), widths.val)
|
|
2747
|
+
)
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
if (oldestReset < Infinity) {
|
|
2751
|
+
O2.message(c.dim(`Tracking since: ${new Date(oldestReset).toLocaleString()} (${formatTimeAgo(oldestReset)})`));
|
|
2752
|
+
}
|
|
2753
|
+
return 0;
|
|
2754
|
+
}
|
|
2410
2755
|
async function cmdRemove(arg, opts = {}) {
|
|
2411
2756
|
const n = parseInt(arg || "", 10);
|
|
2412
2757
|
if (isNaN(n) || n < 1) {
|
|
@@ -2454,54 +2799,126 @@ async function cmdRemove(arg, opts = {}) {
|
|
|
2454
2799
|
}
|
|
2455
2800
|
return 0;
|
|
2456
2801
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2802
|
+
function cmdAuthGroupHelp(group) {
|
|
2803
|
+
const bin = "opencode-anthropic-auth";
|
|
2804
|
+
switch (group) {
|
|
2805
|
+
case "auth":
|
|
2806
|
+
console.log(`
|
|
2807
|
+
${c.bold("Auth Commands")}
|
|
2808
|
+
|
|
2809
|
+
${pad(c.cyan("login"), 20)}Add a new account via browser OAuth flow (alias: ln)
|
|
2810
|
+
${pad(c.cyan("logout") + " <N>", 20)}Revoke tokens and remove account N (alias: lo)
|
|
2811
|
+
${pad(c.cyan("logout") + " --all", 20)}Revoke all tokens and clear all accounts
|
|
2812
|
+
${pad(c.cyan("reauth") + " <N>", 20)}Re-authenticate account N with fresh tokens (alias: ra)
|
|
2813
|
+
${pad(c.cyan("refresh") + " <N>", 20)}Attempt token refresh without browser (alias: rf)
|
|
2814
|
+
|
|
2815
|
+
${c.dim("Examples:")}
|
|
2816
|
+
${bin} auth login
|
|
2817
|
+
${bin} auth logout 2
|
|
2818
|
+
${bin} auth reauth 1
|
|
2819
|
+
`);
|
|
2820
|
+
return 0;
|
|
2821
|
+
case "account":
|
|
2822
|
+
console.log(`
|
|
2823
|
+
${c.bold("Account Commands")}
|
|
2824
|
+
|
|
2825
|
+
${pad(c.cyan("list"), 20)}Show all accounts with status (alias: ls)
|
|
2826
|
+
${pad(c.cyan("switch") + " <N>", 20)}Set account N as active (alias: sw)
|
|
2827
|
+
${pad(c.cyan("enable") + " <N>", 20)}Enable a disabled account (alias: en)
|
|
2828
|
+
${pad(c.cyan("disable") + " <N>", 20)}Disable an account (alias: dis)
|
|
2829
|
+
${pad(c.cyan("remove") + " <N>", 20)}Remove an account permanently (alias: rm)
|
|
2830
|
+
${pad(c.cyan("reset"), 20)}Clear rate-limit / failure tracking
|
|
2831
|
+
|
|
2832
|
+
${c.dim("Examples:")}
|
|
2833
|
+
${bin} account list
|
|
2834
|
+
${bin} account switch 2
|
|
2835
|
+
${bin} account disable 3
|
|
2836
|
+
`);
|
|
2837
|
+
return 0;
|
|
2483
2838
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/cli/commands/config.ts
|
|
2842
|
+
var STRATEGY_DESCRIPTIONS = {
|
|
2843
|
+
sticky: "Stay on one account until it fails or is rate-limited",
|
|
2844
|
+
"round-robin": "Rotate through accounts on every request",
|
|
2845
|
+
hybrid: "Prefer healthy accounts, rotate when degraded"
|
|
2846
|
+
};
|
|
2847
|
+
function cmdGroupHelp(group) {
|
|
2848
|
+
const bin = "opencode-anthropic-auth";
|
|
2849
|
+
switch (group) {
|
|
2850
|
+
case "usage":
|
|
2851
|
+
console.log(`
|
|
2852
|
+
${c.bold("Usage Commands")}
|
|
2853
|
+
|
|
2854
|
+
${pad(c.cyan("stats"), 20)}Show per-account usage statistics
|
|
2855
|
+
${pad(c.cyan("reset-stats") + " [N|all]", 20)}Reset usage statistics
|
|
2856
|
+
${pad(c.cyan("status"), 20)}Compact one-liner for scripts/prompts (alias: st)
|
|
2857
|
+
|
|
2858
|
+
${c.dim("Examples:")}
|
|
2859
|
+
${bin} usage stats
|
|
2860
|
+
${bin} usage status
|
|
2861
|
+
`);
|
|
2862
|
+
return 0;
|
|
2863
|
+
case "config":
|
|
2864
|
+
console.log(`
|
|
2865
|
+
${c.bold("Config Commands")}
|
|
2866
|
+
|
|
2867
|
+
${pad(c.cyan("show"), 20)}Show current configuration and file paths (alias: cfg)
|
|
2868
|
+
${pad(c.cyan("strategy") + " [name]", 20)}Show or change selection strategy (alias: strat)
|
|
2869
|
+
|
|
2870
|
+
${c.dim("Examples:")}
|
|
2871
|
+
${bin} config show
|
|
2872
|
+
${bin} config strategy round-robin
|
|
2873
|
+
`);
|
|
2874
|
+
return 0;
|
|
2875
|
+
case "manage":
|
|
2876
|
+
console.log(`
|
|
2877
|
+
${c.bold("Manage Command")}
|
|
2878
|
+
|
|
2879
|
+
${pad(c.cyan("manage"), 20)}Interactive account management menu (alias: mg)
|
|
2880
|
+
|
|
2881
|
+
${c.dim("Examples:")}
|
|
2882
|
+
${bin} manage
|
|
2883
|
+
`);
|
|
2884
|
+
return 0;
|
|
2488
2885
|
}
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2886
|
+
}
|
|
2887
|
+
function renderManageAccounts(accounts, activeIndex, currentStrategy) {
|
|
2888
|
+
console.log("");
|
|
2889
|
+
console.log(c.bold(`${accounts.length} account(s):`));
|
|
2890
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
2891
|
+
const num = i + 1;
|
|
2892
|
+
const label = accounts[i].email || `Account ${num}`;
|
|
2893
|
+
const active = i === activeIndex ? c.green(" (active)") : "";
|
|
2894
|
+
const disabled = !accounts[i].enabled ? c.yellow(" [disabled]") : "";
|
|
2895
|
+
console.log(` ${c.bold(String(num))}. ${label}${active}${disabled}`);
|
|
2896
|
+
}
|
|
2897
|
+
console.log("");
|
|
2898
|
+
console.log(c.dim(`Strategy: ${currentStrategy}`));
|
|
2899
|
+
}
|
|
2900
|
+
function buildManageTargetOptions(accounts, activeIndex) {
|
|
2901
|
+
return accounts.map((account, index) => {
|
|
2902
|
+
const num = index + 1;
|
|
2903
|
+
const label = account.email || `Account ${num}`;
|
|
2904
|
+
const statuses = [index === activeIndex ? "active" : null, !account.enabled ? "disabled" : null].filter(
|
|
2905
|
+
Boolean
|
|
2906
|
+
);
|
|
2907
|
+
return {
|
|
2908
|
+
value: String(index),
|
|
2909
|
+
label: `#${num} ${label}`,
|
|
2910
|
+
hint: statuses.length > 0 ? statuses.join(", ") : void 0
|
|
2911
|
+
};
|
|
2912
|
+
});
|
|
2496
2913
|
}
|
|
2497
2914
|
async function cmdConfig() {
|
|
2498
2915
|
const config = loadConfig();
|
|
2499
2916
|
const stored = await loadAccounts();
|
|
2500
|
-
O2.info(
|
|
2917
|
+
O2.info(c.bold("Anthropic Auth Configuration"));
|
|
2501
2918
|
const generalLines = [
|
|
2502
|
-
`Strategy: ${
|
|
2919
|
+
`Strategy: ${c.cyan(config.account_selection_strategy)}`,
|
|
2503
2920
|
`Failure TTL: ${config.failure_ttl_seconds}s`,
|
|
2504
|
-
`Debug: ${config.debug ?
|
|
2921
|
+
`Debug: ${config.debug ? c.yellow("on") : "off"}`
|
|
2505
2922
|
];
|
|
2506
2923
|
wt(generalLines.join("\n"), "General");
|
|
2507
2924
|
const healthLines = [
|
|
@@ -2524,10 +2941,10 @@ async function cmdConfig() {
|
|
|
2524
2941
|
`Accounts: ${shortPath(getStoragePath())}`
|
|
2525
2942
|
];
|
|
2526
2943
|
if (stored) {
|
|
2527
|
-
const enabled = stored.accounts.filter((
|
|
2944
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
2528
2945
|
fileLines.push(`Accounts total: ${stored.accounts.length} (${enabled} enabled)`);
|
|
2529
2946
|
} else {
|
|
2530
|
-
fileLines.push(
|
|
2947
|
+
fileLines.push("Accounts total: none");
|
|
2531
2948
|
}
|
|
2532
2949
|
wt(fileLines.join("\n"), "Files");
|
|
2533
2950
|
const envOverrides = [];
|
|
@@ -2538,28 +2955,23 @@ async function cmdConfig() {
|
|
|
2538
2955
|
envOverrides.push(`OPENCODE_ANTHROPIC_DEBUG=${process.env.OPENCODE_ANTHROPIC_DEBUG}`);
|
|
2539
2956
|
}
|
|
2540
2957
|
if (envOverrides.length > 0) {
|
|
2541
|
-
O2.warn("Environment overrides:\n" + envOverrides.map((
|
|
2958
|
+
O2.warn("Environment overrides:\n" + envOverrides.map((override) => ` ${c.yellow(override)}`).join("\n"));
|
|
2542
2959
|
}
|
|
2543
2960
|
return 0;
|
|
2544
2961
|
}
|
|
2545
2962
|
async function cmdStrategy(arg) {
|
|
2546
2963
|
const config = loadConfig();
|
|
2547
2964
|
if (!arg) {
|
|
2548
|
-
O2.info(
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
const current = s === config.account_selection_strategy;
|
|
2556
|
-
const marker = current ? c2.green("\u25B8 ") : " ";
|
|
2557
|
-
const name = current ? c2.bold(c2.cyan(s)) : c2.dim(s);
|
|
2558
|
-
const desc = current ? descriptions[s] : c2.dim(descriptions[s]);
|
|
2559
|
-
return `${marker}${pad(name, 16)}${desc}`;
|
|
2965
|
+
O2.info(c.bold("Account Selection Strategy"));
|
|
2966
|
+
const lines = VALID_STRATEGIES.map((strategy) => {
|
|
2967
|
+
const current = strategy === config.account_selection_strategy;
|
|
2968
|
+
const marker = current ? c.green("\u25B8 ") : " ";
|
|
2969
|
+
const name = current ? c.bold(c.cyan(strategy)) : c.dim(strategy);
|
|
2970
|
+
const description = current ? STRATEGY_DESCRIPTIONS[strategy] : c.dim(STRATEGY_DESCRIPTIONS[strategy]);
|
|
2971
|
+
return `${marker}${pad(name, 16)}${description}`;
|
|
2560
2972
|
});
|
|
2561
2973
|
O2.message(lines.join("\n"));
|
|
2562
|
-
O2.message(
|
|
2974
|
+
O2.message(c.dim(`Change with: opencode-anthropic-auth strategy <${VALID_STRATEGIES.join("|")}>`));
|
|
2563
2975
|
if (process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
2564
2976
|
O2.warn(
|
|
2565
2977
|
`OPENCODE_ANTHROPIC_STRATEGY=${process.env.OPENCODE_ANTHROPIC_STRATEGY} overrides config file at runtime.`
|
|
@@ -2573,64 +2985,42 @@ async function cmdStrategy(arg) {
|
|
|
2573
2985
|
return 1;
|
|
2574
2986
|
}
|
|
2575
2987
|
if (normalized === config.account_selection_strategy && !process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
2576
|
-
O2.message(
|
|
2988
|
+
O2.message(c.dim(`Strategy is already '${normalized}'.`));
|
|
2577
2989
|
return 0;
|
|
2578
2990
|
}
|
|
2579
2991
|
saveConfig({ account_selection_strategy: normalized });
|
|
2580
2992
|
O2.success(`Strategy changed to '${normalized}'.`);
|
|
2581
2993
|
if (process.env.OPENCODE_ANTHROPIC_STRATEGY) {
|
|
2582
|
-
O2.warn(
|
|
2994
|
+
O2.warn(
|
|
2995
|
+
`OPENCODE_ANTHROPIC_STRATEGY=${process.env.OPENCODE_ANTHROPIC_STRATEGY} will override this at runtime.`
|
|
2996
|
+
);
|
|
2583
2997
|
}
|
|
2584
2998
|
return 0;
|
|
2585
2999
|
}
|
|
2586
|
-
function
|
|
2587
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
2588
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
2589
|
-
return String(n);
|
|
2590
|
-
}
|
|
2591
|
-
async function cmdStats() {
|
|
3000
|
+
async function cmdStatus2() {
|
|
2592
3001
|
const stored = await loadAccounts();
|
|
2593
3002
|
if (!stored || stored.accounts.length === 0) {
|
|
2594
|
-
|
|
3003
|
+
console.log("anthropic: no accounts configured");
|
|
2595
3004
|
return 1;
|
|
2596
3005
|
}
|
|
2597
|
-
const
|
|
2598
|
-
const
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
)
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
const acc = stored.accounts[i];
|
|
2608
|
-
const s = acc.stats || createDefaultStats();
|
|
2609
|
-
const isActive = i === stored.activeIndex;
|
|
2610
|
-
const marker = isActive ? c2.green("\u25CF") : " ";
|
|
2611
|
-
const num = `${marker} ${i + 1}`;
|
|
2612
|
-
const name = acc.email || `Account ${i + 1}`;
|
|
2613
|
-
O2.message(
|
|
2614
|
-
" " + pad(num, W.num) + pad(name, W.name) + rpad(String(s.requests), W.val) + rpad(fmtTokens(s.inputTokens), W.val) + rpad(fmtTokens(s.outputTokens), W.val) + rpad(fmtTokens(s.cacheReadTokens), W.val) + rpad(fmtTokens(s.cacheWriteTokens), W.val)
|
|
2615
|
-
);
|
|
2616
|
-
totReq += s.requests;
|
|
2617
|
-
totIn += s.inputTokens;
|
|
2618
|
-
totOut += s.outputTokens;
|
|
2619
|
-
totCR += s.cacheReadTokens;
|
|
2620
|
-
totCW += s.cacheWriteTokens;
|
|
2621
|
-
if (s.lastReset < oldestReset) oldestReset = s.lastReset;
|
|
2622
|
-
}
|
|
2623
|
-
if (stored.accounts.length > 1) {
|
|
2624
|
-
O2.message(RULE);
|
|
2625
|
-
O2.message(
|
|
2626
|
-
c2.bold(
|
|
2627
|
-
" " + pad("", W.num) + pad("Total", W.name) + rpad(String(totReq), W.val) + rpad(fmtTokens(totIn), W.val) + rpad(fmtTokens(totOut), W.val) + rpad(fmtTokens(totCR), W.val) + rpad(fmtTokens(totCW), W.val)
|
|
2628
|
-
)
|
|
2629
|
-
);
|
|
3006
|
+
const config = loadConfig();
|
|
3007
|
+
const total = stored.accounts.length;
|
|
3008
|
+
const enabled = stored.accounts.filter((account) => account.enabled).length;
|
|
3009
|
+
const now = Date.now();
|
|
3010
|
+
let rateLimited = 0;
|
|
3011
|
+
for (const account of stored.accounts) {
|
|
3012
|
+
if (!account.enabled) continue;
|
|
3013
|
+
const resetTimes = account.rateLimitResetTimes || {};
|
|
3014
|
+
const maxReset = Math.max(0, ...Object.values(resetTimes));
|
|
3015
|
+
if (maxReset > now) rateLimited++;
|
|
2630
3016
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
3017
|
+
let line = `anthropic: ${total} account${total !== 1 ? "s" : ""} (${enabled} active)`;
|
|
3018
|
+
line += `, strategy: ${config.account_selection_strategy}`;
|
|
3019
|
+
line += `, next: #${stored.activeIndex + 1}`;
|
|
3020
|
+
if (rateLimited > 0) {
|
|
3021
|
+
line += `, ${rateLimited} rate-limited`;
|
|
2633
3022
|
}
|
|
3023
|
+
console.log(line);
|
|
2634
3024
|
return 0;
|
|
2635
3025
|
}
|
|
2636
3026
|
async function cmdResetStats(arg) {
|
|
@@ -2641,8 +3031,8 @@ async function cmdResetStats(arg) {
|
|
|
2641
3031
|
}
|
|
2642
3032
|
const now = Date.now();
|
|
2643
3033
|
if (!arg || arg === "all") {
|
|
2644
|
-
for (const
|
|
2645
|
-
|
|
3034
|
+
for (const account of stored.accounts) {
|
|
3035
|
+
account.stats = createDefaultStats(now);
|
|
2646
3036
|
}
|
|
2647
3037
|
await saveAccounts(stored);
|
|
2648
3038
|
O2.success("Reset usage statistics for all accounts.");
|
|
@@ -2662,35 +3052,25 @@ async function cmdResetStats(arg) {
|
|
|
2662
3052
|
async function cmdManage() {
|
|
2663
3053
|
let stored = await loadAccounts();
|
|
2664
3054
|
if (!stored || stored.accounts.length === 0) {
|
|
2665
|
-
console.log(
|
|
2666
|
-
console.log(
|
|
3055
|
+
console.log(c.yellow("No accounts configured."));
|
|
3056
|
+
console.log(c.dim("Run 'opencode auth login' and select 'Claude Pro/Max' to add accounts."));
|
|
2667
3057
|
return 1;
|
|
2668
3058
|
}
|
|
2669
3059
|
if (!process.stdin.isTTY) {
|
|
2670
|
-
console.error(
|
|
2671
|
-
console.error(
|
|
3060
|
+
console.error(c.red("Error: 'manage' requires an interactive terminal."));
|
|
3061
|
+
console.error(c.dim("Use 'enable', 'disable', 'remove', 'switch' for non-interactive use."));
|
|
2672
3062
|
return 1;
|
|
2673
3063
|
}
|
|
2674
3064
|
while (true) {
|
|
2675
3065
|
stored = await loadAccounts();
|
|
2676
3066
|
if (!stored || stored.accounts.length === 0) {
|
|
2677
|
-
console.log(
|
|
3067
|
+
console.log(c.dim("No accounts remaining."));
|
|
2678
3068
|
break;
|
|
2679
3069
|
}
|
|
2680
3070
|
const accounts = stored.accounts;
|
|
2681
3071
|
const activeIndex = stored.activeIndex;
|
|
2682
3072
|
const currentStrategy = loadConfig().account_selection_strategy;
|
|
2683
|
-
|
|
2684
|
-
console.log(c2.bold(`${accounts.length} account(s):`));
|
|
2685
|
-
for (let i = 0; i < accounts.length; i++) {
|
|
2686
|
-
const num2 = i + 1;
|
|
2687
|
-
const label = accounts[i].email || `Account ${num2}`;
|
|
2688
|
-
const active = i === stored.activeIndex ? c2.green(" (active)") : "";
|
|
2689
|
-
const disabled = !accounts[i].enabled ? c2.yellow(" [disabled]") : "";
|
|
2690
|
-
console.log(` ${c2.bold(String(num2))}. ${label}${active}${disabled}`);
|
|
2691
|
-
}
|
|
2692
|
-
console.log("");
|
|
2693
|
-
console.log(c2.dim(`Strategy: ${currentStrategy}`));
|
|
3073
|
+
renderManageAccounts(accounts, activeIndex, currentStrategy);
|
|
2694
3074
|
const action = await _t({
|
|
2695
3075
|
message: "Choose an action.",
|
|
2696
3076
|
options: [
|
|
@@ -2716,24 +3096,13 @@ async function cmdManage() {
|
|
|
2716
3096
|
});
|
|
2717
3097
|
if (q(strategy)) break;
|
|
2718
3098
|
saveConfig({ account_selection_strategy: strategy });
|
|
2719
|
-
console.log(
|
|
3099
|
+
console.log(c.green(`Strategy changed to '${strategy}'.`));
|
|
2720
3100
|
continue;
|
|
2721
3101
|
}
|
|
2722
3102
|
const target = await _t({
|
|
2723
3103
|
message: "Choose an account.",
|
|
2724
3104
|
initialValue: String(activeIndex),
|
|
2725
|
-
options: accounts
|
|
2726
|
-
const num2 = index + 1;
|
|
2727
|
-
const label = account.email || `Account ${num2}`;
|
|
2728
|
-
const statuses = [index === activeIndex ? "active" : null, !account.enabled ? "disabled" : null].filter(
|
|
2729
|
-
Boolean
|
|
2730
|
-
);
|
|
2731
|
-
return {
|
|
2732
|
-
value: String(index),
|
|
2733
|
-
label: `#${num2} ${label}`,
|
|
2734
|
-
hint: statuses.length > 0 ? statuses.join(", ") : void 0
|
|
2735
|
-
};
|
|
2736
|
-
})
|
|
3105
|
+
options: buildManageTargetOptions(accounts, activeIndex)
|
|
2737
3106
|
});
|
|
2738
3107
|
if (q(target)) break;
|
|
2739
3108
|
const idx = Number.parseInt(target, 10);
|
|
@@ -2741,42 +3110,44 @@ async function cmdManage() {
|
|
|
2741
3110
|
switch (action) {
|
|
2742
3111
|
case "switch": {
|
|
2743
3112
|
if (!accounts[idx].enabled) {
|
|
2744
|
-
console.log(
|
|
3113
|
+
console.log(c.yellow(`Account ${num} is disabled. Enable it first.`));
|
|
2745
3114
|
break;
|
|
2746
3115
|
}
|
|
2747
3116
|
stored.activeIndex = idx;
|
|
2748
3117
|
await saveAccounts(stored);
|
|
2749
3118
|
const switchLabel = accounts[idx].email || `Account ${num}`;
|
|
2750
|
-
console.log(
|
|
3119
|
+
console.log(c.green(`Switched to #${num} (${switchLabel}).`));
|
|
2751
3120
|
break;
|
|
2752
3121
|
}
|
|
2753
3122
|
case "enable": {
|
|
2754
3123
|
if (accounts[idx].enabled) {
|
|
2755
|
-
console.log(
|
|
3124
|
+
console.log(c.dim(`Account ${num} is already enabled.`));
|
|
2756
3125
|
break;
|
|
2757
3126
|
}
|
|
2758
3127
|
stored.accounts[idx].enabled = true;
|
|
2759
3128
|
await saveAccounts(stored);
|
|
2760
|
-
console.log(
|
|
3129
|
+
console.log(c.green(`Enabled account #${num}.`));
|
|
2761
3130
|
break;
|
|
2762
3131
|
}
|
|
2763
3132
|
case "disable": {
|
|
2764
3133
|
if (!accounts[idx].enabled) {
|
|
2765
|
-
console.log(
|
|
3134
|
+
console.log(c.dim(`Account ${num} is already disabled.`));
|
|
2766
3135
|
break;
|
|
2767
3136
|
}
|
|
2768
3137
|
const enabledCount = accounts.filter((account) => account.enabled).length;
|
|
2769
3138
|
if (enabledCount <= 1) {
|
|
2770
|
-
console.log(
|
|
3139
|
+
console.log(c.red("Cannot disable the last enabled account."));
|
|
2771
3140
|
break;
|
|
2772
3141
|
}
|
|
2773
3142
|
stored.accounts[idx].enabled = false;
|
|
2774
3143
|
if (idx === stored.activeIndex) {
|
|
2775
|
-
const nextEnabled = accounts.findIndex(
|
|
3144
|
+
const nextEnabled = accounts.findIndex(
|
|
3145
|
+
(account, accountIndex) => accountIndex !== idx && account.enabled
|
|
3146
|
+
);
|
|
2776
3147
|
if (nextEnabled >= 0) stored.activeIndex = nextEnabled;
|
|
2777
3148
|
}
|
|
2778
3149
|
await saveAccounts(stored);
|
|
2779
|
-
console.log(
|
|
3150
|
+
console.log(c.yellow(`Disabled account #${num}.`));
|
|
2780
3151
|
break;
|
|
2781
3152
|
}
|
|
2782
3153
|
case "remove": {
|
|
@@ -2795,9 +3166,9 @@ async function cmdManage() {
|
|
|
2795
3166
|
stored.activeIndex--;
|
|
2796
3167
|
}
|
|
2797
3168
|
await saveAccounts(stored);
|
|
2798
|
-
console.log(
|
|
3169
|
+
console.log(c.green(`Removed account #${num}.`));
|
|
2799
3170
|
} else {
|
|
2800
|
-
console.log(
|
|
3171
|
+
console.log(c.dim("Cancelled."));
|
|
2801
3172
|
}
|
|
2802
3173
|
break;
|
|
2803
3174
|
}
|
|
@@ -2806,7 +3177,7 @@ async function cmdManage() {
|
|
|
2806
3177
|
stored.accounts[idx].consecutiveFailures = 0;
|
|
2807
3178
|
stored.accounts[idx].lastFailureTime = null;
|
|
2808
3179
|
await saveAccounts(stored);
|
|
2809
|
-
console.log(
|
|
3180
|
+
console.log(c.green(`Reset tracking for account #${num}.`));
|
|
2810
3181
|
break;
|
|
2811
3182
|
}
|
|
2812
3183
|
}
|
|
@@ -2816,78 +3187,137 @@ async function cmdManage() {
|
|
|
2816
3187
|
function cmdHelp() {
|
|
2817
3188
|
const bin = "opencode-anthropic-auth";
|
|
2818
3189
|
console.log(`
|
|
2819
|
-
${
|
|
3190
|
+
${c.bold("Anthropic Multi-Account Auth CLI")}
|
|
2820
3191
|
|
|
2821
|
-
${
|
|
3192
|
+
${c.dim("Usage:")}
|
|
2822
3193
|
${bin} [group] [command] [args]
|
|
2823
|
-
${bin} [command] [args] ${
|
|
2824
|
-
oaa [group] [command] [args] ${
|
|
3194
|
+
${bin} [command] [args] ${c.dim("(legacy format, still supported)")}
|
|
3195
|
+
oaa [group] [command] [args] ${c.dim("(short alias)")}
|
|
2825
3196
|
|
|
2826
|
-
${
|
|
2827
|
-
${pad(
|
|
2828
|
-
${pad(
|
|
2829
|
-
${pad(
|
|
2830
|
-
${pad(
|
|
2831
|
-
${pad(
|
|
3197
|
+
${c.dim("Command Groups:")}
|
|
3198
|
+
${pad(c.cyan("auth"), 22)}Authentication: login, logout, reauth, refresh
|
|
3199
|
+
${pad(c.cyan("account"), 22)}Account management: list, switch, enable, disable, remove, reset
|
|
3200
|
+
${pad(c.cyan("usage"), 22)}Usage statistics: stats, reset-stats, status
|
|
3201
|
+
${pad(c.cyan("config"), 22)}Configuration: show, strategy
|
|
3202
|
+
${pad(c.cyan("manage"), 22)}Interactive account management menu
|
|
2832
3203
|
|
|
2833
|
-
${
|
|
2834
|
-
${pad(
|
|
2835
|
-
${pad(
|
|
2836
|
-
${pad(
|
|
2837
|
-
${pad(
|
|
2838
|
-
${pad(
|
|
3204
|
+
${c.dim("Auth Commands:")}
|
|
3205
|
+
${pad(c.cyan("login"), 22)}Add a new account via browser OAuth (alias: ln)
|
|
3206
|
+
${pad(c.cyan("logout") + " <N>", 22)}Revoke tokens and remove account N (alias: lo)
|
|
3207
|
+
${pad(c.cyan("logout") + " --all", 22)}Revoke all tokens and clear all accounts
|
|
3208
|
+
${pad(c.cyan("reauth") + " <N>", 22)}Re-authenticate account N (alias: ra)
|
|
3209
|
+
${pad(c.cyan("refresh") + " <N>", 22)}Attempt token refresh (alias: rf)
|
|
2839
3210
|
|
|
2840
|
-
${
|
|
2841
|
-
${pad(
|
|
2842
|
-
${pad(
|
|
2843
|
-
${pad(
|
|
2844
|
-
${pad(
|
|
2845
|
-
${pad(
|
|
2846
|
-
${pad(
|
|
3211
|
+
${c.dim("Account Commands:")}
|
|
3212
|
+
${pad(c.cyan("list"), 22)}Show all accounts with status ${c.dim("(default, alias: ls)")}
|
|
3213
|
+
${pad(c.cyan("switch") + " <N>", 22)}Set account N as active (alias: sw)
|
|
3214
|
+
${pad(c.cyan("enable") + " <N>", 22)}Enable a disabled account (alias: en)
|
|
3215
|
+
${pad(c.cyan("disable") + " <N>", 22)}Disable an account (alias: dis)
|
|
3216
|
+
${pad(c.cyan("remove") + " <N>", 22)}Remove an account permanently (alias: rm)
|
|
3217
|
+
${pad(c.cyan("reset") + " <N|all>", 22)}Clear rate-limit / failure tracking
|
|
2847
3218
|
|
|
2848
|
-
${
|
|
2849
|
-
${pad(
|
|
2850
|
-
${pad(
|
|
2851
|
-
${pad(
|
|
3219
|
+
${c.dim("Usage Commands:")}
|
|
3220
|
+
${pad(c.cyan("stats"), 22)}Show per-account usage statistics
|
|
3221
|
+
${pad(c.cyan("reset-stats") + " [N|all]", 22)}Reset usage statistics
|
|
3222
|
+
${pad(c.cyan("status"), 22)}Compact one-liner for scripts/prompts (alias: st)
|
|
2852
3223
|
|
|
2853
|
-
${
|
|
2854
|
-
${pad(
|
|
2855
|
-
${pad(
|
|
3224
|
+
${c.dim("Config Commands:")}
|
|
3225
|
+
${pad(c.cyan("config"), 22)}Show configuration and file paths (alias: cfg)
|
|
3226
|
+
${pad(c.cyan("strategy") + " [name]", 22)}Show or change selection strategy (alias: strat)
|
|
2856
3227
|
|
|
2857
|
-
${
|
|
2858
|
-
${pad(
|
|
2859
|
-
${pad(
|
|
3228
|
+
${c.dim("Manage Commands:")}
|
|
3229
|
+
${pad(c.cyan("manage"), 22)}Interactive account management menu (alias: mg)
|
|
3230
|
+
${pad(c.cyan("help"), 22)}Show this help message
|
|
2860
3231
|
|
|
2861
|
-
${
|
|
2862
|
-
${bin} auth help ${
|
|
2863
|
-
${bin} account help ${
|
|
2864
|
-
${bin} usage help ${
|
|
2865
|
-
${bin} config help ${
|
|
3232
|
+
${c.dim("Group Help:")}
|
|
3233
|
+
${bin} auth help ${c.dim("# Show auth commands")}
|
|
3234
|
+
${bin} account help ${c.dim("# Show account commands")}
|
|
3235
|
+
${bin} usage help ${c.dim("# Show usage commands")}
|
|
3236
|
+
${bin} config help ${c.dim("# Show config commands")}
|
|
2866
3237
|
|
|
2867
|
-
${
|
|
3238
|
+
${c.dim("Options:")}
|
|
2868
3239
|
--force Skip confirmation prompts
|
|
2869
3240
|
--all Target all accounts (for logout)
|
|
2870
3241
|
--no-color Disable colored output
|
|
2871
3242
|
|
|
2872
|
-
${
|
|
2873
|
-
${bin} login ${
|
|
2874
|
-
${bin} auth login ${
|
|
2875
|
-
oaa login ${
|
|
2876
|
-
${bin} logout 2 ${
|
|
2877
|
-
${bin} auth logout 2 ${
|
|
2878
|
-
${bin} list ${
|
|
2879
|
-
${bin} account list ${
|
|
2880
|
-
${bin} switch 2 ${
|
|
2881
|
-
${bin} account switch 2 ${
|
|
2882
|
-
${bin} stats ${
|
|
2883
|
-
${bin} usage stats ${
|
|
3243
|
+
${c.dim("Examples:")}
|
|
3244
|
+
${bin} login ${c.dim("# Add a new account via browser")}
|
|
3245
|
+
${bin} auth login ${c.dim("# Same as above (group format)")}
|
|
3246
|
+
oaa login ${c.dim("# Same as above (short alias)")}
|
|
3247
|
+
${bin} logout 2 ${c.dim("# Revoke tokens & remove account 2")}
|
|
3248
|
+
${bin} auth logout 2 ${c.dim("# Same as above (group format)")}
|
|
3249
|
+
${bin} list ${c.dim("# Show all accounts (default)")}
|
|
3250
|
+
${bin} account list ${c.dim("# Same as above (group format)")}
|
|
3251
|
+
${bin} switch 2 ${c.dim("# Make account 2 active")}
|
|
3252
|
+
${bin} account switch 2 ${c.dim("# Same as above (group format)")}
|
|
3253
|
+
${bin} stats ${c.dim("# Show token usage per account")}
|
|
3254
|
+
${bin} usage stats ${c.dim("# Same as above (group format)")}
|
|
2884
3255
|
|
|
2885
|
-
${
|
|
3256
|
+
${c.dim("Files:")}
|
|
2886
3257
|
Config: ${shortPath(getConfigPath())}
|
|
2887
3258
|
Accounts: ${shortPath(getStoragePath())}
|
|
2888
3259
|
`);
|
|
2889
3260
|
return 0;
|
|
2890
3261
|
}
|
|
3262
|
+
async function dispatchUsageCommands(args) {
|
|
3263
|
+
const subcommand = args[0] || "stats";
|
|
3264
|
+
const arg = args[1];
|
|
3265
|
+
switch (subcommand) {
|
|
3266
|
+
case "stats":
|
|
3267
|
+
return cmdStats();
|
|
3268
|
+
case "reset-stats":
|
|
3269
|
+
return cmdResetStats(arg);
|
|
3270
|
+
case "status":
|
|
3271
|
+
case "st":
|
|
3272
|
+
return cmdStatus2();
|
|
3273
|
+
case "help":
|
|
3274
|
+
case "-h":
|
|
3275
|
+
case "--help":
|
|
3276
|
+
return cmdGroupHelp("usage");
|
|
3277
|
+
default:
|
|
3278
|
+
console.error(c.red(`Unknown usage command: ${subcommand}`));
|
|
3279
|
+
console.error(c.dim("Run 'opencode-anthropic-auth usage help' for usage."));
|
|
3280
|
+
return 1;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
async function dispatchConfigCommands(args) {
|
|
3284
|
+
const subcommand = args[0] || "show";
|
|
3285
|
+
const arg = args[1];
|
|
3286
|
+
switch (subcommand) {
|
|
3287
|
+
case "show":
|
|
3288
|
+
case "cfg":
|
|
3289
|
+
return cmdConfig();
|
|
3290
|
+
case "strategy":
|
|
3291
|
+
case "strat":
|
|
3292
|
+
return cmdStrategy(arg);
|
|
3293
|
+
case "help":
|
|
3294
|
+
case "-h":
|
|
3295
|
+
case "--help":
|
|
3296
|
+
return cmdGroupHelp("config");
|
|
3297
|
+
default:
|
|
3298
|
+
console.error(c.red(`Unknown config command: ${subcommand}`));
|
|
3299
|
+
console.error(c.dim("Run 'opencode-anthropic-auth config help' for usage."));
|
|
3300
|
+
return 1;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
async function dispatchManageCommands(args) {
|
|
3304
|
+
const subcommand = args[0] || "manage";
|
|
3305
|
+
switch (subcommand) {
|
|
3306
|
+
case "manage":
|
|
3307
|
+
case "mg":
|
|
3308
|
+
return cmdManage();
|
|
3309
|
+
case "help":
|
|
3310
|
+
case "-h":
|
|
3311
|
+
case "--help":
|
|
3312
|
+
return cmdGroupHelp("manage");
|
|
3313
|
+
default:
|
|
3314
|
+
console.error(c.red(`Unknown manage command: ${subcommand}`));
|
|
3315
|
+
console.error(c.dim("Run 'opencode-anthropic-auth manage help' for usage."));
|
|
3316
|
+
return 1;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
// src/cli.ts
|
|
2891
3321
|
var ioContext = new AsyncLocalStorage();
|
|
2892
3322
|
var nativeConsoleLog = console.log.bind(console);
|
|
2893
3323
|
var nativeConsoleError = console.error.bind(console);
|
|
@@ -2922,82 +3352,6 @@ async function runWithIoContext(io, fn) {
|
|
|
2922
3352
|
uninstallConsoleRouter();
|
|
2923
3353
|
}
|
|
2924
3354
|
}
|
|
2925
|
-
function cmdGroupHelp(group) {
|
|
2926
|
-
const bin = "opencode-anthropic-auth";
|
|
2927
|
-
switch (group) {
|
|
2928
|
-
case "auth":
|
|
2929
|
-
console.log(`
|
|
2930
|
-
${c2.bold("Auth Commands")}
|
|
2931
|
-
|
|
2932
|
-
${pad(c2.cyan("login"), 20)}Add a new account via browser OAuth flow (alias: ln)
|
|
2933
|
-
${pad(c2.cyan("logout") + " <N>", 20)}Revoke tokens and remove account N (alias: lo)
|
|
2934
|
-
${pad(c2.cyan("logout") + " --all", 20)}Revoke all tokens and clear all accounts
|
|
2935
|
-
${pad(c2.cyan("reauth") + " <N>", 20)}Re-authenticate account N with fresh tokens (alias: ra)
|
|
2936
|
-
${pad(c2.cyan("refresh") + " <N>", 20)}Attempt token refresh without browser (alias: rf)
|
|
2937
|
-
|
|
2938
|
-
${c2.dim("Examples:")}
|
|
2939
|
-
${bin} auth login
|
|
2940
|
-
${bin} auth logout 2
|
|
2941
|
-
${bin} auth reauth 1
|
|
2942
|
-
`);
|
|
2943
|
-
return 0;
|
|
2944
|
-
case "account":
|
|
2945
|
-
console.log(`
|
|
2946
|
-
${c2.bold("Account Commands")}
|
|
2947
|
-
|
|
2948
|
-
${pad(c2.cyan("list"), 20)}Show all accounts with status (alias: ls)
|
|
2949
|
-
${pad(c2.cyan("switch") + " <N>", 20)}Set account N as active (alias: sw)
|
|
2950
|
-
${pad(c2.cyan("enable") + " <N>", 20)}Enable a disabled account (alias: en)
|
|
2951
|
-
${pad(c2.cyan("disable") + " <N>", 20)}Disable an account (alias: dis)
|
|
2952
|
-
${pad(c2.cyan("remove") + " <N>", 20)}Remove an account permanently (alias: rm)
|
|
2953
|
-
${pad(c2.cyan("reset"), 20)}Clear rate-limit / failure tracking
|
|
2954
|
-
|
|
2955
|
-
${c2.dim("Examples:")}
|
|
2956
|
-
${bin} account list
|
|
2957
|
-
${bin} account switch 2
|
|
2958
|
-
${bin} account disable 3
|
|
2959
|
-
`);
|
|
2960
|
-
return 0;
|
|
2961
|
-
case "usage":
|
|
2962
|
-
console.log(`
|
|
2963
|
-
${c2.bold("Usage Commands")}
|
|
2964
|
-
|
|
2965
|
-
${pad(c2.cyan("stats"), 20)}Show per-account usage statistics
|
|
2966
|
-
${pad(c2.cyan("reset-stats") + " [N|all]", 20)}Reset usage statistics
|
|
2967
|
-
${pad(c2.cyan("status"), 20)}Compact one-liner for scripts/prompts (alias: st)
|
|
2968
|
-
|
|
2969
|
-
${c2.dim("Examples:")}
|
|
2970
|
-
${bin} usage stats
|
|
2971
|
-
${bin} usage status
|
|
2972
|
-
`);
|
|
2973
|
-
return 0;
|
|
2974
|
-
case "config":
|
|
2975
|
-
console.log(`
|
|
2976
|
-
${c2.bold("Config Commands")}
|
|
2977
|
-
|
|
2978
|
-
${pad(c2.cyan("show"), 20)}Show current configuration and file paths (alias: cfg)
|
|
2979
|
-
${pad(c2.cyan("strategy") + " [name]", 20)}Show or change selection strategy (alias: strat)
|
|
2980
|
-
|
|
2981
|
-
${c2.dim("Examples:")}
|
|
2982
|
-
${bin} config show
|
|
2983
|
-
${bin} config strategy round-robin
|
|
2984
|
-
`);
|
|
2985
|
-
return 0;
|
|
2986
|
-
case "manage":
|
|
2987
|
-
console.log(`
|
|
2988
|
-
${c2.bold("Manage Command")}
|
|
2989
|
-
|
|
2990
|
-
${pad(c2.cyan("manage"), 20)}Interactive account management menu (alias: mg)
|
|
2991
|
-
|
|
2992
|
-
${c2.dim("Examples:")}
|
|
2993
|
-
${bin} manage
|
|
2994
|
-
`);
|
|
2995
|
-
return 0;
|
|
2996
|
-
default:
|
|
2997
|
-
console.error(c2.red(`Unknown command group: ${group}`));
|
|
2998
|
-
return 1;
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
3355
|
async function dispatchAuth(args, flags) {
|
|
3002
3356
|
const subcommand = args[0] || "help";
|
|
3003
3357
|
const arg = args[1];
|
|
@@ -3017,10 +3371,10 @@ async function dispatchAuth(args, flags) {
|
|
|
3017
3371
|
case "help":
|
|
3018
3372
|
case "-h":
|
|
3019
3373
|
case "--help":
|
|
3020
|
-
return
|
|
3374
|
+
return cmdAuthGroupHelp("auth");
|
|
3021
3375
|
default:
|
|
3022
|
-
console.error(
|
|
3023
|
-
console.error(
|
|
3376
|
+
console.error(c.red(`Unknown auth command: ${subcommand}`));
|
|
3377
|
+
console.error(c.dim("Run 'opencode-anthropic-auth auth help' for usage."));
|
|
3024
3378
|
return 1;
|
|
3025
3379
|
}
|
|
3026
3380
|
}
|
|
@@ -3048,93 +3402,36 @@ async function dispatchAccount(args, flags) {
|
|
|
3048
3402
|
case "help":
|
|
3049
3403
|
case "-h":
|
|
3050
3404
|
case "--help":
|
|
3051
|
-
return
|
|
3052
|
-
default:
|
|
3053
|
-
console.error(c2.red(`Unknown account command: ${subcommand}`));
|
|
3054
|
-
console.error(c2.dim("Run 'opencode-anthropic-auth account help' for usage."));
|
|
3055
|
-
return 1;
|
|
3056
|
-
}
|
|
3057
|
-
}
|
|
3058
|
-
async function dispatchUsage(args) {
|
|
3059
|
-
const subcommand = args[0] || "stats";
|
|
3060
|
-
const arg = args[1];
|
|
3061
|
-
switch (subcommand) {
|
|
3062
|
-
case "stats":
|
|
3063
|
-
return cmdStats();
|
|
3064
|
-
case "reset-stats":
|
|
3065
|
-
return cmdResetStats(arg);
|
|
3066
|
-
case "status":
|
|
3067
|
-
case "st":
|
|
3068
|
-
return cmdStatus();
|
|
3069
|
-
case "help":
|
|
3070
|
-
case "-h":
|
|
3071
|
-
case "--help":
|
|
3072
|
-
return cmdGroupHelp("usage");
|
|
3073
|
-
default:
|
|
3074
|
-
console.error(c2.red(`Unknown usage command: ${subcommand}`));
|
|
3075
|
-
console.error(c2.dim("Run 'opencode-anthropic-auth usage help' for usage."));
|
|
3076
|
-
return 1;
|
|
3077
|
-
}
|
|
3078
|
-
}
|
|
3079
|
-
async function dispatchConfig(args) {
|
|
3080
|
-
const subcommand = args[0] || "show";
|
|
3081
|
-
const arg = args[1];
|
|
3082
|
-
switch (subcommand) {
|
|
3083
|
-
case "show":
|
|
3084
|
-
case "cfg":
|
|
3085
|
-
return cmdConfig();
|
|
3086
|
-
case "strategy":
|
|
3087
|
-
case "strat":
|
|
3088
|
-
return cmdStrategy(arg);
|
|
3089
|
-
case "help":
|
|
3090
|
-
case "-h":
|
|
3091
|
-
case "--help":
|
|
3092
|
-
return cmdGroupHelp("config");
|
|
3093
|
-
default:
|
|
3094
|
-
console.error(c2.red(`Unknown config command: ${subcommand}`));
|
|
3095
|
-
console.error(c2.dim("Run 'opencode-anthropic-auth config help' for usage."));
|
|
3096
|
-
return 1;
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
async function dispatchManage(args) {
|
|
3100
|
-
const subcommand = args[0] || "help";
|
|
3101
|
-
switch (subcommand) {
|
|
3102
|
-
case "manage":
|
|
3103
|
-
case "mg":
|
|
3104
|
-
return cmdManage();
|
|
3105
|
-
case "help":
|
|
3106
|
-
case "-h":
|
|
3107
|
-
case "--help":
|
|
3108
|
-
return cmdGroupHelp("manage");
|
|
3405
|
+
return cmdAuthGroupHelp("account");
|
|
3109
3406
|
default:
|
|
3110
|
-
console.error(
|
|
3111
|
-
console.error(
|
|
3407
|
+
console.error(c.red(`Unknown account command: ${subcommand}`));
|
|
3408
|
+
console.error(c.dim("Run 'opencode-anthropic-auth account help' for usage."));
|
|
3112
3409
|
return 1;
|
|
3113
3410
|
}
|
|
3114
3411
|
}
|
|
3115
3412
|
async function dispatch(argv) {
|
|
3116
3413
|
const args = argv.filter((a) => !a.startsWith("--"));
|
|
3117
3414
|
const flags = argv.filter((a) => a.startsWith("--"));
|
|
3118
|
-
if (flags.includes("--no-color"))
|
|
3415
|
+
if (flags.includes("--no-color")) setUseColor(false);
|
|
3119
3416
|
if (flags.includes("--help")) return cmdHelp();
|
|
3120
3417
|
const command = args[0] || "list";
|
|
3121
3418
|
const remainingArgs = args.slice(1);
|
|
3122
3419
|
const force = flags.includes("--force");
|
|
3123
3420
|
const all = flags.includes("--all");
|
|
3124
3421
|
switch (command) {
|
|
3125
|
-
// Group dispatchers
|
|
3422
|
+
// ── Group dispatchers ──────────────────────────────────────────────
|
|
3126
3423
|
case "auth":
|
|
3127
3424
|
return dispatchAuth(remainingArgs, { force, all });
|
|
3128
3425
|
case "account":
|
|
3129
3426
|
return dispatchAccount(remainingArgs, { force });
|
|
3130
3427
|
case "usage":
|
|
3131
|
-
return
|
|
3428
|
+
return dispatchUsageCommands(remainingArgs);
|
|
3132
3429
|
case "config":
|
|
3133
|
-
return
|
|
3430
|
+
return dispatchConfigCommands(remainingArgs);
|
|
3134
3431
|
case "manage":
|
|
3135
|
-
return
|
|
3136
|
-
// Legacy
|
|
3137
|
-
// Auth
|
|
3432
|
+
return dispatchManageCommands(remainingArgs);
|
|
3433
|
+
// ── Legacy flat aliases (backward compat) ──────────────────────────
|
|
3434
|
+
// Auth
|
|
3138
3435
|
case "login":
|
|
3139
3436
|
case "ln":
|
|
3140
3437
|
return cmdLogin();
|
|
@@ -3147,7 +3444,7 @@ async function dispatch(argv) {
|
|
|
3147
3444
|
case "refresh":
|
|
3148
3445
|
case "rf":
|
|
3149
3446
|
return cmdRefresh(remainingArgs[0]);
|
|
3150
|
-
// Account
|
|
3447
|
+
// Account
|
|
3151
3448
|
case "list":
|
|
3152
3449
|
case "ls":
|
|
3153
3450
|
return cmdList();
|
|
@@ -3165,7 +3462,7 @@ async function dispatch(argv) {
|
|
|
3165
3462
|
return cmdRemove(remainingArgs[0], { force });
|
|
3166
3463
|
case "reset":
|
|
3167
3464
|
return cmdReset(remainingArgs[0]);
|
|
3168
|
-
// Usage
|
|
3465
|
+
// Usage
|
|
3169
3466
|
case "stats":
|
|
3170
3467
|
return cmdStats();
|
|
3171
3468
|
case "reset-stats":
|
|
@@ -3173,13 +3470,13 @@ async function dispatch(argv) {
|
|
|
3173
3470
|
case "status":
|
|
3174
3471
|
case "st":
|
|
3175
3472
|
return cmdStatus();
|
|
3176
|
-
// Config
|
|
3473
|
+
// Config
|
|
3177
3474
|
case "strategy":
|
|
3178
3475
|
case "strat":
|
|
3179
3476
|
return cmdStrategy(remainingArgs[0]);
|
|
3180
3477
|
case "cfg":
|
|
3181
|
-
return
|
|
3182
|
-
// Manage
|
|
3478
|
+
return dispatchConfigCommands(["show"]);
|
|
3479
|
+
// Manage
|
|
3183
3480
|
case "mg":
|
|
3184
3481
|
return cmdManage();
|
|
3185
3482
|
// Help
|
|
@@ -3188,8 +3485,8 @@ async function dispatch(argv) {
|
|
|
3188
3485
|
case "--help":
|
|
3189
3486
|
return cmdHelp();
|
|
3190
3487
|
default:
|
|
3191
|
-
console.error(
|
|
3192
|
-
console.error(
|
|
3488
|
+
console.error(c.red(`Unknown command: ${command}`));
|
|
3489
|
+
console.error(c.dim("Run 'opencode-anthropic-auth help' for usage."));
|
|
3193
3490
|
return 1;
|
|
3194
3491
|
}
|
|
3195
3492
|
}
|
|
@@ -3212,7 +3509,7 @@ async function detectMain() {
|
|
|
3212
3509
|
}
|
|
3213
3510
|
if (await detectMain()) {
|
|
3214
3511
|
main(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {
|
|
3215
|
-
console.error(
|
|
3512
|
+
console.error(c.red(`Fatal: ${err.message}`));
|
|
3216
3513
|
process.exit(1);
|
|
3217
3514
|
});
|
|
3218
3515
|
}
|