qwenproxy-cli 1.6.0 → 1.6.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.
package/dist/login.js CHANGED
@@ -826,7 +826,7 @@ function addAccountsBatch(entries) {
826
826
  }
827
827
  return { added, skipped, invalid };
828
828
  }
829
- function addAccount(email, password, id) {
829
+ function addAccount(email, password = "", id) {
830
830
  if (!email || typeof email !== "string" || email.trim().length === 0) {
831
831
  throw new Error("Email is required");
832
832
  }
@@ -1102,6 +1102,44 @@ var init_logger = __esm({
1102
1102
  }
1103
1103
  });
1104
1104
 
1105
+ // src/core/process-guard.ts
1106
+ function isPlaywrightAlreadyClosedMessage(message) {
1107
+ if (!message) return false;
1108
+ return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
1109
+ }
1110
+ function isBenignTeardownError(error) {
1111
+ if (!error) return false;
1112
+ const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
1113
+ return isPlaywrightAlreadyClosedMessage(message);
1114
+ }
1115
+ function installProcessTeardownGuards(label = "Process") {
1116
+ if (guardsInstalled) return;
1117
+ guardsInstalled = true;
1118
+ process.on("uncaughtException", (error) => {
1119
+ if (isBenignTeardownError(error)) {
1120
+ const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
1121
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
1122
+ return;
1123
+ }
1124
+ console.error(`\u274C [${label}] Uncaught Exception:`, error);
1125
+ });
1126
+ process.on("unhandledRejection", (reason) => {
1127
+ if (isBenignTeardownError(reason)) {
1128
+ const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
1129
+ console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
1130
+ return;
1131
+ }
1132
+ console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
1133
+ });
1134
+ }
1135
+ var guardsInstalled;
1136
+ var init_process_guard = __esm({
1137
+ "src/core/process-guard.ts"() {
1138
+ "use strict";
1139
+ guardsInstalled = false;
1140
+ }
1141
+ });
1142
+
1105
1143
  // src/core/config.ts
1106
1144
  var config_exports = {};
1107
1145
  __export(config_exports, {
@@ -1251,11 +1289,11 @@ var init_config = __esm({
1251
1289
  // nothing in this window is dead and should fail fast, retryable).
1252
1290
  QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
1253
1291
  TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
1254
- // Mid-stream silence window for thinking models: 3 min with ZERO upstream
1292
+ // Mid-stream silence window for thinking models: 5 min with ZERO upstream
1255
1293
  // bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
1256
1294
  // let the retry policy rotate accounts. Flowing reasoning chunks RESET this
1257
1295
  // timer, so legitimate slow thinking is never cut; only total silence is.
1258
- REASONING_MODEL_TIMEOUT: z.string().default("180000"),
1296
+ REASONING_MODEL_TIMEOUT: z.string().default("300000"),
1259
1297
  CACHE_TTL: z.string().default("3600"),
1260
1298
  RESPONSE_TTL: z.string().default("1800"),
1261
1299
  CACHE_COMPRESSION_ENABLED: z.string().default("true"),
@@ -3127,6 +3165,8 @@ var init_clean_cache = __esm({
3127
3165
  "src/clean-cache.ts"() {
3128
3166
  "use strict";
3129
3167
  init_playwright();
3168
+ init_process_guard();
3169
+ installProcessTeardownGuards("CleanCache");
3130
3170
  isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
3131
3171
  if (isDirectRun) {
3132
3172
  main().catch((err) => {
@@ -3142,6 +3182,7 @@ var playwright_exports = {};
3142
3182
  __export(playwright_exports, {
3143
3183
  assertAntiBotHeaders: () => assertAntiBotHeaders,
3144
3184
  buildChromiumLaunchArgs: () => buildChromiumLaunchArgs,
3185
+ cancelEmailOtpLogin: () => cancelEmailOtpLogin,
3145
3186
  captureQwenHeaders: () => captureQwenHeaders,
3146
3187
  classifyQwenAuthError: () => classifyQwenAuthError,
3147
3188
  cleanupOrphanProfiles: () => cleanupOrphanProfiles,
@@ -3182,8 +3223,10 @@ __export(playwright_exports, {
3182
3223
  removePlaywrightProfile: () => removePlaywrightProfile,
3183
3224
  saveStorageState: () => saveStorageState,
3184
3225
  schedulePlaywrightProfileReset: () => schedulePlaywrightProfileReset,
3226
+ startEmailOtpLogin: () => startEmailOtpLogin,
3185
3227
  unregisterPlaywrightAccountForTests: () => unregisterPlaywrightAccountForTests,
3186
3228
  validateAccountLogin: () => validateAccountLogin,
3229
+ verifyEmailOtpLogin: () => verifyEmailOtpLogin,
3187
3230
  withAccountPage: () => withAccountPage
3188
3231
  });
3189
3232
  import { chromium as chromium2 } from "patchright";
@@ -3351,15 +3394,18 @@ function loadStorageState(accountId) {
3351
3394
  return void 0;
3352
3395
  }
3353
3396
  }
3354
- async function saveStorageState(context, accountId, timeoutMs = 5e3) {
3397
+ async function saveStorageState(context, accountId, timeoutMs = 1e4) {
3355
3398
  try {
3356
3399
  const stateFile = getStorageStatePath(accountId);
3357
3400
  const dir = path5.dirname(stateFile);
3358
3401
  if (!fs8.existsSync(dir)) {
3359
3402
  fs8.mkdirSync(dir, { recursive: true });
3360
3403
  }
3404
+ const rawPromise = context.storageState({ path: stateFile }).catch(() => {
3405
+ return void 0;
3406
+ });
3361
3407
  await withTimeout(
3362
- context.storageState({ path: stateFile }),
3408
+ rawPromise,
3363
3409
  timeoutMs,
3364
3410
  `storageState timed out after ${timeoutMs}ms`
3365
3411
  );
@@ -3522,6 +3568,10 @@ async function clearVisibleChallenge(page) {
3522
3568
  "button[aria-label*='Close' i]",
3523
3569
  "button[aria-label*='Fechar' i]",
3524
3570
  ".index-module__whats-new-modal-close",
3571
+ ".age-confirmation-modal button",
3572
+ ".age-confirmation-modal .qwen-chat-v2-btn-black",
3573
+ "button:has-text('Continuar')",
3574
+ "button:has-text('Continue')",
3525
3575
  "button:has-text('Cancel')",
3526
3576
  "button:has-text('Cancelar')",
3527
3577
  "button:has-text('I understand')",
@@ -4716,6 +4766,292 @@ async function validateAccountLogin(rawAccount, headless = true, browserType = "
4716
4766
  release();
4717
4767
  }
4718
4768
  }
4769
+ async function startEmailOtpLogin(email, options = {}) {
4770
+ const normalizedEmail = email.trim().toLowerCase();
4771
+ if (!normalizedEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
4772
+ return { success: false, error: "Formato de e-mail inv\xE1lido" };
4773
+ }
4774
+ const { listAccounts: listAccounts2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
4775
+ if (listAccounts2().some((a) => a.email.toLowerCase() === normalizedEmail)) {
4776
+ return {
4777
+ success: false,
4778
+ error: `Uma conta com o e-mail "${normalizedEmail}" j\xE1 est\xE1 cadastrada.`
4779
+ };
4780
+ }
4781
+ if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
4782
+ const mockSessionId = "mock-otp-" + Date.now();
4783
+ mockOtpSessions.set(mockSessionId, normalizedEmail);
4784
+ return { success: true, sessionId: mockSessionId };
4785
+ }
4786
+ const accountId = crypto3.randomUUID();
4787
+ const sessionId = accountId;
4788
+ const profilePath = getAccountProfilePath(accountId);
4789
+ const fingerprint = getFingerprintProfile(accountId);
4790
+ const browserType = options.browserType ?? config.playwright.browser;
4791
+ const { engine, channel } = resolveBrowserEngine(browserType);
4792
+ const headless = options.headless ?? config.playwright.headless;
4793
+ const launchOptions = {
4794
+ headless,
4795
+ channel,
4796
+ userAgent: fingerprint.userAgent,
4797
+ locale: fingerprint.locale,
4798
+ timezoneId: fingerprint.timezoneId,
4799
+ viewport: fingerprint.viewport,
4800
+ screen: fingerprint.viewport,
4801
+ deviceScaleFactor: 1,
4802
+ isMobile: false,
4803
+ hasTouch: false,
4804
+ colorScheme: "light",
4805
+ extraHTTPHeaders: {
4806
+ "sec-ch-ua": fingerprint.secChUa,
4807
+ "sec-ch-ua-mobile": "?0",
4808
+ "sec-ch-ua-platform": '"Windows"'
4809
+ },
4810
+ ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
4811
+ args: buildChromiumLaunchArgs(fingerprint.viewport)
4812
+ };
4813
+ let acctContext;
4814
+ try {
4815
+ acctContext = await withTimeout(
4816
+ engine.launchPersistentContext(profilePath, launchOptions),
4817
+ 3e4,
4818
+ `O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
4819
+ );
4820
+ } catch (launchErr) {
4821
+ if (launchErr?.message?.includes("Executable doesn't exist")) {
4822
+ autoInstallPlaywrightChromium();
4823
+ acctContext = await withTimeout(
4824
+ engine.launchPersistentContext(profilePath, launchOptions),
4825
+ 3e4,
4826
+ `O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
4827
+ );
4828
+ } else {
4829
+ return { success: false, error: launchErr?.message || String(launchErr) };
4830
+ }
4831
+ }
4832
+ try {
4833
+ await acctContext.addInitScript(getStealthScript(fingerprint));
4834
+ const existingPages = acctContext.pages().filter((p) => !p.isClosed());
4835
+ const acctPage = existingPages.find((p) => p.url().startsWith(qwenOrigin())) ?? existingPages[0] ?? await acctContext.newPage();
4836
+ await acctPage.goto(qwenUrl("/auth?mode=register"), {
4837
+ waitUntil: "domcontentloaded",
4838
+ timeout: Math.min(config.timeouts.navigation, 2e4)
4839
+ }).catch(() => {
4840
+ });
4841
+ await sleep2(1e3);
4842
+ await clearVisibleChallenge(acctPage);
4843
+ const requestId = crypto3.randomUUID();
4844
+ const evalRes = await acctPage.evaluate(
4845
+ async ({ email: email2, requestId: requestId2, version, timezone }) => {
4846
+ try {
4847
+ const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/request", {
4848
+ method: "POST",
4849
+ credentials: "include",
4850
+ headers: {
4851
+ accept: "application/json, text/plain, */*",
4852
+ "content-type": "application/json",
4853
+ source: "web",
4854
+ version,
4855
+ "x-request-origin": "https://chat.qwen.ai",
4856
+ timezone,
4857
+ "x-request-id": requestId2
4858
+ },
4859
+ body: JSON.stringify({
4860
+ identifier: email2,
4861
+ accept_terms: true,
4862
+ language: "pt-BR"
4863
+ })
4864
+ });
4865
+ const json = await response.json().catch(() => null);
4866
+ return { ok: response.ok, status: response.status, data: json };
4867
+ } catch (e) {
4868
+ return { ok: false, error: e.message };
4869
+ }
4870
+ },
4871
+ {
4872
+ email: normalizedEmail,
4873
+ requestId,
4874
+ version: getQwenWebVersion() || "0.3.12",
4875
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
4876
+ }
4877
+ ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4878
+ if (!evalRes.ok || evalRes.data?.success !== true) {
4879
+ const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "Falha ao solicitar c\xF3digo de verifica\xE7\xE3o";
4880
+ await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
4881
+ });
4882
+ cleanupPlaywrightAccountState(accountId);
4883
+ return { success: false, error: errorMsg };
4884
+ }
4885
+ const timer = setTimeout(async () => {
4886
+ activeOtpSessions.delete(sessionId);
4887
+ await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
4888
+ });
4889
+ cleanupPlaywrightAccountState(accountId);
4890
+ }, 5 * 60 * 1e3);
4891
+ timer.unref?.();
4892
+ activeOtpSessions.set(sessionId, {
4893
+ sessionId,
4894
+ accountId,
4895
+ email: normalizedEmail,
4896
+ context: acctContext,
4897
+ page: acctPage,
4898
+ fingerprint,
4899
+ createdAt: Date.now(),
4900
+ timer
4901
+ });
4902
+ return { success: true, sessionId };
4903
+ } catch (err) {
4904
+ await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
4905
+ });
4906
+ cleanupPlaywrightAccountState(accountId);
4907
+ return { success: false, error: err?.message || String(err) };
4908
+ }
4909
+ }
4910
+ async function verifyEmailOtpLogin(sessionId, code) {
4911
+ const cleanCode = code.trim();
4912
+ if (!cleanCode) {
4913
+ return { success: false, error: "C\xF3digo de verifica\xE7\xE3o \xE9 obrigat\xF3rio" };
4914
+ }
4915
+ if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
4916
+ const mockEmail = mockOtpSessions.get(sessionId);
4917
+ if (!mockEmail) {
4918
+ return { success: false, error: "Sess\xE3o OTP inv\xE1lida ou expirada" };
4919
+ }
4920
+ if (cleanCode === "000000") {
4921
+ return { success: false, error: "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado" };
4922
+ }
4923
+ const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
4924
+ const mockAccId = "mock-otp-acc-" + Date.now();
4925
+ const newAccount = addAccount2(mockEmail, "", mockAccId);
4926
+ mockOtpSessions.delete(sessionId);
4927
+ return { success: true, account: newAccount };
4928
+ }
4929
+ const session = activeOtpSessions.get(sessionId);
4930
+ if (!session) {
4931
+ return { success: false, error: "Sess\xE3o OTP expirada ou n\xE3o encontrada. Solicite um novo c\xF3digo." };
4932
+ }
4933
+ if (session.page.isClosed()) {
4934
+ activeOtpSessions.delete(sessionId);
4935
+ return { success: false, error: "Navegador foi fechado. Solicite um novo c\xF3digo." };
4936
+ }
4937
+ const requestId = crypto3.randomUUID();
4938
+ const evalRes = await session.page.evaluate(
4939
+ async ({ email, code: code2, requestId: requestId2, version, timezone }) => {
4940
+ try {
4941
+ const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/verify", {
4942
+ method: "POST",
4943
+ credentials: "include",
4944
+ headers: {
4945
+ accept: "application/json, text/plain, */*",
4946
+ "content-type": "application/json",
4947
+ source: "web",
4948
+ version,
4949
+ "x-request-origin": "https://chat.qwen.ai",
4950
+ timezone,
4951
+ "x-request-id": requestId2
4952
+ },
4953
+ body: JSON.stringify({
4954
+ identifier: email,
4955
+ code: code2,
4956
+ name: null
4957
+ })
4958
+ });
4959
+ const json = await response.json().catch(() => null);
4960
+ return { ok: response.ok, status: response.status, data: json };
4961
+ } catch (e) {
4962
+ return { ok: false, error: e.message };
4963
+ }
4964
+ },
4965
+ {
4966
+ email: session.email,
4967
+ code: cleanCode,
4968
+ requestId,
4969
+ version: getQwenWebVersion() || "0.3.12",
4970
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
4971
+ }
4972
+ ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4973
+ if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
4974
+ const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado";
4975
+ return { success: false, error: errorMsg };
4976
+ }
4977
+ const resData = evalRes.data.data;
4978
+ const accessToken = resData.access_token || resData.token;
4979
+ const refreshToken = resData.refresh_token;
4980
+ const userId = resData.id;
4981
+ try {
4982
+ await session.page.evaluate(({ token, rTok }) => {
4983
+ try {
4984
+ localStorage.setItem("token", token);
4985
+ localStorage.setItem("access_token", token);
4986
+ if (rTok) localStorage.setItem("refresh_token", rTok);
4987
+ document.cookie = `token=${encodeURIComponent(token)}; path=/; domain=.qwen.ai; max-age=31536000`;
4988
+ } catch {
4989
+ }
4990
+ }, { token: accessToken, rTok: refreshToken });
4991
+ await session.context.addCookies([
4992
+ {
4993
+ name: "token",
4994
+ value: accessToken,
4995
+ domain: ".qwen.ai",
4996
+ path: "/",
4997
+ expires: Math.floor(Date.now() / 1e3) + 31536e3,
4998
+ httpOnly: false,
4999
+ secure: true,
5000
+ sameSite: "Lax"
5001
+ }
5002
+ ]);
5003
+ await session.page.goto(qwenUrl("/"), {
5004
+ waitUntil: "domcontentloaded",
5005
+ timeout: Math.min(config.timeouts.navigation, 15e3)
5006
+ }).catch(() => {
5007
+ });
5008
+ await sleep2(1e3);
5009
+ await saveStorageState(session.context, session.accountId);
5010
+ const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5011
+ const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
5012
+ const exp = parseJwtExpiry2(accessToken);
5013
+ saveAuthSession2(session.accountId, {
5014
+ cookie: `token=${accessToken}`,
5015
+ userAgent: session.fingerprint.userAgent,
5016
+ bxV: "2.5.37",
5017
+ version: getQwenWebVersion() || "0.3.12",
5018
+ userId: userId || void 0,
5019
+ tokenExpiresAt: exp || void 0,
5020
+ refreshToken: refreshToken || void 0,
5021
+ capturedAt: Date.now()
5022
+ });
5023
+ const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5024
+ const newAccount = addAccount2(session.email, "", session.accountId);
5025
+ clearTimeout(session.timer);
5026
+ activeOtpSessions.delete(sessionId);
5027
+ await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
5028
+ });
5029
+ cleanupPlaywrightAccountState(session.accountId);
5030
+ return { success: true, account: newAccount };
5031
+ } catch (err) {
5032
+ clearTimeout(session.timer);
5033
+ activeOtpSessions.delete(sessionId);
5034
+ await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
5035
+ });
5036
+ cleanupPlaywrightAccountState(session.accountId);
5037
+ return { success: false, error: err?.message || String(err) };
5038
+ }
5039
+ }
5040
+ async function cancelEmailOtpLogin(sessionId) {
5041
+ if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
5042
+ mockOtpSessions.delete(sessionId);
5043
+ return;
5044
+ }
5045
+ const session = activeOtpSessions.get(sessionId);
5046
+ if (!session) return;
5047
+ clearTimeout(session.timer);
5048
+ activeOtpSessions.delete(sessionId);
5049
+ await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
5050
+ });
5051
+ cleanupPlaywrightAccountState(session.accountId);
5052
+ const { wipeAccountSessionFiles: wipeAccountSessionFiles2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
5053
+ wipeAccountSessionFiles2(session.accountId);
5054
+ }
4719
5055
  function classifyQwenAuthError(code, details) {
4720
5056
  const c = (code || "").trim().toLowerCase();
4721
5057
  const d = (details || "").trim().toLowerCase();
@@ -6084,7 +6420,7 @@ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION
6084
6420
  return result;
6085
6421
  } catch (error) {
6086
6422
  const message = getErrorMessage(error);
6087
- if (recoverOnTimeout && message.includes("Playwright page operation timed out")) {
6423
+ if (recoverOnTimeout && message.includes("Playwright page operation timed out") && (!page || page.isClosed())) {
6088
6424
  console.warn(
6089
6425
  `\u23F1\uFE0F [Playwright] Resetting account context after a stuck page operation: ${accountId}`
6090
6426
  );
@@ -6633,7 +6969,7 @@ async function getTokenDiagnostics(accountId) {
6633
6969
  }
6634
6970
  };
6635
6971
  }
6636
- var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
6972
+ var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, activeOtpSessions, mockOtpSessions, PROFILE_RESET_TIMEOUT_MS;
6637
6973
  var init_playwright = __esm({
6638
6974
  "src/services/playwright.ts"() {
6639
6975
  "use strict";
@@ -6678,11 +7014,13 @@ var init_playwright = __esm({
6678
7014
  PLAYWRIGHT_MUTEX_WAIT_MS = 6e4;
6679
7015
  ACCOUNT_PAGE_OPERATION_TIMEOUT_MS = config.timeouts.page;
6680
7016
  SESSION_PROBE_NAVIGATION_TIMEOUT_MS = 15e3;
6681
- HEADER_CAPTURE_TRIGGER_GRACE_MS = 15e3;
6682
- FIRST_TRIGGER_GRACE_MS = 8e3;
7017
+ HEADER_CAPTURE_TRIGGER_GRACE_MS = 2e4;
7018
+ FIRST_TRIGGER_GRACE_MS = 12e3;
6683
7019
  HEADER_CAPTURE_TRIGGER_ATTEMPTS = 3;
6684
7020
  CHAT_INPUT_APPEAR_TIMEOUT_MS = 15e3;
6685
7021
  CHAT_INPUT_ACTION_TIMEOUT_MS = 1e4;
7022
+ activeOtpSessions = /* @__PURE__ */ new Map();
7023
+ mockOtpSessions = /* @__PURE__ */ new Map();
6686
7024
  PROFILE_RESET_TIMEOUT_MS = Math.max(9e4, config.timeouts.headers);
6687
7025
  setWafContextResetListener((accountId) => {
6688
7026
  if (!accountPages.has(accountId)) return;
@@ -6698,10 +7036,12 @@ var init_playwright = __esm({
6698
7036
  // src/login.ts
6699
7037
  init_accounts();
6700
7038
  init_logger();
7039
+ init_process_guard();
6701
7040
  import crypto4 from "crypto";
6702
7041
  import * as readline from "readline";
6703
7042
  import * as dotenv2 from "dotenv";
6704
7043
  dotenv2.config();
7044
+ installProcessTeardownGuards("Login");
6705
7045
  var rl = readline.createInterface({
6706
7046
  input: process.stdin,
6707
7047
  output: process.stdout
@@ -6734,7 +7074,8 @@ async function showMenu() {
6734
7074
  console.log("No accounts configured yet.\n");
6735
7075
  }
6736
7076
  console.log("\nOptions:");
6737
- console.log(" [A] Add account");
7077
+ console.log(" [A] Add account (Password)");
7078
+ console.log(" [O] Add account (Passwordless / Email OTP)");
6738
7079
  if (accounts.length > 0) {
6739
7080
  console.log(" [R] Remove an account");
6740
7081
  }
@@ -6745,7 +7086,21 @@ async function showMenu() {
6745
7086
  process.exit(0);
6746
7087
  }
6747
7088
  if (choice === "A") {
6748
- await addAccountFlow();
7089
+ clear();
7090
+ console.log("=== Add New Account ===\n");
7091
+ console.log("Authentication method:");
7092
+ console.log(" [1] Traditional Password (Email + Password)");
7093
+ console.log(" [2] Passwordless (Email Verification Code / OTP)\n");
7094
+ const method = (await askQuestion("Select method [1/2] (default 1): ")).trim();
7095
+ if (method === "2") {
7096
+ await addAccountOtpFlow();
7097
+ } else {
7098
+ await addAccountPasswordFlow();
7099
+ }
7100
+ continue;
7101
+ }
7102
+ if (choice === "O") {
7103
+ await addAccountOtpFlow();
6749
7104
  continue;
6750
7105
  }
6751
7106
  if (choice === "R" && accounts.length > 0) {
@@ -6754,9 +7109,9 @@ async function showMenu() {
6754
7109
  }
6755
7110
  }
6756
7111
  }
6757
- async function addAccountFlow() {
7112
+ async function addAccountPasswordFlow() {
6758
7113
  clear();
6759
- console.log("=== Add New Account ===\n");
7114
+ console.log("=== Add New Account (Password Login) ===\n");
6760
7115
  const email = await askQuestion("Email: ");
6761
7116
  if (!email || !email.trim()) {
6762
7117
  console.log("Email is required.");
@@ -6825,6 +7180,70 @@ async function addAccountFlow() {
6825
7180
  }
6826
7181
  await askQuestion("\nPress Enter to continue...");
6827
7182
  }
7183
+ async function addAccountOtpFlow() {
7184
+ clear();
7185
+ console.log("=== Add New Account (Passwordless / Email OTP) ===\n");
7186
+ const email = await askQuestion("Email: ");
7187
+ if (!email || !email.trim()) {
7188
+ console.log("Email is required.");
7189
+ await askQuestion("Press Enter to continue...");
7190
+ return;
7191
+ }
7192
+ const trimmedEmail = email.trim();
7193
+ const existingAccounts = listAccounts();
7194
+ if (existingAccounts.some(
7195
+ (a) => a.email.toLowerCase() === trimmedEmail.toLowerCase()
7196
+ )) {
7197
+ console.log(
7198
+ `
7199
+ \u274C Error: An account with email "${trimmedEmail}" already exists.`
7200
+ );
7201
+ await askQuestion("Press Enter to continue...");
7202
+ return;
7203
+ }
7204
+ console.log(
7205
+ `
7206
+ \u23F3 Requesting OTP verification code for ${maskEmail(trimmedEmail)}...`
7207
+ );
7208
+ const { startEmailOtpLogin: startEmailOtpLogin2, verifyEmailOtpLogin: verifyEmailOtpLogin2, cancelEmailOtpLogin: cancelEmailOtpLogin2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
7209
+ const startRes = await startEmailOtpLogin2(trimmedEmail, { headless: false });
7210
+ if (!startRes.success || !startRes.sessionId) {
7211
+ console.log(
7212
+ `
7213
+ \u274C Failed to request code: ${startRes.error || "Unknown error"}`
7214
+ );
7215
+ await askQuestion("Press Enter to continue...");
7216
+ return;
7217
+ }
7218
+ console.log(`
7219
+ \u2709\uFE0F Verification code sent to ${trimmedEmail}!`);
7220
+ console.log("Check your email inbox (and spam folder).\n");
7221
+ while (true) {
7222
+ const code = await askQuestion("Enter 6-digit code (or 'C' to cancel): ");
7223
+ if (!code || !code.trim()) continue;
7224
+ if (code.trim().toUpperCase() === "C") {
7225
+ await cancelEmailOtpLogin2(startRes.sessionId);
7226
+ console.log("\nLogin cancelled.");
7227
+ await askQuestion("Press Enter to continue...");
7228
+ return;
7229
+ }
7230
+ console.log("\n\u23F3 Verifying code and establishing 30-day session...");
7231
+ const verifyRes = await verifyEmailOtpLogin2(startRes.sessionId, code.trim());
7232
+ if (verifyRes.success && verifyRes.account) {
7233
+ console.log(
7234
+ `
7235
+ \u2705 Account verified and saved: ${maskEmail(verifyRes.account.email)} (${verifyRes.account.id})`
7236
+ );
7237
+ console.log("Passwordless 30-day session active.");
7238
+ await askQuestion("\nPress Enter to continue...");
7239
+ return;
7240
+ } else {
7241
+ console.log(`
7242
+ \u274C ${verifyRes.error || "Invalid code"}`);
7243
+ console.log("Please try again or enter 'C' to cancel.\n");
7244
+ }
7245
+ }
7246
+ }
6828
7247
  async function removeAccountFlow() {
6829
7248
  const accounts = listAccounts();
6830
7249
  if (accounts.length === 0) return;