codex-team 0.0.5 → 0.0.6
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/dist/cli.cjs +64 -28
- package/dist/main.cjs +64 -28
- package/dist/main.js +64 -28
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -13,8 +13,9 @@ var __webpack_modules__ = {
|
|
|
13
13
|
const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
|
|
14
14
|
var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
|
|
15
15
|
var package_namespaceObject = {
|
|
16
|
-
rE: "0.0.
|
|
16
|
+
rE: "0.0.6"
|
|
17
17
|
};
|
|
18
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
18
19
|
const promises_namespaceObject = require("node:fs/promises");
|
|
19
20
|
function isRecord(value) {
|
|
20
21
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -37,6 +38,29 @@ var __webpack_modules__ = {
|
|
|
37
38
|
if ("number" != typeof value || Number.isNaN(value)) throw new Error(`Field "${fieldName}" must be a number.`);
|
|
38
39
|
return value;
|
|
39
40
|
}
|
|
41
|
+
function normalizeAuthMode(authMode) {
|
|
42
|
+
return authMode.trim().toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
function isApiKeyAuthMode(authMode) {
|
|
45
|
+
return "apikey" === normalizeAuthMode(authMode);
|
|
46
|
+
}
|
|
47
|
+
function isSupportedChatGPTAuthMode(authMode) {
|
|
48
|
+
const normalized = normalizeAuthMode(authMode);
|
|
49
|
+
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
50
|
+
}
|
|
51
|
+
function fingerprintApiKey(apiKey) {
|
|
52
|
+
return (0, external_node_crypto_namespaceObject.createHash)("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
53
|
+
}
|
|
54
|
+
function getSnapshotIdentity(snapshot) {
|
|
55
|
+
if (isApiKeyAuthMode(snapshot.auth_mode)) {
|
|
56
|
+
const apiKey = snapshot.OPENAI_API_KEY;
|
|
57
|
+
if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
58
|
+
return `key_${fingerprintApiKey(apiKey)}`;
|
|
59
|
+
}
|
|
60
|
+
const accountId = snapshot.tokens?.account_id;
|
|
61
|
+
if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
|
|
62
|
+
return accountId;
|
|
63
|
+
}
|
|
40
64
|
function defaultQuotaSnapshot() {
|
|
41
65
|
return {
|
|
42
66
|
status: "stale"
|
|
@@ -81,15 +105,28 @@ var __webpack_modules__ = {
|
|
|
81
105
|
}
|
|
82
106
|
if (!isRecord(parsed)) throw new Error("Auth snapshot must be a JSON object.");
|
|
83
107
|
const authMode = asNonEmptyString(parsed.auth_mode, "auth_mode");
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
const tokens = parsed.tokens;
|
|
109
|
+
if (null != tokens && !isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
110
|
+
const apiKeyMode = isApiKeyAuthMode(authMode);
|
|
111
|
+
const normalizedApiKey = null === parsed.OPENAI_API_KEY || void 0 === parsed.OPENAI_API_KEY ? parsed.OPENAI_API_KEY : asNonEmptyString(parsed.OPENAI_API_KEY, "OPENAI_API_KEY");
|
|
112
|
+
if (apiKeyMode) {
|
|
113
|
+
if ("string" != typeof normalizedApiKey || "" === normalizedApiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
114
|
+
} else {
|
|
115
|
+
if (!isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
116
|
+
asNonEmptyString(tokens.account_id, "tokens.account_id");
|
|
117
|
+
}
|
|
86
118
|
return {
|
|
87
119
|
...parsed,
|
|
88
120
|
auth_mode: authMode,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
OPENAI_API_KEY: normalizedApiKey,
|
|
122
|
+
...isRecord(tokens) ? {
|
|
123
|
+
tokens: {
|
|
124
|
+
...tokens,
|
|
125
|
+
..."string" == typeof tokens.account_id && "" !== tokens.account_id.trim() ? {
|
|
126
|
+
account_id: tokens.account_id
|
|
127
|
+
} : {}
|
|
128
|
+
}
|
|
129
|
+
} : {}
|
|
93
130
|
};
|
|
94
131
|
}
|
|
95
132
|
async function readAuthSnapshotFile(filePath) {
|
|
@@ -101,7 +138,7 @@ var __webpack_modules__ = {
|
|
|
101
138
|
return {
|
|
102
139
|
name,
|
|
103
140
|
auth_mode: snapshot.auth_mode,
|
|
104
|
-
account_id: snapshot
|
|
141
|
+
account_id: getSnapshotIdentity(snapshot),
|
|
105
142
|
created_at: existingCreatedAt ?? timestamp,
|
|
106
143
|
updated_at: timestamp,
|
|
107
144
|
last_switched_at: null,
|
|
@@ -158,16 +195,13 @@ var __webpack_modules__ = {
|
|
|
158
195
|
const value = payload[key];
|
|
159
196
|
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
160
197
|
}
|
|
161
|
-
function isSupportedChatGPTMode(authMode) {
|
|
162
|
-
const normalized = authMode.trim().toLowerCase();
|
|
163
|
-
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
164
|
-
}
|
|
165
198
|
function parsePlanType(snapshot) {
|
|
199
|
+
const tokens = snapshot.tokens ?? {};
|
|
166
200
|
for (const tokenName of [
|
|
167
201
|
"id_token",
|
|
168
202
|
"access_token"
|
|
169
203
|
]){
|
|
170
|
-
const token =
|
|
204
|
+
const token = tokens[tokenName];
|
|
171
205
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
172
206
|
const payload = decodeJwtPayload(token);
|
|
173
207
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -178,10 +212,11 @@ var __webpack_modules__ = {
|
|
|
178
212
|
}
|
|
179
213
|
function extractChatGPTAuth(snapshot) {
|
|
180
214
|
const authMode = snapshot.auth_mode ?? "";
|
|
181
|
-
const supported =
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
215
|
+
const supported = isSupportedChatGPTAuthMode(authMode);
|
|
216
|
+
const tokens = snapshot.tokens ?? {};
|
|
217
|
+
const accessTokenValue = tokens.access_token;
|
|
218
|
+
const refreshTokenValue = tokens.refresh_token;
|
|
219
|
+
const directAccountId = tokens.account_id;
|
|
185
220
|
let accountId = "string" == typeof directAccountId && "" !== directAccountId.trim() ? directAccountId : void 0;
|
|
186
221
|
let planType;
|
|
187
222
|
let issuer;
|
|
@@ -190,7 +225,7 @@ var __webpack_modules__ = {
|
|
|
190
225
|
"id_token",
|
|
191
226
|
"access_token"
|
|
192
227
|
]){
|
|
193
|
-
const token =
|
|
228
|
+
const token = tokens[tokenName];
|
|
194
229
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
195
230
|
const payload = decodeJwtPayload(token);
|
|
196
231
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -372,7 +407,7 @@ var __webpack_modules__ = {
|
|
|
372
407
|
...snapshot,
|
|
373
408
|
last_refresh: (options.now ?? new Date()).toISOString(),
|
|
374
409
|
tokens: {
|
|
375
|
-
...snapshot.tokens,
|
|
410
|
+
...snapshot.tokens ?? {},
|
|
376
411
|
access_token: payload.access_token,
|
|
377
412
|
id_token: payload.id_token,
|
|
378
413
|
refresh_token: payload.refresh_token ?? extracted.refreshToken,
|
|
@@ -523,7 +558,7 @@ var __webpack_modules__ = {
|
|
|
523
558
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
524
559
|
try {
|
|
525
560
|
const currentSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
526
|
-
if (currentSnapshot
|
|
561
|
+
if (getSnapshotIdentity(currentSnapshot) !== getSnapshotIdentity(snapshot)) return;
|
|
527
562
|
await atomicWriteFile(this.paths.currentAuthPath, stringifyJson(snapshot));
|
|
528
563
|
} catch {}
|
|
529
564
|
}
|
|
@@ -579,7 +614,7 @@ var __webpack_modules__ = {
|
|
|
579
614
|
]);
|
|
580
615
|
const meta = parseSnapshotMeta(rawMeta);
|
|
581
616
|
if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
|
|
582
|
-
if (meta.account_id !== snapshot
|
|
617
|
+
if (meta.account_id !== getSnapshotIdentity(snapshot)) throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
583
618
|
return {
|
|
584
619
|
...meta,
|
|
585
620
|
authPath,
|
|
@@ -622,11 +657,12 @@ var __webpack_modules__ = {
|
|
|
622
657
|
warnings
|
|
623
658
|
};
|
|
624
659
|
const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
625
|
-
const
|
|
660
|
+
const currentIdentity = getSnapshotIdentity(snapshot);
|
|
661
|
+
const matchedAccounts = accounts.filter((account)=>account.account_id === currentIdentity).map((account)=>account.name);
|
|
626
662
|
return {
|
|
627
663
|
exists: true,
|
|
628
664
|
auth_mode: snapshot.auth_mode,
|
|
629
|
-
account_id:
|
|
665
|
+
account_id: currentIdentity,
|
|
630
666
|
matched_accounts: matchedAccounts,
|
|
631
667
|
managed: matchedAccounts.length > 0,
|
|
632
668
|
duplicate_match: matchedAccounts.length > 1,
|
|
@@ -689,7 +725,7 @@ var __webpack_modules__ = {
|
|
|
689
725
|
const rawAuth = await readJsonFile(account.authPath);
|
|
690
726
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
691
727
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
692
|
-
if (writtenSnapshot
|
|
728
|
+
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
693
729
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
694
730
|
meta.last_switched_at = new Date().toISOString();
|
|
695
731
|
meta.updated_at = meta.last_switched_at;
|
|
@@ -733,7 +769,7 @@ var __webpack_modules__ = {
|
|
|
733
769
|
await this.syncCurrentAuthIfMatching(result.authSnapshot);
|
|
734
770
|
}
|
|
735
771
|
meta.auth_mode = result.authSnapshot.auth_mode;
|
|
736
|
-
meta.account_id = result.authSnapshot
|
|
772
|
+
meta.account_id = getSnapshotIdentity(result.authSnapshot);
|
|
737
773
|
meta.updated_at = now.toISOString();
|
|
738
774
|
meta.quota = result.quota;
|
|
739
775
|
await this.writeAccountMeta(name, meta);
|
|
@@ -845,7 +881,7 @@ var __webpack_modules__ = {
|
|
|
845
881
|
const metaStat = await (0, promises_namespaceObject.stat)(account.metaPath);
|
|
846
882
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
847
883
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
848
|
-
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares
|
|
884
|
+
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
849
885
|
}
|
|
850
886
|
let currentAuthPresent = false;
|
|
851
887
|
if (await pathExists(this.paths.currentAuthPath)) {
|
|
@@ -928,7 +964,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
|
928
964
|
if (status.exists) {
|
|
929
965
|
lines.push("Current auth: present");
|
|
930
966
|
lines.push(`Auth mode: ${status.auth_mode}`);
|
|
931
|
-
lines.push(`
|
|
967
|
+
lines.push(`Identity: ${maskAccountId(status.account_id ?? "")}`);
|
|
932
968
|
if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
|
|
933
969
|
else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
|
|
934
970
|
else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
|
|
@@ -1061,7 +1097,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
|
1061
1097
|
},
|
|
1062
1098
|
{
|
|
1063
1099
|
key: "account_id",
|
|
1064
|
-
label: "
|
|
1100
|
+
label: "IDENTITY"
|
|
1065
1101
|
},
|
|
1066
1102
|
{
|
|
1067
1103
|
key: "plan_type",
|
package/dist/main.cjs
CHANGED
|
@@ -43,8 +43,9 @@ 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.6"
|
|
47
47
|
};
|
|
48
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
48
49
|
const promises_namespaceObject = require("node:fs/promises");
|
|
49
50
|
function isRecord(value) {
|
|
50
51
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -67,6 +68,29 @@ function asOptionalNumber(value, fieldName) {
|
|
|
67
68
|
if ("number" != typeof value || Number.isNaN(value)) throw new Error(`Field "${fieldName}" must be a number.`);
|
|
68
69
|
return value;
|
|
69
70
|
}
|
|
71
|
+
function normalizeAuthMode(authMode) {
|
|
72
|
+
return authMode.trim().toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
function isApiKeyAuthMode(authMode) {
|
|
75
|
+
return "apikey" === normalizeAuthMode(authMode);
|
|
76
|
+
}
|
|
77
|
+
function isSupportedChatGPTAuthMode(authMode) {
|
|
78
|
+
const normalized = normalizeAuthMode(authMode);
|
|
79
|
+
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
80
|
+
}
|
|
81
|
+
function fingerprintApiKey(apiKey) {
|
|
82
|
+
return (0, external_node_crypto_namespaceObject.createHash)("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
83
|
+
}
|
|
84
|
+
function getSnapshotIdentity(snapshot) {
|
|
85
|
+
if (isApiKeyAuthMode(snapshot.auth_mode)) {
|
|
86
|
+
const apiKey = snapshot.OPENAI_API_KEY;
|
|
87
|
+
if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
88
|
+
return `key_${fingerprintApiKey(apiKey)}`;
|
|
89
|
+
}
|
|
90
|
+
const accountId = snapshot.tokens?.account_id;
|
|
91
|
+
if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
|
|
92
|
+
return accountId;
|
|
93
|
+
}
|
|
70
94
|
function defaultQuotaSnapshot() {
|
|
71
95
|
return {
|
|
72
96
|
status: "stale"
|
|
@@ -111,15 +135,28 @@ function parseAuthSnapshot(raw) {
|
|
|
111
135
|
}
|
|
112
136
|
if (!isRecord(parsed)) throw new Error("Auth snapshot must be a JSON object.");
|
|
113
137
|
const authMode = asNonEmptyString(parsed.auth_mode, "auth_mode");
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
const tokens = parsed.tokens;
|
|
139
|
+
if (null != tokens && !isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
140
|
+
const apiKeyMode = isApiKeyAuthMode(authMode);
|
|
141
|
+
const normalizedApiKey = null === parsed.OPENAI_API_KEY || void 0 === parsed.OPENAI_API_KEY ? parsed.OPENAI_API_KEY : asNonEmptyString(parsed.OPENAI_API_KEY, "OPENAI_API_KEY");
|
|
142
|
+
if (apiKeyMode) {
|
|
143
|
+
if ("string" != typeof normalizedApiKey || "" === normalizedApiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
144
|
+
} else {
|
|
145
|
+
if (!isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
146
|
+
asNonEmptyString(tokens.account_id, "tokens.account_id");
|
|
147
|
+
}
|
|
116
148
|
return {
|
|
117
149
|
...parsed,
|
|
118
150
|
auth_mode: authMode,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
151
|
+
OPENAI_API_KEY: normalizedApiKey,
|
|
152
|
+
...isRecord(tokens) ? {
|
|
153
|
+
tokens: {
|
|
154
|
+
...tokens,
|
|
155
|
+
..."string" == typeof tokens.account_id && "" !== tokens.account_id.trim() ? {
|
|
156
|
+
account_id: tokens.account_id
|
|
157
|
+
} : {}
|
|
158
|
+
}
|
|
159
|
+
} : {}
|
|
123
160
|
};
|
|
124
161
|
}
|
|
125
162
|
async function readAuthSnapshotFile(filePath) {
|
|
@@ -131,7 +168,7 @@ function createSnapshotMeta(name, snapshot, now, existingCreatedAt) {
|
|
|
131
168
|
return {
|
|
132
169
|
name,
|
|
133
170
|
auth_mode: snapshot.auth_mode,
|
|
134
|
-
account_id: snapshot
|
|
171
|
+
account_id: getSnapshotIdentity(snapshot),
|
|
135
172
|
created_at: existingCreatedAt ?? timestamp,
|
|
136
173
|
updated_at: timestamp,
|
|
137
174
|
last_switched_at: null,
|
|
@@ -188,16 +225,13 @@ function extractStringClaim(payload, key) {
|
|
|
188
225
|
const value = payload[key];
|
|
189
226
|
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
190
227
|
}
|
|
191
|
-
function isSupportedChatGPTMode(authMode) {
|
|
192
|
-
const normalized = authMode.trim().toLowerCase();
|
|
193
|
-
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
194
|
-
}
|
|
195
228
|
function parsePlanType(snapshot) {
|
|
229
|
+
const tokens = snapshot.tokens ?? {};
|
|
196
230
|
for (const tokenName of [
|
|
197
231
|
"id_token",
|
|
198
232
|
"access_token"
|
|
199
233
|
]){
|
|
200
|
-
const token =
|
|
234
|
+
const token = tokens[tokenName];
|
|
201
235
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
202
236
|
const payload = decodeJwtPayload(token);
|
|
203
237
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -208,10 +242,11 @@ function parsePlanType(snapshot) {
|
|
|
208
242
|
}
|
|
209
243
|
function extractChatGPTAuth(snapshot) {
|
|
210
244
|
const authMode = snapshot.auth_mode ?? "";
|
|
211
|
-
const supported =
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
245
|
+
const supported = isSupportedChatGPTAuthMode(authMode);
|
|
246
|
+
const tokens = snapshot.tokens ?? {};
|
|
247
|
+
const accessTokenValue = tokens.access_token;
|
|
248
|
+
const refreshTokenValue = tokens.refresh_token;
|
|
249
|
+
const directAccountId = tokens.account_id;
|
|
215
250
|
let accountId = "string" == typeof directAccountId && "" !== directAccountId.trim() ? directAccountId : void 0;
|
|
216
251
|
let planType;
|
|
217
252
|
let issuer;
|
|
@@ -220,7 +255,7 @@ function extractChatGPTAuth(snapshot) {
|
|
|
220
255
|
"id_token",
|
|
221
256
|
"access_token"
|
|
222
257
|
]){
|
|
223
|
-
const token =
|
|
258
|
+
const token = tokens[tokenName];
|
|
224
259
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
225
260
|
const payload = decodeJwtPayload(token);
|
|
226
261
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -402,7 +437,7 @@ async function refreshChatGPTAuthTokens(snapshot, options) {
|
|
|
402
437
|
...snapshot,
|
|
403
438
|
last_refresh: (options.now ?? new Date()).toISOString(),
|
|
404
439
|
tokens: {
|
|
405
|
-
...snapshot.tokens,
|
|
440
|
+
...snapshot.tokens ?? {},
|
|
406
441
|
access_token: payload.access_token,
|
|
407
442
|
id_token: payload.id_token,
|
|
408
443
|
refresh_token: payload.refresh_token ?? extracted.refreshToken,
|
|
@@ -553,7 +588,7 @@ class AccountStore {
|
|
|
553
588
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
554
589
|
try {
|
|
555
590
|
const currentSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
556
|
-
if (currentSnapshot
|
|
591
|
+
if (getSnapshotIdentity(currentSnapshot) !== getSnapshotIdentity(snapshot)) return;
|
|
557
592
|
await atomicWriteFile(this.paths.currentAuthPath, stringifyJson(snapshot));
|
|
558
593
|
} catch {}
|
|
559
594
|
}
|
|
@@ -609,7 +644,7 @@ class AccountStore {
|
|
|
609
644
|
]);
|
|
610
645
|
const meta = parseSnapshotMeta(rawMeta);
|
|
611
646
|
if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
|
|
612
|
-
if (meta.account_id !== snapshot
|
|
647
|
+
if (meta.account_id !== getSnapshotIdentity(snapshot)) throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
613
648
|
return {
|
|
614
649
|
...meta,
|
|
615
650
|
authPath,
|
|
@@ -652,11 +687,12 @@ class AccountStore {
|
|
|
652
687
|
warnings
|
|
653
688
|
};
|
|
654
689
|
const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
655
|
-
const
|
|
690
|
+
const currentIdentity = getSnapshotIdentity(snapshot);
|
|
691
|
+
const matchedAccounts = accounts.filter((account)=>account.account_id === currentIdentity).map((account)=>account.name);
|
|
656
692
|
return {
|
|
657
693
|
exists: true,
|
|
658
694
|
auth_mode: snapshot.auth_mode,
|
|
659
|
-
account_id:
|
|
695
|
+
account_id: currentIdentity,
|
|
660
696
|
matched_accounts: matchedAccounts,
|
|
661
697
|
managed: matchedAccounts.length > 0,
|
|
662
698
|
duplicate_match: matchedAccounts.length > 1,
|
|
@@ -719,7 +755,7 @@ class AccountStore {
|
|
|
719
755
|
const rawAuth = await readJsonFile(account.authPath);
|
|
720
756
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
721
757
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
722
|
-
if (writtenSnapshot
|
|
758
|
+
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
723
759
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
724
760
|
meta.last_switched_at = new Date().toISOString();
|
|
725
761
|
meta.updated_at = meta.last_switched_at;
|
|
@@ -763,7 +799,7 @@ class AccountStore {
|
|
|
763
799
|
await this.syncCurrentAuthIfMatching(result.authSnapshot);
|
|
764
800
|
}
|
|
765
801
|
meta.auth_mode = result.authSnapshot.auth_mode;
|
|
766
|
-
meta.account_id = result.authSnapshot
|
|
802
|
+
meta.account_id = getSnapshotIdentity(result.authSnapshot);
|
|
767
803
|
meta.updated_at = now.toISOString();
|
|
768
804
|
meta.quota = result.quota;
|
|
769
805
|
await this.writeAccountMeta(name, meta);
|
|
@@ -875,7 +911,7 @@ class AccountStore {
|
|
|
875
911
|
const metaStat = await (0, promises_namespaceObject.stat)(account.metaPath);
|
|
876
912
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
877
913
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
878
|
-
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares
|
|
914
|
+
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
879
915
|
}
|
|
880
916
|
let currentAuthPresent = false;
|
|
881
917
|
if (await pathExists(this.paths.currentAuthPath)) {
|
|
@@ -958,7 +994,7 @@ function describeCurrentStatus(status) {
|
|
|
958
994
|
if (status.exists) {
|
|
959
995
|
lines.push("Current auth: present");
|
|
960
996
|
lines.push(`Auth mode: ${status.auth_mode}`);
|
|
961
|
-
lines.push(`
|
|
997
|
+
lines.push(`Identity: ${maskAccountId(status.account_id ?? "")}`);
|
|
962
998
|
if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
|
|
963
999
|
else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
|
|
964
1000
|
else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
|
|
@@ -1091,7 +1127,7 @@ function describeQuotaAccounts(accounts, warnings) {
|
|
|
1091
1127
|
},
|
|
1092
1128
|
{
|
|
1093
1129
|
key: "account_id",
|
|
1094
|
-
label: "
|
|
1130
|
+
label: "IDENTITY"
|
|
1095
1131
|
},
|
|
1096
1132
|
{
|
|
1097
1133
|
key: "plan_type",
|
package/dist/main.js
CHANGED
|
@@ -2,13 +2,14 @@ import { stderr, stdin, stdout as external_node_process_stdout } from "node:proc
|
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
3
|
import timezone from "dayjs/plugin/timezone.js";
|
|
4
4
|
import utc from "dayjs/plugin/utc.js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { chmod, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { basename, dirname, join } from "node:path";
|
|
8
9
|
import { execFile } from "node:child_process";
|
|
9
10
|
import { promisify } from "node:util";
|
|
10
11
|
var package_namespaceObject = {
|
|
11
|
-
rE: "0.0.
|
|
12
|
+
rE: "0.0.6"
|
|
12
13
|
};
|
|
13
14
|
function isRecord(value) {
|
|
14
15
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -31,6 +32,29 @@ function asOptionalNumber(value, fieldName) {
|
|
|
31
32
|
if ("number" != typeof value || Number.isNaN(value)) throw new Error(`Field "${fieldName}" must be a number.`);
|
|
32
33
|
return value;
|
|
33
34
|
}
|
|
35
|
+
function normalizeAuthMode(authMode) {
|
|
36
|
+
return authMode.trim().toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
function isApiKeyAuthMode(authMode) {
|
|
39
|
+
return "apikey" === normalizeAuthMode(authMode);
|
|
40
|
+
}
|
|
41
|
+
function isSupportedChatGPTAuthMode(authMode) {
|
|
42
|
+
const normalized = normalizeAuthMode(authMode);
|
|
43
|
+
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
44
|
+
}
|
|
45
|
+
function fingerprintApiKey(apiKey) {
|
|
46
|
+
return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
function getSnapshotIdentity(snapshot) {
|
|
49
|
+
if (isApiKeyAuthMode(snapshot.auth_mode)) {
|
|
50
|
+
const apiKey = snapshot.OPENAI_API_KEY;
|
|
51
|
+
if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
52
|
+
return `key_${fingerprintApiKey(apiKey)}`;
|
|
53
|
+
}
|
|
54
|
+
const accountId = snapshot.tokens?.account_id;
|
|
55
|
+
if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
|
|
56
|
+
return accountId;
|
|
57
|
+
}
|
|
34
58
|
function defaultQuotaSnapshot() {
|
|
35
59
|
return {
|
|
36
60
|
status: "stale"
|
|
@@ -75,15 +99,28 @@ function parseAuthSnapshot(raw) {
|
|
|
75
99
|
}
|
|
76
100
|
if (!isRecord(parsed)) throw new Error("Auth snapshot must be a JSON object.");
|
|
77
101
|
const authMode = asNonEmptyString(parsed.auth_mode, "auth_mode");
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
const tokens = parsed.tokens;
|
|
103
|
+
if (null != tokens && !isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
104
|
+
const apiKeyMode = isApiKeyAuthMode(authMode);
|
|
105
|
+
const normalizedApiKey = null === parsed.OPENAI_API_KEY || void 0 === parsed.OPENAI_API_KEY ? parsed.OPENAI_API_KEY : asNonEmptyString(parsed.OPENAI_API_KEY, "OPENAI_API_KEY");
|
|
106
|
+
if (apiKeyMode) {
|
|
107
|
+
if ("string" != typeof normalizedApiKey || "" === normalizedApiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
|
|
108
|
+
} else {
|
|
109
|
+
if (!isRecord(tokens)) throw new Error('Field "tokens" must be an object.');
|
|
110
|
+
asNonEmptyString(tokens.account_id, "tokens.account_id");
|
|
111
|
+
}
|
|
80
112
|
return {
|
|
81
113
|
...parsed,
|
|
82
114
|
auth_mode: authMode,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
OPENAI_API_KEY: normalizedApiKey,
|
|
116
|
+
...isRecord(tokens) ? {
|
|
117
|
+
tokens: {
|
|
118
|
+
...tokens,
|
|
119
|
+
..."string" == typeof tokens.account_id && "" !== tokens.account_id.trim() ? {
|
|
120
|
+
account_id: tokens.account_id
|
|
121
|
+
} : {}
|
|
122
|
+
}
|
|
123
|
+
} : {}
|
|
87
124
|
};
|
|
88
125
|
}
|
|
89
126
|
async function readAuthSnapshotFile(filePath) {
|
|
@@ -95,7 +132,7 @@ function createSnapshotMeta(name, snapshot, now, existingCreatedAt) {
|
|
|
95
132
|
return {
|
|
96
133
|
name,
|
|
97
134
|
auth_mode: snapshot.auth_mode,
|
|
98
|
-
account_id: snapshot
|
|
135
|
+
account_id: getSnapshotIdentity(snapshot),
|
|
99
136
|
created_at: existingCreatedAt ?? timestamp,
|
|
100
137
|
updated_at: timestamp,
|
|
101
138
|
last_switched_at: null,
|
|
@@ -148,16 +185,13 @@ function extractStringClaim(payload, key) {
|
|
|
148
185
|
const value = payload[key];
|
|
149
186
|
return "string" == typeof value && "" !== value.trim() ? value : void 0;
|
|
150
187
|
}
|
|
151
|
-
function isSupportedChatGPTMode(authMode) {
|
|
152
|
-
const normalized = authMode.trim().toLowerCase();
|
|
153
|
-
return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
|
|
154
|
-
}
|
|
155
188
|
function parsePlanType(snapshot) {
|
|
189
|
+
const tokens = snapshot.tokens ?? {};
|
|
156
190
|
for (const tokenName of [
|
|
157
191
|
"id_token",
|
|
158
192
|
"access_token"
|
|
159
193
|
]){
|
|
160
|
-
const token =
|
|
194
|
+
const token = tokens[tokenName];
|
|
161
195
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
162
196
|
const payload = decodeJwtPayload(token);
|
|
163
197
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -168,10 +202,11 @@ function parsePlanType(snapshot) {
|
|
|
168
202
|
}
|
|
169
203
|
function extractChatGPTAuth(snapshot) {
|
|
170
204
|
const authMode = snapshot.auth_mode ?? "";
|
|
171
|
-
const supported =
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
205
|
+
const supported = isSupportedChatGPTAuthMode(authMode);
|
|
206
|
+
const tokens = snapshot.tokens ?? {};
|
|
207
|
+
const accessTokenValue = tokens.access_token;
|
|
208
|
+
const refreshTokenValue = tokens.refresh_token;
|
|
209
|
+
const directAccountId = tokens.account_id;
|
|
175
210
|
let accountId = "string" == typeof directAccountId && "" !== directAccountId.trim() ? directAccountId : void 0;
|
|
176
211
|
let planType;
|
|
177
212
|
let issuer;
|
|
@@ -180,7 +215,7 @@ function extractChatGPTAuth(snapshot) {
|
|
|
180
215
|
"id_token",
|
|
181
216
|
"access_token"
|
|
182
217
|
]){
|
|
183
|
-
const token =
|
|
218
|
+
const token = tokens[tokenName];
|
|
184
219
|
if ("string" == typeof token && "" !== token.trim()) try {
|
|
185
220
|
const payload = decodeJwtPayload(token);
|
|
186
221
|
const authClaim = extractAuthClaim(payload);
|
|
@@ -362,7 +397,7 @@ async function refreshChatGPTAuthTokens(snapshot, options) {
|
|
|
362
397
|
...snapshot,
|
|
363
398
|
last_refresh: (options.now ?? new Date()).toISOString(),
|
|
364
399
|
tokens: {
|
|
365
|
-
...snapshot.tokens,
|
|
400
|
+
...snapshot.tokens ?? {},
|
|
366
401
|
access_token: payload.access_token,
|
|
367
402
|
id_token: payload.id_token,
|
|
368
403
|
refresh_token: payload.refresh_token ?? extracted.refreshToken,
|
|
@@ -513,7 +548,7 @@ class AccountStore {
|
|
|
513
548
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
514
549
|
try {
|
|
515
550
|
const currentSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
516
|
-
if (currentSnapshot
|
|
551
|
+
if (getSnapshotIdentity(currentSnapshot) !== getSnapshotIdentity(snapshot)) return;
|
|
517
552
|
await atomicWriteFile(this.paths.currentAuthPath, stringifyJson(snapshot));
|
|
518
553
|
} catch {}
|
|
519
554
|
}
|
|
@@ -569,7 +604,7 @@ class AccountStore {
|
|
|
569
604
|
]);
|
|
570
605
|
const meta = parseSnapshotMeta(rawMeta);
|
|
571
606
|
if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
|
|
572
|
-
if (meta.account_id !== snapshot
|
|
607
|
+
if (meta.account_id !== getSnapshotIdentity(snapshot)) throw new Error(`Account metadata account_id mismatch for "${name}".`);
|
|
573
608
|
return {
|
|
574
609
|
...meta,
|
|
575
610
|
authPath,
|
|
@@ -612,11 +647,12 @@ class AccountStore {
|
|
|
612
647
|
warnings
|
|
613
648
|
};
|
|
614
649
|
const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
615
|
-
const
|
|
650
|
+
const currentIdentity = getSnapshotIdentity(snapshot);
|
|
651
|
+
const matchedAccounts = accounts.filter((account)=>account.account_id === currentIdentity).map((account)=>account.name);
|
|
616
652
|
return {
|
|
617
653
|
exists: true,
|
|
618
654
|
auth_mode: snapshot.auth_mode,
|
|
619
|
-
account_id:
|
|
655
|
+
account_id: currentIdentity,
|
|
620
656
|
matched_accounts: matchedAccounts,
|
|
621
657
|
managed: matchedAccounts.length > 0,
|
|
622
658
|
duplicate_match: matchedAccounts.length > 1,
|
|
@@ -679,7 +715,7 @@ class AccountStore {
|
|
|
679
715
|
const rawAuth = await readJsonFile(account.authPath);
|
|
680
716
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
681
717
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
682
|
-
if (writtenSnapshot
|
|
718
|
+
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
683
719
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
684
720
|
meta.last_switched_at = new Date().toISOString();
|
|
685
721
|
meta.updated_at = meta.last_switched_at;
|
|
@@ -723,7 +759,7 @@ class AccountStore {
|
|
|
723
759
|
await this.syncCurrentAuthIfMatching(result.authSnapshot);
|
|
724
760
|
}
|
|
725
761
|
meta.auth_mode = result.authSnapshot.auth_mode;
|
|
726
|
-
meta.account_id = result.authSnapshot
|
|
762
|
+
meta.account_id = getSnapshotIdentity(result.authSnapshot);
|
|
727
763
|
meta.updated_at = now.toISOString();
|
|
728
764
|
meta.quota = result.quota;
|
|
729
765
|
await this.writeAccountMeta(name, meta);
|
|
@@ -835,7 +871,7 @@ class AccountStore {
|
|
|
835
871
|
const metaStat = await stat(account.metaPath);
|
|
836
872
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
837
873
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
838
|
-
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares
|
|
874
|
+
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
839
875
|
}
|
|
840
876
|
let currentAuthPresent = false;
|
|
841
877
|
if (await pathExists(this.paths.currentAuthPath)) {
|
|
@@ -918,7 +954,7 @@ function describeCurrentStatus(status) {
|
|
|
918
954
|
if (status.exists) {
|
|
919
955
|
lines.push("Current auth: present");
|
|
920
956
|
lines.push(`Auth mode: ${status.auth_mode}`);
|
|
921
|
-
lines.push(`
|
|
957
|
+
lines.push(`Identity: ${maskAccountId(status.account_id ?? "")}`);
|
|
922
958
|
if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
|
|
923
959
|
else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
|
|
924
960
|
else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
|
|
@@ -1051,7 +1087,7 @@ function describeQuotaAccounts(accounts, warnings) {
|
|
|
1051
1087
|
},
|
|
1052
1088
|
{
|
|
1053
1089
|
key: "account_id",
|
|
1054
|
-
label: "
|
|
1090
|
+
label: "IDENTITY"
|
|
1055
1091
|
},
|
|
1056
1092
|
{
|
|
1057
1093
|
key: "plan_type",
|