codex-team 0.0.8 → 0.0.10
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 +2 -0
- package/dist/cli.cjs +145 -37
- package/dist/main.cjs +145 -37
- package/dist/main.js +145 -37
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -9,7 +9,7 @@ import { basename, dirname, join } from "node:path";
|
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
var package_namespaceObject = {
|
|
12
|
-
rE: "0.0.
|
|
12
|
+
rE: "0.0.10"
|
|
13
13
|
};
|
|
14
14
|
function isRecord(value) {
|
|
15
15
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -42,10 +42,44 @@ function isSupportedChatGPTAuthMode(authMode) {
|
|
|
42
42
|
const normalized = normalizeAuthMode(authMode);
|
|
43
43
|
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
44
44
|
}
|
|
45
|
+
function extractAuthClaim(payload) {
|
|
46
|
+
const value = payload["https://api.openai.com/auth"];
|
|
47
|
+
return isRecord(value) ? value : void 0;
|
|
48
|
+
}
|
|
49
|
+
function extractStringClaim(payload, key) {
|
|
50
|
+
const value = payload[key];
|
|
51
|
+
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
52
|
+
}
|
|
53
|
+
function extractSnapshotJwtPayloads(snapshot) {
|
|
54
|
+
const tokens = snapshot.tokens ?? {};
|
|
55
|
+
const payloads = [];
|
|
56
|
+
for (const tokenName of [
|
|
57
|
+
"id_token",
|
|
58
|
+
"access_token"
|
|
59
|
+
]){
|
|
60
|
+
const token = tokens[tokenName];
|
|
61
|
+
if ("string" == typeof token && "" !== token.trim()) try {
|
|
62
|
+
payloads.push(decodeJwtPayload(token));
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
return payloads;
|
|
66
|
+
}
|
|
67
|
+
function getSnapshotUserId(snapshot) {
|
|
68
|
+
for (const payload of extractSnapshotJwtPayloads(snapshot)){
|
|
69
|
+
const authClaim = extractAuthClaim(payload);
|
|
70
|
+
const chatGPTUserId = authClaim?.chatgpt_user_id;
|
|
71
|
+
if ("string" == typeof chatGPTUserId && "" !== chatGPTUserId.trim()) return chatGPTUserId;
|
|
72
|
+
const userId = extractStringClaim(payload, "user_id");
|
|
73
|
+
if (userId) return userId;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
45
76
|
function fingerprintApiKey(apiKey) {
|
|
46
77
|
return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
47
78
|
}
|
|
48
|
-
function
|
|
79
|
+
function composeIdentity(accountId, userId) {
|
|
80
|
+
return "string" == typeof userId && "" !== userId.trim() ? `${accountId}:${userId}` : accountId;
|
|
81
|
+
}
|
|
82
|
+
function getSnapshotAccountId(snapshot) {
|
|
49
83
|
if (isApiKeyAuthMode(snapshot.auth_mode)) {
|
|
50
84
|
const apiKey = snapshot.OPENAI_API_KEY;
|
|
51
85
|
if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
@@ -55,6 +89,12 @@ function getSnapshotIdentity(snapshot) {
|
|
|
55
89
|
if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
|
|
56
90
|
return accountId;
|
|
57
91
|
}
|
|
92
|
+
function getSnapshotIdentity(snapshot) {
|
|
93
|
+
return composeIdentity(getSnapshotAccountId(snapshot), isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0);
|
|
94
|
+
}
|
|
95
|
+
function getMetaIdentity(meta) {
|
|
96
|
+
return composeIdentity(meta.account_id, meta.user_id);
|
|
97
|
+
}
|
|
58
98
|
function defaultQuotaSnapshot() {
|
|
59
99
|
return {
|
|
60
100
|
status: "stale"
|
|
@@ -132,7 +172,8 @@ function createSnapshotMeta(name, snapshot, now, existingCreatedAt) {
|
|
|
132
172
|
return {
|
|
133
173
|
name,
|
|
134
174
|
auth_mode: snapshot.auth_mode,
|
|
135
|
-
account_id:
|
|
175
|
+
account_id: getSnapshotAccountId(snapshot),
|
|
176
|
+
user_id: isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0,
|
|
136
177
|
created_at: existingCreatedAt ?? timestamp,
|
|
137
178
|
updated_at: timestamp,
|
|
138
179
|
last_switched_at: null,
|
|
@@ -153,6 +194,7 @@ function parseSnapshotMeta(raw) {
|
|
|
153
194
|
name: asNonEmptyString(parsed.name, "name"),
|
|
154
195
|
auth_mode: asNonEmptyString(parsed.auth_mode, "auth_mode"),
|
|
155
196
|
account_id: asNonEmptyString(parsed.account_id, "account_id"),
|
|
197
|
+
user_id: asOptionalString(parsed.user_id, "user_id"),
|
|
156
198
|
created_at: asNonEmptyString(parsed.created_at, "created_at"),
|
|
157
199
|
updated_at: asNonEmptyString(parsed.updated_at, "updated_at"),
|
|
158
200
|
last_switched_at: lastSwitchedAt,
|
|
@@ -174,14 +216,17 @@ function decodeJwtPayload(token) {
|
|
|
174
216
|
}
|
|
175
217
|
const DEFAULT_CHATGPT_BASE_URL = "https://chatgpt.com";
|
|
176
218
|
const USER_AGENT = "codexm/0.1";
|
|
219
|
+
const USAGE_FETCH_ATTEMPTS = 3;
|
|
220
|
+
const USAGE_FETCH_RETRY_DELAY_MS = 250;
|
|
221
|
+
const USAGE_FETCH_TIMEOUT_MS = 15000;
|
|
177
222
|
function quota_client_isRecord(value) {
|
|
178
223
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
179
224
|
}
|
|
180
|
-
function
|
|
225
|
+
function quota_client_extractAuthClaim(payload) {
|
|
181
226
|
const value = payload["https://api.openai.com/auth"];
|
|
182
227
|
return quota_client_isRecord(value) ? value : void 0;
|
|
183
228
|
}
|
|
184
|
-
function
|
|
229
|
+
function quota_client_extractStringClaim(payload, key) {
|
|
185
230
|
const value = payload[key];
|
|
186
231
|
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
187
232
|
}
|
|
@@ -194,7 +239,7 @@ function parsePlanType(snapshot) {
|
|
|
194
239
|
const token = tokens[tokenName];
|
|
195
240
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
196
241
|
const payload = decodeJwtPayload(token);
|
|
197
|
-
const authClaim =
|
|
242
|
+
const authClaim = quota_client_extractAuthClaim(payload);
|
|
198
243
|
const planType = authClaim?.chatgpt_plan_type;
|
|
199
244
|
if ("string" == typeof planType && "" !== planType.trim()) return planType;
|
|
200
245
|
} catch {}
|
|
@@ -218,7 +263,7 @@ function extractChatGPTAuth(snapshot) {
|
|
|
218
263
|
const token = tokens[tokenName];
|
|
219
264
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
220
265
|
const payload = decodeJwtPayload(token);
|
|
221
|
-
const authClaim =
|
|
266
|
+
const authClaim = quota_client_extractAuthClaim(payload);
|
|
222
267
|
if (!accountId) {
|
|
223
268
|
const maybeAccountId = authClaim?.chatgpt_account_id;
|
|
224
269
|
if ("string" == typeof maybeAccountId && "" !== maybeAccountId.trim()) accountId = maybeAccountId;
|
|
@@ -227,8 +272,8 @@ function extractChatGPTAuth(snapshot) {
|
|
|
227
272
|
const maybePlanType = authClaim?.chatgpt_plan_type;
|
|
228
273
|
if ("string" == typeof maybePlanType && "" !== maybePlanType.trim()) planType = maybePlanType;
|
|
229
274
|
}
|
|
230
|
-
issuer ??=
|
|
231
|
-
clientId ??=
|
|
275
|
+
issuer ??= quota_client_extractStringClaim(payload, "iss");
|
|
276
|
+
clientId ??= quota_client_extractStringClaim(payload, "client_id") ?? quota_client_extractStringClaim(payload, "azp") ?? ("string" == typeof payload.aud ? payload.aud : void 0);
|
|
232
277
|
} catch {}
|
|
233
278
|
}
|
|
234
279
|
if (!supported) return {
|
|
@@ -271,8 +316,6 @@ async function resolveUsageUrls(homeDir) {
|
|
|
271
316
|
const normalizedBaseUrl = baseUrl.replace(/\/+$/u, "");
|
|
272
317
|
const candidates = [
|
|
273
318
|
`${normalizedBaseUrl}/backend-api/wham/usage`,
|
|
274
|
-
`${normalizedBaseUrl}/wham/usage`,
|
|
275
|
-
`${normalizedBaseUrl}/api/codex/usage`,
|
|
276
319
|
"https://chatgpt.com/backend-api/wham/usage"
|
|
277
320
|
];
|
|
278
321
|
return [
|
|
@@ -282,6 +325,19 @@ async function resolveUsageUrls(homeDir) {
|
|
|
282
325
|
function normalizeFetchError(error) {
|
|
283
326
|
return error instanceof Error ? error.message : String(error);
|
|
284
327
|
}
|
|
328
|
+
async function delay(milliseconds) {
|
|
329
|
+
await new Promise((resolve)=>setTimeout(resolve, milliseconds));
|
|
330
|
+
}
|
|
331
|
+
function isTransientUsageStatus(status) {
|
|
332
|
+
return 408 === status || 429 === status || status >= 500 && status <= 599;
|
|
333
|
+
}
|
|
334
|
+
function isLastAttempt(attempt) {
|
|
335
|
+
return attempt >= USAGE_FETCH_ATTEMPTS;
|
|
336
|
+
}
|
|
337
|
+
function formatUsageAttemptError(url, attempt, message) {
|
|
338
|
+
const retryContext = USAGE_FETCH_ATTEMPTS > 1 ? ` attempt ${attempt}/${USAGE_FETCH_ATTEMPTS}` : "";
|
|
339
|
+
return `${url}${retryContext} -> ${message}`;
|
|
340
|
+
}
|
|
285
341
|
function shouldRetryWithTokenRefresh(message) {
|
|
286
342
|
const normalized = message.toLowerCase();
|
|
287
343
|
return normalized.includes("401") || normalized.includes("403") || normalized.includes("unauthorized") || normalized.includes("invalid_token") || normalized.includes("deactivated_workspace");
|
|
@@ -337,7 +393,9 @@ async function requestUsage(snapshot, options) {
|
|
|
337
393
|
const urls = await resolveUsageUrls(options.homeDir);
|
|
338
394
|
const now = (options.now ?? new Date()).toISOString();
|
|
339
395
|
const errors = [];
|
|
340
|
-
for (const url of urls){
|
|
396
|
+
for (const url of urls)for(let attempt = 1; attempt <= USAGE_FETCH_ATTEMPTS; attempt += 1){
|
|
397
|
+
const abortController = new AbortController();
|
|
398
|
+
const timeout = setTimeout(()=>abortController.abort(), USAGE_FETCH_TIMEOUT_MS);
|
|
341
399
|
let response;
|
|
342
400
|
try {
|
|
343
401
|
response = await fetchImpl(url, {
|
|
@@ -347,23 +405,35 @@ async function requestUsage(snapshot, options) {
|
|
|
347
405
|
"ChatGPT-Account-Id": extracted.accountId,
|
|
348
406
|
Accept: "application/json",
|
|
349
407
|
"User-Agent": USER_AGENT
|
|
350
|
-
}
|
|
408
|
+
},
|
|
409
|
+
signal: abortController.signal
|
|
351
410
|
});
|
|
352
411
|
} catch (error) {
|
|
353
|
-
|
|
354
|
-
|
|
412
|
+
clearTimeout(timeout);
|
|
413
|
+
const message = abortController.signal.aborted ? `timed out after ${USAGE_FETCH_TIMEOUT_MS}ms` : normalizeFetchError(error);
|
|
414
|
+
errors.push(formatUsageAttemptError(url, attempt, message));
|
|
415
|
+
if (!isLastAttempt(attempt)) {
|
|
416
|
+
await delay(USAGE_FETCH_RETRY_DELAY_MS * attempt);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
355
420
|
}
|
|
421
|
+
clearTimeout(timeout);
|
|
356
422
|
if (!response.ok) {
|
|
357
423
|
const body = await response.text();
|
|
358
|
-
errors.push(
|
|
359
|
-
|
|
424
|
+
errors.push(formatUsageAttemptError(url, attempt, `${response.status}: ${body.slice(0, 140).replace(/\s+/gu, " ").trim()}`));
|
|
425
|
+
if (isTransientUsageStatus(response.status) && !isLastAttempt(attempt)) {
|
|
426
|
+
await delay(USAGE_FETCH_RETRY_DELAY_MS * attempt);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
360
430
|
}
|
|
361
431
|
let payload;
|
|
362
432
|
try {
|
|
363
433
|
payload = await response.json();
|
|
364
434
|
} catch (error) {
|
|
365
|
-
errors.push(
|
|
366
|
-
|
|
435
|
+
errors.push(formatUsageAttemptError(url, attempt, `failed to parse JSON: ${normalizeFetchError(error)}`));
|
|
436
|
+
break;
|
|
367
437
|
}
|
|
368
438
|
return mapUsagePayload(payload, extracted.planType, now);
|
|
369
439
|
}
|
|
@@ -498,6 +568,13 @@ async function pathExists(path) {
|
|
|
498
568
|
async function readJsonFile(path) {
|
|
499
569
|
return readFile(path, "utf8");
|
|
500
570
|
}
|
|
571
|
+
function canAutoMigrateLegacyChatGPTMeta(meta, snapshot) {
|
|
572
|
+
if (!isSupportedChatGPTAuthMode(meta.auth_mode) || !isSupportedChatGPTAuthMode(snapshot.auth_mode)) return false;
|
|
573
|
+
if ("string" == typeof meta.user_id && "" !== meta.user_id.trim()) return false;
|
|
574
|
+
const snapshotUserId = getSnapshotUserId(snapshot);
|
|
575
|
+
if (!snapshotUserId) return false;
|
|
576
|
+
return meta.account_id === getSnapshotAccountId(snapshot);
|
|
577
|
+
}
|
|
501
578
|
async function detectRunningCodexProcesses() {
|
|
502
579
|
try {
|
|
503
580
|
const { stdout } = await account_store_execFile("ps", [
|
|
@@ -595,6 +672,8 @@ class AccountStore {
|
|
|
595
672
|
return {
|
|
596
673
|
name: account.name,
|
|
597
674
|
account_id: account.account_id,
|
|
675
|
+
user_id: account.user_id ?? null,
|
|
676
|
+
identity: account.identity,
|
|
598
677
|
plan_type: planType,
|
|
599
678
|
credits_balance: account.quota.credits_balance ?? null,
|
|
600
679
|
status: account.quota.status,
|
|
@@ -635,11 +714,21 @@ class AccountStore {
|
|
|
635
714
|
readJsonFile(metaPath),
|
|
636
715
|
readAuthSnapshotFile(authPath)
|
|
637
716
|
]);
|
|
638
|
-
|
|
717
|
+
let meta = parseSnapshotMeta(rawMeta);
|
|
639
718
|
if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
|
|
640
|
-
|
|
719
|
+
const snapshotIdentity = getSnapshotIdentity(snapshot);
|
|
720
|
+
if (getMetaIdentity(meta) !== snapshotIdentity) if (canAutoMigrateLegacyChatGPTMeta(meta, snapshot)) {
|
|
721
|
+
meta = {
|
|
722
|
+
...meta,
|
|
723
|
+
account_id: getSnapshotAccountId(snapshot),
|
|
724
|
+
user_id: getSnapshotUserId(snapshot)
|
|
725
|
+
};
|
|
726
|
+
await this.writeAccountMeta(name, meta);
|
|
727
|
+
} else throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
728
|
+
if (getMetaIdentity(meta) !== snapshotIdentity) throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
641
729
|
return {
|
|
642
730
|
...meta,
|
|
731
|
+
identity: getMetaIdentity(meta),
|
|
643
732
|
authPath,
|
|
644
733
|
metaPath,
|
|
645
734
|
configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
|
|
@@ -659,12 +748,12 @@ class AccountStore {
|
|
|
659
748
|
warnings.push(`Account "${entry.name}" is invalid: ${error.message}`);
|
|
660
749
|
}
|
|
661
750
|
const counts = new Map();
|
|
662
|
-
for (const account of accounts)counts.set(account.
|
|
751
|
+
for (const account of accounts)counts.set(account.identity, (counts.get(account.identity) ?? 0) + 1);
|
|
663
752
|
accounts.sort((left, right)=>left.name.localeCompare(right.name));
|
|
664
753
|
return {
|
|
665
754
|
accounts: accounts.map((account)=>({
|
|
666
755
|
...account,
|
|
667
|
-
duplicateAccountId: (counts.get(account.
|
|
756
|
+
duplicateAccountId: (counts.get(account.identity) ?? 0) > 1
|
|
668
757
|
})),
|
|
669
758
|
warnings
|
|
670
759
|
};
|
|
@@ -675,6 +764,8 @@ class AccountStore {
|
|
|
675
764
|
exists: false,
|
|
676
765
|
auth_mode: null,
|
|
677
766
|
account_id: null,
|
|
767
|
+
user_id: null,
|
|
768
|
+
identity: null,
|
|
678
769
|
matched_accounts: [],
|
|
679
770
|
managed: false,
|
|
680
771
|
duplicate_match: false,
|
|
@@ -682,11 +773,15 @@ class AccountStore {
|
|
|
682
773
|
};
|
|
683
774
|
const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
684
775
|
const currentIdentity = getSnapshotIdentity(snapshot);
|
|
685
|
-
const
|
|
776
|
+
const currentAccountId = getSnapshotAccountId(snapshot);
|
|
777
|
+
const currentUserId = getSnapshotUserId(snapshot) ?? null;
|
|
778
|
+
const matchedAccounts = accounts.filter((account)=>account.identity === currentIdentity).map((account)=>account.name);
|
|
686
779
|
return {
|
|
687
780
|
exists: true,
|
|
688
781
|
auth_mode: snapshot.auth_mode,
|
|
689
|
-
account_id:
|
|
782
|
+
account_id: currentAccountId,
|
|
783
|
+
user_id: currentUserId,
|
|
784
|
+
identity: currentIdentity,
|
|
690
785
|
matched_accounts: matchedAccounts,
|
|
691
786
|
managed: matchedAccounts.length > 0,
|
|
692
787
|
duplicate_match: matchedAccounts.length > 1,
|
|
@@ -709,7 +804,7 @@ class AccountStore {
|
|
|
709
804
|
const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
|
|
710
805
|
if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
|
|
711
806
|
const { accounts } = await this.listAccounts();
|
|
712
|
-
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.
|
|
807
|
+
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.identity === identity);
|
|
713
808
|
if (duplicateIdentityAccounts.length > 0) {
|
|
714
809
|
const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
|
|
715
810
|
throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
|
|
@@ -784,7 +879,7 @@ class AccountStore {
|
|
|
784
879
|
await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
|
|
785
880
|
}
|
|
786
881
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
787
|
-
if (getSnapshotIdentity(writtenSnapshot) !== account.
|
|
882
|
+
if (getSnapshotIdentity(writtenSnapshot) !== account.identity) throw new Error(`Switch verification failed for account "${name}".`);
|
|
788
883
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
789
884
|
meta.last_switched_at = new Date().toISOString();
|
|
790
885
|
meta.updated_at = meta.last_switched_at;
|
|
@@ -828,7 +923,8 @@ class AccountStore {
|
|
|
828
923
|
await this.syncCurrentAuthIfMatching(result.authSnapshot);
|
|
829
924
|
}
|
|
830
925
|
meta.auth_mode = result.authSnapshot.auth_mode;
|
|
831
|
-
meta.account_id =
|
|
926
|
+
meta.account_id = getSnapshotAccountId(result.authSnapshot);
|
|
927
|
+
meta.user_id = getSnapshotUserId(result.authSnapshot);
|
|
832
928
|
meta.updated_at = now.toISOString();
|
|
833
929
|
meta.quota = result.quota;
|
|
834
930
|
await this.writeAccountMeta(name, meta);
|
|
@@ -941,7 +1037,7 @@ class AccountStore {
|
|
|
941
1037
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
942
1038
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
943
1039
|
if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
|
|
944
|
-
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.
|
|
1040
|
+
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.identity} with another saved account.`);
|
|
945
1041
|
}
|
|
946
1042
|
let currentAuthPresent = false;
|
|
947
1043
|
if (await pathExists(this.paths.currentAuthPath)) {
|
|
@@ -1022,7 +1118,7 @@ function describeCurrentStatus(status) {
|
|
|
1022
1118
|
if (status.exists) {
|
|
1023
1119
|
lines.push("Current auth: present");
|
|
1024
1120
|
lines.push(`Auth mode: ${status.auth_mode}`);
|
|
1025
|
-
lines.push(`Identity: ${maskAccountId(status.
|
|
1121
|
+
lines.push(`Identity: ${maskAccountId(status.identity ?? "")}`);
|
|
1026
1122
|
if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
|
|
1027
1123
|
else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
|
|
1028
1124
|
else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
|
|
@@ -1075,6 +1171,7 @@ function toAutoSwitchCandidate(account) {
|
|
|
1075
1171
|
return {
|
|
1076
1172
|
name: account.name,
|
|
1077
1173
|
account_id: account.account_id,
|
|
1174
|
+
identity: account.identity,
|
|
1078
1175
|
plan_type: account.plan_type,
|
|
1079
1176
|
available: computeAvailability(account),
|
|
1080
1177
|
refresh_status: "ok",
|
|
@@ -1107,7 +1204,7 @@ function rankAutoSwitchCandidates(accounts) {
|
|
|
1107
1204
|
}
|
|
1108
1205
|
function describeAutoSwitchSelection(candidate, dryRun, backupPath, warnings) {
|
|
1109
1206
|
const lines = [
|
|
1110
|
-
dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.
|
|
1207
|
+
dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.identity)}).` : `Auto-switched to "${candidate.name}" (${maskAccountId(candidate.identity)}).`,
|
|
1111
1208
|
`Score: ${candidate.effective_score}`,
|
|
1112
1209
|
`5H remaining: ${candidate.remain_5h}%`,
|
|
1113
1210
|
`1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
|
|
@@ -1118,7 +1215,7 @@ function describeAutoSwitchSelection(candidate, dryRun, backupPath, warnings) {
|
|
|
1118
1215
|
}
|
|
1119
1216
|
function describeAutoSwitchNoop(candidate, warnings) {
|
|
1120
1217
|
const lines = [
|
|
1121
|
-
`Current account "${candidate.name}" (${maskAccountId(candidate.
|
|
1218
|
+
`Current account "${candidate.name}" (${maskAccountId(candidate.identity)}) is already the best available account.`,
|
|
1122
1219
|
`Score: ${candidate.effective_score}`,
|
|
1123
1220
|
`5H remaining: ${candidate.remain_5h}%`,
|
|
1124
1221
|
`1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
|
|
@@ -1130,7 +1227,7 @@ function describeQuotaAccounts(accounts, warnings) {
|
|
|
1130
1227
|
if (0 === accounts.length) return 0 === warnings.length ? "No saved accounts." : warnings.map((warning)=>`Warning: ${warning}`).join("\n");
|
|
1131
1228
|
const table = formatTable(accounts.map((account)=>({
|
|
1132
1229
|
name: account.name,
|
|
1133
|
-
account_id: maskAccountId(account.
|
|
1230
|
+
account_id: maskAccountId(account.identity),
|
|
1134
1231
|
plan_type: account.plan_type ?? "-",
|
|
1135
1232
|
available: computeAvailability(account) ?? "-",
|
|
1136
1233
|
five_hour: formatUsagePercent(account.five_hour),
|
|
@@ -1255,11 +1352,13 @@ async function runCli(argv, options = {}) {
|
|
|
1255
1352
|
account: {
|
|
1256
1353
|
name: account.name,
|
|
1257
1354
|
account_id: account.account_id,
|
|
1355
|
+
user_id: account.user_id ?? null,
|
|
1356
|
+
identity: account.identity,
|
|
1258
1357
|
auth_mode: account.auth_mode
|
|
1259
1358
|
}
|
|
1260
1359
|
};
|
|
1261
1360
|
if (json) writeJson(streams.stdout, payload);
|
|
1262
|
-
else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.
|
|
1361
|
+
else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.identity)}).\n`);
|
|
1263
1362
|
return 0;
|
|
1264
1363
|
}
|
|
1265
1364
|
case "update":
|
|
@@ -1281,6 +1380,8 @@ async function runCli(argv, options = {}) {
|
|
|
1281
1380
|
account: {
|
|
1282
1381
|
name: result.account.name,
|
|
1283
1382
|
account_id: result.account.account_id,
|
|
1383
|
+
user_id: result.account.user_id ?? null,
|
|
1384
|
+
identity: result.account.identity,
|
|
1284
1385
|
auth_mode: result.account.auth_mode
|
|
1285
1386
|
},
|
|
1286
1387
|
quota,
|
|
@@ -1288,7 +1389,7 @@ async function runCli(argv, options = {}) {
|
|
|
1288
1389
|
};
|
|
1289
1390
|
if (json) writeJson(streams.stdout, payload);
|
|
1290
1391
|
else {
|
|
1291
|
-
streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.
|
|
1392
|
+
streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
|
|
1292
1393
|
for (const warning of warnings)streams.stdout.write(`Warning: ${warning}\n`);
|
|
1293
1394
|
}
|
|
1294
1395
|
return 0;
|
|
@@ -1331,7 +1432,8 @@ async function runCli(argv, options = {}) {
|
|
|
1331
1432
|
reason: "already_current_best",
|
|
1332
1433
|
account: {
|
|
1333
1434
|
name: selected.name,
|
|
1334
|
-
account_id: selected.account_id
|
|
1435
|
+
account_id: selected.account_id,
|
|
1436
|
+
identity: selected.identity
|
|
1335
1437
|
},
|
|
1336
1438
|
selected,
|
|
1337
1439
|
candidates,
|
|
@@ -1351,6 +1453,8 @@ async function runCli(argv, options = {}) {
|
|
|
1351
1453
|
account: {
|
|
1352
1454
|
name: result.account.name,
|
|
1353
1455
|
account_id: result.account.account_id,
|
|
1456
|
+
user_id: result.account.user_id ?? null,
|
|
1457
|
+
identity: result.account.identity,
|
|
1354
1458
|
auth_mode: result.account.auth_mode
|
|
1355
1459
|
},
|
|
1356
1460
|
selected,
|
|
@@ -1380,6 +1484,8 @@ async function runCli(argv, options = {}) {
|
|
|
1380
1484
|
account: {
|
|
1381
1485
|
name: result.account.name,
|
|
1382
1486
|
account_id: result.account.account_id,
|
|
1487
|
+
user_id: result.account.user_id ?? null,
|
|
1488
|
+
identity: result.account.identity,
|
|
1383
1489
|
auth_mode: result.account.auth_mode
|
|
1384
1490
|
},
|
|
1385
1491
|
quota,
|
|
@@ -1388,7 +1494,7 @@ async function runCli(argv, options = {}) {
|
|
|
1388
1494
|
};
|
|
1389
1495
|
if (json) writeJson(streams.stdout, payload);
|
|
1390
1496
|
else {
|
|
1391
|
-
streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.
|
|
1497
|
+
streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
|
|
1392
1498
|
if (result.backup_path) streams.stdout.write(`Backup: ${result.backup_path}\n`);
|
|
1393
1499
|
for (const warning of result.warnings)streams.stdout.write(`Warning: ${warning}\n`);
|
|
1394
1500
|
}
|
|
@@ -1430,6 +1536,8 @@ async function runCli(argv, options = {}) {
|
|
|
1430
1536
|
account: {
|
|
1431
1537
|
name: account.name,
|
|
1432
1538
|
account_id: account.account_id,
|
|
1539
|
+
user_id: account.user_id ?? null,
|
|
1540
|
+
identity: account.identity,
|
|
1433
1541
|
auth_mode: account.auth_mode
|
|
1434
1542
|
}
|
|
1435
1543
|
});
|