@kenkaiiii/gg-core 5.93.0 → 5.95.0

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.
@@ -1,1149 +1,959 @@
1
1
  import {
2
2
  getAppPaths
3
- } from "./chunk-2X5L6U4Z.js";
3
+ } from "./chunk-GXOFTHM5.js";
4
4
 
5
- // src/oauth/kimi.ts
6
- import { execFileSync } from "child_process";
7
- import { randomUUID } from "crypto";
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
- import { arch, hostname, release, type } from "os";
10
- import path from "path";
11
-
12
- // src/oauth/request-timeout.ts
13
- var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
14
- function oauthRequestSignal() {
15
- return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
16
- }
17
-
18
- // src/oauth/kimi.ts
19
- var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
20
- var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
21
- var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
22
- var KIMI_PLATFORM = "kimi_code_cli";
23
- var DEFAULT_KIMI_VERSION = "1.0.11";
24
- var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
25
- function oauthHost() {
26
- const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
27
- return host.replace(/\/+$/, "");
28
- }
29
- function kimiCodeBaseUrl() {
30
- return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
31
- }
32
- function kimiVersion() {
33
- const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
34
- return asciiHeader(v, DEFAULT_KIMI_VERSION);
35
- }
36
- function asciiHeader(value, fallback = "unknown") {
37
- const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
38
- return cleaned.length > 0 ? cleaned : fallback;
39
- }
40
- function macOsProductVersion() {
41
- try {
42
- const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
43
- encoding: "utf-8",
44
- timeout: 1e3
45
- }).trim();
46
- return version.length > 0 ? version : void 0;
47
- } catch {
48
- return void 0;
49
- }
50
- }
51
- function deviceModel() {
52
- const os = type();
53
- const version = release();
54
- const osArch = arch();
55
- if (os === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
56
- if (os === "Windows_NT") return `Windows ${version} ${osArch}`;
57
- return `${os} ${version} ${osArch}`.trim();
58
- }
59
- function deviceId() {
60
- const idPath = path.join(getAppPaths().agentDir, "kimi_device_id");
61
- if (existsSync(idPath)) {
62
- try {
63
- const text = readFileSync(idPath, "utf-8").trim();
64
- if (text.length > 0) return text;
65
- } catch {
66
- }
67
- }
68
- const id = randomUUID();
5
+ // src/kimi-region.ts
6
+ import { GG_CODER_USER_AGENT } from "@kenkaiiii/gg-ai";
7
+ var KIMI_CODE_DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
8
+ var KIMI_CODE_OVERSEAS_BASE_URL = "https://api.kimi.ai/coding/v1";
9
+ var KIMI_CODE_BASE_URLS = [KIMI_CODE_DEFAULT_BASE_URL, KIMI_CODE_OVERSEAS_BASE_URL];
10
+ async function probeHost(baseUrl, apiKey, fetchImpl, timeoutMs) {
69
11
  try {
70
- mkdirSync(getAppPaths().agentDir, { recursive: true, mode: 448 });
71
- writeFileSync(idPath, id, { encoding: "utf-8", mode: 384 });
12
+ const res = await fetchImpl(`${baseUrl}/models`, {
13
+ headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": GG_CODER_USER_AGENT },
14
+ signal: AbortSignal.timeout(timeoutMs)
15
+ });
16
+ if (res.ok) return "authorized";
17
+ if (res.status === 401 || res.status === 403) return "unauthorized";
18
+ return "inconclusive";
72
19
  } catch {
20
+ return "inconclusive";
73
21
  }
74
- return id;
75
22
  }
76
- function deviceHeaders() {
77
- return {
78
- "X-Msh-Platform": KIMI_PLATFORM,
79
- "X-Msh-Version": kimiVersion(),
80
- "X-Msh-Device-Name": asciiHeader(hostname()),
81
- "X-Msh-Device-Model": asciiHeader(deviceModel()),
82
- "X-Msh-Os-Version": asciiHeader(release()),
83
- "X-Msh-Device-Id": deviceId()
84
- };
85
- }
86
- function kimiCodingHeaders() {
87
- return {
88
- "User-Agent": `kimi-code-cli/${kimiVersion()}`,
89
- ...deviceHeaders()
90
- };
23
+ async function detectKimiCodeBaseUrl(apiKey, options = {}) {
24
+ const fetchImpl = options.fetch ?? fetch;
25
+ const timeoutMs = options.timeoutMs ?? 5e3;
26
+ const results = await Promise.all(
27
+ KIMI_CODE_BASE_URLS.map((url) => probeHost(url, apiKey, fetchImpl, timeoutMs))
28
+ );
29
+ const index = results.indexOf("authorized");
30
+ return index === -1 ? void 0 : KIMI_CODE_BASE_URLS[index];
91
31
  }
92
32
  function isKimiCodingEndpoint(baseUrl) {
93
33
  if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
94
- const normalized = baseUrl.replace(/\/+$/, "");
95
- return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
96
- }
97
- async function postForm(endpoint, params) {
98
- const response = await fetch(`${oauthHost()}${endpoint}`, {
99
- method: "POST",
100
- headers: {
101
- ...deviceHeaders(),
102
- "Content-Type": "application/x-www-form-urlencoded",
103
- Accept: "application/json"
104
- },
105
- body: new URLSearchParams(params).toString(),
106
- signal: oauthRequestSignal()
107
- });
108
- let data = {};
109
- try {
110
- const parsed = await response.json();
111
- if (parsed && typeof parsed === "object") data = parsed;
112
- } catch {
113
- }
114
- return { status: response.status, data };
115
- }
116
- function errorDetail(data) {
117
- const desc = data.error_description ?? data.message ?? data.error;
118
- return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
119
- }
120
- function credsFromTokenResponse(data, opts) {
121
- const accessToken = data.access_token;
122
- const responseRefreshToken = data.refresh_token;
123
- const expiresIn = Number(data.expires_in);
124
- if (typeof accessToken !== "string" || accessToken.length === 0) {
125
- throw new Error("Kimi OAuth response missing access_token.");
126
- }
127
- const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
128
- if (refreshToken.length === 0) {
129
- throw new Error("Kimi OAuth response missing refresh_token.");
130
- }
131
- if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
132
- throw new Error("Kimi OAuth response missing or invalid expires_in.");
133
- }
134
- return {
135
- accessToken,
136
- refreshToken,
137
- expiresAt: Date.now() + expiresIn * 1e3,
138
- expiresIn,
139
- baseUrl: kimiCodeBaseUrl()
140
- };
141
- }
142
- async function requestDeviceAuthorization() {
143
- const { status, data } = await postForm("/api/oauth/device_authorization", {
144
- client_id: CLIENT_ID
145
- });
146
- if (status !== 200) {
147
- throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
148
- }
149
- const userCode = data.user_code;
150
- const deviceCode = data.device_code;
151
- const verificationUriComplete = data.verification_uri_complete;
152
- if (typeof userCode !== "string" || typeof deviceCode !== "string") {
153
- throw new Error("Kimi device authorization response missing user_code/device_code.");
154
- }
155
- return {
156
- userCode,
157
- deviceCode,
158
- verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
159
- verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
160
- interval: Number(data.interval ?? 5) || 5
161
- };
162
- }
163
- async function pollDeviceToken(deviceCode) {
164
- const { status, data } = await postForm("/api/oauth/token", {
165
- client_id: CLIENT_ID,
166
- device_code: deviceCode,
167
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
168
- });
169
- if (status === 200 && typeof data.access_token === "string") {
170
- return { kind: "success", creds: credsFromTokenResponse(data) };
171
- }
172
- if (status >= 500) {
173
- throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
174
- }
175
- const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
176
- switch (errorCode) {
177
- case "authorization_pending":
178
- return { kind: "pending" };
179
- case "slow_down":
180
- return { kind: "slow_down" };
181
- case "expired_token":
182
- return { kind: "expired" };
183
- case "access_denied":
184
- return { kind: "denied" };
185
- default:
186
- throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
187
- }
188
- }
189
- function sleep(ms) {
190
- return new Promise((resolve) => {
191
- setTimeout(resolve, ms);
192
- });
193
- }
194
- async function loginKimi(callbacks) {
195
- const auth = await requestDeviceAuthorization();
196
- callbacks.onStatus(
197
- `Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
198
- );
199
- callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
200
- callbacks.onStatus("Waiting for you to authorize in the browser...");
201
- const deadline = Date.now() + DEVICE_TIMEOUT_MS;
202
- let interval = Math.max(auth.interval, 1);
203
- while (Date.now() < deadline) {
204
- await sleep(interval * 1e3);
205
- const result = await pollDeviceToken(auth.deviceCode);
206
- if (result.kind === "success") return result.creds;
207
- if (result.kind === "denied") {
208
- throw new Error("Kimi authorization was denied.");
209
- }
210
- if (result.kind === "expired") {
211
- throw new Error("Kimi device code expired. Please run login again.");
212
- }
213
- if (result.kind === "slow_down") {
214
- interval += 5;
215
- }
216
- }
217
- throw new Error("Kimi login timed out. Please run login again.");
218
- }
219
- async function refreshKimiToken(refreshToken) {
220
- const { status, data } = await postForm("/api/oauth/token", {
221
- client_id: CLIENT_ID,
222
- grant_type: "refresh_token",
223
- refresh_token: refreshToken
224
- });
225
- if (status === 200 && typeof data.access_token === "string") {
226
- return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
227
- }
228
- const errorCode = typeof data.error === "string" ? data.error : "";
229
- throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
34
+ return /^https:\/\/api\.kimi\.(com|ai)\/coding(\/|$)/i.test(baseUrl);
230
35
  }
231
36
 
232
37
  // src/auth-storage.ts
233
- import fs3 from "fs/promises";
234
- import { readFileSync as readFileSync2 } from "fs";
235
- import crypto5 from "crypto";
38
+ import fs6 from "fs/promises";
39
+ import { readFileSync } from "fs";
40
+ import crypto4 from "crypto";
236
41
 
237
- // src/oauth/anthropic.ts
42
+ // src/claude-code/runtime.ts
43
+ import { spawn } from "child_process";
238
44
  import crypto2 from "crypto";
45
+ import fs2 from "fs";
46
+ import fsp2 from "fs/promises";
47
+ import path from "path";
239
48
 
240
- // src/oauth/pkce.ts
241
- function base64urlEncode(bytes) {
242
- let binary = "";
243
- for (const byte of bytes) {
244
- binary += String.fromCharCode(byte);
49
+ // src/claude-code/tar.ts
50
+ import fs from "fs";
51
+ import fsp from "fs/promises";
52
+ import zlib from "zlib";
53
+ var BLOCK = 512;
54
+ function readString(buf, offset, length) {
55
+ const end = buf.indexOf(0, offset);
56
+ return buf.toString("utf8", offset, end === -1 || end > offset + length ? offset + length : end);
57
+ }
58
+ function readSize(buf, offset) {
59
+ if (buf[offset] & 128) {
60
+ let value = buf[offset] & 127;
61
+ for (let i = 1; i < 12; i++) value = value * 256 + buf[offset + i];
62
+ return value;
63
+ }
64
+ const text = readString(buf, offset, 12).trim();
65
+ return text ? parseInt(text, 8) : 0;
66
+ }
67
+ function paxPath(data) {
68
+ let pos = 0;
69
+ while (pos < data.length) {
70
+ const space = data.indexOf(32, pos);
71
+ if (space === -1) break;
72
+ const len = parseInt(data.toString("utf8", pos, space), 10);
73
+ if (!Number.isFinite(len) || len <= 0) break;
74
+ const record = data.toString("utf8", space + 1, pos + len - 1);
75
+ const eq = record.indexOf("=");
76
+ if (eq !== -1 && record.slice(0, eq) === "path") return record.slice(eq + 1);
77
+ pos += len;
245
78
  }
246
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
79
+ return void 0;
247
80
  }
248
- async function generatePKCE() {
249
- const verifierBytes = new Uint8Array(32);
250
- crypto.getRandomValues(verifierBytes);
251
- const verifier = base64urlEncode(verifierBytes);
252
- const data = new TextEncoder().encode(verifier);
253
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
254
- const challenge = base64urlEncode(new Uint8Array(hashBuffer));
255
- return { verifier, challenge };
81
+ async function extractFileFromTarGz(tgzPath, entryName, destPath) {
82
+ const input = fs.createReadStream(tgzPath).pipe(zlib.createGunzip());
83
+ let pending = Buffer.alloc(0);
84
+ let remaining = 0;
85
+ let padding = 0;
86
+ let kind = "skip";
87
+ let meta = [];
88
+ let overridePath;
89
+ let out;
90
+ let found = false;
91
+ try {
92
+ for await (const chunk of input) {
93
+ pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
94
+ let pos = 0;
95
+ while (pos < pending.length) {
96
+ if (remaining > 0) {
97
+ const take = Math.min(remaining, pending.length - pos);
98
+ const slice = pending.subarray(pos, pos + take);
99
+ if (kind === "target") await out.write(slice);
100
+ else if (kind === "pax" || kind === "longname") meta.push(Buffer.from(slice));
101
+ pos += take;
102
+ remaining -= take;
103
+ if (remaining === 0) {
104
+ if (kind === "target") {
105
+ await out.close();
106
+ out = void 0;
107
+ found = true;
108
+ } else if (kind === "pax") {
109
+ overridePath = paxPath(Buffer.concat(meta)) ?? overridePath;
110
+ } else if (kind === "longname") {
111
+ overridePath = readString(Buffer.concat(meta), 0, Number.MAX_SAFE_INTEGER);
112
+ }
113
+ }
114
+ continue;
115
+ }
116
+ if (padding > 0) {
117
+ const skip = Math.min(padding, pending.length - pos);
118
+ pos += skip;
119
+ padding -= skip;
120
+ continue;
121
+ }
122
+ if (found) break;
123
+ if (pending.length - pos < BLOCK) break;
124
+ const header = pending.subarray(pos, pos + BLOCK);
125
+ pos += BLOCK;
126
+ if (header.every((b) => b === 0)) continue;
127
+ const size = readSize(header, 124);
128
+ const type = String.fromCharCode(header[156] || 48);
129
+ let name = readString(header, 0, 100);
130
+ if (readString(header, 257, 6) === "ustar") {
131
+ const prefix = readString(header, 345, 155);
132
+ if (prefix) name = `${prefix}/${name}`;
133
+ }
134
+ remaining = size;
135
+ padding = (BLOCK - size % BLOCK) % BLOCK;
136
+ meta = [];
137
+ if (type === "x") kind = "pax";
138
+ else if (type === "L") kind = "longname";
139
+ else {
140
+ const path4 = overridePath ?? name;
141
+ overridePath = void 0;
142
+ if ((type === "0" || type === "\0" || type === "7") && path4 === entryName) {
143
+ kind = "target";
144
+ out = await fsp.open(destPath, "w", 493);
145
+ if (size === 0) {
146
+ await out.close();
147
+ out = void 0;
148
+ found = true;
149
+ }
150
+ } else {
151
+ kind = "skip";
152
+ }
153
+ }
154
+ }
155
+ pending = pos < pending.length ? Buffer.from(pending.subarray(pos)) : Buffer.alloc(0);
156
+ if (found) break;
157
+ }
158
+ if (!found && out) throw new Error(`Archive ended inside ${entryName}`);
159
+ return found;
160
+ } finally {
161
+ if (out) await out.close().catch(() => {
162
+ });
163
+ input.destroy();
164
+ }
256
165
  }
257
166
 
258
- // src/claude-code-version.ts
259
- import {
260
- getClaudeCodeVersion,
261
- getClaudeCliUserAgent,
262
- pendingClaudeCodeVersionRefresh
263
- } from "@kenkaiiii/gg-ai";
264
-
265
- // src/oauth/anthropic.ts
266
- var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
267
- var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
268
- var TOKEN_URLS = [
269
- "https://platform.claude.com/v1/oauth/token",
270
- "https://console.anthropic.com/v1/oauth/token"
271
- ];
272
- var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
273
- var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
274
- async function postTokenRequest(body, label) {
275
- const encoded = JSON.stringify(body);
276
- const headers = {
277
- "Content-Type": "application/json",
278
- "User-Agent": await getClaudeCliUserAgent(),
279
- "anthropic-beta": "oauth-2025-04-20"
280
- };
281
- let lastError = null;
282
- for (const url of TOKEN_URLS) {
283
- let response;
284
- try {
285
- response = await fetch(url, {
286
- method: "POST",
287
- headers,
288
- body: encoded,
289
- signal: oauthRequestSignal()
290
- });
291
- } catch (err) {
292
- lastError = err instanceof Error ? err : new Error(String(err));
293
- continue;
294
- }
295
- if (response.ok) {
296
- return await response.json();
297
- }
298
- const text = await response.text();
299
- if (response.status >= 400 && response.status < 500) {
300
- throw new Error(`Anthropic ${label} failed (${response.status}): ${text}`);
167
+ // src/claude-code/manifest.json
168
+ var manifest_default = {
169
+ sdkVersion: "0.3.318",
170
+ claudeCodeVersion: "2.1.318",
171
+ platforms: {
172
+ "darwin-arm64": {
173
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.318.tgz",
174
+ integrity: "sha512-AWoXeDXzAMD3RaA21lWuHvCrKd8xmbTTmKjovninn4j07jheoVSMzFbCFjpp4kk5TdhT36JtWPkAxvjNMbcO8w==",
175
+ bin: "claude"
176
+ },
177
+ "darwin-x64": {
178
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.318.tgz",
179
+ integrity: "sha512-6CkZ5PCdebSY/LePp16sf/HYRtM6zVuxtn8qYICFS5KUZFsjccJJ9swVsOdHXNHi4SWZcSmIFTwoUbfFCzisRw==",
180
+ bin: "claude"
181
+ },
182
+ "linux-arm64": {
183
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.318.tgz",
184
+ integrity: "sha512-mBm4su17aMitLBBCQj3bHdejyUDY6z/ormGih+HG24N689RSbdKECjGjkT87tDBIR/4jyoqmuSeHqM1DI8TJlg==",
185
+ bin: "claude"
186
+ },
187
+ "linux-arm64-musl": {
188
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.318.tgz",
189
+ integrity: "sha512-l4LSYFOrvlYCrUImGfxMTQ0wg+T3s9CBhrBvPF/0oJGKZg+QnxHHU8jUVm9e9/xYmDRTtqFIrr4id/5AfH4XGw==",
190
+ bin: "claude"
191
+ },
192
+ "linux-x64": {
193
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.318.tgz",
194
+ integrity: "sha512-+mE+ueBD3oRFdaG16rd0h+LMjdeivGxL0pr/+FZK7JeLWVu+yPSw/f/X02b9oK36/DWykiFTu2G6HnJ9/+YO6w==",
195
+ bin: "claude"
196
+ },
197
+ "linux-x64-musl": {
198
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.318.tgz",
199
+ integrity: "sha512-SAF3lj/2OzTU84p+OOA13QEKA9+5fYAw0M9E7AJjdnRmVFGqQfbV7Do1i1f9Sj0mIubB3rLTRphGRw8O4HZjgA==",
200
+ bin: "claude"
201
+ },
202
+ "win32-arm64": {
203
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.318.tgz",
204
+ integrity: "sha512-19iVSZ4379QbZU1XnGUtfFvhKS1WzS8H1sbU/DbrzqbJ5Kq1eB1drj+obB1fCxTh6m26tRPj0zMJmdeu/jk3Gw==",
205
+ bin: "claude.exe"
206
+ },
207
+ "win32-x64": {
208
+ url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.318.tgz",
209
+ integrity: "sha512-ZdKVWpyVNVt0cXrOf8sUBaxcxLEAP+oGMdQDLBWr/BqOxTWLy7TBWOj1kzw+wVg/9WIkjkgQWeJzGXPWulR1sA==",
210
+ bin: "claude.exe"
301
211
  }
302
- lastError = new Error(`Anthropic ${label} failed (${response.status}): ${text}`);
303
212
  }
304
- throw lastError ?? new Error(`Anthropic ${label} failed: all endpoints unreachable`);
213
+ };
214
+
215
+ // src/claude-code/runtime.ts
216
+ var CLAUDE_CODE_MANIFEST = manifest_default;
217
+ var CLAUDE_CODE_AUTH_KEY = "anthropic-claude-code";
218
+ var CLAUDE_CODE_AUTH_MARKER = { via: "claude-code" };
219
+ function isClaudeCodeAuthMarker(value) {
220
+ return value?.via === CLAUDE_CODE_AUTH_MARKER.via;
221
+ }
222
+ function isLegacyClaudeOAuth(value) {
223
+ const token = value?.accessToken;
224
+ return typeof token === "string" && token.startsWith("sk-ant-oat");
225
+ }
226
+ function isMusl() {
227
+ if (process.platform !== "linux") return false;
228
+ const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
229
+ return report != null && report.header?.glibcVersionRuntime === void 0;
230
+ }
231
+ function claudeCodePlatformKey(info = { platform: process.platform, arch: process.arch, musl: isMusl() }) {
232
+ const arch = info.arch === "arm64" || info.arch === "x64" ? info.arch : null;
233
+ if (!arch) return null;
234
+ if (info.platform === "darwin" || info.platform === "win32") return `${info.platform}-${arch}`;
235
+ if (info.platform === "linux") return `linux-${arch}${info.musl ? "-musl" : ""}`;
236
+ return null;
237
+ }
238
+ function resolveAsset(opts) {
239
+ const manifest = opts.manifest ?? CLAUDE_CODE_MANIFEST;
240
+ const key = opts.platformKey ?? claudeCodePlatformKey();
241
+ const asset = key ? manifest.platforms[key] : void 0;
242
+ if (!asset) {
243
+ throw new Error(
244
+ `Claude Code isn't available for ${process.platform}-${process.arch}. Use an Anthropic API key instead.`
245
+ );
246
+ }
247
+ return { manifest, asset };
305
248
  }
306
- function toCredentials(data) {
307
- return {
308
- accessToken: data.access_token,
309
- refreshToken: data.refresh_token,
310
- expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
311
- };
249
+ function claudeCodeHome(opts = {}) {
250
+ return opts.home ?? getAppPaths().claudeCodeDir;
312
251
  }
313
- async function loginAnthropic(callbacks) {
314
- const { verifier, challenge } = await generatePKCE();
315
- const state = crypto2.randomBytes(16).toString("hex");
316
- const params = new URLSearchParams({
317
- code: "true",
318
- client_id: CLIENT_ID2,
319
- response_type: "code",
320
- redirect_uri: REDIRECT_URI,
321
- scope: SCOPES,
322
- code_challenge: challenge,
323
- code_challenge_method: "S256",
324
- state
325
- });
326
- const authUrl = `${AUTHORIZE_URL}?${params}`;
327
- callbacks.onOpenUrl(authUrl);
328
- const raw = await callbacks.onPromptCode("Paste the code from the browser (format: code#state):");
329
- const parts = raw.trim().split("#");
330
- if (parts.length !== 2 || !parts[0] || parts[1] !== state) {
331
- throw new Error("Invalid code or state mismatch. Please try again.");
332
- }
333
- return exchangeAnthropicCode(parts[0], parts[1], verifier);
334
- }
335
- async function exchangeAnthropicCode(code, state, verifier) {
336
- const data = await postTokenRequest(
337
- {
338
- grant_type: "authorization_code",
339
- client_id: CLIENT_ID2,
340
- code,
341
- state,
342
- redirect_uri: REDIRECT_URI,
343
- code_verifier: verifier
344
- },
345
- "token exchange"
346
- );
347
- return toCredentials(data);
348
- }
349
- async function refreshAnthropicToken(refreshToken) {
350
- const data = await postTokenRequest(
351
- {
352
- grant_type: "refresh_token",
353
- client_id: CLIENT_ID2,
354
- refresh_token: refreshToken
355
- },
356
- "token refresh"
357
- );
358
- return toCredentials(data);
252
+ function claudeCodeConfigDir(opts = {}) {
253
+ return opts.home ? path.join(opts.home, "config") : getAppPaths().claudeCodeConfigDir;
359
254
  }
360
-
361
- // src/oauth/openai.ts
362
- import http from "http";
363
- import crypto3 from "crypto";
364
- var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
365
- var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
366
- var TOKEN_URL = "https://auth.openai.com/oauth/token";
367
- var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
368
- var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
369
- var JWT_CLAIM_PATH = "https://api.openai.com/auth";
370
- var CALLBACK_TIMEOUT_MS = 12e4;
371
- var MAX_PASTE_ATTEMPTS = 3;
372
- var MIN_RAW_CODE_LENGTH = 10;
373
- async function loginOpenAI(callbacks) {
374
- const { verifier, challenge } = await generatePKCE();
375
- const state = crypto3.randomBytes(16).toString("hex");
376
- const url = new URL(AUTHORIZE_URL2);
377
- url.searchParams.set("response_type", "code");
378
- url.searchParams.set("client_id", CLIENT_ID3);
379
- url.searchParams.set("redirect_uri", REDIRECT_URI2);
380
- url.searchParams.set("scope", SCOPE);
381
- url.searchParams.set("code_challenge", challenge);
382
- url.searchParams.set("code_challenge_method", "S256");
383
- url.searchParams.set("state", state);
384
- url.searchParams.set("prompt", "login");
385
- url.searchParams.set("id_token_add_organizations", "true");
386
- url.searchParams.set("codex_cli_simplified_flow", "true");
387
- url.searchParams.set("originator", "ggcoder");
388
- const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
389
- const creds = await exchangeOpenAICode(code, verifier);
390
- const accountId = getAccountId(creds.accessToken);
391
- if (!accountId) {
392
- throw new Error("Failed to extract accountId from OpenAI token.");
393
- }
394
- creds.accountId = accountId;
395
- return creds;
396
- }
397
- function parseAuthorizationInput(input) {
398
- const value = input.trim();
399
- if (!value) return {};
255
+ function claudeCodeBinaryPath(opts = {}) {
256
+ const { manifest, asset } = resolveAsset(opts);
257
+ return path.join(claudeCodeHome(opts), manifest.claudeCodeVersion, asset.bin);
258
+ }
259
+ function resolveClaudeCodeBinary(opts = {}) {
260
+ let binary;
400
261
  try {
401
- const url = new URL(value);
402
- return {
403
- code: url.searchParams.get("code") ?? void 0,
404
- state: url.searchParams.get("state") ?? void 0
405
- };
262
+ binary = claudeCodeBinaryPath(opts);
406
263
  } catch {
264
+ return null;
407
265
  }
408
- if (value.includes("#")) {
409
- const [code, state] = value.split("#", 2);
410
- return { code, state };
411
- }
412
- if (value.includes("code=")) {
413
- const params = new URLSearchParams(value);
414
- return {
415
- code: params.get("code") ?? void 0,
416
- state: params.get("state") ?? void 0
417
- };
418
- }
419
- if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
420
- return { code: value };
421
- }
422
- function decodeJwt(token) {
423
266
  try {
424
- const parts = token.split(".");
425
- if (parts.length !== 3) return null;
426
- const decoded = atob(parts[1]);
427
- return JSON.parse(decoded);
267
+ return fs2.statSync(binary).isFile() ? binary : null;
428
268
  } catch {
429
269
  return null;
430
270
  }
431
271
  }
432
- function getAccountId(accessToken) {
433
- const payload = decodeJwt(accessToken);
434
- const auth = payload?.[JWT_CLAIM_PATH];
435
- const accountId = auth?.chatgpt_account_id;
436
- return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
437
- }
438
- async function acquireAuthorizationCode(authUrl, expectedState, callbacks) {
439
- callbacks.onOpenUrl(authUrl);
440
- callbacks.onStatus("Waiting for browser callback...");
441
- const done = new AbortController();
272
+ var installs = /* @__PURE__ */ new Map();
273
+ async function ensureClaudeCodeBinary(opts = {}) {
274
+ const existing = resolveClaudeCodeBinary(opts);
275
+ if (existing) return existing;
276
+ const target = claudeCodeBinaryPath(opts);
277
+ let install = installs.get(target);
278
+ if (!install) {
279
+ install = installClaudeCode(opts).finally(() => installs.delete(target));
280
+ installs.set(target, install);
281
+ }
282
+ return install;
283
+ }
284
+ async function installClaudeCode(opts) {
285
+ const { manifest, asset } = resolveAsset(opts);
286
+ const home = claudeCodeHome(opts);
287
+ const versionDir = path.join(home, manifest.claudeCodeVersion);
288
+ const binary = path.join(versionDir, asset.bin);
289
+ await fsp2.mkdir(home, { recursive: true });
290
+ const tag = `${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
291
+ const tarball = path.join(home, `.download-${tag}.tgz`);
292
+ const staging = path.join(home, `.staging-${tag}`);
442
293
  try {
443
- return await new Promise((resolve, reject) => {
444
- let serverFailed = false;
445
- let pasteError;
446
- const failIfExhausted = () => {
447
- if (!serverFailed || pasteError === void 0) return;
448
- reject(
449
- pasteError instanceof Error ? pasteError : new Error("Could not obtain an authorization code.")
450
- );
451
- };
452
- listenForCallback(expectedState, done.signal).then(resolve, () => {
453
- serverFailed = true;
454
- failIfExhausted();
455
- });
456
- promptForCode(expectedState, callbacks, done.signal).then(resolve, (err) => {
457
- if (err instanceof FatalLoginError) {
458
- reject(err);
459
- return;
460
- }
461
- pasteError = err;
462
- failIfExhausted();
463
- });
464
- });
465
- } finally {
466
- done.abort();
467
- }
468
- }
469
- var FatalLoginError = class extends Error {
470
- };
471
- async function promptForCode(expectedState, callbacks, signal) {
472
- let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
473
- for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
474
- if (signal.aborted) throw new Error("Authorization code already received.");
475
- const raw = await callbacks.onPromptCode(message, signal);
476
- if (signal.aborted) throw new Error("Authorization code already received.");
477
- const parsed = parseAuthorizationInput(raw);
478
- if (parsed.code && parsed.state && parsed.state !== expectedState) {
479
- throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
294
+ await download(asset, tarball, opts);
295
+ opts.onProgress?.({ phase: "extract" });
296
+ await fsp2.mkdir(staging, { recursive: true });
297
+ const found = await extractFileFromTarGz(
298
+ tarball,
299
+ `package/${asset.bin}`,
300
+ path.join(staging, asset.bin)
301
+ );
302
+ if (!found) throw new Error(`The Claude Code download has no ${asset.bin} binary.`);
303
+ await fsp2.chmod(path.join(staging, asset.bin), 493);
304
+ try {
305
+ await fsp2.rename(staging, versionDir);
306
+ } catch (err) {
307
+ if (!fs2.existsSync(binary)) throw err;
480
308
  }
481
- if (parsed.code) return parsed.code;
482
- message = "That didn't contain an authorization code. Paste the full callback URL:";
483
- }
484
- throw new Error("No authorization code found in input.");
485
- }
486
- async function listenForCallback(expectedState, signal) {
487
- return new Promise((resolve, reject) => {
488
- let receivedCode = null;
489
- const server = http.createServer((req, res) => {
490
- const url = new URL(req.url || "", "http://localhost");
491
- if (url.pathname !== "/auth/callback") {
492
- res.statusCode = 404;
493
- res.end("Not found");
494
- return;
495
- }
496
- if (url.searchParams.get("state") !== expectedState) {
497
- res.statusCode = 400;
498
- res.end("State mismatch");
499
- return;
500
- }
501
- receivedCode = url.searchParams.get("code");
502
- res.writeHead(200, { "Content-Type": "text/html" });
503
- res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
504
- server.close();
505
- });
506
- server.on("error", (err) => {
507
- reject(err);
309
+ await removeOldVersions(home, manifest.claudeCodeVersion);
310
+ return binary;
311
+ } finally {
312
+ await fsp2.rm(tarball, { force: true }).catch(() => {
508
313
  });
509
- server.listen(1455, "127.0.0.1");
510
- const timeout = setTimeout(() => {
511
- if (!receivedCode) server.close();
512
- }, CALLBACK_TIMEOUT_MS);
513
- timeout.unref();
514
- const onAbort = () => {
515
- server.close();
516
- };
517
- signal.addEventListener("abort", onAbort, { once: true });
518
- server.on("close", () => {
519
- clearTimeout(timeout);
520
- signal.removeEventListener("abort", onAbort);
521
- if (receivedCode) {
522
- resolve(receivedCode);
523
- } else {
524
- reject(new Error("Server closed without receiving code"));
525
- }
314
+ await fsp2.rm(staging, { recursive: true, force: true }).catch(() => {
526
315
  });
527
- });
528
- }
529
- async function exchangeOpenAICode(code, verifier) {
530
- const response = await fetch(TOKEN_URL, {
531
- method: "POST",
532
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
533
- body: new URLSearchParams({
534
- grant_type: "authorization_code",
535
- client_id: CLIENT_ID3,
536
- code,
537
- redirect_uri: REDIRECT_URI2,
538
- code_verifier: verifier
539
- }),
540
- signal: oauthRequestSignal()
541
- });
542
- if (!response.ok) {
543
- const text = await response.text();
544
- throw new Error(`OpenAI token exchange failed (${response.status}): ${text}`);
545
316
  }
546
- const data = await response.json();
547
- return {
548
- accessToken: data.access_token,
549
- refreshToken: data.refresh_token,
550
- expiresAt: Date.now() + data.expires_in * 1e3
551
- };
552
317
  }
553
- async function refreshOpenAIToken(refreshToken) {
554
- const response = await fetch(TOKEN_URL, {
555
- method: "POST",
556
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
557
- body: new URLSearchParams({
558
- grant_type: "refresh_token",
559
- refresh_token: refreshToken,
560
- client_id: CLIENT_ID3
561
- }),
562
- signal: oauthRequestSignal()
563
- });
564
- if (!response.ok) {
565
- const text = await response.text();
566
- throw new Error(`OpenAI token refresh failed (${response.status}): ${text}`);
318
+ async function download(asset, dest, opts) {
319
+ const doFetch = opts.fetch ?? fetch;
320
+ const res = await doFetch(asset.url, { signal: opts.signal });
321
+ if (!res.ok || !res.body) {
322
+ throw new Error(`Claude Code download failed (HTTP ${res.status}).`);
567
323
  }
568
- const data = await response.json();
569
- const creds = {
570
- accessToken: data.access_token,
571
- refreshToken: data.refresh_token,
572
- expiresAt: Date.now() + data.expires_in * 1e3
573
- };
574
- const accountId = getAccountId(creds.accessToken);
575
- if (accountId) {
576
- creds.accountId = accountId;
324
+ const totalBytes = Number(res.headers.get("content-length")) || void 0;
325
+ const hash = crypto2.createHash("sha512");
326
+ const out = await fsp2.open(dest, "w");
327
+ let receivedBytes = 0;
328
+ let lastReported = -1;
329
+ try {
330
+ opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
331
+ for await (const chunk of res.body) {
332
+ if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("Download cancelled.");
333
+ hash.update(chunk);
334
+ await out.write(chunk);
335
+ receivedBytes += chunk.byteLength;
336
+ const step = totalBytes ? Math.floor(receivedBytes / totalBytes * 100) : Math.floor(receivedBytes / 4194304);
337
+ if (step !== lastReported) {
338
+ lastReported = step;
339
+ opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
340
+ }
341
+ }
342
+ } finally {
343
+ await out.close();
577
344
  }
578
- return creds;
579
- }
580
-
581
- // src/oauth/gemini.ts
582
- import http2 from "http";
583
- import crypto4 from "crypto";
584
- var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
585
- var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
586
- var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
587
- var DEFAULT_CLIENT_SECRET = "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";
588
- var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
589
- var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
590
- var CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
591
- var CODE_ASSIST_API_VERSION = "v1internal";
592
- var CODE_ASSIST_POST_RETRIES = 3;
593
- var CODE_ASSIST_POST_RETRY_DELAY_MS = 100;
594
- var SCOPE2 = [
595
- "https://www.googleapis.com/auth/cloud-platform",
596
- "https://www.googleapis.com/auth/userinfo.email",
597
- "https://www.googleapis.com/auth/userinfo.profile"
598
- ].join(" ");
599
- var USER_TIER_FREE = "free-tier";
600
- var USER_TIER_LEGACY = "legacy-tier";
601
- var USER_TIER_STANDARD = "standard-tier";
602
- var VALIDATION_REQUIRED_REASON = "VALIDATION_REQUIRED";
603
- var VPC_SC_REASON = "SECURITY_POLICY_VIOLATED";
604
- var CodeAssistHttpError = class extends Error {
605
- status;
606
- body;
607
- constructor(label, status, body) {
608
- super(`Gemini Code Assist ${label} failed (${status}): ${body}`);
609
- this.name = "CodeAssistHttpError";
610
- this.status = status;
611
- this.body = body;
345
+ opts.onProgress?.({ phase: "verify" });
346
+ const expected = asset.integrity.replace(/^sha512-/, "");
347
+ if (hash.digest("base64") !== expected) {
348
+ throw new Error(
349
+ "The Claude Code download didn't match its pinned checksum, so it was discarded."
350
+ );
612
351
  }
613
- };
614
- async function loginGemini(callbacks) {
615
- const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
616
- const { verifier, challenge } = await generatePKCE();
617
- const state = crypto4.randomBytes(32).toString("hex");
618
- const redirectUri = await getLoopbackRedirectUri();
619
- const url = new URL(AUTHORIZE_URL3);
620
- url.searchParams.set("response_type", "code");
621
- url.searchParams.set("client_id", clientId2);
622
- url.searchParams.set("redirect_uri", redirectUri);
623
- url.searchParams.set("scope", SCOPE2);
624
- url.searchParams.set("access_type", "offline");
625
- url.searchParams.set("prompt", "consent");
626
- url.searchParams.set("code_challenge", challenge);
627
- url.searchParams.set("code_challenge_method", "S256");
628
- url.searchParams.set("state", state);
629
- let code;
352
+ }
353
+ async function removeOldVersions(home, keep) {
354
+ let entries;
630
355
  try {
631
- code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
356
+ entries = await fsp2.readdir(home, { withFileTypes: true });
632
357
  } catch {
633
- callbacks.onOpenUrl(url.toString());
634
- const raw = await callbacks.onPromptCode(
635
- "Could not start local server. Paste the callback URL or code from the browser:"
636
- );
637
- const parsed = parseAuthorizationInput2(raw);
638
- if (!parsed.code) {
639
- throw new Error("No authorization code found in input.");
640
- }
641
- if (parsed.state && parsed.state !== state) {
642
- throw new Error("Invalid state. Please try again.");
643
- }
644
- code = parsed.code;
358
+ return;
359
+ }
360
+ for (const entry of entries) {
361
+ if (!entry.isDirectory() || entry.name === keep || entry.name === "config") continue;
362
+ if (!/^\d+\.\d+\.\d+/.test(entry.name)) continue;
363
+ await fsp2.rm(path.join(home, entry.name), { recursive: true, force: true }).catch(() => {
364
+ });
645
365
  }
646
- const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
647
- callbacks.onStatus("Setting up Gemini Code Assist access...");
648
- const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
649
- return {
650
- ...creds,
651
- projectId
652
- };
653
- }
654
- async function refreshGeminiToken(refreshToken) {
655
- const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
656
- const data = await postTokenRequest2({
657
- grant_type: "refresh_token",
658
- refresh_token: refreshToken,
659
- client_id: clientId2,
660
- client_secret: clientSecret
661
- });
662
- return {
663
- accessToken: data.access_token,
664
- refreshToken: data.refresh_token ?? refreshToken,
665
- expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
666
- };
667
- }
668
- function getGeminiOAuthClientCredentials() {
669
- const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
670
- const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
671
- return { clientId: clientId2, clientSecret };
672
366
  }
673
- async function getLoopbackRedirectUri() {
367
+ function formatClaudeCodeInstallProgress(progress) {
368
+ if (progress.phase === "verify") return "Checking the Claude Code download\u2026";
369
+ if (progress.phase === "extract") return "Unpacking Claude Code\u2026";
370
+ const mb = (bytes) => Math.round(bytes / 1048576);
371
+ if (!progress.totalBytes) return `Downloading Claude Code\u2026 ${mb(progress.receivedBytes)} MB`;
372
+ const pct = Math.floor(progress.receivedBytes / progress.totalBytes * 100);
373
+ return `Downloading Claude Code\u2026 ${pct}% (${mb(progress.receivedBytes)} of ${mb(progress.totalBytes)} MB)`;
374
+ }
375
+ var STRIPPED_ENV = [
376
+ "ANTHROPIC_API_KEY",
377
+ "ANTHROPIC_AUTH_TOKEN",
378
+ "ANTHROPIC_BASE_URL",
379
+ "CLAUDE_CODE_OAUTH_TOKEN",
380
+ "CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
381
+ "CLAUDE_CODE_USE_BEDROCK",
382
+ "CLAUDE_CODE_USE_VERTEX",
383
+ "CLAUDE_CODE_USE_FOUNDRY",
384
+ "NODE_OPTIONS"
385
+ ];
386
+ function claudeCodeEnv(opts = {}, base = process.env) {
387
+ const env = {};
388
+ for (const [key, value] of Object.entries(base)) {
389
+ if (value !== void 0 && !STRIPPED_ENV.includes(key)) env[key] = value;
390
+ }
391
+ env.CLAUDE_CONFIG_DIR = claudeCodeConfigDir(opts);
392
+ env.DISABLE_AUTOUPDATER = "1";
393
+ return env;
394
+ }
395
+ function runCommand(opts) {
396
+ if (opts.binary) return { command: opts.binary, prefix: opts.binaryArgs ?? [] };
397
+ const binary = resolveClaudeCodeBinary(opts);
398
+ if (!binary) throw new Error("Claude Code isn't installed yet. Sign in with Claude first.");
399
+ return { command: binary, prefix: [] };
400
+ }
401
+ function runClaude(args, opts) {
402
+ const { command, prefix } = runCommand(opts);
674
403
  return new Promise((resolve, reject) => {
675
- const server = http2.createServer();
676
- server.listen(0, "127.0.0.1", () => {
677
- const addr = server.address();
678
- server.close(() => {
679
- if (addr && typeof addr === "object") {
680
- resolve(`http://127.0.0.1:${addr.port}/oauth2callback`);
681
- } else {
682
- reject(new Error("Failed to allocate OAuth callback port."));
683
- }
684
- });
404
+ const child = spawn(command, [...prefix, ...args], {
405
+ env: claudeCodeEnv(opts),
406
+ stdio: ["ignore", "pipe", "pipe"],
407
+ windowsHide: true,
408
+ signal: opts.signal
685
409
  });
686
- server.on("error", reject);
410
+ let stdout = "";
411
+ let stderr = "";
412
+ child.stdout.on("data", (d) => stdout += d.toString());
413
+ child.stderr.on("data", (d) => stderr += d.toString());
414
+ child.on("error", reject);
415
+ child.on("close", (code) => resolve({ code, stdout, stderr }));
687
416
  });
688
417
  }
