theclawbay 0.1.13 → 0.1.15
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/dist/commands/agent.js
CHANGED
|
@@ -231,9 +231,13 @@ class AgentCommand extends base_command_1.BaseCommand {
|
|
|
231
231
|
const filePath = node_path_1.default.join(paths_1.accountsDir, `${account.name}.json`);
|
|
232
232
|
try {
|
|
233
233
|
const verification = (0, sync_accounts_1.buildVerificationRequestConfig)(managed, account);
|
|
234
|
+
const requiresLocalOtp = verification === null;
|
|
235
|
+
if (requiresLocalOtp) {
|
|
236
|
+
this.log(`Banned-signal check for "${accountName}" will use local browser OTP flow (Outlook mailbox detected).`);
|
|
237
|
+
}
|
|
234
238
|
await (0, account_login_1.createAuthSnapshotForAccount)(account, filePath, {
|
|
235
|
-
oauthShowBrowser: flags.showBrowser,
|
|
236
|
-
verification,
|
|
239
|
+
oauthShowBrowser: flags.showBrowser || requiresLocalOtp,
|
|
240
|
+
verification: verification !== null && verification !== void 0 ? verification : undefined,
|
|
237
241
|
log: (message) => this.log(message),
|
|
238
242
|
});
|
|
239
243
|
banVerificationState.set(accountName, { checkedAtMs: nowMs, confirmed: false });
|
|
@@ -16,6 +16,7 @@ const ZENDRIVER_IMPORT_ERROR_TOKEN = "THECLAWBAY_ZENDRIVER_IMPORT_ERROR";
|
|
|
16
16
|
const ZENDRIVER_ERROR_TOKEN = "THECLAWBAY_ZENDRIVER_ERROR";
|
|
17
17
|
const ZENDRIVER_LAUNCH_TIMEOUT_MS = 15000;
|
|
18
18
|
const ACCOUNT_DEACTIVATED_ERROR_MARKER = "THECLAWBAY_ACCOUNT_DEACTIVATED";
|
|
19
|
+
const CAPTCHA_CHALLENGE_ERROR_MARKER = "THECLAWBAY_CAPTCHA_CHALLENGE";
|
|
19
20
|
const DEFAULT_WIN_PATH_EXTENSIONS = [".EXE", ".CMD", ".BAT", ".COM"];
|
|
20
21
|
function trimWrappingQuotes(value) {
|
|
21
22
|
const trimmed = value.trim();
|
|
@@ -229,6 +230,8 @@ LOG_TOKEN = "THECLAWBAY_ZENDRIVER_LOG"
|
|
|
229
230
|
IMPORT_ERROR_TOKEN = "THECLAWBAY_ZENDRIVER_IMPORT_ERROR"
|
|
230
231
|
ERROR_TOKEN = "${ZENDRIVER_ERROR_TOKEN}"
|
|
231
232
|
ACCOUNT_DEACTIVATED_TOKEN = "${ACCOUNT_DEACTIVATED_ERROR_MARKER}"
|
|
233
|
+
CAPTCHA_CHALLENGE_TOKEN = "${CAPTCHA_CHALLENGE_ERROR_MARKER}"
|
|
234
|
+
ALLOW_MANUAL_OTP = os.environ.get("THECLAWBAY_ALLOW_MANUAL_OTP", "").strip() == "1"
|
|
232
235
|
|
|
233
236
|
try:
|
|
234
237
|
import zendriver as zd
|
|
@@ -297,6 +300,15 @@ OTP_PROMPT_SNIPPETS = [
|
|
|
297
300
|
"código de verificação",
|
|
298
301
|
"código de confirmação",
|
|
299
302
|
]
|
|
303
|
+
CAPTCHA_PROMPT_SNIPPETS = [
|
|
304
|
+
"captcha",
|
|
305
|
+
"verify you are human",
|
|
306
|
+
"prove you're human",
|
|
307
|
+
"human verification",
|
|
308
|
+
"security challenge",
|
|
309
|
+
"security check",
|
|
310
|
+
"i'm not a robot",
|
|
311
|
+
]
|
|
300
312
|
CONSENT_PROMPT_SNIPPETS = [
|
|
301
313
|
"allow access",
|
|
302
314
|
"authorize",
|
|
@@ -316,6 +328,32 @@ ACCOUNT_DEACTIVATED_SNIPPETS = [
|
|
|
316
328
|
]
|
|
317
329
|
|
|
318
330
|
|
|
331
|
+
def _extract_email_domain(email):
|
|
332
|
+
value = str(email or "").strip().lower()
|
|
333
|
+
at = value.rfind("@")
|
|
334
|
+
if at < 0 or at == len(value) - 1:
|
|
335
|
+
return ""
|
|
336
|
+
return value[at + 1 :]
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _is_outlook_domain(domain):
|
|
340
|
+
if not domain:
|
|
341
|
+
return False
|
|
342
|
+
if domain in ("outlook.com", "hotmail.com", "live.com", "msn.com"):
|
|
343
|
+
return True
|
|
344
|
+
return (
|
|
345
|
+
domain.startswith("outlook.")
|
|
346
|
+
or domain.startswith("hotmail.")
|
|
347
|
+
or domain.startswith("live.")
|
|
348
|
+
or domain.startswith("msn.")
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
PREFER_OUTLOOK_LOCAL = _is_outlook_domain(
|
|
353
|
+
_extract_email_domain(VERIFICATION_ACCOUNT_EMAIL or LOGIN_EMAIL)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
319
357
|
def _log(message):
|
|
320
358
|
print(LOG_TOKEN + ":" + str(message), flush=True)
|
|
321
359
|
|
|
@@ -554,6 +592,14 @@ async def _fill_otp(tab, code):
|
|
|
554
592
|
return "missing"
|
|
555
593
|
|
|
556
594
|
|
|
595
|
+
async def _open_outlook_mail_tab(tab):
|
|
596
|
+
try:
|
|
597
|
+
opened = await tab.evaluate("(() => { window.open('https://outlook.live.com/mail/0/', '_blank'); return true; })()")
|
|
598
|
+
return bool(opened)
|
|
599
|
+
except Exception:
|
|
600
|
+
return False
|
|
601
|
+
|
|
602
|
+
|
|
557
603
|
def _contains_snippet(html, snippets):
|
|
558
604
|
return any(snippet in html for snippet in snippets)
|
|
559
605
|
|
|
@@ -574,6 +620,10 @@ def _is_account_deactivated_state(html):
|
|
|
574
620
|
return _contains_snippet(html, ACCOUNT_DEACTIVATED_SNIPPETS)
|
|
575
621
|
|
|
576
622
|
|
|
623
|
+
def _is_captcha_state(html):
|
|
624
|
+
return _contains_snippet(html, CAPTCHA_PROMPT_SNIPPETS)
|
|
625
|
+
|
|
626
|
+
|
|
577
627
|
def _request_verification_code_sync():
|
|
578
628
|
if not VERIFICATION_ENDPOINT or not VERIFICATION_AUTH:
|
|
579
629
|
raise RuntimeError("verification endpoint/auth missing for OTP retrieval")
|
|
@@ -660,6 +710,8 @@ async def _automation_loop(tab):
|
|
|
660
710
|
otp_submit_attempts = 0
|
|
661
711
|
otp_total_submit_attempts = 0
|
|
662
712
|
otp_needs_submit = False
|
|
713
|
+
manual_otp_mode = False
|
|
714
|
+
outlook_tab_opened = False
|
|
663
715
|
transient_error_hits = 0
|
|
664
716
|
success_seen_at = 0.0
|
|
665
717
|
|
|
@@ -673,6 +725,15 @@ async def _automation_loop(tab):
|
|
|
673
725
|
ACCOUNT_DEACTIVATED_TOKEN + ": OpenAI reported this account is deleted or deactivated."
|
|
674
726
|
)
|
|
675
727
|
|
|
728
|
+
if _is_captcha_state(html):
|
|
729
|
+
if show:
|
|
730
|
+
_log("captcha/security challenge detected; complete it manually in this browser")
|
|
731
|
+
await asyncio.sleep(0.9)
|
|
732
|
+
continue
|
|
733
|
+
raise RuntimeError(
|
|
734
|
+
CAPTCHA_CHALLENGE_TOKEN + ": captcha/security challenge detected and browser is headless"
|
|
735
|
+
)
|
|
736
|
+
|
|
676
737
|
if _is_success_state(html, current_url):
|
|
677
738
|
if success_seen_at <= 0.0:
|
|
678
739
|
success_seen_at = now
|
|
@@ -740,9 +801,41 @@ async def _automation_loop(tab):
|
|
|
740
801
|
continue
|
|
741
802
|
|
|
742
803
|
if _contains_snippet(html, OTP_PROMPT_SNIPPETS):
|
|
804
|
+
if manual_otp_mode:
|
|
805
|
+
await asyncio.sleep(0.9)
|
|
806
|
+
continue
|
|
743
807
|
if not otp_code:
|
|
808
|
+
if not VERIFICATION_ENDPOINT or not VERIFICATION_AUTH:
|
|
809
|
+
if show and ALLOW_MANUAL_OTP:
|
|
810
|
+
manual_otp_mode = True
|
|
811
|
+
if PREFER_OUTLOOK_LOCAL and not outlook_tab_opened:
|
|
812
|
+
outlook_tab_opened = await _open_outlook_mail_tab(tab)
|
|
813
|
+
if outlook_tab_opened:
|
|
814
|
+
_log("opened Outlook mailbox tab for local OTP retrieval")
|
|
815
|
+
_log("backend OTP API disabled for this account; enter OTP manually in this browser to continue")
|
|
816
|
+
await asyncio.sleep(0.9)
|
|
817
|
+
continue
|
|
818
|
+
raise RuntimeError("verification endpoint/auth missing for OTP retrieval")
|
|
819
|
+
|
|
744
820
|
_log("requesting OTP code from backend")
|
|
745
|
-
|
|
821
|
+
try:
|
|
822
|
+
otp_code = await asyncio.to_thread(_request_verification_code_sync)
|
|
823
|
+
except Exception as exc:
|
|
824
|
+
reason = _compact_error_text(exc)
|
|
825
|
+
if show and ALLOW_MANUAL_OTP:
|
|
826
|
+
manual_otp_mode = True
|
|
827
|
+
if PREFER_OUTLOOK_LOCAL and not outlook_tab_opened:
|
|
828
|
+
outlook_tab_opened = await _open_outlook_mail_tab(tab)
|
|
829
|
+
if outlook_tab_opened:
|
|
830
|
+
_log("opened Outlook mailbox tab for local OTP retrieval")
|
|
831
|
+
_log(
|
|
832
|
+
"backend OTP retrieval failed"
|
|
833
|
+
+ (f" ({reason})" if reason else "")
|
|
834
|
+
+ "; enter OTP manually in this browser to continue"
|
|
835
|
+
)
|
|
836
|
+
await asyncio.sleep(0.9)
|
|
837
|
+
continue
|
|
838
|
+
raise
|
|
746
839
|
_log("received OTP code from backend")
|
|
747
840
|
otp_submit_attempts = 0
|
|
748
841
|
otp_needs_submit = True
|
|
@@ -883,6 +976,21 @@ function extractZendriverFailureReason(output) {
|
|
|
883
976
|
return summarizeErrorDetails(runtimeError, 700);
|
|
884
977
|
return null;
|
|
885
978
|
}
|
|
979
|
+
function isCaptchaChallengeAuthError(error) {
|
|
980
|
+
if (!(error instanceof Error))
|
|
981
|
+
return false;
|
|
982
|
+
const message = error.message.toLowerCase();
|
|
983
|
+
return (message.includes(CAPTCHA_CHALLENGE_ERROR_MARKER.toLowerCase()) ||
|
|
984
|
+
message.includes("captcha/security challenge detected"));
|
|
985
|
+
}
|
|
986
|
+
function isBackendOtpRetrievalAuthError(error) {
|
|
987
|
+
if (!(error instanceof Error))
|
|
988
|
+
return false;
|
|
989
|
+
const message = error.message.toLowerCase();
|
|
990
|
+
return (message.includes("backend verification job failed") ||
|
|
991
|
+
message.includes("backend verification request failed") ||
|
|
992
|
+
message.includes("timed out waiting for verification code"));
|
|
993
|
+
}
|
|
886
994
|
function extractOauthAuthorizeUrl(output) {
|
|
887
995
|
var _a, _b, _c;
|
|
888
996
|
const cleaned = stripAnsi(output);
|
|
@@ -922,6 +1030,7 @@ async function launchWithZendriverPython(pythonBin, oauthUrl, showBrowser, autom
|
|
|
922
1030
|
THECLAWBAY_VERIFICATION_AUTH: (_d = (_c = automation.verification) === null || _c === void 0 ? void 0 : _c.authorization) !== null && _d !== void 0 ? _d : "",
|
|
923
1031
|
THECLAWBAY_VERIFICATION_ACCOUNT_EMAIL: (_f = (_e = automation.verification) === null || _e === void 0 ? void 0 : _e.accountEmail) !== null && _f !== void 0 ? _f : automation.loginEmail,
|
|
924
1032
|
THECLAWBAY_VERIFICATION_ACCOUNT_NAME: (_h = (_g = automation.verification) === null || _g === void 0 ? void 0 : _g.accountName) !== null && _h !== void 0 ? _h : "",
|
|
1033
|
+
THECLAWBAY_ALLOW_MANUAL_OTP: automation.allowManualOtpFallback ? "1" : "",
|
|
925
1034
|
},
|
|
926
1035
|
});
|
|
927
1036
|
return new Promise((resolve) => {
|
|
@@ -1090,7 +1199,10 @@ async function runCodexBrowserOAuth(codexHome, codexSpawnPlan, timeoutMs, automa
|
|
|
1090
1199
|
if (!oauthUrl)
|
|
1091
1200
|
return;
|
|
1092
1201
|
oauthUrlDiscovered = true;
|
|
1093
|
-
browserSessionTask = launchOauthUrlInZendriver(oauthUrl, options.showBrowser === true,
|
|
1202
|
+
browserSessionTask = launchOauthUrlInZendriver(oauthUrl, options.showBrowser === true, {
|
|
1203
|
+
...automation,
|
|
1204
|
+
allowManualOtpFallback: options.allowManualOtpFallback === true,
|
|
1205
|
+
}, options.log)
|
|
1094
1206
|
.then((session) => {
|
|
1095
1207
|
oauthBrowserStarted = true;
|
|
1096
1208
|
const runtimeTask = session
|
|
@@ -1240,15 +1352,41 @@ async function createAuthSnapshotForAccount(account, destinationPath, options) {
|
|
|
1240
1352
|
const authPath = node_path_1.default.join(tempCodexHome, "auth.json");
|
|
1241
1353
|
try {
|
|
1242
1354
|
log === null || log === void 0 ? void 0 : log(`[${account.name}] starting Codex OAuth login flow`);
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1355
|
+
try {
|
|
1356
|
+
await runCodexBrowserOAuth(tempCodexHome, codexSpawnPlan, timeoutMs, {
|
|
1357
|
+
accountName: account.name,
|
|
1358
|
+
loginEmail: account.loginEmail,
|
|
1359
|
+
loginPassword: account.loginPassword,
|
|
1360
|
+
verification: options.verification,
|
|
1361
|
+
}, {
|
|
1362
|
+
log,
|
|
1363
|
+
showBrowser: oauthShowBrowser,
|
|
1364
|
+
allowManualOtpFallback: oauthShowBrowser,
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
catch (error) {
|
|
1368
|
+
const shouldRetryInVisibleBrowser = !oauthShowBrowser &&
|
|
1369
|
+
(isCaptchaChallengeAuthError(error) || isBackendOtpRetrievalAuthError(error));
|
|
1370
|
+
if (!shouldRetryInVisibleBrowser) {
|
|
1371
|
+
throw error;
|
|
1372
|
+
}
|
|
1373
|
+
if (isCaptchaChallengeAuthError(error)) {
|
|
1374
|
+
log === null || log === void 0 ? void 0 : log(`[${account.name}] captcha/security challenge detected; reopening visible browser for manual completion`);
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
log === null || log === void 0 ? void 0 : log(`[${account.name}] backend OTP fetch failed; reopening visible browser so OTP can be completed manually`);
|
|
1378
|
+
}
|
|
1379
|
+
await runCodexBrowserOAuth(tempCodexHome, codexSpawnPlan, timeoutMs, {
|
|
1380
|
+
accountName: account.name,
|
|
1381
|
+
loginEmail: account.loginEmail,
|
|
1382
|
+
loginPassword: account.loginPassword,
|
|
1383
|
+
verification: options.verification,
|
|
1384
|
+
}, {
|
|
1385
|
+
log,
|
|
1386
|
+
showBrowser: true,
|
|
1387
|
+
allowManualOtpFallback: true,
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1252
1390
|
const authReady = await waitForFile(authPath, 2500);
|
|
1253
1391
|
if (!authReady) {
|
|
1254
1392
|
throw new Error("Codex OAuth login finished without auth.json (login not completed).");
|
|
@@ -6,7 +6,8 @@ export type ManagedAccountPayload = {
|
|
|
6
6
|
loginPassword?: string | null;
|
|
7
7
|
};
|
|
8
8
|
export declare function normalizeManagedCredentialAccount(item: ManagedAccountPayload): ManagedCredentialAccount | null;
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function requiresLocalOtpAutomation(account: ManagedCredentialAccount): boolean;
|
|
10
|
+
export declare function buildVerificationRequestConfig(managedConfig: ManagedConfig, account: ManagedCredentialAccount): VerificationRequestConfig | null;
|
|
10
11
|
export declare function syncManagedAccounts(accounts: ManagedAccountPayload[], options: {
|
|
11
12
|
managedConfig: ManagedConfig;
|
|
12
13
|
log?: (message: string) => void;
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.normalizeManagedCredentialAccount = normalizeManagedCredentialAccount;
|
|
7
|
+
exports.requiresLocalOtpAutomation = requiresLocalOtpAutomation;
|
|
7
8
|
exports.buildVerificationRequestConfig = buildVerificationRequestConfig;
|
|
8
9
|
exports.syncManagedAccounts = syncManagedAccounts;
|
|
9
10
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -13,6 +14,8 @@ const account_service_1 = require("../accounts/account-service");
|
|
|
13
14
|
const paths_1 = require("../config/paths");
|
|
14
15
|
const account_login_1 = require("./account-login");
|
|
15
16
|
const ACCOUNT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
17
|
+
const OUTLOOK_EXACT_DOMAINS = new Set(["outlook.com", "hotmail.com", "live.com", "msn.com"]);
|
|
18
|
+
const OUTLOOK_DOMAIN_PREFIXES = ["outlook.", "hotmail.", "live.", "msn."];
|
|
16
19
|
const EMPTY_STATE = {
|
|
17
20
|
version: 1,
|
|
18
21
|
accounts: {},
|
|
@@ -45,8 +48,32 @@ function joinUrl(base, pathname) {
|
|
|
45
48
|
url.pathname = `${url.pathname.replace(/\/+$/g, "")}${pathname.startsWith("/") ? "" : "/"}${pathname}`;
|
|
46
49
|
return url.toString();
|
|
47
50
|
}
|
|
51
|
+
function extractEmailDomain(email) {
|
|
52
|
+
const normalized = email.trim().toLowerCase();
|
|
53
|
+
const atIndex = normalized.lastIndexOf("@");
|
|
54
|
+
if (atIndex < 0 || atIndex === normalized.length - 1)
|
|
55
|
+
return "";
|
|
56
|
+
return normalized.slice(atIndex + 1);
|
|
57
|
+
}
|
|
58
|
+
function isOutlookDomain(domain) {
|
|
59
|
+
if (!domain)
|
|
60
|
+
return false;
|
|
61
|
+
if (OUTLOOK_EXACT_DOMAINS.has(domain))
|
|
62
|
+
return true;
|
|
63
|
+
for (const prefix of OUTLOOK_DOMAIN_PREFIXES) {
|
|
64
|
+
if (domain.startsWith(prefix))
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
function requiresLocalOtpAutomation(account) {
|
|
70
|
+
return isOutlookDomain(extractEmailDomain(account.loginEmail));
|
|
71
|
+
}
|
|
48
72
|
function buildVerificationRequestConfig(managedConfig, account) {
|
|
49
73
|
var _a, _b, _c;
|
|
74
|
+
if (requiresLocalOtpAutomation(account)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
50
77
|
if (managedConfig.authType === "apiKey") {
|
|
51
78
|
return {
|
|
52
79
|
endpoint: joinUrl(managedConfig.backendUrl, "/api/codex-auth/v1/verification-code"),
|
|
@@ -129,10 +156,14 @@ async function syncManagedAccounts(accounts, options) {
|
|
|
129
156
|
log === null || log === void 0 ? void 0 : log(`[${account.name}] refreshing account session via Codex OAuth login`);
|
|
130
157
|
try {
|
|
131
158
|
const verification = buildVerificationRequestConfig(options.managedConfig, account);
|
|
159
|
+
const requiresLocalOtp = verification === null;
|
|
160
|
+
if (requiresLocalOtp) {
|
|
161
|
+
log === null || log === void 0 ? void 0 : log(`[${account.name}] outlook mailbox detected; skipping backend OTP API and using local browser OTP flow`);
|
|
162
|
+
}
|
|
132
163
|
await (0, account_login_1.createAuthSnapshotForAccount)(account, filePath, {
|
|
133
164
|
log,
|
|
134
|
-
oauthShowBrowser: options.oauthShowBrowser,
|
|
135
|
-
verification,
|
|
165
|
+
oauthShowBrowser: options.oauthShowBrowser || requiresLocalOtp,
|
|
166
|
+
verification: verification !== null && verification !== void 0 ? verification : undefined,
|
|
136
167
|
});
|
|
137
168
|
log === null || log === void 0 ? void 0 : log(`[${account.name}] account session refreshed`);
|
|
138
169
|
}
|