better-opencode-openai-codex-auth 0.1.1 → 0.1.3
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 +24 -0
- package/config/README.md +19 -93
- package/config/opencode-modern.json +149 -42
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +215 -109
- package/dist/index.js.map +1 -1
- package/dist/lib/account-pool.d.ts +29 -3
- package/dist/lib/account-pool.d.ts.map +1 -1
- package/dist/lib/account-pool.js +211 -44
- package/dist/lib/account-pool.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +55 -4
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +142 -13
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/auth/server.d.ts.map +1 -1
- package/dist/lib/auth/server.js +12 -8
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/auth/ui/account-menu.d.ts +50 -0
- package/dist/lib/auth/ui/account-menu.d.ts.map +1 -0
- package/dist/lib/auth/ui/account-menu.js +258 -0
- package/dist/lib/auth/ui/account-menu.js.map +1 -0
- package/dist/lib/auth/ui/ansi.d.ts +23 -0
- package/dist/lib/auth/ui/ansi.d.ts.map +1 -0
- package/dist/lib/auth/ui/ansi.js +37 -0
- package/dist/lib/auth/ui/ansi.js.map +1 -0
- package/dist/lib/auth/ui/confirm.d.ts +2 -0
- package/dist/lib/auth/ui/confirm.d.ts.map +1 -0
- package/dist/lib/auth/ui/confirm.js +8 -0
- package/dist/lib/auth/ui/confirm.js.map +1 -0
- package/dist/lib/auth/ui/select.d.ts +21 -0
- package/dist/lib/auth/ui/select.d.ts.map +1 -0
- package/dist/lib/auth/ui/select.js +208 -0
- package/dist/lib/auth/ui/select.js.map +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +13 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +76 -3
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +80 -3
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/logger.d.ts +6 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +11 -2
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/prompts/codex.d.ts +1 -1
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +15 -2
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/refresh-queue.d.ts +56 -0
- package/dist/lib/refresh-queue.d.ts.map +1 -0
- package/dist/lib/refresh-queue.js +148 -0
- package/dist/lib/refresh-queue.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +2 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +25 -5
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/input-utils.js +12 -10
- package/dist/lib/request/helpers/input-utils.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +14 -1
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +33 -29
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +17 -13
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/schemas.d.ts +85 -0
- package/dist/lib/schemas.d.ts.map +1 -0
- package/dist/lib/schemas.js +93 -0
- package/dist/lib/schemas.js.map +1 -0
- package/dist/lib/types.d.ts +18 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +6 -7
- package/scripts/install-opencode-codex-auth.js +7 -27
- package/config/opencode-legacy.json +0 -571
package/dist/lib/auth/auth.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { DEVICE_AUTH } from "../constants.js";
|
|
3
4
|
// OAuth constants (from openai/codex)
|
|
4
5
|
export const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
5
6
|
export const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
6
7
|
export const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
7
8
|
export const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
8
9
|
export const SCOPE = "openid profile email offline_access";
|
|
10
|
+
const refreshInFlight = new Map();
|
|
9
11
|
/**
|
|
10
12
|
* Generate a random state value for OAuth flow
|
|
11
13
|
* @returns Random hex string
|
|
@@ -44,11 +46,14 @@ export function parseAuthorizationInput(input) {
|
|
|
44
46
|
return { code: value };
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
|
-
* Exchange authorization code for access and refresh tokens
|
|
49
|
+
* Exchange authorization code for access and refresh tokens.
|
|
50
|
+
* Captures id_token when returned — it contains chatgpt_account_id as a
|
|
51
|
+
* dedicated JWT claim, making account-ID extraction more reliable.
|
|
52
|
+
*
|
|
48
53
|
* @param code - Authorization code from OAuth flow
|
|
49
54
|
* @param verifier - PKCE verifier
|
|
50
55
|
* @param redirectUri - OAuth redirect URI
|
|
51
|
-
* @returns Token result
|
|
56
|
+
* @returns Token result (includes id_token when available)
|
|
52
57
|
*/
|
|
53
58
|
export async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI) {
|
|
54
59
|
const res = await fetch(TOKEN_URL, {
|
|
@@ -64,14 +69,14 @@ export async function exchangeAuthorizationCode(code, verifier, redirectUri = RE
|
|
|
64
69
|
});
|
|
65
70
|
if (!res.ok) {
|
|
66
71
|
const text = await res.text().catch(() => "");
|
|
67
|
-
console.error(
|
|
72
|
+
console.error(`[openai-codex-plugin] code->token failed: ${res.status}`, text);
|
|
68
73
|
return { type: "failed" };
|
|
69
74
|
}
|
|
70
75
|
const json = (await res.json());
|
|
71
76
|
if (!json?.access_token ||
|
|
72
77
|
!json?.refresh_token ||
|
|
73
78
|
typeof json?.expires_in !== "number") {
|
|
74
|
-
console.error("[openai-codex-plugin] token response missing fields
|
|
79
|
+
console.error("[openai-codex-plugin] token response missing fields");
|
|
75
80
|
return { type: "failed" };
|
|
76
81
|
}
|
|
77
82
|
return {
|
|
@@ -79,6 +84,7 @@ export async function exchangeAuthorizationCode(code, verifier, redirectUri = RE
|
|
|
79
84
|
access: json.access_token,
|
|
80
85
|
refresh: json.refresh_token,
|
|
81
86
|
expires: Date.now() + json.expires_in * 1000,
|
|
87
|
+
id_token: json.id_token,
|
|
82
88
|
};
|
|
83
89
|
}
|
|
84
90
|
/**
|
|
@@ -92,15 +98,71 @@ export function decodeJWT(token) {
|
|
|
92
98
|
if (parts.length !== 3)
|
|
93
99
|
return null;
|
|
94
100
|
const payload = parts[1];
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
if (!payload)
|
|
102
|
+
return null;
|
|
103
|
+
const decoded = Buffer.from(payload, "base64url").toString("utf-8");
|
|
104
|
+
const parsed = JSON.parse(decoded);
|
|
105
|
+
// Validate that the parsed payload is a non-null object
|
|
106
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
97
110
|
}
|
|
98
111
|
catch {
|
|
99
112
|
return null;
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
/**
|
|
103
|
-
*
|
|
116
|
+
* Extract the ChatGPT account ID from a JWT token string.
|
|
117
|
+
* Checks (in priority order, matching first-party opencode CodexAuthPlugin):
|
|
118
|
+
* 1. Root-level chatgpt_account_id claim
|
|
119
|
+
* 2. Nested https://api.openai.com/auth.chatgpt_account_id claim
|
|
120
|
+
* 3. organizations[0].id fallback
|
|
121
|
+
*
|
|
122
|
+
* @param token - JWT string (id_token or access_token)
|
|
123
|
+
* @returns Account ID or undefined if not found
|
|
124
|
+
*/
|
|
125
|
+
export function extractAccountIdFromToken(token) {
|
|
126
|
+
if (!token)
|
|
127
|
+
return undefined;
|
|
128
|
+
const claims = decodeJWT(token);
|
|
129
|
+
if (!claims)
|
|
130
|
+
return undefined;
|
|
131
|
+
// 1. Root-level claim (most authoritative)
|
|
132
|
+
if (typeof claims.chatgpt_account_id === "string" && claims.chatgpt_account_id) {
|
|
133
|
+
return claims.chatgpt_account_id;
|
|
134
|
+
}
|
|
135
|
+
// 2. Nested claim
|
|
136
|
+
const nested = claims["https://api.openai.com/auth"];
|
|
137
|
+
if (nested?.chatgpt_account_id) {
|
|
138
|
+
return nested.chatgpt_account_id;
|
|
139
|
+
}
|
|
140
|
+
// 3. Organizations fallback
|
|
141
|
+
const orgs = claims.organizations;
|
|
142
|
+
if (Array.isArray(orgs) && orgs[0]?.id) {
|
|
143
|
+
return orgs[0].id;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Extract account ID from a token-response object.
|
|
149
|
+
* Tries id_token first (more authoritative), then access_token.
|
|
150
|
+
*
|
|
151
|
+
* @param tokens - Object with access_token and optional id_token
|
|
152
|
+
* @returns Account ID or undefined
|
|
153
|
+
*/
|
|
154
|
+
export function extractAccountId(tokens) {
|
|
155
|
+
if (tokens.id_token) {
|
|
156
|
+
const fromIdToken = extractAccountIdFromToken(tokens.id_token);
|
|
157
|
+
if (fromIdToken)
|
|
158
|
+
return fromIdToken;
|
|
159
|
+
}
|
|
160
|
+
return extractAccountIdFromToken(tokens.access_token);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Refresh access token using refresh token.
|
|
164
|
+
* Captures id_token when returned by the server.
|
|
165
|
+
*
|
|
104
166
|
* @param refreshToken - Refresh token
|
|
105
167
|
* @returns Token result
|
|
106
168
|
*/
|
|
@@ -117,13 +179,13 @@ export async function refreshAccessToken(refreshToken) {
|
|
|
117
179
|
});
|
|
118
180
|
if (!response.ok) {
|
|
119
181
|
const text = await response.text().catch(() => "");
|
|
120
|
-
console.error(
|
|
182
|
+
console.error(`[openai-codex-plugin] Token refresh failed: ${response.status}`, text);
|
|
121
183
|
return { type: "failed" };
|
|
122
184
|
}
|
|
123
185
|
const json = (await response.json());
|
|
124
186
|
if (!json?.access_token ||
|
|
125
187
|
typeof json?.expires_in !== "number") {
|
|
126
|
-
console.error("[openai-codex-plugin] Token refresh response missing fields
|
|
188
|
+
console.error("[openai-codex-plugin] Token refresh response missing fields");
|
|
127
189
|
return { type: "failed" };
|
|
128
190
|
}
|
|
129
191
|
return {
|
|
@@ -131,16 +193,29 @@ export async function refreshAccessToken(refreshToken) {
|
|
|
131
193
|
access: json.access_token,
|
|
132
194
|
refresh: json.refresh_token || refreshToken,
|
|
133
195
|
expires: Date.now() + json.expires_in * 1000,
|
|
196
|
+
id_token: json.id_token,
|
|
134
197
|
};
|
|
135
198
|
}
|
|
136
199
|
catch (error) {
|
|
137
|
-
const err = error;
|
|
138
|
-
console.error("[openai-codex-plugin] Token refresh error:", err);
|
|
200
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
201
|
+
console.error("[openai-codex-plugin] Token refresh error:", err.message);
|
|
139
202
|
return { type: "failed" };
|
|
140
203
|
}
|
|
141
204
|
}
|
|
205
|
+
export function refreshAccessTokenShared(accountKey, refreshToken) {
|
|
206
|
+
const key = `${accountKey}:${refreshToken}`;
|
|
207
|
+
const existing = refreshInFlight.get(key);
|
|
208
|
+
if (existing) {
|
|
209
|
+
return existing;
|
|
210
|
+
}
|
|
211
|
+
const promise = refreshAccessToken(refreshToken).finally(() => {
|
|
212
|
+
refreshInFlight.delete(key);
|
|
213
|
+
});
|
|
214
|
+
refreshInFlight.set(key, promise);
|
|
215
|
+
return promise;
|
|
216
|
+
}
|
|
142
217
|
/**
|
|
143
|
-
* Create OAuth authorization flow
|
|
218
|
+
* Create OAuth authorization flow (browser PKCE)
|
|
144
219
|
* @returns Authorization flow details
|
|
145
220
|
*/
|
|
146
221
|
export async function createAuthorizationFlow() {
|
|
@@ -156,7 +231,61 @@ export async function createAuthorizationFlow() {
|
|
|
156
231
|
url.searchParams.set("state", state);
|
|
157
232
|
url.searchParams.set("id_token_add_organizations", "true");
|
|
158
233
|
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
159
|
-
|
|
234
|
+
// Use "opencode" — matching the first-party opencode CodexAuthPlugin (#2 fix)
|
|
235
|
+
url.searchParams.set("originator", "opencode");
|
|
160
236
|
return { pkce, state, url: url.toString() };
|
|
161
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Create a headless/device-code authorization flow.
|
|
240
|
+
*
|
|
241
|
+
* This flow requires no browser on the machine running opencode. The user
|
|
242
|
+
* visits a URL on any device and enters a short code to authenticate.
|
|
243
|
+
* Matches the "headless" method in the first-party opencode CodexAuthPlugin.
|
|
244
|
+
*
|
|
245
|
+
* @returns Device authorization flow details
|
|
246
|
+
*/
|
|
247
|
+
export async function createDeviceAuthorizationFlow() {
|
|
248
|
+
const response = await fetch(DEVICE_AUTH.USERCODE_URL, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: { "Content-Type": "application/json" },
|
|
251
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
throw new Error(`[openai-codex-plugin] Device auth usercode request failed: ${response.status}`);
|
|
255
|
+
}
|
|
256
|
+
const data = (await response.json());
|
|
257
|
+
const serverIntervalMs = Math.max(parseInt(data.interval) || 5, 1) * 1000;
|
|
258
|
+
const pollIntervalMs = serverIntervalMs + DEVICE_AUTH.POLL_SAFETY_MARGIN_MS;
|
|
259
|
+
const poll = async () => {
|
|
260
|
+
// eslint-disable-next-line no-constant-condition
|
|
261
|
+
while (true) {
|
|
262
|
+
const tokenResponse = await fetch(DEVICE_AUTH.TOKEN_URL, {
|
|
263
|
+
method: "POST",
|
|
264
|
+
headers: { "Content-Type": "application/json" },
|
|
265
|
+
body: JSON.stringify({
|
|
266
|
+
device_auth_id: data.device_auth_id,
|
|
267
|
+
user_code: data.user_code,
|
|
268
|
+
}),
|
|
269
|
+
});
|
|
270
|
+
if (tokenResponse.ok) {
|
|
271
|
+
const tokenData = (await tokenResponse.json());
|
|
272
|
+
// Exchange the authorization_code for final tokens
|
|
273
|
+
return exchangeAuthorizationCode(tokenData.authorization_code, tokenData.code_verifier, DEVICE_AUTH.REDIRECT_URI);
|
|
274
|
+
}
|
|
275
|
+
// 403 / 404 = still waiting for user to complete auth
|
|
276
|
+
if (tokenResponse.status !== 403 && tokenResponse.status !== 404) {
|
|
277
|
+
console.error(`[openai-codex-plugin] Device token poll failed: ${tokenResponse.status}`);
|
|
278
|
+
return { type: "failed" };
|
|
279
|
+
}
|
|
280
|
+
// Wait before next poll
|
|
281
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
activateUrl: DEVICE_AUTH.ACTIVATE_URL,
|
|
286
|
+
userCode: data.user_code,
|
|
287
|
+
pollIntervalMs,
|
|
288
|
+
poll,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
162
291
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAOnD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAqE5F"}
|
package/dist/lib/auth/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import http from "node:http";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { OAUTH_SERVER, PLUGIN_NAME } from "../constants.js";
|
|
5
6
|
// Resolve path to oauth-success.html (one level up from auth/ subfolder)
|
|
6
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const successHtml = fs.readFileSync(path.join(__dirname, "..", "oauth-success.html"), "utf-8");
|
|
@@ -35,21 +36,22 @@ export function startLocalOAuthServer({ state }) {
|
|
|
35
36
|
res.end(successHtml);
|
|
36
37
|
server._lastCode = code;
|
|
37
38
|
}
|
|
38
|
-
catch {
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(`[${PLUGIN_NAME}] OAuth callback handler error:`, error);
|
|
39
41
|
res.statusCode = 500;
|
|
40
42
|
res.end("Internal error");
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
return new Promise((resolve) => {
|
|
44
46
|
server
|
|
45
|
-
.listen(
|
|
47
|
+
.listen(OAUTH_SERVER.PORT, "127.0.0.1", () => {
|
|
46
48
|
resolve({
|
|
47
|
-
port:
|
|
49
|
+
port: OAUTH_SERVER.PORT,
|
|
48
50
|
ready: true,
|
|
49
51
|
close: () => server.close(),
|
|
50
52
|
waitForCode: async () => {
|
|
51
|
-
const poll = () => new Promise((r) => setTimeout(r,
|
|
52
|
-
for (let i = 0; i <
|
|
53
|
+
const poll = () => new Promise((r) => setTimeout(r, OAUTH_SERVER.POLL_INTERVAL_MS));
|
|
54
|
+
for (let i = 0; i < OAUTH_SERVER.MAX_POLL_ITERATIONS; i++) {
|
|
53
55
|
const lastCode = server._lastCode;
|
|
54
56
|
if (lastCode)
|
|
55
57
|
return { code: lastCode };
|
|
@@ -60,15 +62,17 @@ export function startLocalOAuthServer({ state }) {
|
|
|
60
62
|
});
|
|
61
63
|
})
|
|
62
64
|
.on("error", (err) => {
|
|
63
|
-
console.error(
|
|
65
|
+
console.error(`[${PLUGIN_NAME}] Failed to bind http://127.0.0.1:${OAUTH_SERVER.PORT} (`, err?.code, ") Falling back to manual paste.");
|
|
64
66
|
resolve({
|
|
65
|
-
port:
|
|
67
|
+
port: OAUTH_SERVER.PORT,
|
|
66
68
|
ready: false,
|
|
67
69
|
close: () => {
|
|
68
70
|
try {
|
|
69
71
|
server.close();
|
|
70
72
|
}
|
|
71
|
-
catch {
|
|
73
|
+
catch (closeErr) {
|
|
74
|
+
console.error(`[${PLUGIN_NAME}] Error closing server:`, closeErr);
|
|
75
|
+
}
|
|
72
76
|
},
|
|
73
77
|
waitForCode: async () => null,
|
|
74
78
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account pool management UI for the Codex OAuth plugin.
|
|
3
|
+
*
|
|
4
|
+
* Shown when the user runs `opencode auth login` and existing accounts
|
|
5
|
+
* are already in the pool. Provides TTY-interactive menu on terminals;
|
|
6
|
+
* falls back to a plain readline prompt on headless/non-TTY environments.
|
|
7
|
+
*
|
|
8
|
+
* Inspired by NoeFabris/opencode-antigravity-auth ui/auth-menu.ts.
|
|
9
|
+
*/
|
|
10
|
+
import type { AccountPoolEntry } from "../../types.js";
|
|
11
|
+
import { AccountPool } from "../../account-pool.js";
|
|
12
|
+
export type AccountStatus = "active" | "rate-limited" | "cooling-down" | "expired" | "unknown";
|
|
13
|
+
export interface DisplayAccount {
|
|
14
|
+
entry: AccountPoolEntry;
|
|
15
|
+
index: number;
|
|
16
|
+
status: AccountStatus;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Derive a human-readable status from pool entry timestamps.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAccountStatus(entry: AccountPoolEntry): AccountStatus;
|
|
22
|
+
export type ManageResult = {
|
|
23
|
+
action: "add";
|
|
24
|
+
} | {
|
|
25
|
+
action: "delete-one";
|
|
26
|
+
index: number;
|
|
27
|
+
} | {
|
|
28
|
+
action: "delete-all";
|
|
29
|
+
} | {
|
|
30
|
+
action: "refresh-token";
|
|
31
|
+
index: number;
|
|
32
|
+
} | {
|
|
33
|
+
action: "done";
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Show the account management menu.
|
|
37
|
+
*
|
|
38
|
+
* Loads the current pool, displays it with live status, and applies the
|
|
39
|
+
* user's chosen action (delete, refresh-token, delete-all, or add).
|
|
40
|
+
*
|
|
41
|
+
* Returns the final `ManageResult` after the user is done. Callers should
|
|
42
|
+
* loop as long as `action !== "done"` && `action !== "add"`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function showAccountManager(): Promise<ManageResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Apply a ManageResult to the pool and persist.
|
|
47
|
+
* Returns true if the caller should proceed to add a new account.
|
|
48
|
+
*/
|
|
49
|
+
export declare function applyManageResult(result: ManageResult, pool: AccountPool): boolean;
|
|
50
|
+
//# sourceMappingURL=account-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-menu.d.ts","sourceRoot":"","sources":["../../../../lib/auth/ui/account-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/F,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAMvE;AAgLD,MAAM,MAAM,YAAY,GACrB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,GACxB;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAkDhE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAuBlF"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account pool management UI for the Codex OAuth plugin.
|
|
3
|
+
*
|
|
4
|
+
* Shown when the user runs `opencode auth login` and existing accounts
|
|
5
|
+
* are already in the pool. Provides TTY-interactive menu on terminals;
|
|
6
|
+
* falls back to a plain readline prompt on headless/non-TTY environments.
|
|
7
|
+
*
|
|
8
|
+
* Inspired by NoeFabris/opencode-antigravity-auth ui/auth-menu.ts.
|
|
9
|
+
*/
|
|
10
|
+
import { createInterface } from "node:readline/promises";
|
|
11
|
+
import { ANSI, isTTY } from "./ansi.js";
|
|
12
|
+
import { select } from "./select.js";
|
|
13
|
+
import { confirm } from "./confirm.js";
|
|
14
|
+
import { AccountPool } from "../../account-pool.js";
|
|
15
|
+
/**
|
|
16
|
+
* Derive a human-readable status from pool entry timestamps.
|
|
17
|
+
*/
|
|
18
|
+
export function getAccountStatus(entry) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
if (entry.rateLimitedUntil && entry.rateLimitedUntil > now)
|
|
21
|
+
return "rate-limited";
|
|
22
|
+
if (entry.coolingDownUntil && entry.coolingDownUntil > now)
|
|
23
|
+
return "cooling-down";
|
|
24
|
+
if (entry.expires < now)
|
|
25
|
+
return "expired";
|
|
26
|
+
return "active";
|
|
27
|
+
}
|
|
28
|
+
function statusBadge(status) {
|
|
29
|
+
switch (status) {
|
|
30
|
+
case "active": return `${ANSI.green}[active]${ANSI.reset}`;
|
|
31
|
+
case "rate-limited": return `${ANSI.yellow}[rate-limited]${ANSI.reset}`;
|
|
32
|
+
case "cooling-down": return `${ANSI.yellow}[cooling-down]${ANSI.reset}`;
|
|
33
|
+
case "expired": return `${ANSI.red}[expired]${ANSI.reset}`;
|
|
34
|
+
default: return `${ANSI.dim}[unknown]${ANSI.reset}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function relativeTime(ts) {
|
|
38
|
+
if (!ts)
|
|
39
|
+
return "never";
|
|
40
|
+
const diff = Date.now() - ts;
|
|
41
|
+
const mins = Math.floor(diff / 60_000);
|
|
42
|
+
if (mins < 1)
|
|
43
|
+
return "just now";
|
|
44
|
+
if (mins < 60)
|
|
45
|
+
return `${mins}m ago`;
|
|
46
|
+
const hrs = Math.floor(mins / 60);
|
|
47
|
+
if (hrs < 24)
|
|
48
|
+
return `${hrs}h ago`;
|
|
49
|
+
const days = Math.floor(hrs / 24);
|
|
50
|
+
if (days < 7)
|
|
51
|
+
return `${days}d ago`;
|
|
52
|
+
return new Date(ts).toLocaleDateString();
|
|
53
|
+
}
|
|
54
|
+
function countdownTime(until) {
|
|
55
|
+
if (!until)
|
|
56
|
+
return "";
|
|
57
|
+
const ms = until - Date.now();
|
|
58
|
+
if (ms <= 0)
|
|
59
|
+
return "";
|
|
60
|
+
const secs = Math.ceil(ms / 1000);
|
|
61
|
+
if (secs < 60)
|
|
62
|
+
return ` (${secs}s)`;
|
|
63
|
+
return ` (${Math.ceil(secs / 60)}m)`;
|
|
64
|
+
}
|
|
65
|
+
async function showMainMenu(accounts) {
|
|
66
|
+
const items = [
|
|
67
|
+
{ label: "Actions", value: { type: "cancel" }, kind: "heading" },
|
|
68
|
+
{ label: "Add another account", value: { type: "add" }, color: "cyan" },
|
|
69
|
+
{ label: "", value: { type: "cancel" }, separator: true },
|
|
70
|
+
{ label: `Accounts (${accounts.length})`, value: { type: "cancel" }, kind: "heading" },
|
|
71
|
+
...accounts.map((acc) => {
|
|
72
|
+
const label = acc.entry.email || `Account ${acc.index + 1}`;
|
|
73
|
+
const badge = statusBadge(acc.status);
|
|
74
|
+
const countdown = acc.status !== "active" ? countdownTime(acc.entry.rateLimitedUntil ?? acc.entry.coolingDownUntil) : "";
|
|
75
|
+
return {
|
|
76
|
+
label: `${acc.index + 1}. ${label} ${badge}${countdown}`,
|
|
77
|
+
hint: acc.entry.lastUsed ? relativeTime(acc.entry.lastUsed) : "",
|
|
78
|
+
value: { type: "select", account: acc },
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
{ label: "", value: { type: "cancel" }, separator: true },
|
|
82
|
+
{ label: "Danger zone", value: { type: "cancel" }, kind: "heading" },
|
|
83
|
+
{ label: "Delete all accounts", value: { type: "delete-all" }, color: "red" },
|
|
84
|
+
];
|
|
85
|
+
while (true) {
|
|
86
|
+
const result = await select(items, {
|
|
87
|
+
message: "OpenAI Codex accounts",
|
|
88
|
+
subtitle: "Select an action or account",
|
|
89
|
+
clearScreen: true,
|
|
90
|
+
});
|
|
91
|
+
if (!result)
|
|
92
|
+
return { type: "cancel" };
|
|
93
|
+
if (result.type === "delete-all") {
|
|
94
|
+
const confirmed = await confirm("Delete ALL accounts? This cannot be undone.");
|
|
95
|
+
if (!confirmed)
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function showAccountDetail(acc) {
|
|
102
|
+
const label = acc.entry.email || `Account ${acc.index + 1}`;
|
|
103
|
+
const badge = statusBadge(acc.status);
|
|
104
|
+
while (true) {
|
|
105
|
+
const result = await select([
|
|
106
|
+
{ label: "Back", value: "back" },
|
|
107
|
+
{ label: "Refresh token (re-authenticate)", value: "refresh-token", color: "cyan" },
|
|
108
|
+
{ label: "Delete this account", value: "delete", color: "red" },
|
|
109
|
+
], {
|
|
110
|
+
message: `${label} ${badge}`,
|
|
111
|
+
subtitle: [
|
|
112
|
+
`ID: ${acc.entry.accountId}`,
|
|
113
|
+
`Last used: ${relativeTime(acc.entry.lastUsed)}`,
|
|
114
|
+
`Expires: ${new Date(acc.entry.expires).toLocaleString()}`,
|
|
115
|
+
].join(" | "),
|
|
116
|
+
clearScreen: true,
|
|
117
|
+
});
|
|
118
|
+
if (result === "delete") {
|
|
119
|
+
const confirmed = await confirm(`Delete ${label}?`);
|
|
120
|
+
if (!confirmed)
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (result === "refresh-token") {
|
|
124
|
+
const confirmed = await confirm(`Re-authenticate ${label}? You will be redirected to log in again.`);
|
|
125
|
+
if (!confirmed)
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
return result ?? "cancel";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Fallback for non-TTY (headless server)
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
async function showMenuFallback(accounts) {
|
|
135
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
136
|
+
try {
|
|
137
|
+
process.stdout.write("\n");
|
|
138
|
+
process.stdout.write(` OpenAI Codex accounts (${accounts.length})\n`);
|
|
139
|
+
process.stdout.write(" ─────────────────────────────\n");
|
|
140
|
+
for (const acc of accounts) {
|
|
141
|
+
const label = acc.entry.email || `Account ${acc.index + 1}`;
|
|
142
|
+
const status = acc.status;
|
|
143
|
+
process.stdout.write(` ${acc.index + 1}. ${label} [${status}] last used: ${relativeTime(acc.entry.lastUsed)}\n`);
|
|
144
|
+
}
|
|
145
|
+
process.stdout.write("\n");
|
|
146
|
+
while (true) {
|
|
147
|
+
const answer = await rl.question(" (a)dd account (d)elete <n> (da) delete all (q)uit: ");
|
|
148
|
+
const parts = answer.trim().toLowerCase().split(/\s+/);
|
|
149
|
+
const cmd = parts[0] ?? "";
|
|
150
|
+
if (cmd === "a" || cmd === "add")
|
|
151
|
+
return { action: "add" };
|
|
152
|
+
if (cmd === "q" || cmd === "quit" || cmd === "")
|
|
153
|
+
return { action: "done" };
|
|
154
|
+
if ((cmd === "da" || cmd === "delete-all")) {
|
|
155
|
+
const confirm = await rl.question(" Delete ALL accounts? (yes/no): ");
|
|
156
|
+
if (confirm.trim().toLowerCase() === "yes")
|
|
157
|
+
return { action: "delete-all" };
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (cmd === "d" || cmd === "delete") {
|
|
161
|
+
const idxStr = parts[1];
|
|
162
|
+
const idx = idxStr ? parseInt(idxStr, 10) - 1 : NaN;
|
|
163
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= accounts.length) {
|
|
164
|
+
process.stdout.write(" Usage: d <account number>\n");
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const label = accounts[idx].entry.email || `Account ${idx + 1}`;
|
|
168
|
+
const confirmStr = await rl.question(` Delete ${label}? (yes/no): `);
|
|
169
|
+
if (confirmStr.trim().toLowerCase() === "yes") {
|
|
170
|
+
return { action: "delete-one", index: idx };
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
process.stdout.write(" Unknown command. Try: a | d <n> | da | q\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
rl.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Show the account management menu.
|
|
183
|
+
*
|
|
184
|
+
* Loads the current pool, displays it with live status, and applies the
|
|
185
|
+
* user's chosen action (delete, refresh-token, delete-all, or add).
|
|
186
|
+
*
|
|
187
|
+
* Returns the final `ManageResult` after the user is done. Callers should
|
|
188
|
+
* loop as long as `action !== "done"` && `action !== "add"`.
|
|
189
|
+
*/
|
|
190
|
+
export async function showAccountManager() {
|
|
191
|
+
const pool = AccountPool.load();
|
|
192
|
+
const accounts = pool.getAccounts();
|
|
193
|
+
if (accounts.length === 0) {
|
|
194
|
+
process.stdout.write("\n No accounts in pool — proceeding to add one.\n\n");
|
|
195
|
+
return { action: "add" };
|
|
196
|
+
}
|
|
197
|
+
const displayAccounts = accounts.map((entry, index) => ({
|
|
198
|
+
entry,
|
|
199
|
+
index,
|
|
200
|
+
status: getAccountStatus(entry),
|
|
201
|
+
}));
|
|
202
|
+
if (!isTTY()) {
|
|
203
|
+
return showMenuFallback(displayAccounts);
|
|
204
|
+
}
|
|
205
|
+
// TTY interactive loop
|
|
206
|
+
while (true) {
|
|
207
|
+
const mainAction = await showMainMenu(displayAccounts);
|
|
208
|
+
if (mainAction.type === "cancel")
|
|
209
|
+
return { action: "done" };
|
|
210
|
+
if (mainAction.type === "add")
|
|
211
|
+
return { action: "add" };
|
|
212
|
+
if (mainAction.type === "delete-all") {
|
|
213
|
+
// Remove all accounts from pool and save
|
|
214
|
+
for (let i = accounts.length - 1; i >= 0; i--) {
|
|
215
|
+
accounts.splice(i, 1);
|
|
216
|
+
}
|
|
217
|
+
pool.save();
|
|
218
|
+
process.stdout.write("\n All accounts removed.\n\n");
|
|
219
|
+
return { action: "add" };
|
|
220
|
+
}
|
|
221
|
+
if (mainAction.type === "select") {
|
|
222
|
+
const detail = await showAccountDetail(mainAction.account);
|
|
223
|
+
if (detail === "back")
|
|
224
|
+
continue;
|
|
225
|
+
if (detail === "delete") {
|
|
226
|
+
return { action: "delete-one", index: mainAction.account.index };
|
|
227
|
+
}
|
|
228
|
+
if (detail === "refresh-token") {
|
|
229
|
+
return { action: "refresh-token", index: mainAction.account.index };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Apply a ManageResult to the pool and persist.
|
|
236
|
+
* Returns true if the caller should proceed to add a new account.
|
|
237
|
+
*/
|
|
238
|
+
export function applyManageResult(result, pool) {
|
|
239
|
+
const accounts = pool.getAccounts();
|
|
240
|
+
if (result.action === "delete-one") {
|
|
241
|
+
accounts.splice(result.index, 1);
|
|
242
|
+
pool.save();
|
|
243
|
+
const label = accounts[result.index]?.email || `Account ${result.index + 1}`;
|
|
244
|
+
process.stdout.write(`\n Account removed.\n\n`);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (result.action === "delete-all") {
|
|
248
|
+
accounts.splice(0, accounts.length);
|
|
249
|
+
pool.save();
|
|
250
|
+
process.stdout.write("\n All accounts removed.\n\n");
|
|
251
|
+
return true; // proceed to add
|
|
252
|
+
}
|
|
253
|
+
if (result.action === "add" || result.action === "refresh-token") {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=account-menu.js.map
|