@bjesuiter/codex-switcher 1.7.3 → 1.7.4
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 +3 -6
- package/cdx.mjs +57 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,15 +6,12 @@ Switch the coding-agents [pi](https://pi.dev/), [codex](https://developers.opena
|
|
|
6
6
|
|
|
7
7
|
## Latest Changes
|
|
8
8
|
|
|
9
|
-
### 1.7.
|
|
9
|
+
### 1.7.4
|
|
10
10
|
|
|
11
11
|
#### Fixes
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
- prompt to kill the existing listener and retry, switch to device flow, or cancel
|
|
16
|
-
- show detected PID/command details when available before confirmation
|
|
17
|
-
- Improve callback server startup diagnostics by exposing startup error reason/code details, making port/listen failures easier to troubleshoot.
|
|
13
|
+
- Detect Cloudflare/bot-protection HTML challenge responses during OAuth device flow startup and token polling, and report an explicit `cloudflare_challenge` reason instead of only a generic HTTP 403.
|
|
14
|
+
- When that challenge is detected, show a clear workaround: retry without `--device-flow` to use browser/manual callback flow.
|
|
18
15
|
|
|
19
16
|
see full changelog here: https://github.com/bjesuiter/codex-switcher/blob/main/CHANGELOG.md
|
|
20
17
|
|
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.
|
|
18
|
+
var version = "1.7.4";
|
|
19
19
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region lib/platform/path-resolver.ts
|
|
@@ -1036,6 +1036,46 @@ const truncateForLog = (value, maxLength = 300) => {
|
|
|
1036
1036
|
if (value.length <= maxLength) return value;
|
|
1037
1037
|
return `${value.slice(0, maxLength)}…`;
|
|
1038
1038
|
};
|
|
1039
|
+
const isCloudflareChallengeResponse = (headers, bodyText) => {
|
|
1040
|
+
if (headers.get("cf-mitigated")?.toLowerCase() === "challenge") return true;
|
|
1041
|
+
if (!bodyText) return false;
|
|
1042
|
+
return [
|
|
1043
|
+
/<title>Just a moment\.\.\.<\/title>/i,
|
|
1044
|
+
/Enable JavaScript and cookies to continue/i,
|
|
1045
|
+
/challenge-platform/i,
|
|
1046
|
+
/_cf_chl_opt/i
|
|
1047
|
+
].some((pattern) => pattern.test(bodyText));
|
|
1048
|
+
};
|
|
1049
|
+
const parseOAuthErrorResponse = async (res) => {
|
|
1050
|
+
let rawBody;
|
|
1051
|
+
try {
|
|
1052
|
+
rawBody = await res.text();
|
|
1053
|
+
} catch {
|
|
1054
|
+
rawBody = void 0;
|
|
1055
|
+
}
|
|
1056
|
+
const failureReason = isCloudflareChallengeResponse(res.headers, rawBody) ? "cloudflare_challenge" : void 0;
|
|
1057
|
+
if (!rawBody) return { ...failureReason ? { failureReason } : {} };
|
|
1058
|
+
const trimmed = rawBody.trim();
|
|
1059
|
+
if (!trimmed) return { ...failureReason ? { failureReason } : {} };
|
|
1060
|
+
try {
|
|
1061
|
+
const json = JSON.parse(trimmed);
|
|
1062
|
+
return {
|
|
1063
|
+
...json.error ? { oauthError: json.error } : {},
|
|
1064
|
+
...typeof json.interval === "number" ? { interval: json.interval } : {},
|
|
1065
|
+
responseBody: truncateForLog(JSON.stringify({
|
|
1066
|
+
...json.error ? { error: json.error } : {},
|
|
1067
|
+
...json.error_description ? { error_description: json.error_description } : {},
|
|
1068
|
+
...typeof json.interval === "number" ? { interval: json.interval } : {}
|
|
1069
|
+
})),
|
|
1070
|
+
...failureReason ? { failureReason } : {}
|
|
1071
|
+
};
|
|
1072
|
+
} catch {
|
|
1073
|
+
return {
|
|
1074
|
+
responseBody: failureReason === "cloudflare_challenge" ? "Cloudflare challenge response detected (HTML page)" : truncateForLog(trimmed),
|
|
1075
|
+
...failureReason ? { failureReason } : {}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1039
1079
|
const startDeviceAuthorizationFlow = async () => {
|
|
1040
1080
|
try {
|
|
1041
1081
|
const res = await fetch(DEVICE_CODE_URL, {
|
|
@@ -1047,28 +1087,14 @@ const startDeviceAuthorizationFlow = async () => {
|
|
|
1047
1087
|
})
|
|
1048
1088
|
});
|
|
1049
1089
|
if (!res.ok) {
|
|
1050
|
-
|
|
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
|
-
}
|
|
1090
|
+
const { oauthError, responseBody, failureReason } = await parseOAuthErrorResponse(res);
|
|
1066
1091
|
return {
|
|
1067
1092
|
type: "failed",
|
|
1068
|
-
error: `Device code request failed with HTTP ${res.status} ${res.statusText}`,
|
|
1093
|
+
error: failureReason === "cloudflare_challenge" ? "Device code request was blocked by a Cloudflare challenge response." : `Device code request failed with HTTP ${res.status} ${res.statusText}`,
|
|
1069
1094
|
status: res.status,
|
|
1070
1095
|
...oauthError ? { oauthError } : {},
|
|
1071
|
-
...responseBody ? { responseBody } : {}
|
|
1096
|
+
...responseBody ? { responseBody } : {},
|
|
1097
|
+
...failureReason ? { failureReason } : {}
|
|
1072
1098
|
};
|
|
1073
1099
|
}
|
|
1074
1100
|
const json = await res.json();
|
|
@@ -1121,25 +1147,7 @@ const pollDeviceAuthorizationToken = async (deviceCode) => {
|
|
|
1121
1147
|
idToken: json.id_token
|
|
1122
1148
|
};
|
|
1123
1149
|
}
|
|
1124
|
-
|
|
1125
|
-
let interval;
|
|
1126
|
-
let responseBody;
|
|
1127
|
-
try {
|
|
1128
|
-
const json = await res.json();
|
|
1129
|
-
errorCode = json.error;
|
|
1130
|
-
interval = json.interval;
|
|
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
|
-
}
|
|
1150
|
+
const { oauthError: errorCode, interval, responseBody, failureReason } = await parseOAuthErrorResponse(res);
|
|
1143
1151
|
if (errorCode === "authorization_pending") return {
|
|
1144
1152
|
type: "pending",
|
|
1145
1153
|
interval: typeof interval === "number" && interval > 0 ? interval : 5
|
|
@@ -1152,10 +1160,11 @@ const pollDeviceAuthorizationToken = async (deviceCode) => {
|
|
|
1152
1160
|
if (errorCode === "expired_token") return { type: "expired" };
|
|
1153
1161
|
return {
|
|
1154
1162
|
type: "failed",
|
|
1155
|
-
error: `Device token polling failed with HTTP ${res.status} ${res.statusText}`,
|
|
1163
|
+
error: failureReason === "cloudflare_challenge" ? "Device token polling was blocked by a Cloudflare challenge response." : `Device token polling failed with HTTP ${res.status} ${res.statusText}`,
|
|
1156
1164
|
status: res.status,
|
|
1157
1165
|
...errorCode ? { oauthError: errorCode } : {},
|
|
1158
|
-
...responseBody ? { responseBody } : {}
|
|
1166
|
+
...responseBody ? { responseBody } : {},
|
|
1167
|
+
...failureReason ? { failureReason } : {}
|
|
1159
1168
|
};
|
|
1160
1169
|
} catch (error) {
|
|
1161
1170
|
return {
|
|
@@ -1561,6 +1570,10 @@ const runDeviceOAuthFlow = async (useSpinner) => {
|
|
|
1561
1570
|
if (typeof deviceFlowResult.status === "number") p.log.error(`HTTP status: ${deviceFlowResult.status}`);
|
|
1562
1571
|
if (deviceFlowResult.oauthError) p.log.error(`OAuth error: ${deviceFlowResult.oauthError}`);
|
|
1563
1572
|
if (deviceFlowResult.responseBody) p.log.error(`Response: ${deviceFlowResult.responseBody}`);
|
|
1573
|
+
if (deviceFlowResult.failureReason === "cloudflare_challenge") {
|
|
1574
|
+
p.log.warning("Detected Cloudflare challenge on auth.openai.com.");
|
|
1575
|
+
p.log.info("Retry without --device-flow to use browser/manual callback flow.");
|
|
1576
|
+
}
|
|
1564
1577
|
return null;
|
|
1565
1578
|
}
|
|
1566
1579
|
const deviceFlow = deviceFlowResult.flow;
|
|
@@ -1610,6 +1623,10 @@ const runDeviceOAuthFlow = async (useSpinner) => {
|
|
|
1610
1623
|
if (typeof pollResult.status === "number") p.log.error(`HTTP status: ${pollResult.status}`);
|
|
1611
1624
|
if (pollResult.oauthError) p.log.error(`OAuth error: ${pollResult.oauthError}`);
|
|
1612
1625
|
if (pollResult.responseBody) p.log.error(`Response: ${pollResult.responseBody}`);
|
|
1626
|
+
if (pollResult.failureReason === "cloudflare_challenge") {
|
|
1627
|
+
p.log.warning("Detected Cloudflare challenge on auth.openai.com.");
|
|
1628
|
+
p.log.info("Retry without --device-flow to use browser/manual callback flow.");
|
|
1629
|
+
}
|
|
1613
1630
|
}
|
|
1614
1631
|
return null;
|
|
1615
1632
|
}
|