@bjesuiter/codex-switcher 1.7.1 → 1.7.2

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.
Files changed (3) hide show
  1. package/README.md +2 -6
  2. package/cdx.mjs +92 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,15 +6,11 @@ Switch the coding-agents [pi](https://pi.dev/), [codex](https://developers.opena
6
6
 
7
7
  ## Latest Changes
8
8
 
9
- ### 1.7.1
10
-
11
- #### Features
12
-
13
- - Add a release helper script (`scripts/wait-for-npm-latest.ts`) plus `bun run wait-npm-latest` to poll npm until the package `latest` tag matches the target version.
9
+ ### 1.7.2
14
10
 
15
11
  #### Fixes
16
12
 
17
- - Fix Windows CI completion test behavior by providing `APPDATA` in the account-completion test environment, so account suggestions are resolved correctly on `windows-latest`.
13
+ - Improve device OAuth failure diagnostics during login/relogin. When device flow startup or polling fails, `cdx` now prints technical details (HTTP status, OAuth error code, and response/body snippets where available) instead of only showing a generic "not available right now" message.
18
14
 
19
15
  see full changelog here: https://github.com/bjesuiter/codex-switcher/blob/main/CHANGELOG.md
20
16
 
package/cdx.mjs CHANGED
@@ -15,7 +15,7 @@ import { generatePKCE } from "@openauthjs/openauth/pkce";
15
15
  import http from "node:http";
16
16
 
17
17
  //#region package.json
18
- var version = "1.7.1";
18
+ var version = "1.7.2";
19
19
 
20
20
  //#endregion
21
21
  //#region lib/platform/path-resolver.ts
@@ -1032,6 +1032,10 @@ const createAuthorizationFlow = async () => {
1032
1032
  url: url.toString()
1033
1033
  };
1034
1034
  };
1035
+ const truncateForLog = (value, maxLength = 300) => {
1036
+ if (value.length <= maxLength) return value;
1037
+ return `${value.slice(0, maxLength)}…`;
1038
+ };
1035
1039
  const startDeviceAuthorizationFlow = async () => {
1036
1040
  try {
1037
1041
  const res = await fetch(DEVICE_CODE_URL, {
@@ -1042,19 +1046,53 @@ const startDeviceAuthorizationFlow = async () => {
1042
1046
  scope: SCOPE
1043
1047
  })
1044
1048
  });
1045
- if (!res.ok) return null;
1049
+ if (!res.ok) {
1050
+ let oauthError;
1051
+ let responseBody;
1052
+ try {
1053
+ const json = await res.json();
1054
+ oauthError = json.error;
1055
+ responseBody = truncateForLog(JSON.stringify({
1056
+ ...json.error ? { error: json.error } : {},
1057
+ ...json.error_description ? { error_description: json.error_description } : {}
1058
+ }));
1059
+ } catch {
1060
+ try {
1061
+ responseBody = truncateForLog(await res.text());
1062
+ } catch {
1063
+ responseBody = void 0;
1064
+ }
1065
+ }
1066
+ return {
1067
+ type: "failed",
1068
+ error: `Device code request failed with HTTP ${res.status} ${res.statusText}`,
1069
+ status: res.status,
1070
+ ...oauthError ? { oauthError } : {},
1071
+ ...responseBody ? { responseBody } : {}
1072
+ };
1073
+ }
1046
1074
  const json = await res.json();
1047
- if (!json?.device_code || !json?.user_code || !json?.verification_uri || typeof json?.expires_in !== "number") return null;
1075
+ if (!json?.device_code || !json?.user_code || !json?.verification_uri || typeof json?.expires_in !== "number") return {
1076
+ type: "failed",
1077
+ error: "Device code response is missing required fields.",
1078
+ responseBody: truncateForLog(JSON.stringify(json))
1079
+ };
1048
1080
  return {
1049
- deviceCode: json.device_code,
1050
- userCode: json.user_code,
1051
- verificationUri: json.verification_uri,
1052
- verificationUriComplete: json.verification_uri_complete,
1053
- expiresIn: json.expires_in,
1054
- interval: typeof json.interval === "number" && json.interval > 0 ? json.interval : 5
1081
+ type: "success",
1082
+ flow: {
1083
+ deviceCode: json.device_code,
1084
+ userCode: json.user_code,
1085
+ verificationUri: json.verification_uri,
1086
+ verificationUriComplete: json.verification_uri_complete,
1087
+ expiresIn: json.expires_in,
1088
+ interval: typeof json.interval === "number" && json.interval > 0 ? json.interval : 5
1089
+ }
1090
+ };
1091
+ } catch (error) {
1092
+ return {
1093
+ type: "failed",
1094
+ error: `Device code request failed: ${error instanceof Error ? error.message : String(error)}`
1055
1095
  };
1056
- } catch {
1057
- return null;
1058
1096
  }
1059
1097
  };
1060
1098
  const pollDeviceAuthorizationToken = async (deviceCode) => {
@@ -1070,7 +1108,11 @@ const pollDeviceAuthorizationToken = async (deviceCode) => {
1070
1108
  });
