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.cjs
CHANGED
|
@@ -43,7 +43,7 @@ var timezone_js_default = /*#__PURE__*/ __webpack_require__.n(timezone_js_namesp
|
|
|
43
43
|
const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
|
|
44
44
|
var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
|
|
45
45
|
var package_namespaceObject = {
|
|
46
|
-
rE: "0.0.
|
|
46
|
+
rE: "0.0.10"
|
|
47
47
|
};
|
|
48
48
|
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
49
49
|
const promises_namespaceObject = require("node:fs/promises");
|
|
@@ -78,10 +78,44 @@ function isSupportedChatGPTAuthMode(authMode) {
|
|
|
78
78
|
const normalized = normalizeAuthMode(authMode);
|
|
79
79
|
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
80
80
|
}
|
|
81
|
+
function extractAuthClaim(payload) {
|
|
82
|
+
const value = payload["https://api.openai.com/auth"];
|
|
83
|
+
return isRecord(value) ? value : void 0;
|
|
84
|
+
}
|
|
85
|
+
function extractStringClaim(payload, key) {
|
|
86
|
+
const value = payload[key];
|
|
87
|
+
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
88
|
+
}
|
|
89
|
+
function extractSnapshotJwtPayloads(snapshot) {
|
|
90
|
+
const tokens = snapshot.tokens ?? {};
|
|
91
|
+
const payloads = [];
|
|
92
|
+
for (const tokenName of [
|
|
93
|
+
"id_token",
|
|
94
|
+
"access_token"
|
|
95
|
+
]){
|
|
96
|
+
const token = tokens[tokenName];
|
|
97
|
+
if ("string" == typeof token && "" !== token.trim()) try {
|
|
98
|
+
payloads.push(decodeJwtPayload(token));
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
return payloads;
|
|
102
|
+
}
|
|
103
|
+
function getSnapshotUserId(snapshot) {
|
|
104
|
+
for (const payload of extractSnapshotJwtPayloads(snapshot)){
|
|
105
|
+
const authClaim = extractAuthClaim(payload);
|
|
106
|
+
const chatGPTUserId = authClaim?.chatgpt_user_id;
|
|
107
|
+
if ("string" == typeof chatGPTUserId && "" !== chatGPTUserId.trim()) return chatGPTUserId;
|
|
108
|
+
const userId = extractStringClaim(payload, "user_id");
|
|
109
|
+
if (userId) return userId;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
81
112
|
function fingerprintApiKey(apiKey) {
|
|
82
113
|
return (0, external_node_crypto_namespaceObject.createHash)("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
83
114
|
}
|
|
84
|
-
function
|
|
115
|
+
function composeIdentity(accountId, userId) {
|
|
116
|
+
return "string" == typeof userId && "" !== userId.trim() ? `${accountId}:${userId}` : accountId;
|
|
117
|
+
}
|
|
118
|
+
function getSnapshotAccountId(snapshot) {
|
|
85
119
|
if (isApiKeyAuthMode(snapshot.auth_mode)) {
|
|
86
120
|
const apiKey = snapshot.OPENAI_API_KEY;
|
|
87
121
|
if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
@@ -91,6 +125,12 @@ function getSnapshotIdentity(snapshot) {
|
|
|
91
125
|
if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
|
|
92
126
|
return accountId;
|
|
93
127
|
}
|
|
128
|
+
function getSnapshotIdentity(snapshot) {
|
|
129
|
+
return composeIdentity(getSnapshotAccountId(snapshot), isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0);
|
|
130
|
+
}
|
|
131
|
+
function getMetaIdentity(meta) {
|
|
132
|
+
return composeIdentity(meta.account_id, meta.user_id);
|
|
133
|
+
}
|
|
94
134
|
function defaultQuotaSnapshot() {
|
|
95
135
|
return {
|
|
96
136
|
status: "stale"
|
|
@@ -168,7 +208,8 @@ function createSnapshotMeta(name, snapshot, now, existingCreatedAt) {
|
|
|
168
208
|
return {
|
|
169
209
|
name,
|
|
170
210
|
auth_mode: snapshot.auth_mode,
|
|
171
|
-
account_id:
|
|
211
|
+
account_id: getSnapshotAccountId(snapshot),
|
|
212
|
+
user_id: isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0,
|
|
172
213
|
created_at: existingCreatedAt ?? timestamp,
|
|
173
214
|
updated_at: timestamp,
|
|
174
215
|
last_switched_at: null,
|
|
@@ -189,6 +230,7 @@ function parseSnapshotMeta(raw) {
|
|
|
189
230
|
name: asNonEmptyString(parsed.name, "name"),
|
|
190
231
|
auth_mode: asNonEmptyString(parsed.auth_mode, "auth_mode"),
|
|
191
232
|
account_id: asNonEmptyString(parsed.account_id, "account_id"),
|
|
233
|
+
user_id: asOptionalString(parsed.user_id, "user_id"),
|
|
192
234
|
created_at: asNonEmptyString(parsed.created_at, "created_at"),
|
|
193
235
|
updated_at: asNonEmptyString(parsed.updated_at, "updated_at"),
|
|
194
236
|
last_switched_at: lastSwitchedAt,
|
|
@@ -214,14 +256,17 @@ const external_node_child_process_namespaceObject = require("node:child_process"
|
|
|
214
256
|
const external_node_util_namespaceObject = require("node:util");
|
|
215
257
|
const DEFAULT_CHATGPT_BASE_URL = "https://chatgpt.com";
|
|
216
258
|
const USER_AGENT = "codexm/0.1";
|
|
259
|
+
const USAGE_FETCH_ATTEMPTS = 3;
|
|
260
|
+
const USAGE_FETCH_RETRY_DELAY_MS = 250;
|
|
261
|
+
const USAGE_FETCH_TIMEOUT_MS = 15000;
|
|
217
262
|
function quota_client_isRecord(value) {
|
|
218
263
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
219
264
|
}
|
|
220
|
-
function
|
|
265
|
+
function quota_client_extractAuthClaim(payload) {
|
|
221
266
|
const value = payload["https://api.openai.com/auth"];
|
|
222
267
|
return quota_client_isRecord(value) ? value : void 0;
|
|
223
268
|
}
|
|
224
|
-
function
|
|
269
|
+
function quota_client_extractStringClaim(payload, key) {
|
|
225
270
|
const value = payload[key];
|
|
226
271
|
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
227
272
|
}
|
|
@@ -234,7 +279,7 @@ function parsePlanType(snapshot) {
|
|
|
234
279
|
const token = tokens[tokenName];
|
|
235
280
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
236
281
|
const payload = decodeJwtPayload(token);
|
|
237
|
-
const authClaim =
|
|
282
|
+
const authClaim = quota_client_extractAuthClaim(payload);
|
|
238
283
|
const planType = authClaim?.chatgpt_plan_type;
|
|
239
284
|
if ("string" == typeof planType && "" !== planType.trim()) return planType;
|
|
240
285
|
} catch {}
|
|
@@ -258,7 +303,7 @@ function extractChatGPTAuth(snapshot) {
|
|
|
258
303
|
const token = tokens[tokenName];
|
|
259
304
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
260
305
|
const payload = decodeJwtPayload(token);
|
|
261
|
-
const authClaim =
|
|
306
|
+
const authClaim = quota_client_extractAuthClaim(payload);
|
|
262
307
|
if (!accountId) {
|
|
263
308
|
const maybeAccountId = authClaim?.chatgpt_account_id;
|
|
264
309
|
if ("string" == typeof maybeAccountId && "" !== maybeAccountId.trim()) accountId = maybeAccountId;
|
|
@@ -267,8 +312,8 @@ function extractChatGPTAuth(snapshot) {
|
|
|
267
312
|
const maybePlanType = authClaim?.chatgpt_plan_type;
|
|
268
313
|
if ("string" == typeof maybePlanType && "" !== maybePlanType.trim()) planType = maybePlanType;
|
|
269
314
|
}
|
|
270
|
-
issuer ??=
|
|
271
|
-
clientId ??=
|
|
315
|
+
issuer ??= quota_client_extractStringClaim(payload, "iss");
|
|
316
|
+
clientId ??= quota_client_extractStringClaim(payload, "client_id") ?? quota_client_extractStringClaim(payload, "azp") ?? ("string" == typeof payload.aud ? payload.aud : void 0);
|
|
272
317
|
} catch {}
|
|
273
318
|
}
|
|
274
319
|
if (!supported) return {
|
|
@@ -311,8 +356,6 @@ async function resolveUsageUrls(homeDir) {
|
|
|
311
356
|
const normalizedBaseUrl = baseUrl.replace(/\/+$/u, "");
|
|
312
357
|
const candidates = [
|
|
313
358
|
`${normalizedBaseUrl}/backend-api/wham/usage`,
|
|
314
|
-
`${normalizedBaseUrl}/wham/usage`,
|
|
315
|
-
`${normalizedBaseUrl}/api/codex/usage`,
|
|
316
359
|
"https://chatgpt.com/backend-api/wham/usage"
|
|
317
360
|
];
|
|
318
361
|
return [
|
|
@@ -322,6 +365,19 @@ async function resolveUsageUrls(homeDir) {
|
|
|
322
365
|
function normalizeFetchError(error) {
|
|
323
366
|
return error instanceof Error ? error.message : String(error);
|
|
324
367
|
}
|
|
368
|
+
async function delay(milliseconds) {
|
|
369
|
+
await new Promise((resolve)=>setTimeout(resolve, milliseconds));
|
|
370
|
+
}
|
|
371
|
+
function isTransientUsageStatus(status) {
|
|
372
|
+
return 408 === status || 429 === status || status >= 500 && status <= 599;
|
|
373
|
+
}
|
|
374
|
+
function isLastAttempt(attempt) {
|
|
375
|
+
return attempt >= USAGE_FETCH_ATTEMPTS;
|
|
376
|
+
}
|
|
377
|
+
function formatUsageAttemptError(url, attempt, message) {
|
|
378
|
+
const retryContext = USAGE_FETCH_ATTEMPTS > 1 ? ` attempt ${attempt}/${USAGE_FETCH_ATTEMPTS}` : "";
|
|
379
|
+
return `${url}${retryContext} -> ${message}`;
|
|
380
|
+
}
|
|
325
381
|
function shouldRetryWithTokenRefresh(message) {
|
|
326
382
|
const normalized = message.toLowerCase();
|
|
327
383
|
return normalized.includes("401") || normalized.includes("403") || normalized.includes("unauthorized") || normalized.includes("invalid_token") || normalized.includes("deactivated_workspace");
|
|
@@ -377,7 +433,9 @@ async function requestUsage(snapshot, options) {
|
|
|
377
433
|
const urls = await resolveUsageUrls(options.homeDir);
|
|
378
434
|
const now = (options.now ?? new Date()).toISOString();
|
|
379
435
|
const errors = [];
|
|
380
|
-
for (const url of urls){
|
|
436
|
+
for (const url of urls)for(let attempt = 1; attempt <= USAGE_FETCH_ATTEMPTS; attempt += 1){
|
|
437
|
+
const abortController = new AbortController();
|
|
438
|
+
const timeout = setTimeout(()=>abortController.abort(), USAGE_FETCH_TIMEOUT_MS);
|
|
381
439
|
let response;
|
|
382
440
|
try {
|
|
383
441
|
response = await fetchImpl(url, {
|
|
@@ -387,23 +445,35 @@ async function requestUsage(snapshot, options) {
|
|
|
387
445
|
"ChatGPT-Account-Id": extracted.accountId,
|
|
388
446
|
Accept: "application/json",
|
|
389
447
|
"User-Agent": USER_AGENT
|
|
390
|
-
}
|
|
448
|
+
},
|
|
449
|
+
signal: abortController.signal
|
|
391
450
|
});
|
|
392
451
|
} catch (error) {
|
|
393
|
-
|
|
394
|
-
|
|
452
|
+
clearTimeout(timeout);
|
|
453
|
+
const message = abortController.signal.aborted ? `timed out after ${USAGE_FETCH_TIMEOUT_MS}ms` : normalizeFetchError(error);
|
|
454
|
+
errors.push(formatUsageAttemptError(url, attempt, message));
|
|
455
|
+
if (!isLastAttempt(attempt)) {
|
|
456
|
+
await delay(USAGE_FETCH_RETRY_DELAY_MS * attempt);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
395
460
|
}
|
|
461
|
+
clearTimeout(timeout);
|
|
396
462
|
if (!response.ok) {
|
|
397
463
|
const body = await response.text();
|
|
398
|
-
errors.push(
|
|
399
|
-
|
|
464
|
+
errors.push(formatUsageAttemptError(url, attempt, `${response.status}: ${body.slice(0, 140).replace(/\s+/gu, " ").trim()}`));
|
|
465
|
+
if (isTransientUsageStatus(response.status) && !isLastAttempt(attempt)) {
|
|
466
|
+
await delay(USAGE_FETCH_RETRY_DELAY_MS * attempt);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
400
470
|
}
|
|
401
471
|
let payload;
|
|
402
472
|
try {
|
|
403
473
|
payload = await response.json();
|
|
404
474
|
} catch (error) {
|
|
405
|
-
errors.push(
|
|
406
|
-
|
|
475
|
+
errors.push(formatUsageAttemptError(url, attempt, `failed to parse JSON: ${normalizeFetchError(error)}`));
|
|
476
|
+
break;
|
|
407
477
|
}
|
|
408
478
|
return mapUsagePayload(payload, extracted.planType, now);
|
|
409
479
|
}
|
|
@@ -538,6 +608,13 @@ async function pathExists(path) {
|
|
|
538
608
|
async function readJsonFile(path) {
|
|
539
609
|
return (0, promises_namespaceObject.readFile)(path, "utf8");
|
|
540
610
|
}
|
|
611
|
+
function canAutoMigrateLegacyChatGPTMeta(meta, snapshot) {
|
|
612
|
+
if (!isSupportedChatGPTAuthMode(meta.auth_mode) || !isSupportedChatGPTAuthMode(snapshot.auth_mode)) return false;
|
|
613
|
+
if ("string" == typeof meta.user_id && "" !== meta.user_id.trim()) return false;
|
|
614
|
+
const snapshotUserId = getSnapshotUserId(snapshot);
|
|
615
|
+
if (!snapshotUserId) return false;
|
|
616
|
+
return meta.account_id === getSnapshotAccountId(snapshot);
|
|
617
|
+
}
|
|
541
618
|
async function detectRunningCodexProcesses() {
|
|
542
619
|
try {
|
|
543
620
|
const { stdout } = await execFile("ps", [
|
|
@@ -635,6 +712,8 @@ class AccountStore {
|
|
|
635
712
|
return {
|
|
636
713
|
name: account.name,
|
|
637
714
|
account_id: account.account_id,
|
|
715
|
+
user_id: account.user_id ?? null,
|
|
716
|
+
identity: account.identity,
|
|
638
717
|
plan_type: planType,
|
|
639
718
|
credits_balance: account.quota.credits_balance ?? null,
|
|
640
719
|
status: account.quota.status,
|
|
@@ -675,11 +754,21 @@ class AccountStore {
|
|
|
675
754
|
readJsonFile(metaPath),
|
|
676
755
|
readAuthSnapshotFile(authPath)
|
|
677
756
|
]);
|
|
678
|
-
|
|
757
|
+
let meta = parseSnapshotMeta(rawMeta);
|
|
679
758
|
if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
|
|
680
|
-
|
|
759
|
+
const snapshotIdentity = getSnapshotIdentity(snapshot);
|
|
760
|
+
if (getMetaIdentity(meta) !== snapshotIdentity) if (canAutoMigrateLegacyChatGPTMeta(meta, snapshot)) {
|
|
761
|
+
meta = {
|
|
762
|
+
...meta,
|
|
763
|
+
account_id: getSnapshotAccountId(snapshot),
|
|
764
|
+
user_id: getSnapshotUserId(snapshot)
|
|
765
|
+
};
|
|
766
|
+
await this.writeAccountMeta(name, meta);
|
|
767
|
+
} else throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
768
|
+
if (getMetaIdentity(meta) !== snapshotIdentity) throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
681
769
|
return {
|
|
682
770
|
...meta,
|
|
771
|
+
identity: getMetaIdentity(meta),
|
|
683
772
|
authPath,
|
|
684
773
|
metaPath,
|
|
685
774
|
configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
|
|
@@ -699,12 +788,12 @@ class AccountStore {
|
|
|
699
788
|
warnings.push(`Account "${entry.name}" is invalid: ${error.message}`);
|
|
700
789
|
}
|
|
701
790
|
const counts = new Map();
|
|
702
|
-
for (const account of accounts)counts.set(account.
|
|
791
|
+
for (const account of accounts)counts.set(account.identity, (counts.get(account.identity) ?? 0) + 1);
|
|
703
792
|
accounts.sort((left, right)=>left.name.localeCompare(right.name));
|
|
704
793
|
return {
|
|
705
794
|
accounts: accounts.map((account)=>({
|
|
706
795
|
...account,
|
|
707
|
-
duplicateAccountId: (counts.get(account.
|
|
796
|
+
duplicateAccountId: (counts.get(account.identity) ?? 0) > 1
|
|
708
797
|
})),
|
|
709
798
|
warnings
|
|
710
799
|
};
|
|
@@ -715,6 +804,8 @@ class AccountStore {
|
|
|
715
804
|
exists: false,
|
|
716
805
|
auth_mode: null,
|
|
717
806
|
account_id: null,
|
|
807
|
+
user_id: null,
|
|
808
|
+
identity: null,
|
|
718
809
|
matched_accounts: [],
|
|
719
810
|
managed: false,
|
|
720
811
|
duplicate_match: false,
|
|
@@ -722,11 +813,15 @@ class AccountStore {
|
|
|
722
813
|
};
|
|
723
814
|
const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
724
815
|
const currentIdentity = getSnapshotIdentity(snapshot);
|
|
725
|
-
const
|
|
816
|
+
const currentAccountId = getSnapshotAccountId(snapshot);
|
|
817
|
+
const currentUserId = getSnapshotUserId(snapshot) ?? null;
|
|
818
|
+
const matchedAccounts = accounts.filter((account)=>account.identity === currentIdentity).map((account)=>account.name);
|
|
726
819
|
return {
|
|
727
820
|
exists: true,
|
|
728
821
|
auth_mode: snapshot.auth_mode,
|
|
729
|
-
account_id:
|
|
822
|
+
account_id: currentAccountId,
|
|
823
|
+
user_id: currentUserId,
|
|
824
|
+
identity: currentIdentity,
|
|
730
825
|
matched_accounts: matchedAccounts,
|
|
731
826
|
managed: matchedAccounts.length > 0,
|
|
732
827
|
duplicate_match: matchedAccounts.length > 1,
|
|
@@ -749,7 +844,7 @@ class AccountStore {
|
|
|
749
844
|
const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
|
|
750
845
|
if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
|
|
751
846
|
const { accounts } = await this.listAccounts();
|
|
752
|
-
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.
|
|
847
|
+
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.identity === identity);
|
|
753
848
|
if (duplicateIdentityAccounts.length > 0) {
|
|
754
849
|
const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
|
|
755
850
|
throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
|
|
@@ -824,7 +919,7 @@ class AccountStore {
|
|
|
824
919
|
await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
|
|
825
920
|
}
|
|
826
921
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
827
|
-
if (getSnapshotIdentity(writtenSnapshot) !== account.
|
|
922
|
+
if (getSnapshotIdentity(writtenSnapshot) !== account.identity) throw new Error(`Switch verification failed for account "${name}".`);
|
|
828
923
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
829
924
|
meta.last_switched_at = new Date().toISOString();
|
|
830
925
|
meta.updated_at = meta.last_switched_at;
|
|
@@ -868,7 +963,8 @@ class AccountStore {
|
|
|
868
963
|
await this.syncCurrentAuthIfMatching(result.authSnapshot);
|
|
869
964
|
}
|
|
870
965
|
meta.auth_mode = result.authSnapshot.auth_mode;
|
|
871
|
-
meta.account_id =
|
|
966
|
+
meta.account_id = getSnapshotAccountId(result.authSnapshot);
|
|
967
|
+
meta.user_id = getSnapshotUserId(result.authSnapshot);
|
|
872
968
|
meta.updated_at = now.toISOString();
|
|
873
969
|
meta.quota = result.quota;
|
|
874
970
|
await this.writeAccountMeta(name, meta);
|
|
@@ -981,7 +1077,7 @@ class AccountStore {
|
|
|
981
1077
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
982
1078
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
983
1079
|
if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
|
|
984
|
-
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.
|
|
1080
|
+
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.identity} with another saved account.`);
|
|
985
1081
|
}
|
|
986
1082
|
let currentAuthPresent = false;
|
|
987
1083
|
if (await pathExists(this.paths.currentAuthPath)) {
|
|
@@ -1062,7 +1158,7 @@ function describeCurrentStatus(status) {
|
|
|
1062
1158
|
if (status.exists) {
|
|
1063
1159
|
lines.push("Current auth: present");
|
|
1064
1160
|
lines.push(`Auth mode: ${status.auth_mode}`);
|
|
1065
|
-
lines.push(`Identity: ${maskAccountId(status.
|
|
1161
|
+
lines.push(`Identity: ${maskAccountId(status.identity ?? "")}`);
|
|
1066
1162
|
if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
|
|
1067
1163
|
else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
|
|
1068
1164
|
else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
|
|
@@ -1115,6 +1211,7 @@ function toAutoSwitchCandidate(account) {
|
|
|
1115
1211
|
return {
|
|
1116
1212
|
name: account.name,
|
|
1117
1213
|
account_id: account.account_id,
|
|
1214
|
+
identity: account.identity,
|
|
1118
1215
|
plan_type: account.plan_type,
|
|
1119
1216
|
available: computeAvailability(account),
|
|
1120
1217
|
refresh_status: "ok",
|
|
@@ -1147,7 +1244,7 @@ function rankAutoSwitchCandidates(accounts) {
|
|
|
1147
1244
|
}
|
|
1148
1245
|
function describeAutoSwitchSelection(candidate, dryRun, backupPath, warnings) {
|
|
1149
1246
|
const lines = [
|
|
1150
|
-
dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.
|
|
1247
|
+
dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.identity)}).` : `Auto-switched to "${candidate.name}" (${maskAccountId(candidate.identity)}).`,
|
|
1151
1248
|
`Score: ${candidate.effective_score}`,
|
|
1152
1249
|
`5H remaining: ${candidate.remain_5h}%`,
|
|
1153
1250
|
`1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
|
|
@@ -1158,7 +1255,7 @@ function describeAutoSwitchSelection(candidate, dryRun, backupPath, warnings) {
|
|
|
1158
1255
|
}
|
|
1159
1256
|
function describeAutoSwitchNoop(candidate, warnings) {
|
|
1160
1257
|
const lines = [
|
|
1161
|
-
`Current account "${candidate.name}" (${maskAccountId(candidate.
|
|
1258
|
+
`Current account "${candidate.name}" (${maskAccountId(candidate.identity)}) is already the best available account.`,
|
|
1162
1259
|
`Score: ${candidate.effective_score}`,
|
|
1163
1260
|
`5H remaining: ${candidate.remain_5h}%`,
|
|
1164
1261
|
`1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
|
|
@@ -1170,7 +1267,7 @@ function describeQuotaAccounts(accounts, warnings) {
|
|
|
1170
1267
|
if (0 === accounts.length) return 0 === warnings.length ? "No saved accounts." : warnings.map((warning)=>`Warning: ${warning}`).join("\n");
|
|
1171
1268
|
const table = formatTable(accounts.map((account)=>({
|
|
1172
1269
|
name: account.name,
|
|
1173
|
-
account_id: maskAccountId(account.
|
|
1270
|
+
account_id: maskAccountId(account.identity),
|
|
1174
1271
|
plan_type: account.plan_type ?? "-",
|
|
1175
1272
|
available: computeAvailability(account) ?? "-",
|
|
1176
1273
|
five_hour: formatUsagePercent(account.five_hour),
|
|
@@ -1295,11 +1392,13 @@ async function runCli(argv, options = {}) {
|
|
|
1295
1392
|
account: {
|
|
1296
1393
|
name: account.name,
|
|
1297
1394
|
account_id: account.account_id,
|
|
1395
|
+
user_id: account.user_id ?? null,
|
|
1396
|
+
identity: account.identity,
|
|
1298
1397
|
auth_mode: account.auth_mode
|
|
1299
1398
|
}
|
|
1300
1399
|
};
|
|
1301
1400
|
if (json) writeJson(streams.stdout, payload);
|
|
1302
|
-
else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.
|
|
1401
|
+
else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.identity)}).\n`);
|
|
1303
1402
|
return 0;
|
|
1304
1403
|
}
|
|
1305
1404
|
case "update":
|
|
@@ -1321,6 +1420,8 @@ async function runCli(argv, options = {}) {
|
|
|
1321
1420
|
account: {
|
|
1322
1421
|
name: result.account.name,
|
|
1323
1422
|
account_id: result.account.account_id,
|
|
1423
|
+
user_id: result.account.user_id ?? null,
|
|
1424
|
+
identity: result.account.identity,
|
|
1324
1425
|
auth_mode: result.account.auth_mode
|
|
1325
1426
|
},
|
|
1326
1427
|
quota,
|
|
@@ -1328,7 +1429,7 @@ async function runCli(argv, options = {}) {
|
|
|
1328
1429
|
};
|
|
1329
1430
|
if (json) writeJson(streams.stdout, payload);
|
|
1330
1431
|
else {
|
|
1331
|
-
streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.
|
|
1432
|
+
streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
|
|
1332
1433
|
for (const warning of warnings)streams.stdout.write(`Warning: ${warning}\n`);
|
|
1333
1434
|
}
|
|
1334
1435
|
return 0;
|
|
@@ -1371,7 +1472,8 @@ async function runCli(argv, options = {}) {
|
|
|
1371
1472
|
reason: "already_current_best",
|
|
1372
1473
|
account: {
|
|
1373
1474
|
name: selected.name,
|
|
1374
|
-
account_id: selected.account_id
|
|
1475
|
+
account_id: selected.account_id,
|
|
1476
|
+
identity: selected.identity
|
|
1375
1477
|
},
|
|
1376
1478
|
selected,
|
|
1377
1479
|
candidates,
|
|
@@ -1391,6 +1493,8 @@ async function runCli(argv, options = {}) {
|
|
|
1391
1493
|
account: {
|
|
1392
1494
|
name: result.account.name,
|
|
1393
1495
|
account_id: result.account.account_id,
|
|
1496
|
+
user_id: result.account.user_id ?? null,
|
|
1497
|
+
identity: result.account.identity,
|
|
1394
1498
|
auth_mode: result.account.auth_mode
|
|
1395
1499
|
},
|
|
1396
1500
|
selected,
|
|
@@ -1420,6 +1524,8 @@ async function runCli(argv, options = {}) {
|
|
|
1420
1524
|
account: {
|
|
1421
1525
|
name: result.account.name,
|
|
1422
1526
|
account_id: result.account.account_id,
|
|
1527
|
+
user_id: result.account.user_id ?? null,
|
|
1528
|
+
identity: result.account.identity,
|
|
1423
1529
|
auth_mode: result.account.auth_mode
|
|
1424
1530
|
},
|
|
1425
1531
|
quota,
|
|
@@ -1428,7 +1534,7 @@ async function runCli(argv, options = {}) {
|
|
|
1428
1534
|
};
|
|
1429
1535
|
if (json) writeJson(streams.stdout, payload);
|
|
1430
1536
|
else {
|
|
1431
|
-
streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.
|
|
1537
|
+
streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
|
|
1432
1538
|
if (result.backup_path) streams.stdout.write(`Backup: ${result.backup_path}\n`);
|
|
1433
1539
|
for (const warning of result.warnings)streams.stdout.write(`Warning: ${warning}\n`);
|
|
1434
1540
|
}
|
|
@@ -1470,6 +1576,8 @@ async function runCli(argv, options = {}) {
|
|
|
1470
1576
|
account: {
|
|
1471
1577
|
name: account.name,
|
|
1472
1578
|
account_id: account.account_id,
|
|
1579
|
+
user_id: account.user_id ?? null,
|
|
1580
|
+
identity: account.identity,
|
|
1473
1581
|
auth_mode: account.auth_mode
|
|
1474
1582
|
}
|
|
1475
1583
|
});
|