689
- function parseAuthorizationInput2(input) {
690
- const value = input.trim();
691
- if (!value) return {};
418
+ async function claudeCodeAuthStatus(opts = {}) {
419
+ if (!opts.binary && !resolveClaudeCodeBinary(opts)) return { loggedIn: false };
420
+ const { stdout } = await runClaude(["auth", "status", "--json"], opts);
692
421
  try {
693
- const url = new URL(value);
422
+ const parsed = JSON.parse(stdout);
694
423
  return {
695
- code: url.searchParams.get("code") ?? void 0,
696
- state: url.searchParams.get("state") ?? void 0
424
+ loggedIn: parsed.loggedIn === true,
425
+ ...typeof parsed.authMethod === "string" ? { authMethod: parsed.authMethod } : {},
426
+ ...typeof parsed.email === "string" ? { email: parsed.email } : {},
427
+ ...typeof parsed.subscriptionType === "string" ? { subscriptionType: parsed.subscriptionType } : {}
697
428
  };
698
429
  } catch {
430
+ return { loggedIn: false };
699
431
  }
700
- if (value.includes("code=")) {
701
- const params = new URLSearchParams(value);
702
- return {
703
- code: params.get("code") ?? void 0,
704
- state: params.get("state") ?? void 0
705
- };
432
+ }
433
+ async function logoutClaudeCode(opts = {}) {
434
+ if (!opts.binary && !resolveClaudeCodeBinary(opts)) return;
435
+ const { code, stderr } = await runClaude(["auth", "logout"], opts);
436
+ if (code !== 0) throw new Error(stderr.trim() || `claude auth logout exited with ${code}`);
437
+ }
438
+ var OPEN_URL_HOOK = `const arg = String(process.argv[1] || "");
439
+ const file = process.env.GG_CLAUDE_CODE_URL_FILE;
440
+ if (file && /https?:/.test(arg)) {
441
+ require("node:fs").appendFileSync(file, arg + "\\n");
442
+ process.exit(0);
443
+ }
444
+ `;
445
+ var CAPTURE_WAIT_MS = 3e3;
446
+ function stripAnsi(text) {
447
+ return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[A-Za-z]/g, "");
448
+ }
449
+ function claudeCodeCallbackUrl(manualUrl, captured) {
450
+ const port = /localhost(?::|%3A)(\d{2,5})(?:\/|%2F)callback/i.exec(captured)?.[1];
451
+ if (!port) return null;
452
+ try {
453
+ const url = new URL(manualUrl);
454
+ url.searchParams.set("redirect_uri", `http://localhost:${port}/callback`);
455
+ return url.toString();
456
+ } catch {
457
+ return null;
706
458
  }
707
- return { code: value };
708
459
  }
709
- async function loginWithServer(authUrl, redirectUri, expectedState, callbacks) {
710
- const redirect = new URL(redirectUri);
711
- const port = Number(redirect.port);
712
- return new Promise((resolve, reject) => {
713
- let receivedCode = null;
714
- const server = http2.createServer((req, res) => {
715
- const url = new URL(req.url || "", redirect.origin);
716
- if (url.pathname !== redirect.pathname) {
717
- res.statusCode = 404;
718
- res.end("Not found");
719
- return;
720
- }
721
- if (url.searchParams.get("state") !== expectedState) {
722
- res.statusCode = 400;
723
- res.end("State mismatch");
724
- return;
725
- }
726
- receivedCode = url.searchParams.get("code");
727
- res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
728
- res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
729
- server.close();
460
+ async function loginClaudeCode(callbacks, opts = {}) {
461
+ const { command, prefix } = runCommand(opts);
462
+ const home = claudeCodeHome(opts);
463
+ await fsp2.mkdir(claudeCodeConfigDir(opts), { recursive: true });
464
+ const tag = `${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
465
+ const hookFile = path.join(home, `.open-url-${tag}.cjs`);
466
+ const urlFile = path.join(home, `.open-url-${tag}.txt`);
467
+ await fsp2.writeFile(hookFile, OPEN_URL_HOOK);
468
+ const env = {
469
+ ...claudeCodeEnv(opts),
470
+ BROWSER: process.execPath,
471
+ NODE_OPTIONS: `--require ${JSON.stringify(hookFile)}`,
472
+ GG_CLAUDE_CODE_URL_FILE: urlFile
473
+ };
474
+ const promptAbort = new AbortController();
475
+ let timer;
476
+ try {
477
+ await new Promise((resolve, reject) => {
478
+ const child = spawn(command, [...prefix, "auth", "login", "--claudeai"], {
479
+ env,
480
+ stdio: ["pipe", "pipe", "pipe"],
481
+ windowsHide: true
482
+ });
483
+ let stdout = "";
484
+ let stderr = "";
485
+ let manualUrl = null;
486
+ let opened = false;
487
+ const onAbort = () => child.kill();
488
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
489
+ const open = (url, needsCode) => {
490
+ if (opened) return;
491
+ opened = true;
492
+ clearInterval(timer);
493
+ callbacks.onOpenUrl(url);
494
+ callbacks.onStatus("Finish signing in to Claude in your browser\u2026");
495
+ if (!needsCode) return;
496
+ void callbacks.onPromptCode("Paste the code shown after signing in to Claude:", promptAbort.signal).then((code) => {
497
+ const trimmed = code.trim();
498
+ if (trimmed && !child.stdin.destroyed) child.stdin.write(`${trimmed}
499
+ `);
500
+ }).catch(() => {
501
+ });
502
+ };
503
+ child.stdout.on("data", (d) => {
504
+ stdout += d.toString();
505
+ if (manualUrl) return;
506
+ const match = /visit:\s*(https:\/\/\S+)/.exec(stripAnsi(stdout));
507
+ if (!match) return;
508
+ manualUrl = match[1];
509
+ const started = Date.now();
510
+ timer = setInterval(() => {
511
+ let captured = "";
512
+ try {
513
+ captured = fs2.readFileSync(urlFile, "utf8");
514
+ } catch {
515
+ }
516
+ const callbackUrl = captured ? claudeCodeCallbackUrl(manualUrl, captured) : null;
517
+ if (callbackUrl) open(callbackUrl, false);
518
+ else if (Date.now() - started > (opts.browserWaitMs ?? CAPTURE_WAIT_MS))
519
+ open(manualUrl, true);
520
+ }, 100);
521
+ });
522
+ child.stderr.on("data", (d) => stderr += d.toString());
523
+ child.on("error", reject);
524
+ child.on("close", (code) => {
525
+ opts.signal?.removeEventListener("abort", onAbort);
526
+ if (opts.signal?.aborted) reject(new Error("Sign-in cancelled."));
527
+ else if (code === 0) resolve();
528
+ else {
529
+ const detail = stripAnsi(stderr).trim().split("\n").pop()?.trim();
530
+ reject(new Error(detail || `Claude sign-in failed (exit ${code}).`));
531
+ }
532
+ });
730
533
  });
731
- server.on("error", (err) => reject(err));
732
- server.listen(port, "127.0.0.1", () => {
733
- callbacks.onOpenUrl(authUrl);
734
- callbacks.onStatus("Waiting for browser callback...");
534
+ } finally {
535
+ clearInterval(timer);
536
+ promptAbort.abort();
537
+ await fsp2.rm(hookFile, { force: true }).catch(() => {
735
538
  });
736
- const timeout = setTimeout(() => {
737
- if (!receivedCode) server.close();
738
- }, 12e4);
739
- timeout.unref();
740
- server.on("close", () => {
741
- clearTimeout(timeout);
742
- if (receivedCode) {
743
- resolve(receivedCode);
744
- } else {
745
- reject(new Error("Server closed without receiving code."));
746
- }
539
+ await fsp2.rm(urlFile, { force: true }).catch(() => {
747
540
  });
748
- });
541
+ }
749
542
  }
750
- async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
751
- const data = await postTokenRequest2({
752
- grant_type: "authorization_code",
753
- client_id: clientId2,
754
- client_secret: clientSecret,
755
- code,
756
- redirect_uri: redirectUri,
757
- code_verifier: verifier
758
- });
759
- if (!data.refresh_token) {
760
- throw new Error("Gemini OAuth did not return a refresh token. Please try login again.");
543
+
544
+ // src/oauth/openai.ts
545
+ import http from "http";
546
+ import crypto3 from "crypto";
547
+ import fs3 from "fs/promises";
548
+ import path2 from "path";
549
+
550
+ // src/oauth/pkce.ts
551
+ function base64urlEncode(bytes) {
552
+ let binary = "";
553
+ for (const byte of bytes) {
554
+ binary += String.fromCharCode(byte);
761
555
  }
762
- return {
763
- accessToken: data.access_token,
764
- refreshToken: data.refresh_token,
765
- expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
766
- };
556
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
767
557
  }
768
- async function postTokenRequest2(body) {
769
- const response = await fetch(TOKEN_URL2, {
770
- method: "POST",
771
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
772
- body: new URLSearchParams(body),
773
- signal: oauthRequestSignal()
774
- });
775
- if (!response.ok) {
776
- const text = await response.text();
777
- throw new Error(`Gemini token request failed (${response.status}): ${text}`);
558
+ async function generatePKCE() {
559
+ const verifierBytes = new Uint8Array(32);
560
+ crypto.getRandomValues(verifierBytes);
561
+ const verifier = base64urlEncode(verifierBytes);
562
+ const data = new TextEncoder().encode(verifier);
563
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
564
+ const challenge = base64urlEncode(new Uint8Array(hashBuffer));
565
+ return { verifier, challenge };
566
+ }
567
+
568
+ // src/oauth/request-timeout.ts
569
+ var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
570
+ function oauthRequestSignal() {
571
+ return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
572
+ }
573
+
574
+ // src/oauth/openai.ts
575
+ var ISSUER = "https://auth.openai.com";
576
+ var AUTHORIZE_URL = `${ISSUER}/api/accounts/authorize`;
577
+ var TOKEN_URL = `${ISSUER}/api/accounts/oauth/token`;
578
+ var REVOKE_URL = `${ISSUER}/api/accounts/oauth/revoke`;
579
+ var JWKS_URL = `${ISSUER}/.well-known/jwks.json`;
580
+ var REGISTRATION_CLIENT_ID = "dynamic_agent_client";
581
+ var AGENT_NAME = "GG Coder";
582
+ var RESOURCE = "https://api.openai.com/v1";
583
+ var CHATGPT_PLAN_SCOPE = "chatgpt.tokens.use.direct";
584
+ var SCOPES = `openid profile email offline_access resource.invoke ${CHATGPT_PLAN_SCOPE}`;
585
+ var CHATGPT_PLAN_BASE_URL = "https://api.openai.com/v1";
586
+ var CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
587
+ var CALLBACK_PATH = "/auth/callback";
588
+ var PREFERRED_PORT = 1455;
589
+ var CALLBACK_TIMEOUT_MS = 12e4;
590
+ var MAX_PASTE_ATTEMPTS = 3;
591
+ var ID_TOKEN_CLOCK_SKEW_S = 60;
592
+ var CHATGPT_PLAN_NOT_ENABLED_MESSAGE = "ChatGPT plan use isn't enabled for GG Coder. Allow Use your ChatGPT plan, or add an OpenAI API key.";
593
+ var ChatGPTPlanNotEnabledError = class extends Error {
594
+ constructor() {
595
+ super(CHATGPT_PLAN_NOT_ENABLED_MESSAGE);
596
+ this.name = "ChatGPTPlanNotEnabledError";
778
597
  }
779
- return await response.json();
598
+ };
599
+ function defaultHostIdFile() {
600
+ return path2.join(getAppPaths().agentDir, "openai-host-id");
780
601
  }
781
- async function setupCodeAssistProject(accessToken, callbacks) {
782
- const envProject = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GOOGLE_CLOUD_PROJECT_ID;
783
- if (envProject && /^\d+$/.test(envProject)) {
784
- throw new Error("GOOGLE_CLOUD_PROJECT must be a project ID, not a numeric project number.");
785
- }
786
- const coreMetadata = {
787
- ideType: "IDE_UNSPECIFIED",
788
- platform: "PLATFORM_UNSPECIFIED",
789
- pluginType: "GEMINI"
790
- };
791
- const projectMetadata = {
792
- ...coreMetadata,
793
- ...envProject ? { duetProject: envProject } : {}
794
- };
795
- let loadRes;
796
- while (true) {
797
- loadRes = await loadCodeAssist(accessToken, envProject, projectMetadata);
798
- const validation = getValidationRequiredTier(loadRes);
799
- if (!validation) break;
800
- callbacks.onStatus(
801
- `Gemini Code Assist requires account validation${validation.reasonMessage ? `: ${validation.reasonMessage}` : ""}`
802
- );
803
- callbacks.onOpenUrl(validation.validationUrl);
804
- const answer = await callbacks.onPromptCode(
805
- "Complete validation in the browser, then press Enter to retry (or type cancel):"
806
- );
807
- if (answer.trim().toLowerCase() === "cancel") {
808
- throw new Error("Gemini Code Assist account validation was cancelled.");
809
- }
810
- }
811
- if (loadRes.currentTier) {
812
- const project2 = loadRes.cloudaicompanionProject ?? envProject;
813
- if (!project2) throwProjectError(loadRes);
814
- return project2;
815
- }
816
- const tier = getOnboardTier(loadRes);
817
- const onboardReq = tier.id === USER_TIER_FREE ? {
818
- tierId: tier.id,
819
- cloudaicompanionProject: void 0,
820
- metadata: coreMetadata
821
- } : {
822
- tierId: tier.id,
823
- cloudaicompanionProject: envProject,
824
- metadata: projectMetadata
825
- };
826
- let operation = await codeAssistPost(
827
- accessToken,
828
- "onboardUser",
829
- onboardReq
830
- );
831
- while (!operation.done && operation.name) {
832
- await new Promise((resolve) => setTimeout(resolve, 5e3));
833
- operation = await codeAssistGet(accessToken, operation.name);
834
- }
835
- const project = operation.response?.cloudaicompanionProject?.id ?? envProject;
836
- if (!project) throwProjectError(loadRes);
837
- return project;
602
+ function defaultRegistrationFile() {
603
+ return path2.join(getAppPaths().agentDir, "openai-chatgpt-client.json");
838
604
  }
839
- async function loadCodeAssist(accessToken, envProject, metadata) {
605
+ async function writePrivateFile(file, content) {
606
+ await fs3.mkdir(path2.dirname(file), { recursive: true });
607
+ const tmp = `${file}.${process.pid}.${crypto3.randomUUID().slice(0, 8)}.tmp`;
840
608
  try {
841
- return await codeAssistPost(accessToken, "loadCodeAssist", {
842
- ...envProject ? { cloudaicompanionProject: envProject } : {},
843
- metadata
844
- });
609
+ await fs3.writeFile(tmp, content, { encoding: "utf-8", mode: 384 });
610
+ await fs3.rename(tmp, file);
845
611
  } catch (err) {
846
- if (err instanceof CodeAssistHttpError && isVpcScAffectedError(err)) {
847
- return { currentTier: { id: USER_TIER_STANDARD } };
848
- }
849
- if (err instanceof CodeAssistHttpError && err.status === 403 && envProject === "cloudshell-gca") {
850
- throw new Error(
851
- "Access to the default Cloud Shell Gemini project was denied.\nPlease set your own Google Cloud project by running:\ngcloud config set project [PROJECT_ID]\nor setting export GOOGLE_CLOUD_PROJECT=...",
852
- { cause: err }
853
- );
854
- }
612
+ await fs3.unlink(tmp).catch(() => {
613
+ });
855
614
  throw err;
856
615
  }
857
616
  }
858
- function getValidationRequiredTier(response) {
859
- return response.ineligibleTiers?.find(
860
- (tier) => tier.reasonCode === VALIDATION_REQUIRED_REASON && typeof tier.validationUrl === "string"
861
- );
862
- }
863
- function getOnboardTier(response) {
864
- const defaultTier = response.allowedTiers?.find((tier) => tier.isDefault);
865
- return defaultTier ?? { id: USER_TIER_LEGACY, name: "" };
617
+ var HOST_ID_PATTERN = /^urn:uuid:[0-9a-f-]{36}$/i;
618
+ async function getChatGPTHostId(file = defaultHostIdFile()) {
619
+ const existing = await fs3.readFile(file, "utf-8").catch(() => "");
620
+ if (HOST_ID_PATTERN.test(existing.trim())) return existing.trim();
621
+ await writePrivateFile(file, `urn:uuid:${crypto3.randomUUID()}`);
622
+ return (await fs3.readFile(file, "utf-8")).trim();
866
623
  }
867
- function throwProjectError(response) {
868
- const reasons = response.ineligibleTiers?.map((tier) => tier.reasonMessage ?? tier.tierName).filter((reason) => Boolean(reason));
869
- if (reasons && reasons.length > 0) {
870
- throw new Error(`Gemini Code Assist setup failed: ${reasons.join(", ")}`);
624
+ async function readRegistration(file) {
625
+ try {
626
+ const value = JSON.parse(await fs3.readFile(file, "utf-8"));
627
+ if (typeof value.clientId === "string" && typeof value.subject === "string") {
628
+ return { clientId: value.clientId, subject: value.subject };
629
+ }
630
+ } catch {
871
631
  }
872
- throw new Error(
873
- "Gemini requires a Google Cloud project for this account. Set GOOGLE_CLOUD_PROJECT and try again."
874
- );
632
+ return void 0;
875
633
  }
876
- async function codeAssistPost(accessToken, method, body) {
877
- let lastError;
878
- for (let attempt = 0; attempt <= CODE_ASSIST_POST_RETRIES; attempt++) {
879
- try {
880
- return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
881
- method: "POST",
882
- body: JSON.stringify(body)
634
+ async function loginChatGPT(callbacks, options = {}) {
635
+ const hostId = await getChatGPTHostId(options.hostIdFile);
636
+ const registrationFile = options.registrationFile ?? defaultRegistrationFile();
637
+ const registration = await readRegistration(registrationFile);
638
+ const { verifier, challenge } = await generatePKCE();
639
+ const state = crypto3.randomBytes(16).toString("hex");
640
+ const nonce = crypto3.randomBytes(16).toString("hex");
641
+ const listener = await startCallbackListener(state, Boolean(registration));
642
+ try {
643
+ const redirectUri = `http://127.0.0.1:${listener.port}${CALLBACK_PATH}`;
644
+ const url = new URL(AUTHORIZE_URL);
645
+ url.searchParams.set("client_id", registration?.clientId ?? REGISTRATION_CLIENT_ID);
646
+ if (!registration) url.searchParams.set("agent_name_hint", AGENT_NAME);
647
+ url.searchParams.set("ext_agent_host_id", hostId);
648
+ if (registration && options.previous?.idToken) {
649
+ url.searchParams.set("id_token_hint", options.previous.idToken);
650
+ }
651
+ url.searchParams.set("response_type", "code");
652
+ url.searchParams.set("redirect_uri", redirectUri);
653
+ url.searchParams.set("scope", SCOPES);
654
+ url.searchParams.set("resource", RESOURCE);
655
+ url.searchParams.set("state", state);
656
+ url.searchParams.set("nonce", nonce);
657
+ url.searchParams.set("code_challenge_method", "S256");
658
+ url.searchParams.set("code_challenge", challenge);
659
+ const callback = await acquireCallback(url.toString(), state, listener, callbacks);
660
+ if (callback.get("error")) {
661
+ if (callback.get("error") === "access_denied") throw new ChatGPTPlanNotEnabledError();
662
+ throw new Error(
663
+ `ChatGPT sign-in failed: ${callback.get("error_description") ?? callback.get("error")}`
664
+ );
665
+ }
666
+ const code = callback.get("code");
667
+ if (!code) throw new Error("ChatGPT sign-in returned no authorization code.");
668
+ const issued = callback.get("client_id") ?? void 0;
669
+ if (registration && issued && issued !== registration.clientId) {
670
+ throw new Error("ChatGPT returned a different app registration. Start the sign-in again.");
671
+ }
672
+ const clientId = registration?.clientId ?? issued;
673
+ if (!clientId || clientId === REGISTRATION_CLIENT_ID) {
674
+ throw new Error(
675
+ "ChatGPT didn't finish registering GG Coder. Start the sign-in again, and paste the full callback URL if you're on another machine."
676
+ );
677
+ }
678
+ const tokens = await exchangeCode({ code, verifier, clientId, redirectUri });
679
+ const claims = await verifyIdToken(tokens.idToken, { clientId, nonce });
680
+ const subject = String(claims.sub ?? "");
681
+ if (!subject) throw new Error("ChatGPT ID token has no subject.");
682
+ if (registration && registration.subject !== subject) {
683
+ await fs3.unlink(registrationFile).catch(() => {
883
684
  });
884
- } catch (err) {
885
- if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
886
- throw err;
887
- }
888
- lastError = err;
685
+ await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
686
+ throw new Error(
687
+ "That was a different ChatGPT account. Continue with ChatGPT again to connect it."
688
+ );
689
+ }
690
+ if (!tokens.scopes.includes(CHATGPT_PLAN_SCOPE)) {
691
+ await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
692
+ throw new ChatGPTPlanNotEnabledError();
693
+ }
694
+ if (!registration) {
695
+ await writePrivateFile(registrationFile, JSON.stringify({ clientId, subject }));
889
696
  }
890
- await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
697
+ return {
698
+ accessToken: tokens.accessToken,
699
+ refreshToken: tokens.refreshToken,
700
+ expiresAt: Date.now() + tokens.expiresIn * 1e3,
701
+ expiresIn: tokens.expiresIn,
702
+ accountId: subject,
703
+ clientId,
704
+ idToken: tokens.idToken,
705
+ scopes: tokens.scopes,
706
+ baseUrl: CHATGPT_PLAN_BASE_URL
707
+ };
708
+ } finally {
709
+ listener.close();
891
710
  }
892
- throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
893
711
  }
894
- async function codeAssistGet(accessToken, operationName) {
895
- return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
896
- method: "GET"
712
+ async function fetchChatGPTPlanModels(accessToken, fetchFn = fetch) {
713
+ const response = await fetchFn(`${CHATGPT_PLAN_BASE_URL}/models`, {
714
+ headers: { Authorization: `Bearer ${accessToken}` },
715
+ signal: oauthRequestSignal()
897
716
  });
717
+ if (!response.ok) throw new Error(`ChatGPT model list failed (${response.status})`);
718
+ const data = await response.json();
719
+ return (data.models ?? []).filter((m) => typeof m.slug === "string" && (m.visibility ?? "list") === "list").map((m) => m.slug);
898
720
  }
899
- async function codeAssistRequest(url, accessToken, label, init) {
900
- const response = await fetch(url, {
901
- ...init,
902
- headers: codeAssistHeaders(accessToken),
903
- signal: oauthRequestSignal()
721
+ async function startCallbackListener(expectedState, anyPort) {
722
+ let settle;
723
+ const result = new Promise((resolve, reject) => {
724
+ settle = { resolve, reject };
904
725
  });
905
- if (!response.ok) {
906
- const text = await response.text();
907
- throw new CodeAssistHttpError(label, response.status, text);
726
+ result.catch(() => {
727
+ });
728
+ const server = http.createServer((req, res) => {
729
+ const url = new URL(req.url || "", "http://127.0.0.1");
730
+ if (url.pathname !== CALLBACK_PATH) {
731
+ res.statusCode = 404;
732
+ res.end("Not found");
733
+ return;
734
+ }
735
+ if (url.searchParams.get("state") !== expectedState) {
736
+ res.statusCode = 400;
737
+ res.end("State mismatch");
738
+ return;
739
+ }
740
+ res.writeHead(200, { "Content-Type": "text/html" });
741
+ res.end(
742
+ "<html><body><h1>You're signed in to GG Coder.</h1><p>You can close this tab.</p></body></html>"
743
+ );
744
+ settle?.resolve(url.searchParams);
745
+ server.close();
746
+ });
747
+ const listen = (port2) => new Promise((resolve, reject) => {
748
+ const onError = (err) => reject(err);
749
+ server.once("error", onError);
750
+ server.listen(port2, "127.0.0.1", () => {
751
+ server.off("error", onError);
752
+ resolve(server.address().port);
753
+ });
754
+ });
755
+ let port;
756
+ for (const candidate of anyPort ? [PREFERRED_PORT, 0] : [PREFERRED_PORT]) {
757
+ try {
758
+ port = await listen(candidate);
759
+ break;
760
+ } catch {
761
+ }
908
762
  }
909
- return await response.json();
910
- }
911
- function getCodeAssistBaseUrl() {
912
- const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
913
- const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
914
- return `${endpoint}/${version}`;
915
- }
916
- function getCodeAssistMethodUrl(method) {
917
- return `${getCodeAssistBaseUrl()}:${method}`;
918
- }
919
- function getCodeAssistOperationUrl(operationName) {
920
- return `${getCodeAssistBaseUrl()}/${operationName}`;
763
+ if (port === void 0) {
764
+ settle?.reject(new Error("Could not start the local sign-in listener."));
765
+ return { port: PREFERRED_PORT, result, close: () => {
766
+ } };
767
+ }
768
+ const timeout = setTimeout(() => server.close(), CALLBACK_TIMEOUT_MS);
769
+ timeout.unref();
770
+ server.on("close", () => {
771
+ clearTimeout(timeout);
772
+ settle?.reject(new Error("Sign-in listener closed without a callback."));
773
+ });
774
+ return { port, result, close: () => server.close() };
921
775
  }
922
- function shouldRetryCodeAssistStatus(status) {
923
- return status === 429 || status === 499 || status >= 500 && status <= 599;
776
+ async function acquireCallback(authUrl, expectedState, listener, callbacks) {
777
+ callbacks.onOpenUrl(authUrl);
778
+ callbacks.onStatus("Waiting for ChatGPT sign-in in your browser...");
779
+ const done = new AbortController();
780
+ try {
781
+ return await new Promise((resolve, reject) => {
782
+ let listenerFailed = false;
783
+ let pasteError;
784
+ const failIfExhausted = () => {
785
+ if (!listenerFailed || pasteError === void 0) return;
786
+ reject(
787
+ pasteError instanceof Error ? pasteError : new Error("Could not complete the sign-in.")
788
+ );
789
+ };
790
+ listener.result.then(resolve, () => {
791
+ listenerFailed = true;
792
+ failIfExhausted();
793
+ });
794
+ promptForCallback(expectedState, callbacks, done.signal).then(resolve, (err) => {
795
+ if (err instanceof FatalLoginError) {
796
+ reject(err);
797
+ return;
798
+ }
799
+ pasteError = err;
800
+ failIfExhausted();
801
+ });
802
+ });
803
+ } finally {
804
+ done.abort();
805
+ listener.close();
806
+ }
924
807
  }
925
- function isVpcScAffectedError(error) {
808
+ var FatalLoginError = class extends Error {
809
+ };
810
+ function parseCallbackInput(input) {
811
+ const value = input.trim();
812
+ if (!value) return void 0;
926
813
  try {
927
- const parsed = JSON.parse(error.body);
928
- if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
929
- const details = parsed.error?.details;
930
- return Array.isArray(details) ? details.some(
931
- (detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
932
- ) : false;
814
+ return new URL(value).searchParams;
933
815
  } catch {
934
- return false;
935
816
  }
817
+ if (/[?&]?(code|error)=/.test(value)) {
818
+ return new URLSearchParams(value.replace(/^.*\?/, ""));
819
+ }
820
+ return void 0;
936
821
  }
937
- function codeAssistHeaders(accessToken) {
938
- return {
939
- Authorization: `Bearer ${accessToken}`,
940
- "Content-Type": "application/json",
941
- "User-Agent": "google-gemini-cli",
942
- "X-Goog-Api-Client": "gemini-cli/0.0.0"
943
- };
944
- }
945
-
946
- // src/oauth/xai.ts
947
- var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
948
- var DEFAULT_ISSUER = "https://auth.x.ai";
949
- var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
950
- var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
951
- var DEFAULT_GROK_CLI_VERSION = "1.0.67";
952
- var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
953
- var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
954
- function issuer() {
955
- return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
956
- }
957
- function clientId() {
958
- return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
959
- }
960
- function grokCliBaseUrl() {
961
- return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
962
- }
963
- function grokCliVersion() {
964
- const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
965
- const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
966
- return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
967
- }
968
- function grokCliHeaders(modelId) {
969
- return {
970
- "X-XAI-Token-Auth": "xai-grok-cli",
971
- "x-grok-client-version": grokCliVersion(),
972
- "x-grok-client-identifier": "ggcoder",
973
- ...modelId ? { "x-grok-model-override": modelId } : {}
974
- };
975
- }
976
- function isGrokCliEndpoint(baseUrl) {
977
- if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
978
- const normalized = baseUrl.replace(/\/+$/, "");
979
- return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
822
+ async function promptForCallback(expectedState, callbacks, signal) {
823
+ let message = "If the browser is on another machine (SSH/headless), paste the full callback URL here:";
824
+ for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
825
+ if (signal.aborted) throw new Error("Callback already received.");
826
+ const raw = await callbacks.onPromptCode(message, signal);
827
+ if (signal.aborted) throw new Error("Callback already received.");
828
+ const params = parseCallbackInput(raw);
829
+ if (params && (params.get("code") || params.get("error"))) {
830
+ if (params.get("state") !== expectedState) {
831
+ throw new FatalLoginError("Sign-in state mismatch \u2014 start the sign-in again.");
832
+ }
833
+ return params;
834
+ }
835
+ message = "That wasn't a callback URL. Paste the full URL from the browser's address bar:";
836
+ }
837
+ throw new Error("No callback URL found in input.");
980
838
  }
981
- async function postForm2(endpoint, params) {
982
- const response = await fetch(`${issuer()}${endpoint}`, {
839
+ async function postForm(url, form) {
840
+ return fetch(url, {
983
841
  method: "POST",
984
- headers: {
985
- "Content-Type": "application/x-www-form-urlencoded",
986
- Accept: "application/json",
987
- "User-Agent": `grok-cli/${grokCliVersion()}`
988
- },
989
- body: new URLSearchParams(params).toString(),
842
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
843
+ body: new URLSearchParams(form),
990
844
  signal: oauthRequestSignal()
991
845
  });
992
- let data = {};
993
- try {
994
- const parsed = await response.json();
995
- if (parsed && typeof parsed === "object") data = parsed;
996
- } catch {
997
- }
998
- return { status: response.status, data };
999
- }
1000
- function errorDetail2(data) {
1001
- const desc = data.error_description ?? data.message ?? data.error;
1002
- return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
1003
- }
1004
- function jwtExpirySeconds(token) {
1005
- const payload = token.split(".")[1];
1006
- if (!payload) return void 0;
1007
- try {
1008
- const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
1009
- "utf-8"
1010
- );
1011
- const claims = JSON.parse(json);
1012
- if (!claims || typeof claims !== "object") return void 0;
1013
- const exp = claims.exp;
1014
- return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
1015
- } catch {
1016
- return void 0;
1017
- }
1018
846
  }
1019
- function credsFromTokenResponse2(data, opts) {
1020
- const accessToken = data.access_token;
1021
- const responseRefreshToken = data.refresh_token;
1022
- if (typeof accessToken !== "string" || accessToken.length === 0) {
1023
- throw new Error("Grok OAuth response missing access_token.");
1024
- }
1025
- const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
1026
- if (refreshToken.length === 0) {
1027
- throw new Error(
1028
- "Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
1029
- );
847
+ async function exchangeCode(input) {
848
+ const response = await postForm(TOKEN_URL, {
849
+ grant_type: "authorization_code",
850
+ client_id: input.clientId,
851
+ code: input.code,
852
+ code_verifier: input.verifier,
853
+ redirect_uri: input.redirectUri,
854
+ resource: RESOURCE
855
+ });
856
+ if (!response.ok) {
857
+ const text = await response.text().catch(() => "");
858
+ throw new Error(`ChatGPT token exchange failed (${response.status}): ${text.slice(0, 300)}`);
1030
859
  }
1031
- const responseExpiresIn = Number(data.expires_in);
1032
- let expiresIn;
1033
- if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
1034
- expiresIn = responseExpiresIn;
1035
- } else {
1036
- const exp = jwtExpirySeconds(accessToken);
1037
- const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
1038
- expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
860
+ const data = await response.json();
861
+ if (!data.access_token || !data.refresh_token || !data.id_token) {
862
+ throw new Error("ChatGPT token response is missing tokens.");
1039
863
  }
1040
864
  return {
1041
- accessToken,
1042
- refreshToken,
1043
- expiresAt: Date.now() + expiresIn * 1e3,
1044
- expiresIn,
1045
- baseUrl: grokCliBaseUrl()
865
+ accessToken: data.access_token,
866
+ refreshToken: data.refresh_token,
867
+ idToken: data.id_token,
868
+ expiresIn: data.expires_in ?? 3600,
869
+ scopes: (data.scope ?? "").split(/\s+/).filter(Boolean)
1046
870
  };
1047
871
  }
1048
- async function requestDeviceAuthorization2() {
1049
- const { status, data } = await postForm2("/oauth2/device/code", {
1050
- client_id: clientId(),
1051
- scope: OAUTH_SCOPE
872
+ async function refreshChatGPTToken(creds) {
873
+ if (!creds.clientId) {
874
+ throw new Error("ChatGPT token refresh failed (401): no issued client ID saved");
875
+ }
876
+ const response = await postForm(TOKEN_URL, {
877
+ grant_type: "refresh_token",
878
+ client_id: creds.clientId,
879
+ refresh_token: creds.refreshToken,
880
+ resource: RESOURCE
1052
881
  });
1053
- if (status !== 200) {
1054
- throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
882
+ if (!response.ok) {
883
+ const text = await response.text().catch(() => "");
884
+ throw new Error(`ChatGPT token refresh failed (${response.status}): ${text.slice(0, 300)}`);
1055
885
  }
1056
- const userCode = data.user_code;
1057
- const deviceCode = data.device_code;
1058
- const verificationUriComplete = data.verification_uri_complete;
1059
- if (typeof userCode !== "string" || typeof deviceCode !== "string") {
1060
- throw new Error("Grok device authorization response missing user_code/device_code.");
886
+ const data = await response.json();
887
+ if (!data.access_token || !data.refresh_token) {
888
+ throw new Error("ChatGPT token refresh returned no tokens.");
1061
889
  }
890
+ const expiresIn = data.expires_in ?? 3600;
1062
891
  return {
1063
- userCode,
1064
- deviceCode,
1065
- verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
1066
- verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
1067
- interval: Number(data.interval ?? 5) || 5,
1068
- // RFC 8628 §3.2: the server states how long the device code lives. Honor it
1069
- // rather than imposing our own budget — polling a code we know is dead only
1070
- // burns requests, and a longer local window would keep a user waiting past
1071
- // the point the code can ever succeed.
1072
- expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
892
+ accessToken: data.access_token,
893
+ refreshToken: data.refresh_token,
894
+ expiresAt: Date.now() + expiresIn * 1e3,
895
+ expiresIn,
896
+ ...data.id_token ? { idToken: data.id_token } : {},
897
+ ...data.scope ? { scopes: data.scope.split(/\s+/).filter(Boolean) } : {}
1073
898
  };
1074
899
  }
1075
- async function pollDeviceToken2(deviceCode) {
1076
- const { status, data } = await postForm2("/oauth2/token", {
1077
- client_id: clientId(),
1078
- device_code: deviceCode,
1079
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1080
- });
1081
- if (status === 200 && typeof data.access_token === "string") {
1082
- return { kind: "success", creds: credsFromTokenResponse2(data) };
1083
- }
1084
- if (status >= 500) {
1085
- throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
1086
- }
1087
- const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
1088
- switch (errorCode) {
1089
- case "authorization_pending":
1090
- return { kind: "pending" };
1091
- case "slow_down":
1092
- return { kind: "slow_down" };
1093
- case "expired_token":
1094
- return { kind: "expired" };
1095
- case "access_denied":
1096
- return { kind: "denied" };
1097
- default:
1098
- throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
1099
- }
1100
- }
1101
- function sleep2(ms) {
1102
- return new Promise((resolve) => {
1103
- setTimeout(resolve, ms);
1104
- });
1105
- }
1106
- async function loginXai(callbacks) {
1107
- const auth = await requestDeviceAuthorization2();
1108
- callbacks.onStatus(
1109
- `Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
1110
- );
1111
- callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
1112
- callbacks.onStatus("Waiting for you to authorize in the browser...");
1113
- const deadline = Date.now() + auth.expiresInMs;
1114
- let interval = Math.max(auth.interval, 1);
1115
- while (Date.now() < deadline) {
1116
- const remaining = deadline - Date.now();
1117
- await sleep2(Math.min(interval * 1e3, remaining));
1118
- const result = await pollDeviceToken2(auth.deviceCode);
1119
- if (result.kind === "success") return result.creds;
1120
- if (result.kind === "denied") {
1121
- throw new Error("Grok authorization was denied.");
1122
- }
1123
- if (result.kind === "expired") {
1124
- throw new Error("Grok device code expired. Please run login again.");
900
+ async function revokeChatGPTSignIn(creds, options = {}) {
901
+ if (!creds.clientId || !creds.refreshToken) return false;
902
+ const attempts = options.attempts ?? 3;
903
+ for (let attempt = 0; attempt < attempts; attempt++) {
904
+ try {
905
+ const response = await postForm(REVOKE_URL, {
906
+ token: creds.refreshToken,
907
+ token_type_hint: "refresh_token",
908
+ client_id: creds.clientId
909
+ });
910
+ if (response.ok) return true;
911
+ if (response.status < 500) return false;
912
+ } catch {
1125
913
  }
1126
- if (result.kind === "slow_down") {
1127
- interval += 5;
914
+ if (attempt < attempts - 1) {
915
+ await new Promise((r) => setTimeout(r, (options.backoffMs ?? 500) * 2 ** attempt));
1128
916
  }
1129
917
  }
1130
- throw new Error("Grok login timed out. Please run login again.");
1131
- }
1132
- async function refreshXaiToken(refreshToken) {
1133
- const { status, data } = await postForm2("/oauth2/token", {
1134
- client_id: clientId(),
1135
- grant_type: "refresh_token",
1136
- refresh_token: refreshToken
1137
- });
1138
- if (status === 200 && typeof data.access_token === "string") {
1139
- return credsFromTokenResponse2(data, { fallbackRefreshToken: refreshToken });
918
+ return false;
919
+ }
920
+ function decodeSegment(segment) {
921
+ return JSON.parse(Buffer.from(segment, "base64url").toString("utf-8"));
922
+ }
923
+ async function verifyIdToken(idToken, expected) {
924
+ const [headerSeg, payloadSeg, signatureSeg] = idToken.split(".");
925
+ if (!headerSeg || !payloadSeg || !signatureSeg) throw new Error("Malformed ChatGPT ID token.");
926
+ const header = decodeSegment(headerSeg);
927
+ if (header.alg !== "RS256") throw new Error("Unexpected ChatGPT ID token algorithm.");
928
+ const response = await fetch(JWKS_URL, { signal: oauthRequestSignal() });
929
+ if (!response.ok) throw new Error(`Could not load OpenAI signing keys (${response.status}).`);
930
+ const { keys = [] } = await response.json();
931
+ const jwk = keys.find((k) => k.kid === header.kid);
932
+ if (!jwk) throw new Error("ChatGPT ID token is signed with an unknown key.");
933
+ const valid = crypto3.verify(
934
+ "RSA-SHA256",
935
+ Buffer.from(`${headerSeg}.${payloadSeg}`),
936
+ crypto3.createPublicKey({ key: jwk, format: "jwk" }),
937
+ Buffer.from(signatureSeg, "base64url")
938
+ );
939
+ if (!valid) throw new Error("ChatGPT ID token signature is invalid.");
940
+ const claims = decodeSegment(payloadSeg);
941
+ const now = Math.floor((expected.now ?? Date.now()) / 1e3);
942
+ const aud = claims.aud;
943
+ const audiences = Array.isArray(aud) ? aud : [aud];
944
+ if (claims.iss !== ISSUER) throw new Error("ChatGPT ID token has the wrong issuer.");
945
+ if (!audiences.includes(expected.clientId)) {
946
+ throw new Error("ChatGPT ID token was issued to another app.");
947
+ }
948
+ if (typeof claims.exp !== "number" || claims.exp + ID_TOKEN_CLOCK_SKEW_S < now) {
949
+ throw new Error("ChatGPT ID token has expired.");
1140
950
  }
1141
- const errorCode = typeof data.error === "string" ? data.error : "";
1142
- throw new Error(`Grok token refresh failed (${status}): ${errorCode || errorDetail2(data)}`);
951
+ if (claims.nonce !== expected.nonce) throw new Error("ChatGPT ID token nonce mismatch.");
952
+ return claims;
1143
953
  }
1144
954
 
1145
955
  // src/file-lock.ts
1146
- import fs from "fs/promises";
956
+ import fs4 from "fs/promises";
1147
957
  import { setTimeout as setTimeout2 } from "timers/promises";
1148
958
  var STALE_TIMEOUT_MS = 1e4;
1149
959
  var RETRY_INTERVAL_MS = 50;
@@ -1164,7 +974,7 @@ async function acquireLock(lockPath) {
1164
974
  while (true) {
1165
975
  try {
1166
976
  const info = { pid: process.pid, timestamp: Date.now() };
1167
- await fs.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
977
+ await fs4.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
1168
978
  return;
1169
979
  } catch (err) {
1170
980
  const code = err.code;
@@ -1177,26 +987,26 @@ async function acquireLock(lockPath) {
1177
987
  continue;
1178
988
  }
1179
989
  try {
1180
- const content = await fs.readFile(lockPath, "utf-8");
990
+ const content = await fs4.readFile(lockPath, "utf-8");
1181
991
  const info = JSON.parse(content);
1182
992
  const isProcessAlive = isAlive(info.pid);
1183
993
  const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
1184
994
  if (!isProcessAlive || isStale) {
1185
- await fs.unlink(lockPath).catch(() => {
995
+ await fs4.unlink(lockPath).catch(() => {
1186
996
  });
1187
997
  continue;
1188
998
  }
1189
999
  } catch {
1190
- const stat = await fs.stat(lockPath).catch(() => null);
1000
+ const stat = await fs4.stat(lockPath).catch(() => null);
1191
1001
  if (!stat) continue;
1192
1002
  if (Date.now() - stat.mtimeMs > STALE_TIMEOUT_MS) {
1193
- await fs.unlink(lockPath).catch(() => {
1003
+ await fs4.unlink(lockPath).catch(() => {
1194
1004
  });
1195
1005
  continue;
1196
1006
  }
1197
1007
  }
1198
1008
  if (Date.now() - startTime > MAX_WAIT_MS) {
1199
- await fs.unlink(lockPath).catch(() => {
1009
+ await fs4.unlink(lockPath).catch(() => {
1200
1010
  });
1201
1011
  continue;
1202
1012
  }
@@ -1206,14 +1016,14 @@ async function acquireLock(lockPath) {
1206
1016
  }
1207
1017
  async function lockFileExists(lockPath) {
1208
1018
  try {
1209
- await fs.stat(lockPath);
1019
+ await fs4.stat(lockPath);
1210
1020
  return true;
1211
1021
  } catch (err) {
1212
1022
  return err.code !== "ENOENT";
1213
1023
  }
1214
1024
  }
1215
1025
  async function releaseLock(lockPath) {
1216
- await fs.unlink(lockPath).catch(() => {
1026
+ await fs4.unlink(lockPath).catch(() => {
1217
1027
  });
1218
1028
  }
1219
1029
  function isAlive(pid) {
@@ -1227,8 +1037,8 @@ function isAlive(pid) {
1227
1037
  }
1228
1038
 
1229
1039
  // src/logger.ts
1230
- import fs2 from "fs";
1231
- import path2 from "path";
1040
+ import fs5 from "fs";
1041
+ import path3 from "path";
1232
1042
  import { randomBytes } from "crypto";
1233
1043
  import { environmentSecrets, redactText, redactValue } from "@kenkaiiii/gg-ai";
1234
1044
  var MAX_BYTES = 10 * 1024 * 1024;
@@ -1245,14 +1055,14 @@ var cleanups = [];
1245
1055
  var exactSecrets = [];
1246
1056
  function rotateIfNeeded(filePath, force = false) {
1247
1057
  try {
1248
- const st = fs2.statSync(filePath);
1058
+ const st = fs5.statSync(filePath);
1249
1059
  if (!force && st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
1250
1060
  const rotated = `${filePath}.1`;
1251
1061
  try {
1252
- fs2.unlinkSync(rotated);
1062
+ fs5.unlinkSync(rotated);
1253
1063
  } catch {
1254
1064
  }
1255
- fs2.renameSync(filePath, rotated);
1065
+ fs5.renameSync(filePath, rotated);
1256
1066
  } catch {
1257
1067
  }
1258
1068
  }
@@ -1260,18 +1070,18 @@ function rotateOpenLog() {
1260
1070
  if (fd === null || logPath === null) return false;
1261
1071
  const sizeBefore = fileBytes;
1262
1072
  try {
1263
- fs2.closeSync(fd);
1073
+ fs5.closeSync(fd);
1264
1074
  } catch {
1265
1075
  }
1266
1076
  fd = null;
1267
1077
  rotateIfNeeded(logPath, true);
1268
1078
  try {
1269
- fd = fs2.openSync(logPath, "a");
1270
- fileBytes = fs2.fstatSync(fd).size;
1079
+ fd = fs5.openSync(logPath, "a");
1080
+ fileBytes = fs5.fstatSync(fd).size;
1271
1081
  } catch {
1272
1082
  if (fd !== null) {
1273
1083
  try {
1274
- fs2.closeSync(fd);
1084
+ fs5.closeSync(fd);
1275
1085
  } catch {
1276
1086
  }
1277
1087
  }
@@ -1287,22 +1097,22 @@ function openLog(filePath, name) {
1287
1097
  appName = name;
1288
1098
  exactSecrets = environmentSecrets(process.env);
1289
1099
  try {
1290
- fs2.mkdirSync(path2.dirname(filePath), { recursive: true, mode: 448 });
1100
+ fs5.mkdirSync(path3.dirname(filePath), { recursive: true, mode: 448 });
1291
1101
  } catch {
1292
1102
  }
1293
1103
  rotateIfNeeded(filePath);
1294
1104
  try {
1295
- fd = fs2.openSync(filePath, "a");
1105
+ fd = fs5.openSync(filePath, "a");
1296
1106
  } catch {
1297
1107
  fd = null;
1298
1108
  fileBytes = 0;
1299
1109
  return false;
1300
1110
  }
1301
1111
  try {
1302
- fileBytes = fs2.fstatSync(fd).size;
1112
+ fileBytes = fs5.fstatSync(fd).size;
1303
1113
  } catch {
1304
1114
  try {
1305
- fs2.closeSync(fd);
1115
+ fs5.closeSync(fd);
1306
1116
  } catch {
1307
1117
  }
1308
1118
  fd = null;
@@ -1315,7 +1125,7 @@ function openLog(filePath, name) {
1315
1125
  sessionId = randomBytes(4).toString("hex");
1316
1126
  try {
1317
1127
  if (fileBytes < MAX_BYTES) {
1318
- const written = fs2.writeSync(fd, "\n");
1128
+ const written = fs5.writeSync(fd, "\n");
1319
1129
  fileBytes += written;
1320
1130
  processBytes += written;
1321
1131
  }
@@ -1350,9 +1160,9 @@ function log(level, category, message, data) {
1350
1160
  `;
1351
1161
  try {
1352
1162
  if (fileBytes + Buffer.byteLength(capLine) <= MAX_BYTES) {
1353
- fileBytes += fs2.writeSync(fd, capLine);
1163
+ fileBytes += fs5.writeSync(fd, capLine);
1354
1164
  }
1355
- fs2.closeSync(fd);
1165
+ fs5.closeSync(fd);
1356
1166
  } catch {
1357
1167
  }
1358
1168
  fd = null;
@@ -1362,7 +1172,7 @@ function log(level, category, message, data) {
1362
1172
  if (fileBytes + lineBytes > MAX_BYTES && !rotationFailed && !rotateOpenLog()) return;
1363
1173
  if (fd === null) return;
1364
1174
  try {
1365
- const written = fs2.writeSync(fd, line);
1175
+ const written = fs5.writeSync(fd, line);
1366
1176
  fileBytes += written;
1367
1177
  processBytes += written;
1368
1178
  } catch {
@@ -1375,7 +1185,7 @@ function closeLogger(opts) {
1375
1185
  if (fd !== null) {
1376
1186
  if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
1377
1187
  try {
1378
- if (fd !== null) fs2.closeSync(fd);
1188
+ if (fd !== null) fs5.closeSync(fd);
1379
1189
  } catch {
1380
1190
  }
1381
1191
  }
@@ -1391,7 +1201,7 @@ function closeLogger(opts) {
1391
1201
  }
1392
1202
 
1393
1203
  // src/hidden-providers.ts
1394
- var HIDDEN_PROVIDERS = ["gemini"];
1204
+ var HIDDEN_PROVIDERS = [];
1395
1205
  function isHiddenProvider(provider) {
1396
1206
  return HIDDEN_PROVIDERS.includes(provider);
1397
1207
  }
@@ -1482,22 +1292,76 @@ async function detectXiaomiTokenPlanBaseUrl(apiKey, options = {}) {
1482
1292
  }
1483
1293
 
1484
1294
  // src/auth-storage.ts
1485
- var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
1486
- var XAI_OAUTH_KEY = "xai-oauth";
1487
- var DUAL_AUTH_PROVIDERS = [
1295
+ var OPENAI_CHATGPT_KEY = "openai-chatgpt";
1296
+ var KIMI_CODE_KEY = "kimi-code";
1297
+ var RETIRED_SIGN_INS = [
1298
+ {
1299
+ provider: "anthropic",
1300
+ storageKey: "anthropic",
1301
+ message: "Your old Claude sign-in no longer works. Sign in with Claude again to keep using your subscription."
1302
+ },
1303
+ {
1304
+ provider: "openai",
1305
+ storageKey: "openai",
1306
+ message: "Your old ChatGPT sign-in no longer works. Continue with ChatGPT again, or add an OpenAI API key."
1307
+ },
1488
1308
  {
1489
1309
  provider: "moonshot",
1490
- oauthKey: MOONSHOT_OAUTH_KEY,
1491
- oauthLabel: "Kimi OAuth",
1492
- apiKeyLabel: "Moonshot API key",
1493
- restoreHint: 'Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
1310
+ storageKey: "moonshot-oauth",
1311
+ message: "Kimi sign-in was removed. Paste a Kimi Code plan key from the Kimi Code Console, or a Moonshot API key."
1494
1312
  },
1495
1313
  {
1496
1314
  provider: "xai",
1497
- oauthKey: XAI_OAUTH_KEY,
1498
- oauthLabel: "Grok OAuth",
1499
- apiKeyLabel: "xAI API key",
1500
- restoreHint: 'Run "ggcoder login" and choose Grok OAuth to restore OAuth auth.'
1315
+ storageKey: "xai-oauth",
1316
+ message: "Grok sign-in was removed: xAI offers SuperGrok and X Premium usage only in apps it partners with. Add an xAI API key instead."
1317
+ },
1318
+ {
1319
+ provider: "gemini",
1320
+ storageKey: "gemini",
1321
+ message: "Google sign-in was removed. Add a Gemini API key from Google AI Studio instead."
1322
+ }
1323
+ ];
1324
+ function isRetiredEntry(retired, value) {
1325
+ if (!value) return false;
1326
+ switch (retired.storageKey) {
1327
+ case "anthropic":
1328
+ return isLegacyClaudeOAuth(value);
1329
+ // These slots now hold API keys; only a refreshable sign-in token is retired.
1330
+ case "openai":
1331
+ case "gemini":
1332
+ return Boolean(value.refreshToken) || Boolean(value.accountId) || Boolean(value.projectId);
1333
+ default:
1334
+ return true;
1335
+ }
1336
+ }
1337
+ function isRetiredStorageKey(data, storageKey) {
1338
+ return RETIRED_SIGN_INS.some(
1339
+ (r) => r.storageKey === storageKey && isRetiredEntry(r, data[storageKey])
1340
+ );
1341
+ }
1342
+ function usableEntry(data, storageKey) {
1343
+ return isRetiredStorageKey(data, storageKey) ? void 0 : data[storageKey];
1344
+ }
1345
+ function storageKeyProvider(storageKey) {
1346
+ if (storageKey === OPENAI_CHATGPT_KEY) return "openai";
1347
+ if (storageKey === KIMI_CODE_KEY) return "moonshot";
1348
+ if (storageKey === XIAOMI_CREDITS_KEY) return "xiaomi";
1349
+ if (storageKey === GEMINI_API_KEY) return "gemini";
1350
+ return storageKey;
1351
+ }
1352
+ function apiKeyStorageKeys(provider) {
1353
+ if (provider === "moonshot") return [KIMI_CODE_KEY, "moonshot"];
1354
+ if (provider === "xiaomi") return ["xiaomi", XIAOMI_CREDITS_KEY];
1355
+ if (provider === "gemini") return [GEMINI_API_KEY];
1356
+ return [provider];
1357
+ }
1358
+ var DUAL_AUTH_PROVIDERS = [
1359
+ {
1360
+ provider: "openai",
1361
+ oauthKey: OPENAI_CHATGPT_KEY,
1362
+ oauthLabel: "ChatGPT plan",
1363
+ apiKeyLabel: "OpenAI API key",
1364
+ restoreHint: 'Run "ggcoder login" and choose Continue with ChatGPT to use your ChatGPT plan again.'
1501
1365
  }
1502
1366
  ];
1503
1367
  function dualAuthProvider(provider) {
@@ -1522,14 +1386,20 @@ function activeBaseUrlEntry(data, provider) {
1522
1386
  const dual = dualAuthProvider(provider);
1523
1387
  if (dual) {
1524
1388
  const oauth = data[dual.oauthKey];
1389
+ const apiKey = usableEntry(data, dual.provider);
1525
1390
  if (oauth) {
1526
1391
  const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
1527
- if (Date.now() < exhaustedUntil && data[dual.provider]) return data[dual.provider];
1392
+ if (Date.now() < exhaustedUntil && apiKey) return apiKey;
1528
1393
  return oauth;
1529
1394
  }
1530
- return data[dual.provider];
1395
+ return apiKey;
1396
+ }
1397
+ const key = apiKeyStorageKeys(provider).find((k) => usableEntry(data, k));
1398
+ const entry = key ? data[key] : void 0;
1399
+ if (key === KIMI_CODE_KEY && entry && !entry.baseUrl) {
1400
+ return { ...entry, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
1531
1401
  }
1532
- return data[provider];
1402
+ return entry;
1533
1403
  }
1534
1404
  var MINIMAX_REGION_RETRY_MS = 5 * 60 * 1e3;
1535
1405
  var miniMaxRegionProbes = /* @__PURE__ */ new Map();
@@ -1540,9 +1410,17 @@ function cachedMiniMaxRegion(apiKey) {
1540
1410
  miniMaxRegionProbes.set(apiKey, { at: Date.now(), result });
1541
1411
  return result;
1542
1412
  }
1413
+ var kimiRegionProbes = /* @__PURE__ */ new Map();
1414
+ function cachedKimiRegion(apiKey) {
1415
+ const cached = kimiRegionProbes.get(apiKey);
1416
+ if (cached && Date.now() - cached.at < MINIMAX_REGION_RETRY_MS) return cached.result;
1417
+ const result = detectKimiCodeBaseUrl(apiKey).catch(() => void 0);
1418
+ kimiRegionProbes.set(apiKey, { at: Date.now(), result });
1419
+ return result;
1420
+ }
1543
1421
  function readStoredBaseUrlSync(authFile, provider) {
1544
1422
  try {
1545
- const data = JSON.parse(readFileSync2(authFile, "utf-8"));
1423
+ const data = JSON.parse(readFileSync(authFile, "utf-8"));
1546
1424
  return activeBaseUrlEntry(data, provider)?.baseUrl;
1547
1425
  } catch {
1548
1426
  return void 0;
@@ -1555,19 +1433,7 @@ function refreshThresholdMs(creds) {
1555
1433
  return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
1556
1434
  }
1557
1435
  var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
1558
- var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
1559
- "glm",
1560
- "moonshot",
1561
- "xiaomi",
1562
- "minimax",
1563
- "deepseek",
1564
- "openrouter",
1565
- "huggingface",
1566
- "sakana",
1567
- "xai",
1568
- // Local endpoints: a fixed (usually placeholder) key, never refreshable.
1569
- "local"
1570
- ]);
1436
+ var REFRESHABLE_STORAGE_KEYS = /* @__PURE__ */ new Set([OPENAI_CHATGPT_KEY]);
1571
1437
  var AuthStorage = class {
1572
1438
  data = {};
1573
1439
  filePath;
@@ -1588,10 +1454,12 @@ var AuthStorage = class {
1588
1454
  refreshLocks = /* @__PURE__ */ new Map();
1589
1455
  detectMiniMaxRegion;
1590
1456
  detectXiaomiCluster;
1457
+ detectKimiRegion;
1591
1458
  constructor(filePath, options) {
1592
1459
  this.filePath = filePath ?? getAppPaths().authFile;
1593
1460
  this.detectMiniMaxRegion = options?.detectMiniMaxBaseUrl ?? cachedMiniMaxRegion;
1594
1461
  this.detectXiaomiCluster = options?.detectXiaomiTokenPlanBaseUrl ?? detectXiaomiTokenPlanBaseUrl;
1462
+ this.detectKimiRegion = options?.detectKimiCodeBaseUrl ?? cachedKimiRegion;
1595
1463
  }
1596
1464
  /** Path to the on-disk auth file. Useful for status output. */
1597
1465
  get path() {
@@ -1619,27 +1487,78 @@ var AuthStorage = class {
1619
1487
  return keys.find((key) => Boolean(this.data[key]));
1620
1488
  }
1621
1489
  /**
1622
- * True if the user has any usable auth for the logical provider. For a
1623
- * dual-auth provider (Kimi/Grok) either the OAuth credential or the API key
1624
- * satisfies it.
1490
+ * True if the user has any usable auth for the logical provider: the plan
1491
+ * sign-in or any of its API keys. A retired sign-in doesn't count.
1625
1492
  */
1626
1493
  async hasProviderAuth(provider) {
1627
1494
  if (isHiddenProvider(provider)) return false;
1628
1495
  await this.ensureFresh();
1629
1496
  const dual = dualAuthProvider(provider);
1630
1497
  if (dual) {
1631
- return Boolean(this.data[dual.oauthKey] || this.data[dual.provider]);
1632
- }
1633
- if (provider === "xiaomi") {
1634
- return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
1498
+ return Boolean(this.data[dual.oauthKey] || usableEntry(this.data, dual.provider));
1635
1499
  }
1636
- if (provider === "gemini") {
1637
- return Boolean(this.data["gemini"] || this.data[GEMINI_API_KEY]);
1500
+ if (provider === "anthropic") {
1501
+ return Boolean(this.data.anthropic) && !isLegacyClaudeOAuth(this.data.anthropic) || isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
1638
1502
  }
1639
1503
  if (provider === "local") {
1640
1504
  return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
1641
1505
  }
1642
- return Boolean(this.data[provider]);
1506
+ return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
1507
+ }
1508
+ /** True if any of `provider`'s API keys is stored. A retired sign-in in a key slot doesn't count. */
1509
+ async hasApiKey(provider) {
1510
+ await this.ensureFresh();
1511
+ if (provider === "anthropic" && isLegacyClaudeOAuth(this.data.anthropic)) return false;
1512
+ return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
1513
+ }
1514
+ /**
1515
+ * `provider`'s first stored API key, never its plan sign-in. For calls that
1516
+ * only an API key may make (OpenAI's Images API isn't on the ChatGPT plan).
1517
+ */
1518
+ async resolveApiKey(provider) {
1519
+ await this.ensureFresh();
1520
+ for (const key of apiKeyStorageKeys(provider)) {
1521
+ const creds = usableEntry(this.data, key);
1522
+ if (creds && !(provider === "anthropic" && isLegacyClaudeOAuth(creds))) return creds;
1523
+ }
1524
+ throw new NotLoggedInError(provider);
1525
+ }
1526
+ /** True when the user signed in with Claude through Claude Code (marker only, no token). */
1527
+ async hasClaudeCodeLogin() {
1528
+ await this.ensureFresh();
1529
+ return isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
1530
+ }
1531
+ /** Record a Claude Code sign-in. Claude Code keeps the token; auth.json gets only the marker. */
1532
+ async setClaudeCodeLogin() {
1533
+ await this.mutateLatest((latest) => {
1534
+ latest[CLAUDE_CODE_AUTH_KEY] = { ...CLAUDE_CODE_AUTH_MARKER };
1535
+ if (isLegacyClaudeOAuth(latest.anthropic)) delete latest.anthropic;
1536
+ });
1537
+ }
1538
+ /** Retired sign-ins still on disk, so the UI can ask the user to reconnect. */
1539
+ async legacySignIns() {
1540
+ await this.ensureFresh();
1541
+ return RETIRED_SIGN_INS.filter((r) => isRetiredEntry(r, this.data[r.storageKey]));
1542
+ }
1543
+ /**
1544
+ * Remove `provider`'s retired sign-in, if any. True when one was removed, so
1545
+ * the caller can tell the user once to reconnect.
1546
+ */
1547
+ async takeLegacySignIn(provider) {
1548
+ const retired = RETIRED_SIGN_INS.find((r) => r.provider === provider);
1549
+ if (!retired) return false;
1550
+ await this.ensureFresh();
1551
+ if (!isRetiredEntry(retired, this.data[retired.storageKey])) return false;
1552
+ let removed = false;
1553
+ await this.mutateLatest((latest) => {
1554
+ if (!isRetiredEntry(retired, latest[retired.storageKey])) return;
1555
+ delete latest[retired.storageKey];
1556
+ removed = true;
1557
+ });
1558
+ return removed;
1559
+ }
1560
+ async clearClaudeCodeLogin() {
1561
+ await this.clearCredentials(CLAUDE_CODE_AUTH_KEY);
1643
1562
  }
1644
1563
  /** Endpoint ids that currently have a `local:<id>` credential stored. */
1645
1564
  async listLocalEndpointIds() {
@@ -1674,18 +1593,18 @@ var AuthStorage = class {
1674
1593
  const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
1675
1594
  if (dual && oauthCreds) {
1676
1595
  const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
1677
- const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data[dual.provider]);
1596
+ const apiKeyActive = Date.now() < exhaustedUntil && Boolean(usableEntry(this.data, dual.provider));
1678
1597
  if (!apiKeyActive) return false;
1679
1598
  }
1680
- return STATIC_API_KEY_PROVIDERS.has(provider);
1599
+ return !REFRESHABLE_STORAGE_KEYS.has(provider);
1681
1600
  }
1682
1601
  /**
1683
1602
  * The base URL on the credential that is active right now, if any.
1684
1603
  * Synchronous — call only after load()/resolveCredentials() populated the
1685
- * snapshot. For a dual-auth provider this is the subscription endpoint (Kimi
1686
- * For Coding, the Grok CLI proxy) whenever the OAuth entry is the one
1687
- * resolveCredentials would serve (i.e. not currently usage-exhausted with an
1688
- * API key configured).
1604
+ * snapshot. For a dual-auth provider this is the plan endpoint whenever the
1605
+ * sign-in is the one resolveCredentials would serve (i.e. not currently
1606
+ * usage-exhausted with an API key configured); for a plan/pay-per-token key
1607
+ * split it is the plan key's endpoint when one is stored.
1689
1608
  */
1690
1609
  getStoredBaseUrl(provider) {
1691
1610
  return activeBaseUrlEntry(this.data, provider)?.baseUrl;
@@ -1694,7 +1613,7 @@ var AuthStorage = class {
1694
1613
  const first = !this.loaded;
1695
1614
  await withFileLock(this.filePath, async () => {
1696
1615
  try {
1697
- const content = await fs3.readFile(this.filePath, "utf-8");
1616
+ const content = await fs6.readFile(this.filePath, "utf-8");
1698
1617
  this.data = JSON.parse(content);
1699
1618
  if (first) {
1700
1619
  log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
@@ -1740,7 +1659,7 @@ var AuthStorage = class {
1740
1659
  }
1741
1660
  let changed;
1742
1661
  try {
1743
- const stat = await fs3.stat(this.filePath);
1662
+ const stat = await fs6.stat(this.filePath);
1744
1663
  changed = stat.ino !== this.snapshotIno || stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
1745
1664
  } catch {
1746
1665
  changed = this.snapshotSize !== -1;
@@ -1753,7 +1672,7 @@ var AuthStorage = class {
1753
1672
  */
1754
1673
  async rememberSnapshot() {
1755
1674
  try {
1756
- const stat = await fs3.stat(this.filePath);
1675
+ const stat = await fs6.stat(this.filePath);
1757
1676
  this.snapshotMtimeMs = stat.mtimeMs;
1758
1677
  this.snapshotSize = stat.size;
1759
1678
  this.snapshotIno = stat.ino;
@@ -1808,9 +1727,34 @@ var AuthStorage = class {
1808
1727
  });
1809
1728
  return { ...creds, baseUrl };
1810
1729
  }
1811
- async setCredentials(provider, creds) {
1730
+ /**
1731
+ * A Kimi Code key saved without an endpoint gets the host that issued it
1732
+ * (`api.kimi.com` or `api.kimi.ai`), saved so the check runs once.
1733
+ */
1734
+ async withKimiRegion(creds) {
1735
+ const baseUrl = await this.detectKimiRegion(creds.accessToken);
1736
+ if (!baseUrl) return { ...creds, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
1737
+ log("INFO", "auth", `Kimi Code key belongs to ${baseUrl} \u2014 saved for future requests`);
1738
+ await this.mutateLatest((latest) => {
1739
+ const current = latest[KIMI_CODE_KEY];
1740
+ if (current?.accessToken === creds.accessToken && !current.baseUrl) {
1741
+ current.baseUrl = baseUrl;
1742
+ }
1743
+ });
1744
+ return { ...creds, baseUrl };
1745
+ }
1746
+ /**
1747
+ * Store `creds` under `storageKey`. Connecting a provider again also removes
1748
+ * its retired sign-in, which is what the reconnect notice asked for.
1749
+ */
1750
+ async setCredentials(storageKey, creds) {
1751
+ const provider = storageKeyProvider(storageKey);
1812
1752
  await this.mutateLatest((latest) => {
1813
- latest[provider] = creds;
1753
+ for (const retired of RETIRED_SIGN_INS) {
1754
+ if (retired.provider !== provider || retired.storageKey === storageKey) continue;
1755
+ if (isRetiredEntry(retired, latest[retired.storageKey])) delete latest[retired.storageKey];
1756
+ }
1757
+ latest[storageKey] = creds;
1814
1758
  });
1815
1759
  }
1816
1760
  /**
@@ -1847,9 +1791,9 @@ var AuthStorage = class {
1847
1791
  * Mark the credential stored under `storageKey` as usage-exhausted until
1848
1792
  * `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
1849
1793
  * 15-minute default when no reset time is known. While the mark is in the
1850
- * future, `resolveCredentials("moonshot")` serves the Moonshot API key
1851
- * instead of the subscription OAuth credential (when both are configured) — OAuth
1852
- * stays the preferred credential and is retried automatically once the mark
1794
+ * future, `resolveCredentials("openai")` serves the OpenAI API key instead of
1795
+ * the ChatGPT plan sign-in (when both are configured) — the plan stays the
1796
+ * preferred credential and is retried automatically once the mark
1853
1797
  * lapses. Persisted to auth.json so a restart (or another gg-app window)
1854
1798
  * doesn't burn a request rediscovering the same exhausted window. No-op if
1855
1799
  * nothing is stored under `storageKey`.
@@ -1894,12 +1838,12 @@ var AuthStorage = class {
1894
1838
  await this.ensureFresh();
1895
1839
  }
1896
1840
  const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
1897
- if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1841
+ if (!directStorageKeys.some((key) => Boolean(usableEntry(this.data, key))) && !(provider === "anthropic" && this.data[CLAUDE_CODE_AUTH_KEY])) {
1898
1842
  await this.reloadLatest();
1899
1843
  }
1900
1844
  if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
1901
1845
  for (const key of opts.storageKeys) {
1902
- const creds2 = this.data[key];
1846
+ const creds2 = usableEntry(this.data, key);
1903
1847
  if (!creds2) continue;
1904
1848
  if (key !== provider && dualAuthProviderByOAuthKey(key)) {
1905
1849
  return await this.resolveCredentials(key, {
@@ -1913,21 +1857,29 @@ var AuthStorage = class {
1913
1857
  ...opts.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1914
1858
  });
1915
1859
  }
1860
+ if (key === KIMI_CODE_KEY && !creds2.baseUrl) return await this.withKimiRegion(creds2);
1916
1861
  return creds2;
1917
1862
  }
1918
1863
  throw new NotLoggedInError(provider);
1919
1864
  }
1865
+ if (provider === "anthropic" && (!this.data.anthropic || isLegacyClaudeOAuth(this.data.anthropic))) {
1866
+ if (isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY])) {
1867
+ return { accessToken: "", refreshToken: "", expiresAt: Number.MAX_SAFE_INTEGER };
1868
+ }
1869
+ if (this.data.anthropic) throw new NotLoggedInError(provider);
1870
+ }
1920
1871
  const dual = dualAuthProvider(provider);
1921
1872
  const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
1873
+ const dualApiKey = dual ? usableEntry(this.data, dual.provider) : void 0;
1922
1874
  if (dual && dualOAuthCreds) {
1923
1875
  const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
1924
- if (Date.now() < exhaustedUntil && this.data[dual.provider]) {
1876
+ if (Date.now() < exhaustedUntil && dualApiKey) {
1925
1877
  log(
1926
1878
  "WARN",
1927
1879
  "auth",
1928
1880
  `${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
1929
1881
  );
1930
- return this.data[dual.provider];
1882
+ return dualApiKey;
1931
1883
  }
1932
1884
  try {
1933
1885
  return await this.resolveCredentials(dual.oauthKey, {
@@ -1935,23 +1887,24 @@ var AuthStorage = class {
1935
1887
  ...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
1936
1888
  });
1937
1889
  } catch (err) {
1938
- if (err instanceof NotLoggedInError && this.data[dual.provider]) {
1890
+ if (err instanceof NotLoggedInError && dualApiKey) {
1939
1891
  log(
1940
1892
  "WARN",
1941
1893
  "auth",
1942
1894
  `${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
1943
1895
  );
1944
- return this.data[dual.provider];
1896
+ return dualApiKey;
1945
1897
  }
1946
1898
  throw err;
1947
1899
  }
1948
1900
  }
1949
- const creds = this.data[provider];
1901
+ const creds = usableEntry(this.data, provider);
1950
1902
  if (!creds) {
1951
1903
  throw new NotLoggedInError(provider);
1952
1904
  }
1953
- if (STATIC_API_KEY_PROVIDERS.has(provider)) {
1905
+ if (!REFRESHABLE_STORAGE_KEYS.has(provider)) {
1954
1906
  if (provider === "minimax" && !creds.baseUrl) return await this.withMiniMaxRegion(creds);
1907
+ if (provider === KIMI_CODE_KEY && !creds.baseUrl) return await this.withKimiRegion(creds);
1955
1908
  return creds;
1956
1909
  }
1957
1910
  if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
@@ -1979,13 +1932,14 @@ var AuthStorage = class {
1979
1932
  this.data = latest;
1980
1933
  return latestCreds;
1981
1934
  }
1982
- const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
1983
1935
  let refreshed;
1984
1936
  try {
1985
- refreshed = await refreshFn(latestCreds.refreshToken);
1937
+ refreshed = await refreshChatGPTToken(latestCreds);
1986
1938
  } catch (err) {
1987
1939
  const msg = err instanceof Error ? err.message : String(err);
1988
- const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1940
+ const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh|token_expired|refresh_token_(?:expired|invalidated|reused)/i.test(
1941
+ msg
1942
+ ) || /unauthorized/i.test(msg);
1989
1943
  if (isAuthFailure) {
1990
1944
  const current = await readAuthData(this.filePath);
1991
1945
  const currentCreds = current[provider];
@@ -2005,15 +1959,10 @@ var AuthStorage = class {
2005
1959
  }
2006
1960
  throw err;
2007
1961
  }
2008
- if (!refreshed.accountId && latestCreds.accountId) {
2009
- refreshed.accountId = latestCreds.accountId;
2010
- }
2011
- if (!refreshed.projectId && latestCreds.projectId) {
2012
- refreshed.projectId = latestCreds.projectId;
2013
- }
2014
- if (!refreshed.baseUrl && latestCreds.baseUrl) {
2015
- refreshed.baseUrl = latestCreds.baseUrl;
2016
- }
1962
+ refreshed = {
1963
+ ...pick(latestCreds, ["accountId", "baseUrl", "clientId", "idToken", "scopes"]),
1964
+ ...refreshed
1965
+ };
2017
1966
  latest[provider] = refreshed;
2018
1967
  this.data = latest;
2019
1968
  await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
@@ -2036,9 +1985,14 @@ var AuthStorage = class {
2036
1985
  return creds.accessToken;
2037
1986
  }
2038
1987
  };
1988
+ function pick(value, keys) {
1989
+ const out = {};
1990
+ for (const key of keys) if (value[key] !== void 0) out[key] = value[key];
1991
+ return out;
1992
+ }
2039
1993
  async function readAuthData(filePath) {
2040
1994
  try {
2041
- const content = await fs3.readFile(filePath, "utf-8");
1995
+ const content = await fs6.readFile(filePath, "utf-8");
2042
1996
  return JSON.parse(content);
2043
1997
  } catch (error) {
2044
1998
  if (error.code === "ENOENT") return {};
@@ -2046,12 +2000,12 @@ async function readAuthData(filePath) {
2046
2000
  }
2047
2001
  }
2048
2002
  async function atomicWriteFile(filePath, content) {
2049
- const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto5.randomUUID().slice(0, 8)}.tmp`;
2003
+ const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto4.randomUUID().slice(0, 8)}.tmp`;
2050
2004
  try {
2051
- await fs3.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
2052
- await fs3.rename(tmpPath, filePath);
2005
+ await fs6.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
2006
+ await fs6.rename(tmpPath, filePath);
2053
2007
  } catch (err) {
2054
- await fs3.unlink(tmpPath).catch(() => {
2008
+ await fs6.unlink(tmpPath).catch(() => {
2055
2009
  });
2056
2010
  throw err;
2057
2011
  }
@@ -2083,7 +2037,8 @@ var CODEX_FAST_BILLING = {
2083
2037
  detail: "Codex Fast mode uses your ChatGPT plan limits at 2.5x the standard rate (purchased credits at 2x).",
2084
2038
  paid: false
2085
2039
  };
2086
- var GEMINI_AUTH_KEYS = [GEMINI_API_KEY, "gemini"];
2040
+ var GEMINI_AUTH_KEYS = [GEMINI_API_KEY];
2041
+ var KIMI_AUTH_KEYS = [KIMI_CODE_KEY, "moonshot"];
2087
2042
  var MODELS = [
2088
2043
  // ── Anthropic ──────────────────────────────────────────
2089
2044
  // NOTE: Claude Mythos 5.1 (`claude-mythos-5-1`, 2026-09-01) is kept commented
@@ -2213,14 +2168,13 @@ var MODELS = [
2213
2168
  {
2214
2169
  // GPT-6 Astra — "Our most capable model for complex, demanding work."
2215
2170
  // (Codex catalog priority 2, listed for every ChatGPT plan, requires a
2216
- // Codex client >= 0.153.0 — see CODEX_CLIENT_VERSION). Same split as 5.6:
2217
- // 1.05M on the public Responses API, 272K on the ChatGPT OAuth route
2171
+ // Codex client >= 0.153.0). Same split as 5.6:
2172
+ // 1.05M on the public Responses API, 272K on the ChatGPT plan route
2218
2173
  // (openai/codex models.json, `gpt-6-astra`). Reasoning ladder low → medium
2219
2174
  // → high → xhigh → max → ultra; `ultra` is the Codex orchestration preset
2220
2175
  // (multi_agent v2) and is Codex-only — the public API tops out at `max`.
2221
- // Note: through a plain API key OpenAI requires the Responses API for tool
2222
- // calling on Astra, so the Chat Completions path is text-only; the OAuth
2223
- // Codex route is the supported way to use it as an agent.
2176
+ // OpenAI requires the Responses API for tool calling on Astra, so both
2177
+ // the ChatGPT plan and API keys ride gg-ai's Responses transport.
2224
2178
  id: "gpt-6-astra",
2225
2179
  name: "GPT-6 Astra",
2226
2180
  provider: "openai",
@@ -2254,18 +2208,16 @@ var MODELS = [
2254
2208
  billing: CODEX_FAST_BILLING
2255
2209
  },
2256
2210
  // GPT-6 Sol + Luna — released 2026-09-22 below Astra, replacing the whole
2257
- // GPT-5.6 family (Sol/Terra/Luna; there is no GPT-6 Terra — OpenAI's Codex
2211
+ // GPT-5.6 family (Sol/Terra/Luna; there was no GPT-6 Terra — OpenAI's Codex
2258
2212
  // catalog upgrades 5.6 Terra to 6 Sol). GPT-6.1 Sol (2026-09-29) then replaced
2259
- // GPT-6 Sol. Same window split as Astra: 1.05M on the public Responses API,
2213
+ // GPT-6 Sol, and GPT-6.1 Terra (2026-10-26) sits between Sol and Luna. Same window split as Astra: 1.05M on the public Responses API,
2260
2214
  // 272K on the Codex route; 128K output, text+image input, freeform
2261
2215
  // apply_patch, responses-lite transport. The GPT-5.6 ids and `gpt-6-sol` are
2262
2216
  // retired — a saved session on one falls back to the provider default on
2263
2217
  // next start.
2264
2218
  {
2265
2219
  // GPT-6.1 Sol — "Latest workhorse model for coding and everyday work."
2266
- // (Codex priority 1, default low). The catalog says Codex client >= 0.153.0,
2267
- // but the ChatGPT backend only serves it from 0.159.0 (see
2268
- // CODEX_CLIENT_VERSION). $2/$10 MTok, cached input $0.10. Ladder low →
2220
+ // (Codex priority 1, default low). $2/$10 MTok, cached input $0.10. Ladder low →
2269
2221
  // medium → high → xhigh → max → ultra; ultra is the Codex orchestration
2270
2222
  // preset (xhigh effort on the wire + proactive local subagent delegation).
2271
2223
  id: "gpt-6.1-sol",
@@ -2298,6 +2250,40 @@ var MODELS = [
2298
2250
  maxThinkingLevel: "ultra",
2299
2251
  billing: CODEX_FAST_BILLING
2300
2252
  },
2253
+ {
2254
+ // GPT-6.1 Terra (Codex priority 7, default low, client >= 0.153.0). $1/$5
2255
+ // MTok, cached input $0.05. Unlike Sol/Astra the Codex route gets a 500K
2256
+ // window (catalog `context_window`). Same ladder and Ultra preset as Sol.
2257
+ id: "gpt-6.1-terra",
2258
+ name: "GPT-6.1 Terra",
2259
+ provider: "openai",
2260
+ contextWindow: 105e4,
2261
+ codexContextWindow: 5e5,
2262
+ maxOutputTokens: 128e3,
2263
+ supportsThinking: true,
2264
+ defaultThinkingLevel: "low",
2265
+ supportsImages: true,
2266
+ supportsVideo: false,
2267
+ costTier: "medium",
2268
+ maxThinkingLevel: "ultra"
2269
+ },
2270
+ {
2271
+ // Codex Fast mode for 6.1 Terra ("1.5x speed") — see `gpt-6-astra-fast`
2272
+ // for the wire mapping and billing.
2273
+ id: "gpt-6.1-terra-fast",
2274
+ name: "GPT-6.1 Terra Fast",
2275
+ provider: "openai",
2276
+ contextWindow: 105e4,
2277
+ codexContextWindow: 5e5,
2278
+ maxOutputTokens: 128e3,
2279
+ supportsThinking: true,
2280
+ defaultThinkingLevel: "low",
2281
+ supportsImages: true,
2282
+ supportsVideo: false,
2283
+ costTier: "medium",
2284
+ maxThinkingLevel: "ultra",
2285
+ billing: CODEX_FAST_BILLING
2286
+ },
2301
2287
  {
2302
2288
  // Luna — "Fast and affordable model for easier tasks." (Codex priority 4,
2303
2289
  // default medium, needs a Codex client >= 0.155.0). $0.10/$0.50 MTok.
@@ -2418,8 +2404,8 @@ var MODELS = [
2418
2404
  maxThinkingLevel: "high",
2419
2405
  authStorageKeys: GEMINI_AUTH_KEYS
2420
2406
  },
2421
- // Keep 3.1 Flash Lite first for the working OAuth default and fast-model routing.
2422
- // New GA models are opt-in; Code Assist access varies by account. Gemini 3.7
2407
+ // Keep 3.1 Flash Lite first for the default and fast-model routing.
2408
+ // Gemini 3.7
2423
2409
  // Flash is retired: Google deprecated it on 2026-10-08 and routes every
2424
2410
  // `gemini-3.7-flash` request to 3.8 Flash (ai.google.dev/gemini-api/docs/
2425
2411
  // changelog), so a saved session on it falls back to the provider default.
@@ -2452,9 +2438,7 @@ var MODELS = [
2452
2438
  authStorageKeys: GEMINI_AUTH_KEYS
2453
2439
  },
2454
2440
  {
2455
- // Wire name `gemini-3-flash` — the Code Assist (OAuth) backend rejects the
2456
- // display string `gemini-3.5-flash` with a 404, so gemini-cli keeps this
2457
- // alternative name (SECONDARY_GEMINI_3_5_FLASH_MODEL) for that endpoint.
2441
+ // Wire name `gemini-3-flash`, the id Google serves 3.5 Flash under.
2458
2442
  id: "gemini-3-flash",
2459
2443
  name: "Gemini 3.5 Flash",
2460
2444
  provider: "gemini",
@@ -2469,9 +2453,7 @@ var MODELS = [
2469
2453
  authStorageKeys: GEMINI_AUTH_KEYS
2470
2454
  },
2471
2455
  {
2472
- // Gemini 3.1 Pro is public preview — gated behind Code Assist preview
2473
- // enablement, so free/personal OAuth accounts 404 on it (see
2474
- // ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
2456
+ // Gemini 3.1 Pro is public preview.
2475
2457
  id: "gemini-3.1-pro-preview",
2476
2458
  name: "Gemini 3.1 Pro (Preview)",
2477
2459
  provider: "gemini",
@@ -2488,8 +2470,8 @@ var MODELS = [
2488
2470
  // ── Moonshot (Kimi) ────────────────────────────────────
2489
2471
  // K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
2490
2472
  // work, and deep reasoning. Its effort ladder is server-declared as
2491
- // low/high/max on both the public API (default max) and the Kimi For Coding
2492
- // OAuth endpoint (default high); thinking can also be fully disabled.
2473
+ // low/high/max on both the public API (default max) and the Kimi Code plan
2474
+ // endpoint (default high); thinking can also be fully disabled.
2493
2475
  {
2494
2476
  id: "kimi-k3",
2495
2477
  name: "Kimi K3",
@@ -2503,13 +2485,14 @@ var MODELS = [
2503
2485
  supportsVideo: true,
2504
2486
  maxVideoBytes: 100 * 1024 * 1024,
2505
2487
  costTier: "high",
2506
- maxThinkingLevel: "max"
2488
+ maxThinkingLevel: "max",
2489
+ authStorageKeys: KIMI_AUTH_KEYS
2507
2490
  },
2508
- // K2.8 Preview (2026-09-11) is served only on the Kimi For Coding OAuth
2509
- // endpoint, under its rolling `kimi-for-coding` id (live /models, 2026-09-28:
2491
+ // K2.8 Preview (2026-09-11) is served only on the Kimi Code plan endpoint,
2492
+ // under its rolling `kimi-for-coding` id (live /models, 2026-09-28:
2510
2493
  // display_name "K2.8 Preview", 1M context, image + video input, efforts
2511
- // low/high/max default max). The public API-key endpoint does not serve it,
2512
- // so it resolves from the Kimi sign-in credential only.
2494
+ // low/high/max default max). The public Moonshot API does not serve it, so
2495
+ // it resolves from a Kimi Code plan key only.
2513
2496
  {
2514
2497
  id: "kimi-for-coding",
2515
2498
  name: "Kimi K2.8 Preview",
@@ -2522,7 +2505,7 @@ var MODELS = [
2522
2505
  maxVideoBytes: 100 * 1024 * 1024,
2523
2506
  costTier: "medium",
2524
2507
  maxThinkingLevel: "max",
2525
- authStorageKeys: [MOONSHOT_OAUTH_KEY]
2508
+ authStorageKeys: [KIMI_CODE_KEY]
2526
2509
  },
2527
2510
  // K2.7 Code is requested by its pinned id (not the `kimi-for-coding` alias
2528
2511
  // that moved to K2.8), so it stays the real K2.7 on both endpoints.
@@ -2537,7 +2520,8 @@ var MODELS = [
2537
2520
  supportsVideo: true,
2538
2521
  maxVideoBytes: 100 * 1024 * 1024,
2539
2522
  costTier: "medium",
2540
- maxThinkingLevel: "high"
2523
+ maxThinkingLevel: "high",
2524
+ authStorageKeys: KIMI_AUTH_KEYS
2541
2525
  },
2542
2526
  // K2.7 Code HighSpeed — the same model as K2.7 Code served at ~180 tok/s
2543
2527
  // (up to ~260 on short contexts), 256K context (platform.kimi.ai model list,
@@ -2556,7 +2540,8 @@ var MODELS = [
2556
2540
  supportsVideo: true,
2557
2541
  maxVideoBytes: 100 * 1024 * 1024,
2558
2542
  costTier: "medium",
2559
- maxThinkingLevel: "high"
2543
+ maxThinkingLevel: "high",
2544
+ authStorageKeys: KIMI_AUTH_KEYS
2560
2545
  },
2561
2546
  // ── Z.AI (GLM) ─────────────────────────────────────────
2562
2547
  // Two GLM entries, both live on the coding endpoint (verified against its
@@ -2969,26 +2954,40 @@ function getSummaryModel(provider, currentModelId) {
2969
2954
  }
2970
2955
 
2971
2956
  export {
2972
- kimiCodeBaseUrl,
2973
- kimiCodingHeaders,
2957
+ KIMI_CODE_DEFAULT_BASE_URL,
2958
+ KIMI_CODE_OVERSEAS_BASE_URL,
2959
+ detectKimiCodeBaseUrl,
2974
2960
  isKimiCodingEndpoint,
2975
- loginKimi,
2976
- refreshKimiToken,
2961
+ CLAUDE_CODE_MANIFEST,
2962
+ CLAUDE_CODE_AUTH_KEY,
2963
+ CLAUDE_CODE_AUTH_MARKER,
2964
+ isClaudeCodeAuthMarker,
2965
+ isLegacyClaudeOAuth,
2966
+ claudeCodePlatformKey,
2967
+ claudeCodeHome,
2968
+ claudeCodeConfigDir,
2969
+ claudeCodeBinaryPath,
2970
+ resolveClaudeCodeBinary,
2971
+ ensureClaudeCodeBinary,
2972
+ formatClaudeCodeInstallProgress,
2973
+ claudeCodeEnv,
2974
+ claudeCodeAuthStatus,
2975
+ logoutClaudeCode,
2976
+ claudeCodeCallbackUrl,
2977
+ loginClaudeCode,
2977
2978
  generatePKCE,
2978
- getClaudeCodeVersion,
2979
- getClaudeCliUserAgent,
2980
- pendingClaudeCodeVersionRefresh,
2981
- loginAnthropic,
2982
- refreshAnthropicToken,
2983
- loginOpenAI,
2984
- refreshOpenAIToken,
2985
- loginGemini,
2986
- refreshGeminiToken,
2987
- grokCliBaseUrl,
2988
- grokCliHeaders,
2989
- isGrokCliEndpoint,
2990
- loginXai,
2991
- refreshXaiToken,
2979
+ CHATGPT_PLAN_SCOPE,
2980
+ CHATGPT_PLAN_BASE_URL,
2981
+ CHATGPT_USAGE_URL,
2982
+ CHATGPT_PLAN_NOT_ENABLED_MESSAGE,
2983
+ ChatGPTPlanNotEnabledError,
2984
+ getChatGPTHostId,
2985
+ loginChatGPT,
2986
+ fetchChatGPTPlanModels,
2987
+ parseCallbackInput,
2988
+ refreshChatGPTToken,
2989
+ revokeChatGPTSignIn,
2990
+ verifyIdToken,
2992
2991
  withFileLock,
2993
2992
  openLog,
2994
2993
  getSessionId,
@@ -3003,8 +3002,11 @@ export {
3003
3002
  detectMiniMaxBaseUrl,
3004
3003
  XIAOMI_TOKEN_PLAN_BASE_URLS,
3005
3004
  detectXiaomiTokenPlanBaseUrl,
3006
- MOONSHOT_OAUTH_KEY,
3007
- XAI_OAUTH_KEY,
3005
+ OPENAI_CHATGPT_KEY,
3006
+ KIMI_CODE_KEY,
3007
+ RETIRED_SIGN_INS,
3008
+ storageKeyProvider,
3009
+ apiKeyStorageKeys,
3008
3010
  dualAuthProvider,
3009
3011
  dualAuthProviderByOAuthKey,
3010
3012
  oauthStorageKey,
@@ -3039,4 +3041,4 @@ export {
3039
3041
  getDefaultThinkingLevel,
3040
3042
  getSummaryModel
3041
3043
  };
3042
- //# sourceMappingURL=chunk-SDUIKPGO.js.map
3044
+ //# sourceMappingURL=chunk-J7W5HS6X.js.map