1071
1109
  if (res.ok) {
1072
1110
  const json = await res.json();
1073
- if (!json?.access_token || !json?.refresh_token || typeof json?.expires_in !== "number") return { type: "failed" };
1111
+ if (!json?.access_token || !json?.refresh_token || typeof json?.expires_in !== "number") return {
1112
+ type: "failed",
1113
+ error: "Device token response is missing access_token/refresh_token/expires_in.",
1114
+ responseBody: truncateForLog(JSON.stringify(json))
1115
+ };
1074
1116
  return {
1075
1117
  type: "success",
1076
1118
  access: json.access_token,
@@ -1081,11 +1123,23 @@ const pollDeviceAuthorizationToken = async (deviceCode) => {
1081
1123
  }
1082
1124
  let errorCode;
1083
1125
  let interval;
1126
+ let responseBody;
1084
1127
  try {
1085
1128
  const json = await res.json();
1086
1129
  errorCode = json.error;
1087
1130
  interval = json.interval;
1088
- } catch {}
1131
+ responseBody = truncateForLog(JSON.stringify({
1132
+ ...json.error ? { error: json.error } : {},
1133
+ ...json.error_description ? { error_description: json.error_description } : {},
1134
+ ...typeof json.interval === "number" ? { interval: json.interval } : {}
1135
+ }));
1136
+ } catch {
1137
+ try {
1138
+ responseBody = truncateForLog(await res.text());
1139
+ } catch {
1140
+ responseBody = void 0;
1141
+ }
1142
+ }
1089
1143
  if (errorCode === "authorization_pending") return {
1090
1144
  type: "pending",
1091
1145
  interval: typeof interval === "number" && interval > 0 ? interval : 5
@@ -1096,9 +1150,18 @@ const pollDeviceAuthorizationToken = async (deviceCode) => {
1096
1150
  };
1097
1151
  if (errorCode === "access_denied") return { type: "access_denied" };
1098
1152
  if (errorCode === "expired_token") return { type: "expired" };
1099
- return { type: "failed" };
1100
- } catch {
1101
- return { type: "failed" };
1153
+ return {
1154
+ type: "failed",
1155
+ error: `Device token polling failed with HTTP ${res.status} ${res.statusText}`,
1156
+ status: res.status,
1157
+ ...errorCode ? { oauthError: errorCode } : {},
1158
+ ...responseBody ? { responseBody } : {}
1159
+ };
1160
+ } catch (error) {
1161
+ return {
1162
+ type: "failed",
1163
+ error: `Device token polling request failed: ${error instanceof Error ? error.message : String(error)}`
1164
+ };
1102
1165
  }
1103
1166
  };
1104
1167
  const exchangeAuthorizationCode = async (code, verifier) => {
@@ -1364,11 +1427,16 @@ const promptManualAuthorizationCode = async (authorizationUrl, expectedState) =>
1364
1427
  return null;
1365
1428
  };
1366
1429
  const runDeviceOAuthFlow = async (useSpinner) => {
1367
- const deviceFlow = await startDeviceAuthorizationFlow();
1368
- if (!deviceFlow) {
1430
+ const deviceFlowResult = await startDeviceAuthorizationFlow();
1431
+ if (deviceFlowResult.type !== "success") {
1369
1432
  p.log.error("Device OAuth flow is not available right now.");
1433
+ p.log.error(`Technical details: ${deviceFlowResult.error}`);
1434
+ if (typeof deviceFlowResult.status === "number") p.log.error(`HTTP status: ${deviceFlowResult.status}`);
1435
+ if (deviceFlowResult.oauthError) p.log.error(`OAuth error: ${deviceFlowResult.oauthError}`);
1436
+ if (deviceFlowResult.responseBody) p.log.error(`Response: ${deviceFlowResult.responseBody}`);
1370
1437
  return null;
1371
1438
  }
1439
+ const deviceFlow = deviceFlowResult.flow;
1372
1440
  p.log.info("Using device OAuth flow.");
1373
1441
  p.log.message(`Verification URL: ${deviceFlow.verificationUri}`);
1374
1442
  p.log.message(`User code: ${deviceFlow.userCode}`);
@@ -1410,6 +1478,12 @@ const runDeviceOAuthFlow = async (useSpinner) => {
1410
1478
  }
1411
1479
  if (spinner) spinner.stop("Device authorization failed.");
1412
1480
  else p.log.error("Device authorization failed.");
1481
+ if (pollResult.type === "failed") {
1482
+ if (pollResult.error) p.log.error(`Technical details: ${pollResult.error}`);
1483
+ if (typeof pollResult.status === "number") p.log.error(`HTTP status: ${pollResult.status}`);
1484
+ if (pollResult.oauthError) p.log.error(`OAuth error: ${pollResult.oauthError}`);
1485
+ if (pollResult.responseBody) p.log.error(`Response: ${pollResult.responseBody}`);
1486
+ }
1413
1487
  return null;
1414
1488
  }
1415
1489
  if (spinner) spinner.stop("Device authorization timed out.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjesuiter/codex-switcher",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "type": "module",
5
5
  "description": "CLI tool to switch between multiple OpenAI accounts for OpenCode",
6
6
  "bin": {