codex-copilot-dx 0.6.21 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -323
- package/bin/cli.mjs +26 -130
- package/package.json +2 -8
- package/scripts/benchmark.mjs +0 -114
- package/src/adapter.mjs +22 -107
- package/src/auth-profile.mjs +18 -130
- package/src/cli-animation.mjs +108 -0
- package/src/cli-auth.mjs +44 -398
- package/src/cli-models.mjs +21 -49
- package/src/cli-options.mjs +53 -75
- package/src/cli-status.mjs +6 -23
- package/src/config.mjs +2 -8
- package/src/copilot.mjs +2 -2
- package/src/doctor.mjs +44 -291
- package/src/model-cache.mjs +4 -11
- package/src/models.mjs +0 -184
- package/src/observability.mjs +1 -38
- package/src/profile-model-runtime.mjs +61 -189
- package/src/profile-routing.mjs +2 -102
- package/src/profile-runtime.mjs +15 -68
- package/src/responses-handler.mjs +1 -1
- package/src/responses-proxy.mjs +1 -1
- package/src/running-adapter.mjs +2 -4
- package/src/stream-errors.mjs +3 -6
- package/src/stream-performance.mjs +1 -1
- package/src/terminal-activity.mjs +40 -34
- package/src/terminal-animation.mjs +249 -0
- package/src/usage.mjs +0 -31
- package/src/user-settings.mjs +50 -0
- package/src/anthropic-handler.mjs +0 -195
- package/src/anthropic.mjs +0 -387
- package/src/claude-config.mjs +0 -57
- package/src/claude-desktop-config.mjs +0 -317
- package/src/cli-error.mjs +0 -50
- package/src/pm-studio-asar.mjs +0 -1244
- package/src/pm-studio-bundle-content.mjs +0 -165
- package/src/pm-studio-relay.mjs +0 -563
- package/src/pm-studio-setup-lock.mjs +0 -336
- package/src/pm-studio-setup.mjs +0 -1581
- package/src/pm-studio-status.mjs +0 -369
- package/src/token-counter-service.mjs +0 -211
- package/src/token-counter-worker.mjs +0 -16
package/src/cli-auth.mjs
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { validateGithubToken } from "./auth.mjs";
|
|
3
3
|
import {
|
|
4
|
-
discoverGithubToken,
|
|
5
|
-
fetchGithubIdentity,
|
|
6
|
-
pollGithubDeviceFlow,
|
|
7
|
-
requestGithubDeviceCode,
|
|
8
|
-
sourceDescription,
|
|
9
|
-
validateGithubToken,
|
|
10
|
-
} from "./auth.mjs";
|
|
11
|
-
import {
|
|
12
|
-
AUTH_PROFILE_CLAUDE,
|
|
13
4
|
AUTH_PROFILE_CODEX,
|
|
14
5
|
readAuthProfileCredentials,
|
|
15
|
-
withAuthProfileLock,
|
|
16
|
-
writeClaudeAuthProfile,
|
|
17
6
|
} from "./auth-profile.mjs";
|
|
18
7
|
import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
|
|
19
|
-
import {
|
|
20
|
-
githubIdentitiesEqual,
|
|
21
|
-
githubTokenFingerprint,
|
|
22
|
-
normalizeGithubIdentity,
|
|
23
|
-
} from "./github-identity.mjs";
|
|
24
8
|
import {
|
|
25
9
|
MAX_UPSTREAM_MODEL_CATALOG_BYTES,
|
|
26
10
|
readBoundedResponseText,
|
|
27
11
|
} from "./http-transport.mjs";
|
|
28
|
-
import { saveModelCache } from "./model-cache.mjs";
|
|
29
|
-
import { isClaudeCopilotModel } from "./models.mjs";
|
|
30
12
|
import { profileRouting } from "./profile-routing.mjs";
|
|
31
13
|
import { status } from "./status.mjs";
|
|
32
14
|
import {
|
|
@@ -38,20 +20,6 @@ import {
|
|
|
38
20
|
|
|
39
21
|
const DEFAULT_COPILOT_CATALOG_TIMEOUT_MS = 30 * 1000;
|
|
40
22
|
|
|
41
|
-
function openAndCopy(userCode, verificationUri) {
|
|
42
|
-
if (process.platform !== "darwin") return;
|
|
43
|
-
try {
|
|
44
|
-
const clipboard = spawn("pbcopy");
|
|
45
|
-
clipboard.on("error", () => {});
|
|
46
|
-
clipboard.stdin.on("error", () => {});
|
|
47
|
-
clipboard.stdin.end(userCode);
|
|
48
|
-
} catch {}
|
|
49
|
-
try {
|
|
50
|
-
const browser = spawn("open", [verificationUri], { detached: true, stdio: "ignore" });
|
|
51
|
-
browser.on("error", () => {});
|
|
52
|
-
} catch {}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
23
|
function responseDetail(value) {
|
|
56
24
|
return String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
|
|
57
25
|
}
|
|
@@ -63,97 +31,6 @@ function abortError(signal) {
|
|
|
63
31
|
return error;
|
|
64
32
|
}
|
|
65
33
|
|
|
66
|
-
function cancelResponseBody(response) {
|
|
67
|
-
try { Promise.resolve(response?.body?.cancel?.()).catch(() => {}); } catch {}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function fetchWithSignal(operation, signal) {
|
|
71
|
-
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
72
|
-
return new Promise((resolve, reject) => {
|
|
73
|
-
let settled = false;
|
|
74
|
-
const finish = (callback, value) => {
|
|
75
|
-
if (settled) return false;
|
|
76
|
-
settled = true;
|
|
77
|
-
signal?.removeEventListener("abort", onAbort);
|
|
78
|
-
callback(value);
|
|
79
|
-
return true;
|
|
80
|
-
};
|
|
81
|
-
const onAbort = () => finish(reject, abortError(signal));
|
|
82
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
83
|
-
Promise.resolve()
|
|
84
|
-
.then(operation)
|
|
85
|
-
.then((response) => {
|
|
86
|
-
if (!finish(resolve, response)) cancelResponseBody(response);
|
|
87
|
-
}, (error) => finish(reject, error));
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function catalogTimeoutError(label, timeoutMs) {
|
|
92
|
-
const error = new Error(`${label} model catalog timed out after ${timeoutMs}ms`);
|
|
93
|
-
error.code = "CCDX_COPILOT_CATALOG_TIMEOUT";
|
|
94
|
-
return error;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function jsonResponse({ response, data, jsonError }, label) {
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
throw new Error(`${label} failed with HTTP ${response.status}`);
|
|
100
|
-
}
|
|
101
|
-
if (jsonError) throw new Error(`${label} returned invalid JSON`);
|
|
102
|
-
return data;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function requestDeviceFlowToken({
|
|
106
|
-
fetchImpl = fetch,
|
|
107
|
-
signal,
|
|
108
|
-
log = console.log,
|
|
109
|
-
openAndCopyFn = openAndCopy,
|
|
110
|
-
deviceCodeTimeoutMs,
|
|
111
|
-
sleepImpl,
|
|
112
|
-
now = Date.now,
|
|
113
|
-
} = {}) {
|
|
114
|
-
const codeResult = await requestGithubDeviceCode({
|
|
115
|
-
fetchImpl,
|
|
116
|
-
signal,
|
|
117
|
-
timeoutMs: deviceCodeTimeoutMs,
|
|
118
|
-
});
|
|
119
|
-
const code = await jsonResponse(codeResult, "Device code request");
|
|
120
|
-
if (!code.device_code || !code.user_code || !code.verification_uri) {
|
|
121
|
-
throw new Error("Device code response is missing required fields");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
log(`\n${status("info", `Open ${code.verification_uri}`)}\n${status("info", `Enter Claude account code: ${code.user_code}`)}\n`);
|
|
125
|
-
openAndCopyFn(code.user_code, code.verification_uri);
|
|
126
|
-
|
|
127
|
-
const result = await pollGithubDeviceFlow({
|
|
128
|
-
deviceCode: code.device_code,
|
|
129
|
-
interval: code.interval,
|
|
130
|
-
expiresIn: code.expires_in,
|
|
131
|
-
fetchImpl,
|
|
132
|
-
signal,
|
|
133
|
-
sleepImpl,
|
|
134
|
-
now,
|
|
135
|
-
});
|
|
136
|
-
if (result.state === "done") return result.token;
|
|
137
|
-
if (result.state === "expired") throw new Error("Claude login failed: device code expired");
|
|
138
|
-
throw new Error(`Claude login failed: ${result.error}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function codexIdentity({ home, fetchImpl, signal }) {
|
|
142
|
-
const codex = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
143
|
-
if (!codex.valid) throw new Error("The existing Codex GitHub account is not configured; refusing to create an unbound Claude profile");
|
|
144
|
-
const tokenFingerprint = githubTokenFingerprint(codex.token);
|
|
145
|
-
if (codex.identity) return { identity: codex.identity, tokenFingerprint };
|
|
146
|
-
const identity = await fetchGithubIdentity(codex.token, { fetchImpl, signal });
|
|
147
|
-
if (!identity.ok) throw new Error("Could not verify the existing Codex GitHub account; its credential was not changed");
|
|
148
|
-
return { identity: normalizeGithubIdentity(identity), tokenFingerprint };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function claudeModels(payload) {
|
|
152
|
-
const models = Array.isArray(payload) ? payload : payload?.data;
|
|
153
|
-
if (!Array.isArray(models)) throw new Error("Claude account model catalog contained no model list");
|
|
154
|
-
return models.filter(isClaudeCopilotModel);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
34
|
function catalogModels(payload) {
|
|
158
35
|
const models = Array.isArray(payload) ? payload : payload?.data;
|
|
159
36
|
if (!Array.isArray(models)) throw new Error("Copilot model catalog contained no model list");
|
|
@@ -164,7 +41,6 @@ async function fetchCopilotCatalog(tokenData, {
|
|
|
164
41
|
fetchImpl,
|
|
165
42
|
signal,
|
|
166
43
|
vscodeVersion = FALLBACK_VSCODE_VERSION,
|
|
167
|
-
label = "Copilot",
|
|
168
44
|
timeoutMs = DEFAULT_COPILOT_CATALOG_TIMEOUT_MS,
|
|
169
45
|
} = {}) {
|
|
170
46
|
const parsedTimeout = Number(timeoutMs);
|
|
@@ -175,8 +51,7 @@ async function fetchCopilotCatalog(tokenData, {
|
|
|
175
51
|
const onCallerAbort = () => controller.abort(signal?.reason);
|
|
176
52
|
signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
177
53
|
if (signal?.aborted) onCallerAbort();
|
|
178
|
-
const timer = setTimeout(() => controller.abort(
|
|
179
|
-
let modelResponse;
|
|
54
|
+
const timer = setTimeout(() => controller.abort(new Error(`Copilot model catalog timed out after ${deadlineMs}ms`)), deadlineMs);
|
|
180
55
|
try {
|
|
181
56
|
const apiBase = parseApiBase(tokenData);
|
|
182
57
|
const headers = buildHeaders({
|
|
@@ -185,26 +60,26 @@ async function fetchCopilotCatalog(tokenData, {
|
|
|
185
60
|
initiator: "user",
|
|
186
61
|
vision: false,
|
|
187
62
|
});
|
|
188
|
-
|
|
63
|
+
const response = await fetchImpl(`${apiBase}/models`, {
|
|
189
64
|
headers,
|
|
190
65
|
redirect: "error",
|
|
191
66
|
signal: controller.signal,
|
|
192
|
-
})
|
|
193
|
-
const modelText = await readBoundedResponseText(
|
|
67
|
+
});
|
|
68
|
+
const modelText = await readBoundedResponseText(response, {
|
|
194
69
|
maxBytes: MAX_UPSTREAM_MODEL_CATALOG_BYTES,
|
|
195
|
-
label:
|
|
70
|
+
label: "Copilot model catalog",
|
|
196
71
|
signal: controller.signal,
|
|
197
72
|
});
|
|
198
|
-
if (!
|
|
73
|
+
if (!response.ok) {
|
|
199
74
|
const detail = responseDetail(modelText);
|
|
200
|
-
throw new Error(
|
|
75
|
+
throw new Error(`Copilot model catalog failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
|
|
201
76
|
}
|
|
202
77
|
|
|
203
78
|
let catalog;
|
|
204
79
|
try {
|
|
205
80
|
catalog = JSON.parse(modelText);
|
|
206
81
|
} catch {
|
|
207
|
-
throw new Error(
|
|
82
|
+
throw new Error("Copilot model catalog returned invalid JSON");
|
|
208
83
|
}
|
|
209
84
|
catalogModels(catalog);
|
|
210
85
|
return { catalog, apiBase };
|
|
@@ -215,185 +90,26 @@ async function fetchCopilotCatalog(tokenData, {
|
|
|
215
90
|
} finally {
|
|
216
91
|
clearTimeout(timer);
|
|
217
92
|
signal?.removeEventListener("abort", onCallerAbort);
|
|
218
|
-
if (controller.signal.aborted) cancelResponseBody(modelResponse);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export async function validateClaudeCandidate(githubToken, {
|
|
223
|
-
home = os.homedir(),
|
|
224
|
-
expectedLogin = "",
|
|
225
|
-
fetchImpl = fetch,
|
|
226
|
-
signal,
|
|
227
|
-
vscodeVersion = FALLBACK_VSCODE_VERSION,
|
|
228
|
-
catalogTimeoutMs,
|
|
229
|
-
} = {}) {
|
|
230
|
-
const validation = await validateGithubToken(githubToken, { fetchImpl, signal });
|
|
231
|
-
if (!validation.ok) {
|
|
232
|
-
const httpStatus = validation.status ? ` (HTTP ${validation.status})` : "";
|
|
233
|
-
throw new Error(`Claude GitHub/Copilot validation failed: ${validation.reason}${httpStatus}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const identity = normalizeGithubIdentity(validation);
|
|
237
|
-
if (!identity) throw new Error("Claude GitHub account identity is missing");
|
|
238
|
-
const pinnedLogin = String(expectedLogin || "").trim();
|
|
239
|
-
if (pinnedLogin && identity.login.toLowerCase() !== pinnedLogin.toLowerCase()) {
|
|
240
|
-
throw new Error(`Authorized GitHub account ${identity.login || identity.id} does not match requested Claude account ${pinnedLogin}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const existingCodex = await codexIdentity({ home, fetchImpl, signal });
|
|
244
|
-
if (githubIdentitiesEqual(identity, existingCodex.identity)) {
|
|
245
|
-
throw new Error(`Claude must use a different GitHub account; ${identity.login || identity.id} is already the Codex account`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const { catalog, apiBase } = await fetchCopilotCatalog(validation.copilotTokenData, {
|
|
249
|
-
fetchImpl,
|
|
250
|
-
signal,
|
|
251
|
-
vscodeVersion,
|
|
252
|
-
label: "Claude account",
|
|
253
|
-
timeoutMs: catalogTimeoutMs,
|
|
254
|
-
});
|
|
255
|
-
const models = claudeModels(catalog);
|
|
256
|
-
if (!models.length) {
|
|
257
|
-
throw new Error(`GitHub account ${identity.login || identity.id} has Copilot access but advertises no enabled Claude models`);
|
|
258
93
|
}
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
identity,
|
|
262
|
-
models,
|
|
263
|
-
catalog,
|
|
264
|
-
apiBase,
|
|
265
|
-
codexTokenFingerprint: existingCodex.tokenFingerprint,
|
|
266
|
-
};
|
|
267
94
|
}
|
|
268
95
|
|
|
269
|
-
|
|
270
|
-
home = os.homedir(),
|
|
271
|
-
env = process.env,
|
|
272
|
-
reauth = false,
|
|
273
|
-
expectedLogin = "",
|
|
274
|
-
fetchImpl = fetch,
|
|
275
|
-
signal,
|
|
276
|
-
log = console.log,
|
|
277
|
-
openAndCopyFn = openAndCopy,
|
|
278
|
-
deviceCodeTimeoutMs,
|
|
279
|
-
catalogTimeoutMs,
|
|
280
|
-
sleepImpl,
|
|
281
|
-
now = Date.now,
|
|
282
|
-
saveModelCacheFn = saveModelCache,
|
|
283
|
-
} = {}) {
|
|
284
|
-
return withAuthProfileLock(AUTH_PROFILE_CLAUDE, async () => {
|
|
285
|
-
const current = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
286
|
-
if (current.configured && current.valid && !reauth) {
|
|
287
|
-
const pinnedLogin = String(expectedLogin || "").trim();
|
|
288
|
-
const currentLogin = String(current.identity?.login || "");
|
|
289
|
-
if (pinnedLogin && currentLogin.toLowerCase() !== pinnedLogin.toLowerCase()) {
|
|
290
|
-
throw new Error(`Claude is already authenticated as ${currentLogin || current.identity?.id || "another account"}; use --reauth to authorize ${pinnedLogin}`);
|
|
291
|
-
}
|
|
292
|
-
return { changed: false, profile: AUTH_PROFILE_CLAUDE, identity: current.identity, models: [] };
|
|
293
|
-
}
|
|
294
|
-
if (current.configured && !reauth) {
|
|
295
|
-
throw new Error(`Claude profile is configured but invalid (${current.reason}); run ccdx auth login claude --reauth`);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
let githubToken = "";
|
|
299
|
-
if (!reauth) {
|
|
300
|
-
const codex = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
301
|
-
if (!codex.valid) {
|
|
302
|
-
throw new Error("The existing Codex GitHub account is not configured; refusing to create an unbound Claude profile");
|
|
303
|
-
}
|
|
304
|
-
const existingCodex = await codexIdentity({ home, fetchImpl, signal });
|
|
305
|
-
const pinnedLogin = String(expectedLogin || "").trim();
|
|
306
|
-
const discovered = await discoverGithubToken({
|
|
307
|
-
home,
|
|
308
|
-
env,
|
|
309
|
-
fetchImpl,
|
|
310
|
-
signal,
|
|
311
|
-
excludeTokens: [codex.token],
|
|
312
|
-
excludeIdentities: [existingCodex.identity],
|
|
313
|
-
expectedIdentity: pinnedLogin ? { login: pinnedLogin } : undefined,
|
|
314
|
-
strictExpectedIdentity: Boolean(pinnedLogin),
|
|
315
|
-
});
|
|
316
|
-
if (discovered.ambiguous) {
|
|
317
|
-
const accounts = [...new Set(discovered.candidates
|
|
318
|
-
.map((candidate) => candidate.login || candidate.id || "unknown"))];
|
|
319
|
-
throw new Error(`Multiple reusable GitHub Copilot accounts were found (${accounts.join(", ")}). Use --github-login or CCDX_GITHUB_TOKEN_PATH to select the Claude account.`);
|
|
320
|
-
}
|
|
321
|
-
if (discovered.ok) {
|
|
322
|
-
githubToken = discovered.token;
|
|
323
|
-
const login = discovered.validation?.login ? ` for ${discovered.validation.login}` : "";
|
|
324
|
-
log(status("info", `Reusing a local Copilot credential from ${sourceDescription(discovered.source)}${login}`));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (!githubToken) {
|
|
328
|
-
githubToken = await requestDeviceFlowToken({
|
|
329
|
-
fetchImpl,
|
|
330
|
-
signal,
|
|
331
|
-
log,
|
|
332
|
-
openAndCopyFn,
|
|
333
|
-
deviceCodeTimeoutMs,
|
|
334
|
-
sleepImpl,
|
|
335
|
-
now,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
const candidate = await validateClaudeCandidate(githubToken, {
|
|
339
|
-
home,
|
|
340
|
-
expectedLogin,
|
|
341
|
-
fetchImpl,
|
|
342
|
-
signal,
|
|
343
|
-
catalogTimeoutMs,
|
|
344
|
-
});
|
|
345
|
-
await withAuthProfileLock(AUTH_PROFILE_CODEX, () => {
|
|
346
|
-
const currentCodex = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
347
|
-
if (!currentCodex.valid
|
|
348
|
-
|| githubTokenFingerprint(currentCodex.token) !== candidate.codexTokenFingerprint) {
|
|
349
|
-
throw new Error("Codex authentication changed while Claude was being authorized; no Claude credential was saved. Retry the command.");
|
|
350
|
-
}
|
|
351
|
-
writeClaudeAuthProfile(githubToken, candidate.identity, { home });
|
|
352
|
-
}, { home, env });
|
|
353
|
-
let modelCacheSaved = false;
|
|
354
|
-
try {
|
|
355
|
-
modelCacheSaved = saveModelCacheFn(candidate.catalog, {
|
|
356
|
-
home,
|
|
357
|
-
profile: AUTH_PROFILE_CLAUDE,
|
|
358
|
-
credentialFingerprint: githubTokenFingerprint(githubToken),
|
|
359
|
-
}) === true;
|
|
360
|
-
if (!modelCacheSaved) throw new Error("model catalog was not cacheable");
|
|
361
|
-
} catch (error) {
|
|
362
|
-
log(status("warn", `Claude authentication succeeded, but its model cache could not be saved (${error.message})`));
|
|
363
|
-
}
|
|
364
|
-
log(status("ok", `Claude authenticated as ${candidate.identity.login || candidate.identity.id}; existing Codex authentication was not changed`));
|
|
365
|
-
return {
|
|
366
|
-
changed: true,
|
|
367
|
-
profile: AUTH_PROFILE_CLAUDE,
|
|
368
|
-
identity: candidate.identity,
|
|
369
|
-
models: candidate.models,
|
|
370
|
-
catalog: candidate.catalog,
|
|
371
|
-
apiBase: candidate.apiBase,
|
|
372
|
-
modelCacheSaved,
|
|
373
|
-
};
|
|
374
|
-
}, { home, env });
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function publicProfile(credentials, source) {
|
|
96
|
+
function publicProfile(credentials) {
|
|
378
97
|
return {
|
|
379
98
|
configured: credentials.configured,
|
|
380
99
|
valid: credentials.valid,
|
|
381
100
|
reason: credentials.reason,
|
|
382
101
|
login: credentials.identity?.login || "",
|
|
383
102
|
id: credentials.identity?.id || "",
|
|
384
|
-
source,
|
|
103
|
+
source: "legacy",
|
|
385
104
|
};
|
|
386
105
|
}
|
|
387
106
|
|
|
388
|
-
function readStatusCredentials(
|
|
107
|
+
function readStatusCredentials(home) {
|
|
389
108
|
try {
|
|
390
|
-
return readAuthProfileCredentials(
|
|
109
|
+
return readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
391
110
|
} catch {
|
|
392
|
-
// Treat an unreadable optional profile as configured-but-invalid. This
|
|
393
|
-
// keeps status useful without leaking a filesystem path or silently
|
|
394
|
-
// borrowing the other account.
|
|
395
111
|
return {
|
|
396
|
-
profile,
|
|
112
|
+
profile: AUTH_PROFILE_CODEX,
|
|
397
113
|
configured: true,
|
|
398
114
|
valid: false,
|
|
399
115
|
reason: "credential_read_failed",
|
|
@@ -404,40 +120,29 @@ function readStatusCredentials(profile, home) {
|
|
|
404
120
|
}
|
|
405
121
|
|
|
406
122
|
export function authStatus({ home = os.homedir() } = {}) {
|
|
407
|
-
const codex = readStatusCredentials(
|
|
408
|
-
const claude = readStatusCredentials(AUTH_PROFILE_CLAUDE, home);
|
|
123
|
+
const codex = readStatusCredentials(home);
|
|
409
124
|
return {
|
|
410
|
-
profiles: {
|
|
411
|
-
|
|
412
|
-
claude: publicProfile(claude, "isolated"),
|
|
413
|
-
},
|
|
414
|
-
routing: profileRouting({ claudeConfigured: claude.configured }),
|
|
125
|
+
profiles: { codex: publicProfile(codex) },
|
|
126
|
+
routing: profileRouting(),
|
|
415
127
|
};
|
|
416
128
|
}
|
|
417
129
|
|
|
418
|
-
async function inspectProfileOnline(credentials, {
|
|
419
|
-
fetchImpl,
|
|
420
|
-
timeoutMs,
|
|
421
|
-
inherited = false,
|
|
422
|
-
} = {}) {
|
|
423
|
-
if (inherited) {
|
|
424
|
-
return { checked: false, inherited: true, ok: null, reason: "inherits_codex" };
|
|
425
|
-
}
|
|
130
|
+
async function inspectProfileOnline(credentials, { fetchImpl, timeoutMs } = {}) {
|
|
426
131
|
if (!credentials.valid) {
|
|
427
|
-
return { checked: false,
|
|
132
|
+
return { checked: false, ok: false, reason: credentials.reason || "unconfigured" };
|
|
428
133
|
}
|
|
429
134
|
|
|
430
135
|
const controller = new AbortController();
|
|
431
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
136
|
+
const timer = setTimeout(() => controller.abort(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
432
137
|
try {
|
|
433
138
|
const validation = await validateGithubToken(credentials.token, {
|
|
434
139
|
fetchImpl,
|
|
435
140
|
signal: controller.signal,
|
|
436
141
|
});
|
|
142
|
+
if (controller.signal.aborted) throw abortError(controller.signal);
|
|
437
143
|
if (!validation.ok) {
|
|
438
144
|
return {
|
|
439
145
|
checked: true,
|
|
440
|
-
inherited: false,
|
|
441
146
|
ok: false,
|
|
442
147
|
reason: validation.reason,
|
|
443
148
|
httpStatus: validation.status || null,
|
|
@@ -446,24 +151,19 @@ async function inspectProfileOnline(credentials, {
|
|
|
446
151
|
const { catalog, apiBase } = await fetchCopilotCatalog(validation.copilotTokenData, {
|
|
447
152
|
fetchImpl,
|
|
448
153
|
signal: controller.signal,
|
|
449
|
-
label: credentials.profile === AUTH_PROFILE_CLAUDE ? "Claude account" : "Codex account",
|
|
450
154
|
});
|
|
451
|
-
const models = catalogModels(catalog);
|
|
452
155
|
let upstreamHost = "GitHub Copilot";
|
|
453
156
|
try { upstreamHost = new URL(apiBase).hostname; } catch {}
|
|
454
157
|
return {
|
|
455
158
|
checked: true,
|
|
456
|
-
inherited: false,
|
|
457
159
|
ok: true,
|
|
458
160
|
login: validation.login || credentials.identity?.login || "",
|
|
459
|
-
models:
|
|
460
|
-
claudeModels: claudeModels(catalog).length,
|
|
161
|
+
models: catalogModels(catalog).length,
|
|
461
162
|
upstreamHost,
|
|
462
163
|
};
|
|
463
164
|
} catch (error) {
|
|
464
165
|
return {
|
|
465
166
|
checked: true,
|
|
466
|
-
inherited: false,
|
|
467
167
|
ok: false,
|
|
468
168
|
reason: controller.signal.aborted ? `timed out after ${timeoutMs}ms` : error.message,
|
|
469
169
|
};
|
|
@@ -478,21 +178,11 @@ export async function authStatusOnline({
|
|
|
478
178
|
timeoutMs = 10000,
|
|
479
179
|
} = {}) {
|
|
480
180
|
const local = authStatus({ home });
|
|
481
|
-
const
|
|
482
|
-
const claudeCredentials = readStatusCredentials(AUTH_PROFILE_CLAUDE, home);
|
|
483
|
-
const [codexOnline, claudeOnline] = await Promise.all([
|
|
484
|
-
inspectProfileOnline(codexCredentials, { fetchImpl, timeoutMs }),
|
|
485
|
-
inspectProfileOnline(claudeCredentials, {
|
|
486
|
-
fetchImpl,
|
|
487
|
-
timeoutMs,
|
|
488
|
-
inherited: !claudeCredentials.configured,
|
|
489
|
-
}),
|
|
490
|
-
]);
|
|
181
|
+
const online = await inspectProfileOnline(readStatusCredentials(home), { fetchImpl, timeoutMs });
|
|
491
182
|
return {
|
|
492
183
|
...local,
|
|
493
184
|
profiles: {
|
|
494
|
-
codex: { ...local.profiles.codex, online
|
|
495
|
-
claude: { ...local.profiles.claude, online: claudeOnline },
|
|
185
|
+
codex: { ...local.profiles.codex, online },
|
|
496
186
|
},
|
|
497
187
|
};
|
|
498
188
|
}
|
|
@@ -503,72 +193,36 @@ function accountLabel(profile) {
|
|
|
503
193
|
return profile.login || profile.id || "configured";
|
|
504
194
|
}
|
|
505
195
|
|
|
506
|
-
function onlineStatusLine(
|
|
196
|
+
function onlineStatusLine(profile) {
|
|
507
197
|
const online = profile.online;
|
|
508
198
|
if (!online) return null;
|
|
509
|
-
if (online.inherited) return status("info", `${name} online: inherits the Codex profile`);
|
|
510
199
|
if (!online.ok) {
|
|
511
200
|
const httpStatus = online.httpStatus ? ` (HTTP ${online.httpStatus})` : "";
|
|
512
|
-
return status("warn",
|
|
201
|
+
return status("warn", `Codex online: ${online.reason || "unavailable"}${httpStatus}`);
|
|
513
202
|
}
|
|
514
203
|
const account = online.login ? `${online.login}; ` : "";
|
|
515
|
-
return status("ok",
|
|
204
|
+
return status("ok", `Codex online: ${account}${online.models} models`);
|
|
516
205
|
}
|
|
517
206
|
|
|
518
207
|
function authStatusPlainLines(snapshot, { commandName }) {
|
|
519
|
-
const claudeLabel = snapshot.profiles.claude.configured
|
|
520
|
-
? `${accountLabel(snapshot.profiles.claude)} [isolated profile]`
|
|
521
|
-
: "inherits Codex [no isolated profile]";
|
|
522
|
-
const claudeKind = snapshot.profiles.claude.valid
|
|
523
|
-
? "ok"
|
|
524
|
-
: snapshot.profiles.claude.configured ? "warn" : "info";
|
|
525
208
|
return [
|
|
526
209
|
`${commandName} auth status`,
|
|
527
210
|
status(snapshot.profiles.codex.valid ? "ok" : "warn", `Codex: ${accountLabel(snapshot.profiles.codex)} [legacy path]`),
|
|
528
|
-
onlineStatusLine(
|
|
529
|
-
status(
|
|
530
|
-
onlineStatusLine("Claude", snapshot.profiles.claude),
|
|
531
|
-
status("info", `Routing: responses -> ${snapshot.routing.responses}; messages -> ${snapshot.routing.messages}`),
|
|
211
|
+
onlineStatusLine(snapshot.profiles.codex),
|
|
212
|
+
status("info", `Routing: responses -> ${snapshot.routing.responses}`),
|
|
532
213
|
].filter(Boolean);
|
|
533
214
|
}
|
|
534
215
|
|
|
535
216
|
function authTableOnline(profile) {
|
|
536
217
|
const online = profile.online;
|
|
537
|
-
if (!online) return { state: "[INFO] not checked", models: "—"
|
|
538
|
-
if (online.inherited) return { state: "[INFO] inherits Codex", models: "—", claudeModels: "—" };
|
|
218
|
+
if (!online) return { state: "[INFO] not checked", models: "—" };
|
|
539
219
|
if (!online.ok) {
|
|
540
220
|
const httpStatus = online.httpStatus ? ` (HTTP ${online.httpStatus})` : "";
|
|
541
|
-
return {
|
|
542
|
-
state: `[WARN] ${online.reason || "unavailable"}${httpStatus}`,
|
|
543
|
-
models: "—",
|
|
544
|
-
claudeModels: "—",
|
|
545
|
-
};
|
|
221
|
+
return { state: `[WARN] ${online.reason || "unavailable"}${httpStatus}`, models: "—" };
|
|
546
222
|
}
|
|
547
223
|
return {
|
|
548
224
|
state: `[OK] verified${online.login ? ` as ${online.login}` : ""}`,
|
|
549
225
|
models: Number.isFinite(online.models) ? String(online.models) : "—",
|
|
550
|
-
claudeModels: Number.isFinite(online.claudeModels) ? String(online.claudeModels) : "—",
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function authTableRow(name, profile, { claude = false } = {}) {
|
|
555
|
-
const inherited = claude && !profile.configured;
|
|
556
|
-
const online = authTableOnline(profile);
|
|
557
|
-
const local = inherited
|
|
558
|
-
? "[INFO] inherits Codex"
|
|
559
|
-
: profile.valid
|
|
560
|
-
? "[OK] ready"
|
|
561
|
-
: `[WARN] ${accountLabel(profile)}`;
|
|
562
|
-
const account = inherited ? "inherits Codex" : accountLabel(profile);
|
|
563
|
-
const mode = inherited ? "inherited" : claude ? "isolated" : "legacy path";
|
|
564
|
-
return {
|
|
565
|
-
profile: name,
|
|
566
|
-
account,
|
|
567
|
-
mode,
|
|
568
|
-
local,
|
|
569
|
-
online: online.state,
|
|
570
|
-
models: online.models,
|
|
571
|
-
claudeModels: online.claudeModels,
|
|
572
226
|
};
|
|
573
227
|
}
|
|
574
228
|
|
|
@@ -583,19 +237,22 @@ export function formatAuthStatus(snapshot = authStatus(), {
|
|
|
583
237
|
return plainLines.join("\n");
|
|
584
238
|
}
|
|
585
239
|
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
240
|
+
const profile = snapshot.profiles.codex;
|
|
241
|
+
const online = authTableOnline(profile);
|
|
242
|
+
const rows = [{
|
|
243
|
+
profile: "Codex",
|
|
244
|
+
account: accountLabel(profile),
|
|
245
|
+
local: profile.valid ? "[OK] ready" : `[WARN] ${accountLabel(profile)}`,
|
|
246
|
+
online: online.state,
|
|
247
|
+
models: online.models,
|
|
248
|
+
}];
|
|
590
249
|
const table = formatResponsiveCliTable({
|
|
591
250
|
columns: [
|
|
592
251
|
{ key: "profile", label: "PROFILE" },
|
|
593
252
|
{ key: "account", label: "ACCOUNT" },
|
|
594
|
-
{ key: "mode", label: "MODE" },
|
|
595
253
|
{ key: "local", label: "LOCAL" },
|
|
596
254
|
{ key: "online", label: "ONLINE" },
|
|
597
255
|
{ key: "models", label: "MODELS", align: "right" },
|
|
598
|
-
{ key: "claudeModels", label: "CLAUDE", align: "right" },
|
|
599
256
|
],
|
|
600
257
|
compactColumns: [
|
|
601
258
|
{ key: "profile", label: "PROFILE" },
|
|
@@ -608,11 +265,7 @@ export function formatAuthStatus(snapshot = authStatus(), {
|
|
|
608
265
|
if (format === "auto" && table.overflow) {
|
|
609
266
|
return plainLines.map((line) => terminalCell(line, { fallback: "" })).join("\n");
|
|
610
267
|
}
|
|
611
|
-
const lines = [
|
|
612
|
-
`${commandName} auth status`,
|
|
613
|
-
"",
|
|
614
|
-
table.output,
|
|
615
|
-
];
|
|
268
|
+
const lines = [`${commandName} auth status`, "", table.output];
|
|
616
269
|
if (table.compact) {
|
|
617
270
|
lines.push("", "Details:", ...plainLines.slice(1, -1).map((line) => terminalCell(line, { fallback: "" })));
|
|
618
271
|
}
|
|
@@ -622,20 +275,13 @@ export function formatAuthStatus(snapshot = authStatus(), {
|
|
|
622
275
|
|
|
623
276
|
export async function runAuthCommand({
|
|
624
277
|
action = "status",
|
|
625
|
-
profile = "",
|
|
626
278
|
online = false,
|
|
627
|
-
reauth = false,
|
|
628
|
-
expectedLogin = "",
|
|
629
279
|
commandName = "ccdx",
|
|
630
280
|
format = "plain",
|
|
631
281
|
output = process.stdout,
|
|
632
282
|
...options
|
|
633
283
|
} = {}) {
|
|
634
|
-
if (action
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
if (profile !== AUTH_PROFILE_CLAUDE) throw new Error("Only the isolated Claude profile can be changed by this command");
|
|
639
|
-
if (action === "login") return { action, ...(await authorizeClaudeProfile({ ...options, reauth, expectedLogin })) };
|
|
640
|
-
throw new Error(`Unsupported auth action: ${action}`);
|
|
284
|
+
if (action !== "status") throw new Error(`Unsupported auth action: ${action}`);
|
|
285
|
+
const snapshot = online ? await authStatusOnline(options) : authStatus(options);
|
|
286
|
+
return { action, output: formatAuthStatus(snapshot, { commandName, format, output }), snapshot };
|
|
641
287
|
}
|