diff --git a/skill/codex-oauth-plus-onboarding/SKILL.md b/skill/codex-oauth-plus-onboarding/SKILL.md index 2f96393..133594c 100644 --- a/skill/codex-oauth-plus-onboarding/SKILL.md +++ b/skill/codex-oauth-plus-onboarding/SKILL.md @@ -1,6 +1,6 @@ --- name: codex-oauth-plus-onboarding -description: "Low-token, stable workflow for BOSS's Codex/OpenAI OAuth onboarding: ClawEmail signup, optional 5sim SMS phone verification, and callback import into CPA/Codex2API/SUB2API." +description: "Low-token, stable workflow for BOSS's Codex/OpenAI OAuth onboarding: ClawEmail or 2925 mailbox signup, 5sim/Luban SMS phone verification, and callback import into CPA/Codex2API/SUB2API." version: 2.0.0 author: Hermes Agent license: MIT @@ -12,7 +12,7 @@ metadata: # Codex OAuth + CPA/Codex2API Onboarding — low-token runbook -Use when BOSS asks to register/login an OpenAI/Codex OAuth account, verify it via ClawEmail and possibly 5sim SMS, then import the OAuth callback into **CPA / CLI Proxy API**, Codex2API, or SUB2API. +Use when BOSS asks to register/login an OpenAI/Codex OAuth account, verify it via ClawEmail **or 2925 Webmail account-pool aliases**, possibly complete SMS phone verification via 5sim/Luban, then import the OAuth callback into **CPA / CLI Proxy API**, Codex2API, or SUB2API. Safety: BOSS-owned infra/accounts only. Stop for CAPTCHA/security challenge/payment confirmation unless BOSS explicitly approves the next step. Never reveal API keys, passwords, proxy URLs, full phone numbers, OAuth `code=`, SMS/email codes, admin keys, or tokens; report `[REDACTED]`. @@ -33,6 +33,7 @@ Prefer script-driven runs that emit compact JSONL. Only send screenshots or long - Read secrets only from `~/.hermes/env/codex_oauth_onboarding.env`; do not paste them into chat. - **Do not blindly `source` this env file in shell**: values such as `CHROME_BIN=/Applications/Google Chrome.app/...` may contain spaces and break `source` if unquoted. For probes/runners, parse `KEY=VALUE` safely in Python or require quoted values before shell sourcing. - Reports should contain: phase, account email, country slug, activation id, high-level status, HTTP status, and redacted error class. +- After any tool-driven step, always return a visible status/result to BOSS. Never end with an empty response after tool calls; if work continues, state the current verified checkpoint and next action. - Avoid dumping HTML, callback URLs, headers, full 5sim responses, or full phone numbers. - Save detailed evidence to `references/` or local logs; final response stays concise. @@ -56,7 +57,7 @@ Use visible Google Chrome for OpenAI/Auth pages. Do not use BOSS's personal brow 1. **Target gate**: exactly one target mode: `cpa`, `codex2api`, or `sub2api`. No default if env is empty. 2. **Password gate**: `CUSTOM_PASSWORD` must be set unless BOSS approves generating one. 3. **Proxy gate**: verify proxy TCP and OpenAI reachability before browser flow. -4. **Mailbox gate**: new ClawEmail sub-mailbox must have external receive enabled before OpenAI signup. +4. **Mailbox gate**: if using ClawEmail, new sub-mailbox must have external receive enabled before OpenAI signup; if using 2925, generated alias and visible Webmail account must be bound to the same selected pool account before clearing inbox or polling code. 5. **OpenAI identity gate before phone**: only buy 5sim if active page is a usable `https://auth.openai.com/add-phone` for a non-deactivated account. 6. **Country gate before phone buy/submit**: OpenAI select value and visible dial code must match target country before buying and again before clicking Continue. 7. **SMS-only gate**: if OpenAI resend channel probes as WhatsApp, do not click it; cancel/rotate under SMS-only policy. @@ -72,10 +73,11 @@ Need OAuth import? └─ SUB2API requested? → use original extension/content-script path Need new OpenAI account? - ├─ Create ClawEmail sub-mailbox with 8 lowercase letters - ├─ Enable external receive via Dashboard API - ├─ Submit email/password, poll ClawEmail code - └─ If phone page appears → 5sim SMS flow + ├─ Prefer 2925 if ClawEmail instability is a concern: pick pool account → generate bound 2925 alias → login same Webmail account → clear inbox → poll/delete code + ├─ If using ClawEmail: create sub-mailbox with 8 lowercase letters + ├─ Enable ClawEmail external receive via Dashboard API before submit + ├─ Submit email/password, poll exact mailbox code + └─ If phone page appears → 5sim/Luban SMS flow Already phone-verified account? └─ Generate fresh target OAuth URL, login/consent, submit callback to target @@ -112,23 +114,140 @@ Minimal flow: ```text fresh OAuth URL / signup page -submit ClawEmail sub-mailbox -submit CUSTOM_PASSWORD -poll ClawEmail inbox/spam for OpenAI code -submit code +submit ClawEmail sub-mailbox using real CDP input events, not only `input.value=` +submit CUSTOM_PASSWORD using real CDP input events +poll ClawEmail inbox/spam for OpenAI code using a mail-cli config/profile that points to the exact sub-mailbox +submit code using real CDP input events fill profile/about-you if shown continue to phone or OAuth consent ``` +OpenAI Auth can route a fresh sub-mailbox first to `log-in/password`; if the password attempt returns `Incorrect email address or password`, click visible `注册` / `Sign up` and continue on `create-account`. Navigation after code/password submit can close the inspected CDP target; reattach and inspect before retrying. + +`mail-cli` default profile may still point to `chickliu@claw.163.com`; it will not show mail delivered to `chickliu.@claw.163.com`. For sub-mailbox code polling, create a temp config/profile for that UID before `mail list`/`read body`; see `clawemail-operations` reference `references/submailbox-profile-polling-openai-code-2026-05-10.md`. + +Expected fields observed in 2026-05-11 run: + +```text +input[name=name] type=text required +input[name=age] type=number required +input[name=birthday] type=hidden +``` + +Set name and a valid numeric age explicitly with the native input value setter and dispatch `input`/`change`; optionally set hidden birthday consistently. Do not fill every input with the same generic text. + If OpenAI returns `account_deactivated`, stop that account immediately and do not buy 5sim numbers. -If login recovery hits `Incorrect email address or password`, `invalid_state`, stale email verification, or tab ambiguity, stop before buying phone numbers; recover account/page first. +If login recovery hits `Incorrect email address or password`, `invalid_state`, stale email verification, or tab ambiguity, stop before buying phone numbers; recover account/page first. For existing accounts with password failure, the login-page `使用一次性验证码登录` path may send an email code, but `mail-cli` output is not guaranteed newest-first: parse/sort the `date` field and submit the newest OpenAI login mail. Do not use passwordless signup (`使用一次性验证码注册`) for an existing account; it can return `passwordless_signup_disabled`. If a fresh email-code submission returns `account_deactivated`, stop the account immediately even if phone verification previously succeeded. ## 7. 5sim SMS phone flow -Use 5sim **SMS activation** for product/service `openai`; do not use WhatsApp receive. +If login recovery hits `Incorrect email address or password`, `invalid_state`, stale email verification, or tab ambiguity, stop before buying phone numbers; recover account/page first. For existing accounts with password failure, the login-page `使用一次性验证码登录` path may send an email code, but `mail-cli` output is not guaranteed newest-first: parse/sort the `date` field and submit the newest OpenAI login mail. Do not use passwordless signup (`使用一次性验证码注册`) for an existing account; it can return `passwordless_signup_disabled`. If a fresh email-code submission returns `account_deactivated`, stop the account immediately even if phone verification previously succeeded. -Env/API: +## 6A. 2925 mailbox provider option + +Use this when ClawEmail accounts appear unstable and BOSS wants 2925 instead. The original extension treats 2925 as a class-level mail provider with two modes: + +```text +mail2925Mode=provide # preferred for replacing ClawEmail: generate 2925 aliases for OpenAI signup +mail2925Mode=receive # registration email comes from elsewhere; 2925 only receives/polls mail +``` + +For `provide`, generate registration emails as `baseLocal + randomSuffix(6) + @2925.com`; **do not** use Gmail-style `+tag`. Example: `abc@2925.com -> abcxk39qz@2925.com`. + +Recommended gates for Hermes runners: + +1. Use an account pool; format can mirror original extension imports: `email@2925.com----password`. +2. Select an enabled, non-cooldown account with the oldest `lastUsedAt`; tie-break by email. +3. Open `https://2925.com/#/mailList` or login at `https://2925.com/login/`; on force relogin clear cookies for `2925.com`, `www.2925.com`, and `mail2.xiyouji.com`. +4. Verify the displayed mailbox email equals the selected 2925 account before polling. If mismatched, relogin to the selected account; do not poll the wrong mailbox. +5. Before requesting an OpenAI code, pre-clear or explicitly ignore stale OpenAI messages in 2925 Inbox/Junk/Deleted where possible. +6. Poll both `收件箱` and `垃圾箱` for signup/login codes; OpenAI/ChatGPT mail may be classified as junk. Open candidate mail, extract a strict 6-digit code, delete after read, return to mailbox. When checking 2925 for fresh mail, explicitly click the page's visible `刷新` button (and log `clicked=true`) before concluding no mail arrived; URL route reload / `Page.navigate` alone is not enough evidence for BOSS. + - BOSS-corrected resend cadence: refresh 2925 `收件箱` + `垃圾箱` every ~15 seconds; after 3 consecutive no-code refresh rounds for the current alias, click OpenAI `重新发送电子邮件` once, then resume the same refresh cadence. If BOSS asks for “10 resends”, this means **10 blocks of `3 mailbox refresh rounds -> 1 OpenAI resend`**, not “resend first then poll” and not “resend then refresh once”. Use `scripts/mail2925_strict_resend_cadence.py` for this exact JSONL runner. Log each resend click. Do not wait indefinitely without resend, and do not buy SMS before this email gate passes. +7. Maintain `seenCodes` and `rejectedCodes`; if OpenAI rejects a code, request/resend and keep polling instead of reusing old mail. +8. Candidate mail/code must match the current generated alias/local-part (for example `chickliu2026rigm1r`) or an unambiguous current-run timestamp/window. Do **not** submit visible codes from older aliases in `垃圾箱` or `已删除`. +9. If BOSS asks to confirm the 2925 login visually, activate the actual Chrome target/window first and send both a CDP page screenshot and a macOS visible screenshot. A macOS `screencapture` without activating the 2925 Chrome target can capture the wrong app/window even when DOM reads prove 2925 is logged in. +10. When switching 2925 accounts, close stale OpenAI/auth tabs and force a clean 2925 login: clear cookies for `2925.com`/`www.2925.com`/`mail2.xiyouji.com` or start a fresh Chrome profile. Verify top-account again before polling. A live run stayed logged into the previous 2925 account until a fresh profile was used. +11. Detect `子邮箱已达上限` / `已达上限邮箱` / `子邮箱上限` / `邮箱已达上限`; mark the current 2925 account cooldown for 24h and switch accounts. If no account pool/next account exists, stop the flow. +11. Live-test caveat 2026-05-11: `chickliu2025...` and `chickliu2026...` OpenAI signup messages were not seen in Inbox; older OpenAI messages existed in Junk/Deleted, and submitting one stale visible code returned `代码不正确`. Stop before SMS purchase if no current-alias code is found. +12. 2026-05-11 strict resend test correction: if automation reports no 2925 OpenAI mail after `3 refresh + 1 resend` loops, first audit the scraper. A live run with `chickliu2030...`/`chickliu2031...` proved messages were present in Inbox but missed by route-based scraping. Correct method: click the left `收件箱` nav item (do not rely on `#/mailList?type=收件箱`), click toolbar `刷新`, read `table.maillist-table tr`, inspect hidden tooltip/title/textContent for the generated alias in OpenAI bounce addresses such as `...-chickliu2031som6d8=2925.com@tm1.openai.com`, then open the newest matching row and extract the 6-digit code near `输入此临时验证码以继续:`. If OpenAI rejects an old code, resend once and select the newest row. Reference: `references/mail2925-correct-inbox-scraping-openai-code-2026-05-11.md`. +13. Session details: `references/mail2925-explicit-refresh-and-visible-confirmation-2026-05-11.md`, `references/mail2925-resend-cadence-and-browser-hygiene-2026-05-11.md`. + +- For 2925 alias polling, use `scripts/mail2925_openai_code_scraper.py` for OpenAI/ChatGPT verification codes. It implements the corrected Webmail flow: click left `收件箱`, click toolbar `刷新`, read `table.maillist-table tr`, inspect hidden tooltip/bounce text for the alias local-part, open the newest matching row, and write the raw 6-digit code only to the requested output file. Use `scripts/mail2925_poll_alias_code.py` only when its implementation is confirmed to follow the same DOM rules; older list/route-based polling can produce false negatives. + +Reference: `references/2925-mail-provider-original-extension-rules-2026-05-11.md` and `references/mail2925-account-pool-integration-2026-05-11.md`. + +BOSS-provided pool is stored locally at `~/.hermes/secrets/mail2925_accounts.txt` with mode `0600` (20 accounts, `chickliu2021@2925.com` through `chickliu2040@2925.com`). Never print or commit raw passwords. Use `scripts/mail2925_pool.py` for account rotation and alias generation. Hard invariant: generated alias local-part must start with the selected account local-part, and the displayed 2925 mailbox must equal the selected account before polling code. If mismatch, clear 2925 cookies and force-login the selected account; never read code from another account's mailbox. + +## 7. 5sim SMS phone flow + +Use SMS activation provider selected by `PHONE_SMS_PROVIDER`: + +```text +5sim (default): PHONE_SMS_PROVIDER=5sim +Luban SMS: PHONE_SMS_PROVIDER=luban +``` + +### Luban SMS provider + +Docs: `https://lubansms.com/api_docs/`. All endpoints are GET under: + +```text +https://lubansms.com/v2/api +``` + +Env: + +```text +LUBAN_SMS_API_KEY=[REDACTED] +LUBAN_SMS_API_BASE=https://lubansms.com/v2/api +LUBAN_SMS_SERVICE_QUERY=OpenAI +LUBAN_SMS_LANGUAGE=en +``` + +Important API quirk: requests without a browser-like `User-Agent` may return HTTP 403. Use a Chrome UA. + +Core endpoints: + +```text +BALANCE: /getBalance?apikey=... +SERVICES: /List?apikey=...&country=&service=OpenAI&language=en&page=1 +BUY: /getNumber?apikey=...&service_id= +CHECK: /getSms?apikey=...&request_id= +CANCEL: /setStatus?apikey=...&request_id=&status=reject +HISTORY: /smsHistory?apikey=...&language=en&page=1 +``` + +Observed OpenAI service lookup returns entries such as `OpenAI / ChatGpt` and `OpenAI`; cheapest current match can be resolved with: + +```bash +/Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/luban_sms.py \ + resolve --service OpenAI --language en --pages 5 +``` + +Verified 2026-05-11 with BOSS key: balance API OK, balance `2.000`; service list OK. Do not print the key. The helper script is at `scripts/luban_sms.py`. + +Integration/verification policy: + +- First validate Luban with `balance` and `resolve` only; do not buy a number while merely adding the provider. +- Luban `/List` output is not a fixed country matrix; resolve `service_id` dynamically from `service=OpenAI`/`OpenAI / ChatGpt`, then pick the cheapest usable match unless BOSS asks for a specific country/provider. +- Treat Luban `request_id` as the activation id. Poll `/getSms`; `msg=wait` means keep polling, `msg=success` plus `sms_code` means submit a strict 6-digit code, and `wrong_status` means the request expired/closed. +- Release failed/rejected Luban numbers with `/setStatus?...&status=reject`; Luban has no 5sim-style `finish`, so a successful code submission is a local no-op finish in the runner. +- Store the key only in `~/.hermes/env/codex_oauth_onboarding.env` as `LUBAN_SMS_API_KEY`; never echo it in command output, logs, or chat. +- Details: `references/luban-sms-provider-integration-2026-05-11.md`. +- Session recap for 2925 account-pool, Luban UA 403, and empty-response guardrail: `references/session-learnings-2925-luban-reporting-2026-05-11.md`. +- Provider abstraction lessons: `references/sms-provider-abstraction-and-luban-2026-05-11.md`. + +If using the long-running runner: + +```bash +PHONE_SMS_PROVIDER=luban START_USED_NUMBERS= MAX_NEW_NUMBERS= CDP_PORT= \ + /Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/phone_verify_until_success.py +``` + +5sim legacy API: ```text PHONE_SMS_PROVIDER=5sim @@ -169,15 +288,45 @@ romania -> RO -> 罗马尼亚 (+40) -> 40 Phone handling: ```text -OpenAI immediate reject → cancel activation, rotate +OpenAI immediate reject (`无法向此电话号码发送验证码`) or invalid-phone (`电话号码无效`) → cancel activation, rotate No immediate reject → poll 5sim Before resend → probe channel text/value/aria/title ├─ WhatsApp → cancel/rotate (SMS-only) └─ SMS/unknown → click resend, poll again -SMS received → submit code, finish activation, verify page leaves phone-verification +SMS received → prefer a 6-digit code candidate for OpenAI phone verification; submit code, verify page leaves phone-verification, then finish activation Interrupted runner → inspect/cancel active RECEIVED activations immediately ``` +If a broad SMS regex finds 4-8 digit candidates, prefer `(?` for CPA OAuth because it can produce OpenAI `unknown_error`, and use same-tab `Page.navigate` instead. If phone is already verified but CPA OAuth still fails with `Workspaces not found in client auth session`, `unknown_error`, `Incorrect email address or password`, `invalid_state`, or `passwordless_signup_disabled`, treat it as an Auth/session/credential problem, not an SMS/proxy problem; do not keep buying phone numbers for the same account. + +Multi-country / free-country runner stability: + +- Count only real 5sim activations with ids toward requested totals. `no free phones`, country-gate failures, page recovery failures, and CDP errors do not count. +- Log compact JSONL phases: `activation_bought`, `phone_submitted`, `activation_cancel_rejected`, `sms_timeout_cancel`, `sms_received`, and final summary. This makes it possible to top up to an exact activation count after page interruptions. +- If OpenAI reaches `phone-verification` but page text says the code was sent by **WhatsApp**, treat it as SMS-only failure: poll 5sim briefly if already purchased, cancel on zero SMS, then explicitly navigate back to `https://auth.openai.com/add-phone` before continuing; otherwise later country gates fail because the page remains on `phone-verification`. +- For OpenAI SMS/email verification code extraction, prefer strict 6-digit extraction (`(? MAX_NEW_NUMBERS=300 CDP_PORT= \ + /Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/phone_verify_until_success.py \ + | tee /tmp/phone_verify_until_success.log +``` + +The script emits `new_numbers` and `total_numbers`, persists `/tmp/phone_verify_until_success_state.json`, and keeps `/tmp/current_fivesim_activation_id.txt` updated for interruption cleanup. `MAX_NEW_NUMBERS=0` means unlimited; otherwise keep a safety cap unless BOSS explicitly approves unlimited spend. See `references/until-success-phone-runner-cumulative-accounting-2026-05-11.md`. + Known result: free-country run succeeded with Vietnam SMS, activation `1006871765`, status `FINISHED`; OpenAI phone verification passed. See `references/openai-phone-free-country-vietnam-sms-success-codex2api-region-block-2026-05-10.md`. ## 8. CPA / CLI Proxy API — preferred callback target @@ -207,7 +356,7 @@ GET /v0/management/codex-auth-url # UI also uses: GET /v0/management/codex-auth-url?is_webui=true ``` -Extract URL from `url|auth_url|authUrl|data.*`; extract state from payload or URL. +Extract URL from `url|auth_url|authUrl|data.*`; extract state from payload or URL. In Chrome 147/CDP, opening this URL via `/json/new?...` can route to OpenAI `unknown_error`; prefer navigating the current intended auth tab with `Page.navigate` when retrying fresh CPA URLs. Submit callback: @@ -223,6 +372,13 @@ GET /v0/management/get-auth-status?state= -> {"status":"ok"} GET /v0/management/auth-files -> codex--free.json active ``` +OAuth recovery pitfalls after phone success: + +- Do not keep buying phone numbers once OpenAI has left `phone-verification`; report phone success separately from OAuth/CPA import status. +- If Codex consent shows `Workspaces not found in client auth session`, the account/session lacks a usable ChatGPT workspace. Establish a valid ChatGPT web session/workspace first, then generate a **new** CPA OAuth URL; repeatedly clicking retry on the same consent error does not fix it. +- If one-time-code login is needed after password login fails, request it from a fresh auth page/state. A stale password page may make `使用一次性验证码登录` return `invalid_state`; polling ClawEmail may still find a code, but submitting it on the stale error page will not recover. +- If many fresh CPA URLs start returning OpenAI `/error` `unknown_error` despite normal URL parameters, close auth tabs/restart the automation Chrome and consider pausing/restarting CPA before generating another URL. + Do **not** use root `/codex-auth-url` or `/api/auth/url`; they can 404 or return unrelated `amp upstream proxy not available`. References: @@ -266,7 +422,9 @@ Before fresh OAuth recovery: ```text close stale auth.openai.com / phone-verification / localhost callback tabs +close or kill obsolete Chrome/CDP processes from prior attempts (especially old OpenAI 9228 tabs) when switching mailbox/account, so CDP target selection cannot land on stale auth state keep only the current intended target tab +prefer same-tab Page.navigate for CPA OAuth URLs; avoid Chrome /json/new? when OpenAI starts returning unknown_error ``` Use visible Chrome with isolated profile. Chrome stable may ignore `--load-extension`; verify extension card/sidepanel if using the extension. Manual CDP flow is acceptable when BOSS only wants registration/OAuth tested; still follow all gates. @@ -325,5 +483,11 @@ references/openai-phone-country-scheduler-cyclic-chunks-2026-05-10.md references/clawemail-external-receive-gate-openai-2026-05-10.md references/openai-add-phone-chile-no-free-phones-2026-05-10.md references/openai-add-phone-eu4-total20-real-activations-2026-05-10.md +references/openai-vietnam-phone-rejects-proxy1085-1083-2026-05-10.md +references/free-country-openai-phone-20-activation-failure-2026-05-11.md +references/openai-phone-free-country-continuation-1083-2026-05-11.md +references/openai-email-code-login-account-deactivated-2026-05-11.md +references/official-extension-phone-update-2026-05-10.mdenai-email-code-login-account-deactivated-2026-05-11.md references/official-extension-phone-update-2026-05-10.md ``` +``` diff --git a/skill/codex-oauth-plus-onboarding/references/2925-mail-provider-original-extension-rules-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/2925-mail-provider-original-extension-rules-2026-05-11.md new file mode 100644 index 0000000..acb64d3 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/2925-mail-provider-original-extension-rules-2026-05-11.md @@ -0,0 +1,195 @@ +# 2925 mail provider rules from original extension — 2026-05-11 + +Source inspected: original extension repo `codex-oauth-automation-extension` (`mail2925-utils.js`, `managed-alias-utils.js`, `background/generated-email-helpers.js`, `background/mail-2925-session.js`, `background/verification-flow.js`, `content/mail-2925.js`, related tests). + +## Provider modes + +`mailProvider=2925` has two distinct modes: + +- `mail2925Mode=provide` (default): 2925 provides the registration email via a managed alias. +- `mail2925Mode=receive`: registration email comes from another generator/manual path; 2925 is only the mailbox used to receive/poll mail. + +For replacing ClawEmail in OpenAI/Codex signup, prefer `provide` first. + +## Alias generation + +2925 does **not** use Gmail-style plus tags. + +```text +base: yourname@2925.com +alias: yourname + randomSuffix(6) + @2925.com +``` + +Examples: + +```text +abc@2925.com -> abcxk39qz@2925.com +``` + +Provider ownership check: + +```text +domain == 2925.com +candidate local == base local OR candidate local startsWith(base local) +``` + +## Account pool + +Import format: + +```text +邮箱----密码 +user1@2925.com----[REDACTED_PASSWORD_1] +user2@2925.com----[REDACTED_PASSWORD_2] +``` + +Normalized account fields: + +```json +{ + "id": "...", + "email": "xxx@2925.com", + "password": "...", + "enabled": true, + "lastUsedAt": 0, + "lastLoginAt": 0, + "lastLimitAt": 0, + "disabledUntil": 0, + "lastError": "" +} +``` + +Available account gate: + +```text +email present +password present +enabled != false +not cooling down +``` + +Selection policy: choose the available account with the oldest `lastUsedAt`; tie-break by email. + +## Login/session gates + +URLs/domains: + +```text +mail list: https://2925.com/#/mailList +login: https://2925.com/login/ +cookie domains: 2925.com, www.2925.com, mail2.xiyouji.com +cookie origins: https://2925.com, https://www.2925.com, https://mail2.xiyouji.com +``` + +Force relogin flow: + +1. Clear 2925 cookies on the domains/origins above. +2. Wait ~3 seconds. +3. Open login URL. +4. Wait ~3 seconds before checking fields. +5. Ensure agreement/remember checkboxes if present. +6. Fill email, wait ~150ms. +7. Fill password, wait ~200ms + 1000ms. +8. Click login. +9. Wait up to 40 seconds for mailbox view. + +Critical gate: the displayed mailbox email at the top of the 2925 page must match the selected account. If mismatched and account pool is enabled, clear/relogin to the target account. If pool is disabled, stop rather than polling the wrong mailbox. + +## Limit/cooldown handling + +Detect page/error text matching: + +```text +子邮箱已达上限 +已达上限邮箱 +子邮箱上限 +邮箱已达上限 +``` + +When detected: + +- Prefix/internal class: `MAIL2925_LIMIT_REACHED::...` +- If account pool disabled: stop the flow. +- If current account is known: set `lastLimitAt=now`, `disabledUntil=now+24h`, `lastError=reason`. +- Pick the next available account excluding the current id. +- Force relogin to the next account. +- End the current attempt/thread; next retry should start fresh. + +Cooldown constant: `24 * 60 * 60 * 1000`. + +## Verification polling + +For 2925, both signup and login verification use: + +```text +maxAttempts = 15 +intervalMs = 15000 +``` + +Total mailbox wait window is about 225 seconds per poll round. + +Signup filters: + +```text +sender: openai, noreply, verify, auth, duckduckgo, forward +subject: verify, verification, code, 验证码, confirm +``` + +Login filters: + +```text +sender: openai, noreply, verify, auth, chatgpt, duckduckgo, forward +subject: verify, verification, code, 验证码, confirm, login +``` + +`receive` mode enables weak target-email matching: if the preview/body contains an explicit target email, it must match the current registration/login email. `provide` mode does not require this because the generated registration email belongs to the current 2925 mailbox. + +## Anti-stale-code rules + +Prefer this sequence over ClawEmail-style newest-list polling: + +1. Before sending/requesting a fresh OpenAI code, pre-clear the 2925 inbox when no reliable `filterAfterTimestamp` is available. +2. Request/resend the OpenAI code. +3. Poll inbox with refresh cycles. +4. For each candidate mail: open it, read preview/body, extract 6-digit code, delete the mail, return to inbox. +5. Maintain `seenCodes` and `rejectedCodes` to avoid resubmitting old/invalid codes. +6. If OpenAI marks a code invalid, add it to `rejectedCodes`, request a new code if allowed, and continue polling. +7. After a successful verification step, trigger best-effort delete-all cleanup for 2925. + +Important: 2925 page list ordering should not be trusted as the only freshness signal. The original extension relies heavily on pre-clear + delete-after-read + seen/rejected-code sets. + +## Recommended Hermes runner design + +For a 2925-backed Codex OAuth runner, add env/config like: + +```text +MAIL_PROVIDER=2925 +MAIL2925_MODE=provide +MAIL2925_ACCOUNTS_FILE=/path/to/2925-accounts.jsonl-or-txt +MAIL2925_USE_ACCOUNT_POOL=true +``` + +Then implement gates: + +```text +select available 2925 account +login/ensure session +verify displayed mailbox email == selected account email +pre-clear inbox +generate alias: baseLocal + 6 random lowercase/digits + @2925.com +submit alias to OpenAI +poll 2925, delete after read, strict 6-digit extraction +submit code +on invalid code: rejectedCodes + resend + repoll +on limit text: disable account 24h and switch account; do not continue current OpenAI attempt +``` + +## Why this is better than ClawEmail for this class + +- No sub-mailbox creation step. +- No external receive permission/Dashboard API gate. +- No `mail-cli` profile ambiguity. +- Delete-after-read and pre-clear reduce old-code pollution. +- Account pool/cooldown explicitly handles 2925 per-account limits. + +Risk: single 2925 account can hit sub-mailbox limits, so account pool support is not optional for high-volume runs. diff --git a/skill/codex-oauth-plus-onboarding/references/free-country-openai-phone-20-activation-failure-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/free-country-openai-phone-20-activation-failure-2026-05-11.md new file mode 100644 index 0000000..c2e0546 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/free-country-openai-phone-20-activation-failure-2026-05-11.md @@ -0,0 +1,66 @@ +# Free-country OpenAI phone run: 20 actual activations, no SMS success (2026-05-11) + +Context: new OpenAI account `chickliu.efxudmrg@claw.163.com` had completed ClawEmail email verification and was stuck on `auth.openai.com/add-phone`. CPA OAuth URL had been generated; callback import was pending phone verification. Browser profile was reused while switching proxy from 1085 to 1083. + +## Proxy switch learning + +- Relaunching the same OAuth Chrome user-data-dir with `--proxy-server=socks5://192.168.2.8:1083` and a new CDP port (`9226`) preserved the OpenAI session and returned to `add-phone`. +- The country selector reset to US after reload/relaunch, so the strict country/dial-code gate must be rerun before buying a number. + +## Vietnam-only attempts + +- 1085 + Vietnam: 12 real 5sim `openai` activations, all immediate OpenAI reject/invalid, all canceled. +- 1083 + Vietnam: 5 more real Vietnam activations, all immediate reject/invalid, all canceled. + +## Free-country 20-activation run + +Goal: unrestricted countries, count **actual bought activation ids** only. `no free phones`, country-gate failures, and recovery failures do not count. + +Summary across main run + top-ups: + +```json +{ + "actual_activations": 20, + "submitted": 20, + "direct_reject_cancelled": 14, + "sms_timeout_cancelled": 6, + "sms_received": 0, + "by_country": { + "argentina": 4, + "netherlands": 2, + "indonesia": 2, + "italy": 2, + "poland": 2, + "spain": 2, + "england": 2, + "germany": 3, + "vietnam": 1 + } +} +``` + +Important events: + +- Germany and Argentina sometimes reached `phone-verification`, but the page text said the code was sent via **WhatsApp**. 5sim SMS polling returned zero messages, so orders were canceled under SMS-only policy. +- Germany WhatsApp-only verification left the page on `phone-verification`; subsequent country gates failed until the browser was explicitly navigated back to `https://auth.openai.com/add-phone`. +- After many attempts/cancellations, OpenAI sometimes showed `糟糕,出错了! Bad request`. A direct navigation/reload to `https://auth.openai.com/add-phone` restored the phone form; verify phone-country controls before buying more numbers. +- Some later countries returned `no free phones`; they were excluded from activation counts. + +Last checked activation was canceled: + +```text +activation_id=1006963727 +status=CANCELED +sms_count=0 +``` + +## Runner implications + +Future free-country runners should: + +1. Emit JSONL logs and summary, not verbose HTML. +2. Count only successful 5sim purchases (`activation_id`). +3. Cancel immediately on OpenAI direct reject/invalid. +4. If `phone-verification` mentions WhatsApp, do not click resend; poll briefly if already bought, cancel, then navigate back to `add-phone`. +5. After every `sms_timeout_cancel`, navigate back to `add-phone` and verify controls; otherwise top-up runs can stall on country gate failures. +6. For exact total budgets, support top-up mode from previous log counts rather than restarting all countries. diff --git a/skill/codex-oauth-plus-onboarding/references/luban-sms-provider-integration-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/luban-sms-provider-integration-2026-05-11.md new file mode 100644 index 0000000..a3f04d9 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/luban-sms-provider-integration-2026-05-11.md @@ -0,0 +1,102 @@ +# Luban SMS provider integration notes — 2026-05-11 + +Source docs: https://lubansms.com/api_docs/ + +## API shape + +All endpoints are GET under: + +```text +https://lubansms.com/v2/api +``` + +Every request uses `apikey=` query parameter. Do not log it. + +Important quirk found during integration: plain Python urllib requests without a browser-like User-Agent returned HTTP 403. Adding a Chrome-like UA made both `https` and `http` balance requests return HTTP 200. + +## Core endpoints + +```text +GET /getBalance?apikey=... +GET /countries?apikey=... +GET /List?apikey=...&country=&service=&language=en&page=1 +GET /getNumber?apikey=...&service_id= +GET /getSms?apikey=...&request_id= +GET /setStatus?apikey=...&request_id=&status=reject +GET /getAgainNmuber?apikey=...&request_id= +GET /smsHistory?apikey=...&language=en&page=1 +``` + +## Response mapping + +Buy success: + +```json +{"code":0,"msg":"","number":"79781901206","request_id":230698} +``` + +Poll waiting: + +```json +{"code":0,"msg":"wait","sms_msg":{"request_id":"244936588","application_id":133,"country_id":1,"number":"79587588703"}} +``` + +Poll success: + +```json +{"code":0,"msg":"success","sms_code":"380682"} +``` + +Cancel/release: + +```json +{"code":0,"msg":"success"} +``` + +## Verified with BOSS key + +Do not disclose the key. It was stored in `~/.hermes/env/codex_oauth_onboarding.env` as `LUBAN_SMS_API_KEY`. + +Verification output after adding Chrome UA: + +```json +{"http":200,"code":0,"ok":true,"balance":"2.000","msg":""} +``` + +OpenAI service search returned usable entries, including: + +```json +{"service_id":"537935","country_en":"United Kingdom/England","country_zh":"英国/英格兰","service_name":"OpenAI","provider":"acsim","cost":0.05} +``` + +## Local helper + +```bash +/Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/luban_sms.py balance + +/Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/luban_sms.py services --service OpenAI --language en --limit 20 + +/Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/luban_sms.py resolve --service OpenAI --language en --pages 5 +``` + +## Runner integration + +`phone_verify_until_success.py` now supports: + +```bash +PHONE_SMS_PROVIDER=luban +``` + +Provider abstraction: + +```text +sms_buy(country) -> Luban getNumber(service_id resolved from /List) +sms_check(id) -> Luban getSms(request_id) +sms_cancel(id) -> Luban setStatus(... status=reject) +sms_finish(id) -> no-op success for Luban +``` + +Country names map from the existing 5sim country slugs to Luban English country names when available. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-account-pool-integration-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-account-pool-integration-2026-05-11.md new file mode 100644 index 0000000..58e54bd --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-account-pool-integration-2026-05-11.md @@ -0,0 +1,154 @@ +# 2925 account pool integration notes — 2026-05-11 + +Decision: abandon POP3/SMTP/IMAP forwarding to ClawEmail. Use the original extension's 2925 webmail/account-pool model directly. + +## Account pool + +Secret file: + +```text +~/.hermes/secrets/mail2925_accounts.txt +``` + +Format: + +```text +邮箱----密码 +user@2925.com----[REDACTED_PASSWORD] +``` + +BOSS provided 20 accounts (`chickliu2021@2925.com` through `chickliu2040@2925.com`). Passwords are stored only in the local secret file with mode `0600`; never print or commit raw passwords. + +State file: + +```text +~/.hermes/state/mail2925_accounts_state.json +``` + +State fields per account: + +```json +{ + "lastUsedAt": 0, + "lastLoginAt": 0, + "lastLimitAt": 0, + "disabledUntil": 0, + "lastError": "" +} +``` + +## Hard invariant: alias/account binding + +A generated 2925 registration address must be derived from the same 2925 account used to read mail. + +Example: + +```text +selected account: chickliu2021@2925.com +allowed alias: chickliu2021rl9sxh@2925.com +mail reader: logged in as chickliu2021@2925.com +``` + +Forbidden: + +```text +alias from chickliu2021*, but mailbox logged in as chickliu2022@2925.com +``` + +Gate before reading any code: + +```text +displayedMailboxEmail == selectedAccount.email +alias.localPart startsWith selectedAccount.localPart +``` + +If the displayed mailbox email differs, clear 2925 cookies and force-login the selected account before continuing. + +## Alias generation + +Use original extension rule, not Gmail-style plus tags: + +```text +baseLocal + randomSuffix(6) + @2925.com +``` + +Example: + +```text +chickliu2021@2925.com -> chickliu2021a8k2mz@2925.com +``` + +Do not generate `chickliu2021+tag@2925.com`. + +## Account selection + +Pick among accounts that are: + +```text +enabled != false +have password +disabledUntil <= now +``` + +Sort by: + +```text +lastUsedAt ascending, then email ascending +``` + +This rotates accounts and avoids overusing one mailbox. + +## Limit handling + +Detect 2925 limit messages: + +```text +子邮箱已达上限 +已达上限邮箱 +子邮箱上限 +邮箱已达上限 +``` + +On hit: + +```text +currentAccount.disabledUntil = now + 24h +currentAccount.lastLimitAt = now +currentAccount.lastError = reason +switch to next available account +terminate current OpenAI attempt and restart next round with a fresh alias +``` + +Do not continue the same OpenAI page after switching 2925 account; it risks mixing alias/account state. + +## Local helper + +```bash +python3 scripts/mail2925_pool.py selftest +python3 scripts/mail2925_pool.py list +python3 scripts/mail2925_pool.py pick --dry-run # preview without changing rotation state +python3 scripts/mail2925_pool.py pick # allocate and update lastUsedAt +python3 scripts/mail2925_pool.py mark-limit chickliu2021@2925.com --reason '子邮箱已达上限邮箱' +python3 scripts/mail2925_pool.py mark-login chickliu2021@2925.com +``` + +`pick` prints only email + generated alias; never prints passwords. + +Verified locally on 2026-05-11: + +```json +{"accounts":20,"aliasFailures":[],"secretMode":"0o600","ok":true} +``` + +## Webmail flow to implement/use + +1. Pick account and generate alias in one operation. +2. Open/login `https://2925.com/login/` or `https://2925.com/#/mailList`. +3. Verify displayed mailbox email equals picked account email. +4. Clear inbox before triggering OpenAI code. +5. Submit generated alias to OpenAI. +6. Poll 2925 inbox: 15 attempts, 15s interval. +7. Match OpenAI/auth/verify/code/login sender/subject/body; extract 6-digit code. +8. Open matching mail, extract body code, delete mail, return inbox. +9. Submit code; if invalid, add to rejectedCodes, resend, and continue polling. +10. On 2925 limit, mark current account cooldown and restart with next account. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-alias-code-polling-lessons-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-alias-code-polling-lessons-2026-05-11.md new file mode 100644 index 0000000..02dec25 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-alias-code-polling-lessons-2026-05-11.md @@ -0,0 +1,33 @@ +# 2925 OpenAI alias-code polling lessons — 2026-05-11 + +Session outcome: OpenAI email verification did not pass, so SMS purchase was intentionally skipped. + +Reusable lessons: + +1. 2925 account-pool binding worked: generated alias local-part started with the selected account local-part, and the visible Webmail top account matched the selected account before polling. +2. OpenAI/ChatGPT verification mail can be classified into 2925 `垃圾箱` instead of `收件箱`. +3. `垃圾箱` and `已删除` can contain many stale OpenAI codes for older aliases. A visible 6-digit code is not enough. +4. A stale code from `垃圾箱` was submitted and OpenAI returned `代码不正确`. +5. After clicking `重新发送电子邮件`, no new message matching the current alias/local-part appeared within the tested polling window. +6. Therefore, future runs must stop before Luban/5sim phone purchase if current-alias email verification cannot be proven. + +Operational rule: + +- Candidate mail must contain the current generated alias local-part (e.g. `chickliu2026rigm1r`) or otherwise be bound to the current run by an unambiguous timestamp/window. +- Use `scripts/mail2925_poll_alias_code.py` rather than grabbing the first 6-digit code from the page. +- Poll at least `收件箱` and `垃圾箱`; treat `已删除` as diagnostic only unless current-alias matching is strict. +- Maintain `rejectedCodes` after OpenAI returns invalid code and pass them as `--reject-code` to the poller. + +Example: + +```bash +python3 scripts/mail2925_poll_alias_code.py \ + --cdp-port 9227 \ + --account chickliu2026@2925.com \ + --alias chickliu2026rigm1r@2925.com \ + --folders inbox,junk \ + --reject-code '[REDACTED]' \ + --out-file /tmp/openai_2925_email_code.txt +``` + +Do not print the saved code. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-correct-inbox-scraping-openai-code-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-correct-inbox-scraping-openai-code-2026-05-11.md new file mode 100644 index 0000000..440a253 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-correct-inbox-scraping-openai-code-2026-05-11.md @@ -0,0 +1,73 @@ +# 2925 Webmail correct scraping flow for OpenAI codes (2026-05-11) + +## Problem found + +BOSS manually checked `chickliu2030@2925.com` / `chickliu2031@2925.com` and saw OpenAI verification messages in Inbox, while the automation reported `candidateCount=0` and no current alias mail. + +Root cause: the previous automation used an unreliable route-based folder switch: + +```text +https://2925.com/#/mailList?type=收件箱 +https://2925.com/#/mailList?type=垃圾箱 +``` + +On 2925 Webmail this can leave the app in an empty-list state even when Inbox has messages. The correct UI state is reached by clicking the left navigation item `收件箱`; the URL then becomes: + +```text +https://2925.com/#/mailList +``` + +## Correct scraping flow + +1. Verify visible account gate: top profile text contains the selected pool account, e.g. `chickliu2031@2925.com`. +2. Click the left nav item whose exact text is `收件箱`. Do not rely on `#/mailList?type=收件箱`. +3. Click the actual toolbar `刷新` button after the list is visible. +4. Read mail rows from: + +```js +document.querySelectorAll('table.maillist-table tr') +``` + +Rows can have class `unread-mail`. + +5. Match OpenAI/ChatGPT rows by visible row text: + +```text +OpenAI / ChatGPT / 你的 ChatGPT 临时验证码 / verification / code +``` + +6. The generated alias/local-part may not be visible in row text. For 2925 OpenAI mail it was found in hidden tooltip/sender DOM, for example: + +```text +bounce+...-chickliu2031som6d8=2925.com@tm1.openai.com +``` + +So row matching must inspect `innerText`, `textContent`, `title`, `aria-label`, and tooltip descendants, not only visible row text. + +7. Click the newest row whose hidden row text contains the current alias local-part. Then extract the strict 6-digit code from the opened mail body near: + +```text +输入此临时验证码以继续: +[6-digit-code] +``` + +8. If OpenAI returns `代码不正确`, click OpenAI resend once, wait briefly, click 2925 left `收件箱` again, refresh, then select the newest row. Old rows remain visible and can produce stale codes. + +## Verified result + +For: + +```text +2925 account: chickliu2031@2925.com +OpenAI alias: chickliu2031som6d8@2925.com +``` + +The corrected scraper found rows in Inbox and extracted a new verification code from the latest message at `2026-5-11 21:16:37`. Submitting that new code succeeded and OpenAI advanced to: + +```text +https://auth.openai.com/add-phone +``` + +## Do not repeat + +Do not conclude “no mail delivered” from `candidateCount=0` if the script only used URL route parameters or did not inspect `table.maillist-table tr`. Confirm by clicking the left Inbox item and checking the table rows. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-explicit-refresh-and-visible-confirmation-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-explicit-refresh-and-visible-confirmation-2026-05-11.md new file mode 100644 index 0000000..f06de99 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-explicit-refresh-and-visible-confirmation-2026-05-11.md @@ -0,0 +1,34 @@ +# 2925 explicit refresh and visual confirmation lessons — 2026-05-11 + +## Context + +During a live OpenAI/Codex signup test using a 2925 account-pool alias, BOSS challenged two assumptions: + +1. Whether the visible screenshot really showed the 2925 mailbox page. +2. Whether checking the mailbox via route reload / DOM polling counted as refreshing for latest mail. + +## Confirmed behavior + +- CDP DOM reads showed the active 2925 account was `chickliu2026@2925.com`, but an initial macOS `screencapture` captured the wrong visible app/window because the actual 2925 Chrome target was not activated first. +- Correct visual confirmation requires: + 1. Find the CDP target whose URL contains `2925.com`. + 2. Call `/json/activate/` or otherwise bring the Chrome target to front. + 3. `tell application "Google Chrome" to activate`. + 4. Capture both: + - `Page.captureScreenshot` from CDP, and + - macOS `screencapture` after activation. +- For mail arrival, BOSS does not consider `Page.navigate` route reload alone enough. Explicitly click the visible 2925 `刷新` button and log that it was clicked. + +## Live result + +For current alias `chickliu2026rigm1r@2925.com`: + +- Inbox: explicit refresh clicked 3 times; no current alias, no OpenAI mail, no code. +- Junk: explicit refresh clicked 3 times; had OpenAI/ChatGPT stale mail/code, but not the current alias. +- Therefore the correct stop condition is: do not buy Luban SMS number until email verification succeeds. + +## Future runner requirements + +- Add a `refresh_clicked=true` field to 2925 polling JSONL. +- Poll both Inbox and Junk, but only accept candidate codes from mail matching the current alias/local-part or a strict current-run timestamp/window. +- For user-visible verification in Feishu, send the CDP screenshot rather than relying only on macOS screenshot; if sending macOS screenshot, activate the correct Chrome target first. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-list-scan-false-negative-open-detail-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-list-scan-false-negative-open-detail-2026-05-11.md new file mode 100644 index 0000000..a1d83ae --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-list-scan-false-negative-open-detail-2026-05-11.md @@ -0,0 +1,39 @@ +# 2925 OpenAI code polling: list-only false negative and detail-opening fix (2026-05-11) + +## Trigger + +During the 2925 + OpenAI signup test, automated polling reported no current-alias code after strict `3 mailbox refresh rounds -> 1 OpenAI resend` cycles. BOSS later manually checked the same 2925 mailbox (`chickliu2030@2925.com`) and found that the OpenAI verification code had actually arrived. + +## Lesson + +A 2925 Webmail list-page text scan can be a **false negative**. Do not conclude non-delivery only because the list page lacks the generated alias/local-part or a visible 6-digit code. + +Likely causes: + +- The list page may not render the full recipient/alias or message body. +- The visible list may only show the base mailbox (`chickliu2030@2925.com`) or a compact row. +- Codes/alias evidence may exist only after opening the message detail pane/page. +- OpenAI mail may arrive after repeated resends even when earlier list scans show `hasLocal=false`. + +## Correct polling pattern + +For 2925 OpenAI verification mail: + +1. Verify the top/displayed mailbox account equals the selected pool account. +2. Explicitly click the visible `刷新` button in both Inbox and Junk. +3. Search for candidate rows by sender/subject keywords: `OpenAI`, `ChatGPT`, `验证码`, `verification`, `code`, and also current local-part if visible. +4. **Open candidate rows/messages before deciding no code exists.** +5. In the detail view, extract a strict 6-digit code only when the detail text or headers match the current alias/local-part, or the message is unambiguously from the current OpenAI send window. +6. If no candidate appears, continue the BOSS cadence: 3 refresh rounds then one OpenAI resend. +7. If BOSS/manual UI finds a code after automation says none, treat it as a polling bug, not a provider-delivery failure. + +## Operational correction + +The earlier `DONE_NO_CURRENT_ALIAS_AFTER_10_RESENDS` result should be interpreted narrowly: + +```text +Automation did not detect the code via list-page scanning. +It does NOT prove the 2925 alias cannot receive OpenAI mail. +``` + +Future runners should log whether they opened a message detail (`opened=true`) and should save a redacted detail snippet for debugging when candidate messages exist but no code is extracted. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-openai-3refresh-10resends-no-code-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-3refresh-10resends-no-code-2026-05-11.md new file mode 100644 index 0000000..0b8e003 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-3refresh-10resends-no-code-2026-05-11.md @@ -0,0 +1,55 @@ +# 2925 OpenAI email verification: 3-refresh + 1-resend ×10 result (2026-05-11) + +## Context + +- 2925 account: `chickliu2030@2925.com` +- OpenAI signup alias: `chickliu2030ih2jpi@2925.com` +- OpenAI browser: proxied through 1085 +- 2925 Webmail browser: direct connection, no proxy +- Rule requested by BOSS: refresh mailbox 3 times first, then click OpenAI resend once; repeat until at least 10 OpenAI resends. + +## Verified cadence + +For each cycle: + +1. Refresh 2925 Inbox (`收件箱`) using the visible refresh button. +2. Refresh 2925 Junk (`垃圾箱`) using the visible refresh button. +3. Wait about 15 seconds between refresh rounds. +4. Complete 3 refresh rounds. +5. If no current-alias code appears, click OpenAI `重新发送电子邮件` once. + +## Result + +The strict cadence completed 10 OpenAI resend clicks. + +Observed final log pattern for cycle 10: + +```json +{"phase":"one_refresh","folder":"inbox","clicked":true,"accountOk":true,"hasLocal":false,"hasOpenAI":false,"codesCount":0,"emails":["chickliu2030@2925.com"]} +{"phase":"one_refresh","folder":"junk","clicked":true,"accountOk":true,"hasLocal":false,"hasOpenAI":false,"codesCount":0,"emails":["chickliu2030@2925.com"]} +{"clicked":true,"body":"检查你的收件箱 ... chickliu2030ih2jpi@2925.com ... 重新发送电子邮件 ..."} +``` + +Final marker: + +```text +DONE_NO_CURRENT_ALIAS_AFTER_10_RESENDS +``` + +## Conclusion + +- 2925 login gate remained valid: displayed mailbox account was `chickliu2030@2925.com`. +- The OpenAI page kept targeting the correct alias: `chickliu2030ih2jpi@2925.com`. +- Inbox and Junk refresh clicks succeeded. +- After 10 OpenAI resend clicks, no mail containing the current alias/local-part appeared and no strict 6-digit current-alias code was found. +- Email gate did not pass; SMS/Luban phone purchase must not start for this account. + +## Next recommended diagnostic + +Before spending SMS balance, verify whether 2925 supports receiving mail for generated aliases of the form `baseLocal + suffix @2925.com` without an explicit alias/sub-mailbox activation step. + +Suggested diagnostic: + +1. Send a controlled external test email to `chickliu2030ih2jpi@2925.com`. +2. Refresh 2925 Inbox/Junk under `chickliu2030@2925.com`. +3. If the controlled email also does not appear, reverse-engineer 2925's alias/sub-mailbox activation UI or API instead of continuing OpenAI resend loops. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-openai-live-test-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-live-test-2026-05-11.md new file mode 100644 index 0000000..bc7c712 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-live-test-2026-05-11.md @@ -0,0 +1,38 @@ +# 2925 + OpenAI live signup test notes — 2026-05-11 + +Scope: BOSS requested one OpenAI/Codex registration test using 2925 account pool, Luban SMS UK, and 1085 proxy. + +## Result + +Stopped before SMS purchase. Email verification did not pass, so Luban was not used. + +## Verified gates + +- Proxy: 1085 Chrome/CDP worked for OpenAI pages. +- CPA OAuth URL generation: worked via `/v0/management/codex-auth-url?is_webui=true`. +- 2925 account pool binding: worked. + - `chickliu2025@2925.com` -> generated alias with `chickliu2025` prefix. + - `chickliu2026@2925.com` -> generated alias with `chickliu2026` prefix. +- 2925 displayed top/current mailbox matched the selected account before polling. +- Inbox pre-clear/check worked. + +## Findings + +1. OpenAI email-verification page displayed that it sent mail to the generated 2925 alias. +2. 2925 Inbox did not receive OpenAI mail during the polling window. +3. 2925 Junk (`垃圾箱`) contained OpenAI/ChatGPT mail and 6-digit codes, but many were stale messages for older aliases. +4. Submitting a visible stale code from Junk returned OpenAI error `代码不正确`. +5. Resend did not produce a new code matching the current generated alias within the tested polling window. +6. `已删除` also contained many stale OpenAI codes and must not be used without current-alias matching. + +## Required rule update + +- Poll both Inbox and Junk. +- Never accept a 6-digit code unless the candidate message matches the current generated alias/local-part or an unambiguous current-run time window. +- Maintain rejected codes and never resubmit them. +- If no current-alias code appears, stop before SMS purchase. + +## Safety + +No Luban number was purchased because email verification did not pass. +No passwords/API keys/codes are included in this note. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-openai-resend-cadence-attempts-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-resend-cadence-attempts-2026-05-11.md new file mode 100644 index 0000000..0bed969 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-openai-resend-cadence-attempts-2026-05-11.md @@ -0,0 +1,39 @@ +# 2925 OpenAI signup attempts with resend cadence — 2026-05-11 + +BOSS requested: use 1085 proxy, 2925 email pool, Luban SMS UK. Luban was not used because email verification never passed. + +## Resend cadence adopted + +For each current alias: + +1. Refresh 2925 `收件箱` and `垃圾箱`. +2. Wait ~15s. +3. Repeat 3 refresh rounds. +4. If no current-alias code, click OpenAI `重新发送电子邮件`. +5. Repeat. + +## Attempts + +- `chickliu2027@2925.com` -> `chickliu20274ynsem@2925.com` + - explicit refresh + poll found no current-alias OpenAI mail. +- `chickliu2028@2925.com` -> `chickliu2028t95bip@2925.com` + - 3-refresh-then-resend cadence executed. + - OpenAI resend clicked 3 times. + - Inbox/Junk showed correct account but no current-alias mail/code. +- `chickliu2029@2925.com` -> `chickliu2029ssejc6@2925.com` + - fresh 2925 profile was required; old 2925 session remained logged into 2028 until profile restart. + - 3-refresh-then-resend cadence executed. + - OpenAI resend clicked 3 times. + - Inbox/Junk showed correct account but no current-alias mail/code. + +## Findings + +- 2925 top-account gate is mandatory. When switching accounts, clear cookies or use a fresh profile; otherwise UI may remain logged into the previous account. +- OpenAI email-verification pages showed delivery to the generated alias, but 2925 Inbox/Junk did not receive current-alias messages for the tested accounts. +- Older OpenAI mail can exist in Junk/Deleted for older aliases; candidate code must match current alias/local-part. +- Stop before SMS purchase if email verification is not complete. + +## Safety + +No Luban UK number was purchased. +No OpenAI/Codex tokens or phone numbers were generated in this blocked run. diff --git a/skill/codex-oauth-plus-onboarding/references/mail2925-resend-cadence-and-browser-hygiene-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/mail2925-resend-cadence-and-browser-hygiene-2026-05-11.md new file mode 100644 index 0000000..21c657b --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/mail2925-resend-cadence-and-browser-hygiene-2026-05-11.md @@ -0,0 +1,41 @@ +# 2925 resend cadence and browser hygiene — 2026-05-11 + +Context: BOSS requested OpenAI/Codex signup using 2925 mailbox pool, Luban SMS UK, and 1085 proxy. Email verification blocked before SMS purchase. + +## User-corrected workflow + +BOSS corrected the email polling cadence: + +1. Refresh 2925 `收件箱` and `垃圾箱`. +2. Wait about 15 seconds. +3. Repeat 3 refresh rounds. +4. If still no current-alias verification code, click OpenAI `重新发送电子邮件` once. +5. Resume the same 3-refresh cadence. + +Do not simply long-poll for 15+ rounds without triggering OpenAI resend. Do not buy SMS until email verification passes. + +## Browser hygiene correction + +When switching 2925 accounts: + +- Close stale OpenAI/auth Chrome/CDP processes from the previous account. +- Clear 2925 cookies or use a fresh Chrome profile. +- Re-login to the selected 2925 account and verify the page top/current email equals the selected pool account. + +Observed pitfall: a 2925 session remained logged into `chickliu2028@2925.com` after the runner switched to `chickliu2029@2925.com`; only a fresh profile produced the correct top account. + +## Live attempts summary + +- `chickliu2028@2925.com` -> current alias `chickliu2028t95bip@2925.com` + - 3-refresh-then-resend cadence executed. + - OpenAI resend clicked 3 times. + - Inbox/Junk account gate stayed correct, but no current-alias mail/code arrived. +- `chickliu2029@2925.com` -> current alias `chickliu2029ssejc6@2925.com` + - Fresh 2925 profile needed to avoid stale login. + - 3-refresh-then-resend cadence executed. + - OpenAI resend clicked 3 times. + - Inbox/Junk account gate stayed correct, but no current-alias mail/code arrived. + +## Result + +Luban SMS UK was not used because the OpenAI email-verification gate never passed. This prevented wasting SMS balance. diff --git a/skill/codex-oauth-plus-onboarding/references/openai-add-phone-vietnam-only-rejects-2026-05-10.md b/skill/codex-oauth-plus-onboarding/references/openai-add-phone-vietnam-only-rejects-2026-05-10.md new file mode 100644 index 0000000..52a7a38 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/openai-add-phone-vietnam-only-rejects-2026-05-10.md @@ -0,0 +1,64 @@ +# Vietnam-only 5sim OpenAI phone test: direct rejects/invalid numbers (2026-05-10) + +## Context + +BOSS asked to register another Codex/OpenAI OAuth account using Vietnam 5sim SMS only. + +Account: + +```text +chickliu.efxudmrg@claw.163.com +``` + +Successful pre-phone stages: + +```text +ClawEmail sub-mailbox created +Dashboard comm settings opened: commLevel=2, extReceiveType=1, extSendType=0 +CPA OAuth URL generated +OpenAI create-account email/password submitted +OpenAI email verification code received in the sub-mailbox and submitted +Reached https://auth.openai.com/add-phone +``` + +The OpenAI phone country gate was enforced before buys/submits: + +```text +select.value = VN +visible country = 越南 (+84) +``` + +## Result + +Vietnam 5sim `openai` SMS activations were bought and submitted one at a time. OpenAI rejected them before any SMS was received. + +Activation ids and classifications: + +```text +1006941018 direct unable-to-send reject; canceled +1006941583 电话号码无效 / invalid phone; canceled +1006942314 rejected; canceled +1006942389 rejected; canceled +1006942456 rejected; canceled +1006942527 rejected; canceled +1006942608 rejected; canceled +``` + +Final state: + +```text +OpenAI email verification: passed +OpenAI phone verification: not passed +Current page: auth.openai.com/add-phone +Active 5sim orders: none +CPA callback import: not reached +``` + +## Lessons + +- A Vietnam number can still be rejected as invalid even when the page gate shows `VN` / `越南 (+84)` correctly. +- Treat both messages as immediate-reject classes and cancel the activation promptly: + - `无法向此电话号码发送验证码` + - `电话号码无效` +- If BOSS insists on Vietnam-only, expect multiple burned-but-canceled activations before success; if allowed, prefer broad/free-country strategy because a previous success occurred only during wider exploration. +- When clearing a previously invalid phone number, use real keyboard select-all/backspace on the visible tel field before inserting the next number; simple JS `.value=''` can leave React/hidden `phoneNumber` state inconsistent. diff --git a/skill/codex-oauth-plus-onboarding/references/openai-email-code-login-account-deactivated-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/openai-email-code-login-account-deactivated-2026-05-11.md new file mode 100644 index 0000000..d01e715 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/openai-email-code-login-account-deactivated-2026-05-11.md @@ -0,0 +1,28 @@ +# OpenAI/Codex OAuth email-code recovery and account-deactivation pitfalls — 2026-05-11 + +Session context: after a long 5sim run, `chickliu.efxudmrg@claw.163.com` passed OpenAI phone verification but later failed CPA OAuth login/recovery. + +## Facts observed + +- Successful phone verification used 5sim Indonesia activation `1007069622`; final count for this account was **335 actual activations**. The 5sim order was `FINISHED`. +- Initial SMS parser incorrectly selected a 4-digit number from the SMS text; OpenAI phone page required a 6-character code. Prefer `(? about-you`. + +## SMS parsing pitfall + +The first parser used `(?` on Chrome 147 sometimes caused OpenAI `https://auth.openai.com/error` with `unknown_error` for a fresh CPA OAuth URL. +- Using the same tab via CDP `Page.navigate` with the same fresh CPA OAuth URL entered the normal OpenAI login page. Prefer same-tab navigation for CPA OAuth recovery. +- After about-you/consent, one error was `Workspaces not found in client auth session`; retrying the button did not recover. ChatGPT homepage still showed logged-out, so the ChatGPT/workspace session was not initialized. +- Fresh CPA OAuth then hit login. Password login returned `Incorrect email address or password` even though signup used the configured password; creating with the same email returned `与此电子邮件地址相关联的帐户已存在`, proving the account exists. +- `使用一次性验证码登录` on the password page returned `invalid_state` in one state. +- `使用一次性验证码注册` from create-password returned `passwordless_signup_disabled`. + +## Current classification + +This is no longer a phone/SMS problem once the 335th activation succeeded. For this account, the blocker became Auth/session/credential recovery: + +```text +email verified: yes +phone verified: yes +5sim activation: FINISHED +oauth callback: not acquired +CPA auth file: not active for this account +blocker: OpenAI login/session/workspace, not proxy or SMS +``` + +## Operational guidance for future retry + +1. Do not buy more phone numbers for this account unless OpenAI explicitly returns to `add-phone` for the same account. +2. Use 1085 if BOSS requests it, but navigate OAuth URL in the same tab (`Page.navigate`), not `/json/new`. +3. If password login fails and passwordless is disabled/invalid_state, try a real password reset/recovery path or a fresh account; do not loop password submission. +4. If about-you is shown after phone verification, fill `name`, `age`, and `birthday` explicitly before consent. +5. For final reporting, state the phone success count separately from CPA/OAuth status: phone success at 335 actual activations; CPA callback not completed due auth/session blocker. diff --git a/skill/codex-oauth-plus-onboarding/references/openai-phone-free-country-continuation-1083-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/openai-phone-free-country-continuation-1083-2026-05-11.md new file mode 100644 index 0000000..a18d5ac --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/openai-phone-free-country-continuation-1083-2026-05-11.md @@ -0,0 +1,33 @@ +# Additional free-country phone runs — 1083 proxy, 2026-05-11 + +Context: after a successful ClawEmail signup/email verification for `chickliu.efxudmrg@claw.163.com`, OpenAI remained on `auth.openai.com/add-phone`. Browser was relaunched with the same OAuth Chrome profile but proxy changed to `socks5://192.168.2.8:1083` and CDP `9226`. + +## Observations + +- Relaunching the same verified-profile Chrome with a different proxy preserved the `add-phone` account state, but the country selector reset to `US`. Always re-run the country/dial-code gate after proxy/profile relaunch. +- Vietnam-only on 1083 produced 5 immediate reject/invalid outcomes; all 5sim activations were canceled. +- A free-country 20-actual-activation run on 1083 completed with no SMS success: + - 20 activations bought/submitted. + - 14 direct reject/invalid and canceled. + - 6 entered verification/wait state but received 0 SMS and were canceled. + - Country distribution: Argentina 4, Netherlands 2, Indonesia 2, Italy 2, Poland 2, Spain 2, England 2, Germany 3, Vietnam 1. +- Germany and Argentina frequently entered `phone-verification` with page text indicating WhatsApp delivery; under SMS-only policy, poll 5sim briefly, cancel on zero SMS, and navigate back to `/add-phone` before continuing. +- After many country/phone attempts, OpenAI may show `糟糕,出错了! Bad request`. Direct navigation back to `https://auth.openai.com/add-phone` restored the page in this run. +- Later continuation asked for another 20 actual activations. Early observations: Argentina repeatedly entered `phone-verification` but timed out with 0 SMS. Treat this as a low-yield country unless BOSS explicitly wants to keep testing it. + +## Runner accounting rule + +For long unrestricted runs, write JSONL logs and compute the final count from `activation_bought` lines across all chunk logs. If page errors or WhatsApp verification interrupt the run, top up only the missing number of **actual activation ids**; do not count `no free phones`, gate failures, CDP reconnects, or page recovery. + +## Recovery pattern + +After `sms_timeout_cancel` from a WhatsApp/SMS-wait page, explicitly do: + +```text +Page.navigate https://auth.openai.com/add-phone +wait 5-8s +verify country controls exist +then select the next country before buying +``` + +Do not continue country-gate checks while still on `/phone-verification`; they will fail and waste scheduler slots. diff --git a/skill/codex-oauth-plus-onboarding/references/openai-phone-long-run-335-and-workspace-pitfalls-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/openai-phone-long-run-335-and-workspace-pitfalls-2026-05-11.md new file mode 100644 index 0000000..0a00583 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/openai-phone-long-run-335-and-workspace-pitfalls-2026-05-11.md @@ -0,0 +1,110 @@ +# OpenAI phone verification long-run + post-phone OAuth pitfalls (2026-05-11) + +Session-specific notes from a long Codex/CPA onboarding run using ClawEmail + 5sim + visible Chrome/CDP. + +## Outcome + +- Account: `chickliu.efxudmrg@claw.163.com`. +- Phone verification finally received SMS on activation `1007069622`. +- Successful country: `indonesia` (`+62`). +- Total real 5sim activations used before SMS success: **335**. +- `no free phones` was not counted; only bought activation ids counted. + +## Long-run runner lessons + +1. For “run until it works”, persist counters in a state file on every bought activation: + - `current_activation_id` + - `new_numbers` + - `total_numbers` + - `country` +2. Add a large but explicit safety cap, e.g. `MAX_NEW_NUMBERS=300`, so runaway spend is bounded while still honoring “run until success”. +3. Always write the latest activation id to `/tmp/current_fivesim_activation_id.txt` so an interrupted runner can check/cancel/finish the order. +4. CDP `WebSocketTimeoutException` during submit/verification should be treated as **automation failure**, not proof of phone failure. Immediately inspect: + - runner tail + - state file + - current activation id + - 5sim `/user/check/` +5. If logs show `sms_received` before the crash, do **not** buy more numbers. Resume code submission against the existing `RECEIVED` activation. + +## SMS code extraction pitfall + +The first generic regex `(?` after OpenAI leaves `phone-verification`. + +## About-you form pitfall + +OpenAI `about-you` had visible required fields: + +```text +input[name=name] +input[name=age] +input[name=birthday] hidden +``` + +A naive loop that wrote `Boss` into all visible inputs left validation errors. Use native React-compatible setters by name: + +```js +function setVal(sel, val) { + const el = document.querySelector(sel); + const proto = HTMLInputElement.prototype; + Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, val); + el.dispatchEvent(new Event('input', {bubbles:true})); + el.dispatchEvent(new Event('change', {bubbles:true})); +} +setVal('input[name=name]', 'Boss'); +setVal('input[name=age]', '35'); +setVal('input[name=birthday]', '1991-01-01'); +``` + +## Consent / workspace pitfall + +After phone + about-you success, Codex OAuth consent can fail with: + +```text +hydra_api_accept_login_request_error +Workspaces not found in client auth session +``` + +Observed state: + +- `auth.openai.com/sign-in-with-chatgpt/codex/consent` showed the account email and a Continue button. +- Clicking Continue produced “Workspaces not found in client auth session”. +- Opening `https://chatgpt.com/` in the same profile showed **not logged in** (“登录 / 免费注册”), so ChatGPT workspace/session was not initialized despite OpenAI auth phone completion. + +Recommended recovery: + +1. Do not buy more phone numbers; phone verification is done. +2. Initialize/login ChatGPT in the same visible Chrome profile first. +3. Then generate a fresh CPA OAuth URL and redo Codex consent. +4. If password login fails for the freshly created account, avoid stale OAuth states; use a fresh CPA URL and the visible ChatGPT/OpenAI login path. OTP links on stale password pages can return `invalid_state`. + +## CDP navigation pitfall + +`Runtime.evaluate(... awaitPromise=True)` around a click that navigates may fail with: + +```text +Inspected target navigated or closed +``` + +For navigation clicks, prefer: + +1. Inspect current DOM. +2. Execute a non-awaited click (`awaitPromise=False`) or dispatch input/click and close the websocket. +3. Sleep briefly. +4. Reattach to the current tab via `/json/list` and inspect the new URL. + +This avoids treating a normal navigation as a fatal browser failure. diff --git a/skill/codex-oauth-plus-onboarding/references/openai-phone-success-335-oauth-workspace-failures-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/openai-phone-success-335-oauth-workspace-failures-2026-05-11.md new file mode 100644 index 0000000..a40cd28 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/openai-phone-success-335-oauth-workspace-failures-2026-05-11.md @@ -0,0 +1,78 @@ +# OpenAI phone success after long 5sim run, then OAuth workspace/session failures (2026-05-11) + +Context: CPA/Codex OAuth onboarding for `chickliu.efxudmrg@claw.163.com` using visible Chrome/CDP `9226`, proxy `socks5://192.168.2.8:1083`, ClawEmail submailbox external receive already enabled. + +## Phone verification outcome + +- Starting count before this continuation: 57 real 5sim activations. +- Continuation runner bought 278 additional real activations before SMS success. +- Final successful phone-verification number was total real activation **335**. +- Successful country: `indonesia` (`ID`, `+62`). +- Successful activation id: `1007069622`. +- 5sim status after handling: `FINISHED`. +- OpenAI moved from `phone-verification` to `about-you` after submitting the correct SMS code. + +## Important SMS parsing pitfall + +The first generic parser used `(?` found the code. + +## Phone results + +Strict gate used before every buy/submit: + +```text +select.value = VN +visible country = 越南 (+84) +``` + +### Proxy 1085 + +Tried 12 Vietnam activations total in this account/session: + +```text +1006941018 direct reject, canceled +1006941583 invalid phone, canceled +1006942314 reject/invalid, canceled +1006942389 reject/invalid, canceled +1006942456 reject/invalid, canceled +1006942527 reject/invalid, canceled +1006942608 reject/invalid, canceled +1006954123 reject/invalid, canceled +1006954195 reject/invalid, canceled +1006954269 reject/invalid, canceled +1006954350 reject/invalid, canceled +1006954418 reject/invalid, canceled +``` + +No SMS page, no SMS received. Last status check: `1006954418` was `CANCELED`, `sms_count=0`. + +### Proxy 1083 + +Proxy gate passed: + +```text +TCP OK +api.ipify.org HTTP 200 +chatgpt.com HTTP 403 +``` + +Relaunched Chrome with the same user-data-dir and `--proxy-server=socks5://192.168.2.8:1083` on CDP `9226`. The account stayed on `add-phone`, but country reset to `US`; rerun the Vietnam country gate after proxy/profile relaunch. + +Tried 5 Vietnam activations: + +```text +1006956003 reject/invalid, canceled +1006956061 reject/invalid, canceled +1006956125 reject/invalid, canceled +1006956185 reject/invalid, canceled +1006956239 reject/invalid, canceled +``` + +No SMS page, no SMS received. Last status check: `1006956239` was `CANCELED`, `sms_count=0`. + +## Takeaways + +- Changing browser proxy from 1085 to 1083 did not improve Vietnam 5sim acceptance for this verified account. +- Vietnam-only testing can burn many activations with immediate OpenAI rejection/invalid-phone outcomes; cancel them immediately. +- For higher success probability, prefer the existing free-country scheduler (max N per country, total real activations budget) rather than Vietnam-only loops unless BOSS explicitly requests Vietnam-only. diff --git a/skill/codex-oauth-plus-onboarding/references/session-learnings-2925-luban-reporting-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/session-learnings-2925-luban-reporting-2026-05-11.md new file mode 100644 index 0000000..b9c3694 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/session-learnings-2925-luban-reporting-2026-05-11.md @@ -0,0 +1,62 @@ +# Session learnings — 2925 account pool + Luban SMS + reporting guardrails (2026-05-11) + +## 2925 provider decision + +BOSS decided to abandon POP3/SMTP/IMAP forwarding from 2925 to ClawEmail. Use 2925 Webmail directly with a local account pool. + +Hard invariant: + +```text +selected account: chickliu2021@2925.com +allowed alias: chickliu2021xxxxxx@2925.com +reader mailbox: chickliu2021@2925.com +``` + +Never generate an alias from one account local-part and read codes from another account. Before polling, verify both: + +```text +alias.localPart startsWith selectedAccount.localPart +displayedMailboxEmail == selectedAccount.email +``` + +If the displayed mailbox mismatches, clear cookies for `2925.com`, `www.2925.com`, `mail2.xiyouji.com` and force-login the selected account. + +BOSS-provided pool is stored locally at: + +```text +~/.hermes/secrets/mail2925_accounts.txt +``` + +Mode must be `0600`; passwords must never be printed or committed. Helper: + +```bash +python3 scripts/mail2925_pool.py selftest +python3 scripts/mail2925_pool.py pick --dry-run # preview without changing rotation state +python3 scripts/mail2925_pool.py pick # allocate and update lastUsedAt +python3 scripts/mail2925_pool.py list +``` + +Use `--dry-run` for probes/reviews so verification does not pollute production rotation state. Real registration allocation should use plain `pick` and persist the selected account + generated alias together for the rest of that OpenAI attempt. + +Boundary: the helper only guarantees alias/account binding at generation time. The Webmail automation must still gate on `displayedMailboxEmail == selectedAccount.email` before clearing inbox or polling mail; otherwise a stale browser session could still read the wrong mailbox. + +## Luban SMS provider + +Luban API docs: `https://lubansms.com/api_docs/`. Requests without a browser-like `User-Agent` can return HTTP 403. The helper `scripts/luban_sms.py` must send a Chrome UA. + +Core provider mapping: + +```text +balance -> /getBalance +service list -> /List +buy -> /getNumber +poll -> /getSms +reject/cancel -> /setStatus?status=reject +success finish -> local no-op +``` + +The API key is stored in `~/.hermes/env/codex_oauth_onboarding.env` as `LUBAN_SMS_API_KEY`; never print it. + +## Reporting guardrail + +A tool-writing step once ended with an empty assistant response, and BOSS explicitly asked to process tool results and continue. For this skill class, after tool calls always return a visible checkpoint: what changed, what was verified, and any remaining next action. Do not leave BOSS with an empty result after tools. diff --git a/skill/codex-oauth-plus-onboarding/references/sms-provider-abstraction-and-luban-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/sms-provider-abstraction-and-luban-2026-05-11.md new file mode 100644 index 0000000..bb52d66 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/sms-provider-abstraction-and-luban-2026-05-11.md @@ -0,0 +1,79 @@ +# OpenAI phone verification provider abstraction — Luban SMS lessons + +This note captures reusable lessons from adding Luban SMS as a second SMS provider for Codex/OpenAI phone verification. + +## Why provider abstraction matters + +The original runner assumed 5sim semantics: + +```text +activation id = id +phone field = phone +poll = /user/check/ +cancel = /user/cancel/ +finish = /user/finish/ +``` + +Luban semantics differ: + +```text +activation id = request_id +phone field = number +poll = /getSms?request_id=... +cancel/release = /setStatus?request_id=...&status=reject +finish = no explicit API; local no-op after browser accepts code +``` + +Future SMS providers should be added behind functions like: + +```text +sms_buy(country) -> {activation_id, phone, metadata} +sms_check(activation_id) -> wait/success/code/expired +sms_cancel(activation_id) +sms_finish(activation_id) +``` + +Keep browser/OpenAI page logic independent from provider-specific APIs. + +## Verification before spending + +When adding a new SMS provider, first do non-spending probes only: + +1. Balance/profile endpoint. +2. Service/country lookup endpoint. +3. Resolve target OpenAI/ChatGPT service id. +4. Compile/smoke-test helper scripts. + +Do not buy a number just to prove integration unless BOSS explicitly asks. + +## Luban-specific quirks + +- API docs say all requests are GET under `https://lubansms.com/v2/api` with `apikey` query parameter. +- Python `urllib` without a browser-like `User-Agent` returned HTTP 403; adding a Chrome UA returned HTTP 200. +- Service lookup uses `/List?country=&service=OpenAI&language=en&page=1`. +- OpenAI can appear as `OpenAI`, `OpenAI / ChatGpt`, etc.; dynamic matching is safer than hardcoding a service id. +- `/getSms` states: + - `{"code":0,"msg":"wait",...}` -> keep polling + - `{"code":0,"msg":"success","sms_code":"123456"}` -> submit code + - `{"code":400,"msg":"wrong_status"}` -> expired/closed/no longer usable +- Release with `/setStatus?...&status=reject`. + +## Logging/redaction + +Never print: + +```text +apikey +full phone number +SMS code +OAuth callback code/state +``` + +Safe logs: + +```json +{"http":200,"ok":true,"balance":"2.000"} +{"ok":true,"selected":{"service_id":"...","country_en":"...","service_name":"OpenAI","provider":"...","cost":0.05}} +``` + +Phone numbers should be redacted to suffix-only at most, e.g. `[REDACTED]1234`. diff --git a/skill/codex-oauth-plus-onboarding/references/until-success-phone-runner-cumulative-accounting-2026-05-11.md b/skill/codex-oauth-plus-onboarding/references/until-success-phone-runner-cumulative-accounting-2026-05-11.md new file mode 100644 index 0000000..8b499a3 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/references/until-success-phone-runner-cumulative-accounting-2026-05-11.md @@ -0,0 +1,52 @@ +# Until-success 5sim phone runner with cumulative accounting (2026-05-11) + +Context: BOSS asked to continue OpenAI/Codex phone verification until it succeeds and then report the final number of phone numbers used. + +Reusable pattern: + +- Use a long-running script instead of repeated 20-number chunks. +- Carry forward `START_USED_NUMBERS` from previous real activations. In the observed session the start was `57` (`17` Vietnam-specific + `40` free-country activations). +- Count only successful 5sim activation purchases toward number usage. `no free phones`, country-gate failures, CDP recovery failures, and page recovery failures do not count. +- Continue emitting JSONL with both `new_numbers` and `total_numbers` so the final report is unambiguous. +- Keep a safety cap (`MAX_NEW_NUMBERS`, default 300) unless BOSS explicitly approves unlimited spend. `MAX_NEW_NUMBERS=0` means unlimited. +- Persist state to `/tmp/phone_verify_until_success_state.json` with current activation id, new count, total count, country, and success summary. +- Keep `/tmp/current_fivesim_activation_id.txt` updated so an interrupted run can cancel the active order quickly. +- Continue SMS-only behavior: immediate OpenAI rejection/invalid -> cancel; phone-verification with no 5sim SMS -> timeout cancel; SMS received -> submit code, finish activation, verify page leaves `phone-verification`. + +Suggested command: + +```bash +START_USED_NUMBERS= \ +MAX_NEW_NUMBERS=300 \ +CDP_PORT=9226 \ +/Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ + /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/phone_verify_until_success.py \ + | tee /tmp/phone_verify_until_success.log +``` + +Key log phases: + +```text +until_success_start +activation_bought +phone_submitted +activation_cancel_rejected +sms_timeout_cancel +sms_received +sms_code_submitted +round_done_no_success +safety_max_reached_no_success +``` + +Final report should include: + +```text +prior real activations +new real activations +final total numbers used +success country + activation id if successful +5sim final state: FINISHED or CANCELED +OpenAI page state after code submission +``` + +Do not print full phone numbers, SMS code, API keys, proxy strings, OAuth callback code/state, or passwords. diff --git a/skill/codex-oauth-plus-onboarding/scripts/luban_sms.py b/skill/codex-oauth-plus-onboarding/scripts/luban_sms.py new file mode 100644 index 0000000..6896499 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/scripts/luban_sms.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +"""Luban SMS API client for Codex/OpenAI phone verification. + +Reads secrets from ~/.hermes/env/codex_oauth_onboarding.env. +Never prints API keys, full phone numbers, or SMS codes unless explicitly requested by caller. + +Docs: https://lubansms.com/api_docs/ +""" +from __future__ import annotations + +import argparse +import json +import os +import pathlib +import re +import shlex +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +from typing import Any, Dict, Iterable, List, Optional, Tuple + +ENV_PATH = pathlib.Path.home() / ".hermes/env/codex_oauth_onboarding.env" +DEFAULT_BASE = "https://lubansms.com/v2/api" + + +def load_env(path: pathlib.Path = ENV_PATH) -> Dict[str, str]: + env: Dict[str, str] = {} + if not path.exists(): + return env + for line in path.read_text().splitlines(): + s = line.strip() + if not s or s.startswith("#") or "=" not in s: + continue + if s.startswith("export "): + s = s[7:].strip() + k, v = s.split("=", 1) + k = k.strip() + v = v.strip() + if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", k): + continue + try: + if v and v[0] in "'\"": + v = shlex.split(v)[0] + except Exception: + pass + env[k] = v.strip("'\"") + return env + + +def redact_phone(phone: Any) -> Optional[str]: + if phone is None: + return None + s = re.sub(r"\D", "", str(phone)) + if len(s) <= 4: + return "[REDACTED]" + return "[REDACTED]" + s[-4:] + + +def strict_code(text: str) -> Optional[str]: + m = re.search(r"(? "LubanSms": + env = load_env() + key = env.get("LUBAN_SMS_API_KEY") or env.get("LUBAN_API_KEY") + if not key: + raise SystemExit("missing LUBAN_SMS_API_KEY in ~/.hermes/env/codex_oauth_onboarding.env") + return cls(key, env.get("LUBAN_SMS_API_BASE", DEFAULT_BASE)) + + def get(self, endpoint: str, **params: Any) -> Tuple[int, Dict[str, Any], str]: + params = {k: v for k, v in params.items() if v is not None} + params["apikey"] = self.apikey + url = f"{self.base}/{endpoint.lstrip('/')}?{urllib.parse.urlencode(params)}" + req = urllib.request.Request(url, headers={ + "Accept": "application/json", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", + }) + try: + with urllib.request.urlopen(req, timeout=self.timeout) as r: + raw = r.read().decode(errors="ignore") + status = r.status + except urllib.error.HTTPError as e: + raw = e.read().decode(errors="ignore") + status = e.code + except Exception as e: + return 0, {"code": 0, "msg": f"{type(e).__name__}: {e}"}, "" + try: + data = json.loads(raw) + except Exception: + data = {"raw": raw[:500]} + return status, data, raw + + def balance(self) -> Tuple[int, Dict[str, Any], str]: + return self.get("getBalance") + + def services(self, country: Optional[str] = None, service: Optional[str] = None, language: str = "en", page: int = 1): + return self.get("List", country=country, service=service, language=language, page=page) + + def countries(self): + return self.get("countries") + + def buy(self, service_id: str) -> Tuple[int, Dict[str, Any], str]: + return self.get("getNumber", service_id=service_id) + + def sms(self, request_id: str) -> Tuple[int, Dict[str, Any], str]: + return self.get("getSms", request_id=request_id) + + def reject(self, request_id: str) -> Tuple[int, Dict[str, Any], str]: + return self.get("setStatus", request_id=request_id, status="reject") + + def again(self, request_id: str) -> Tuple[int, Dict[str, Any], str]: + return self.get("getAgainNmuber", request_id=request_id) + + def history(self, language: str = "en", page: int = 1) -> Tuple[int, Dict[str, Any], str]: + return self.get("smsHistory", language=language, page=page) + + +def summarize_services(data: Dict[str, Any], limit: int = 20) -> List[Dict[str, Any]]: + msg = data.get("msg") + rows = msg if isinstance(msg, list) else [] + out: List[Dict[str, Any]] = [] + for r in rows[:limit]: + if not isinstance(r, dict): + continue + out.append({ + "service_id": str(r.get("service_id", "")), + "country_en": r.get("country_name_en") or r.get("country_name") or r.get("country"), + "country_zh": r.get("country_name_zh"), + "service_name": r.get("service_name"), + "provider": r.get("provider"), + "cost": r.get("cost"), + }) + return out + + +def resolve_service_id(api: LubanSms, country: Optional[str], service: str, language: str = "en", pages: int = 3) -> Optional[Dict[str, Any]]: + # Exact service_name match preferred, then substring match. Keep first cheapest by numeric cost when possible. + candidates: List[Dict[str, Any]] = [] + for page in range(1, pages + 1): + st, data, _ = api.services(country=country, service=service, language=language, page=page) + if st != 200 or data.get("code") != 0: + continue + for row in summarize_services(data, limit=500): + name = str(row.get("service_name") or "") + if service.lower() in name.lower() or name.lower() in service.lower(): + candidates.append(row) + if not candidates: + return None + def cost_key(r: Dict[str, Any]) -> float: + try: return float(r.get("cost")) + except Exception: return 999999.0 + exact = [r for r in candidates if str(r.get("service_name", "")).lower() == service.lower()] + return sorted(exact or candidates, key=cost_key)[0] + + +def cmd_balance(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + st, data, _ = api.balance() + print(json.dumps({"http": st, "code": data.get("code"), "ok": st == 200 and data.get("code") == 0, "balance": data.get("balance"), "msg": data.get("msg")}, ensure_ascii=False)) + return 0 if st == 200 and data.get("code") == 0 else 1 + + +def cmd_services(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + st, data, _ = api.services(country=args.country, service=args.service, language=args.language, page=args.page) + print(json.dumps({"http": st, "code": data.get("code"), "ok": st == 200 and data.get("code") == 0, "services": summarize_services(data, args.limit), "msg_type": type(data.get("msg")).__name__}, ensure_ascii=False)) + return 0 if st == 200 and data.get("code") == 0 else 1 + + +def cmd_resolve(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + row = resolve_service_id(api, args.country, args.service, args.language, args.pages) + print(json.dumps({"ok": bool(row), "selected": row}, ensure_ascii=False)) + return 0 if row else 2 + + +def cmd_buy(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + service_id = args.service_id + selected = None + if not service_id: + selected = resolve_service_id(api, args.country, args.service, args.language, args.pages) + if not selected: + print(json.dumps({"phase": "resolve_service_fail", "country": args.country, "service": args.service}, ensure_ascii=False)) + return 2 + service_id = selected["service_id"] + st, data, _ = api.buy(service_id) + ok = st == 200 and data.get("code") == 0 and data.get("request_id") and data.get("number") + print(json.dumps({"http": st, "ok": bool(ok), "code": data.get("code"), "msg": data.get("msg"), "request_id": data.get("request_id"), "phone_redacted": redact_phone(data.get("number")), "selected": selected}, ensure_ascii=False)) + return 0 if ok else 1 + + +def cmd_sms(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + st, data, _ = api.sms(args.request_id) + code = data.get("sms_code") or strict_code(json.dumps(data, ensure_ascii=False)) + print(json.dumps({"http": st, "ok": st == 200 and data.get("code") == 0, "msg": data.get("msg"), "has_code": bool(code), "code_len": len(str(code)) if code else 0}, ensure_ascii=False)) + return 0 + + +def cmd_reject(args: argparse.Namespace) -> int: + api = LubanSms.from_env() + st, data, _ = api.reject(args.request_id) + print(json.dumps({"http": st, "ok": st == 200 and data.get("code") == 0, "code": data.get("code"), "msg": data.get("msg")}, ensure_ascii=False)) + return 0 if st == 200 and data.get("code") == 0 else 1 + + +def build_parser() -> argparse.ArgumentParser: + p = argparse.ArgumentParser(description="Luban SMS API helper") + sub = p.add_subparsers(dest="cmd", required=True) + sub.add_parser("balance") + s = sub.add_parser("services"); s.add_argument("--country"); s.add_argument("--service", default="OpenAI"); s.add_argument("--language", default="en"); s.add_argument("--page", type=int, default=1); s.add_argument("--limit", type=int, default=30) + r = sub.add_parser("resolve"); r.add_argument("--country"); r.add_argument("--service", default="OpenAI"); r.add_argument("--language", default="en"); r.add_argument("--pages", type=int, default=3) + b = sub.add_parser("buy"); b.add_argument("--service-id"); b.add_argument("--country"); b.add_argument("--service", default="OpenAI"); b.add_argument("--language", default="en"); b.add_argument("--pages", type=int, default=3) + g = sub.add_parser("sms"); g.add_argument("request_id") + x = sub.add_parser("reject"); x.add_argument("request_id") + return p + + +def main(argv: Optional[List[str]] = None) -> int: + args = build_parser().parse_args(argv) + return globals()[f"cmd_{args.cmd}"](args) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skill/codex-oauth-plus-onboarding/scripts/mail2925_openai_code_scraper.py b/skill/codex-oauth-plus-onboarding/scripts/mail2925_openai_code_scraper.py new file mode 100644 index 0000000..f1c44c9 --- /dev/null +++ b/skill/codex-oauth-plus-onboarding/scripts/mail2925_openai_code_scraper.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Scrape OpenAI/ChatGPT verification codes from 2925 Webmail via Chrome CDP. + +Key 2925 quirks captured from live runs: +- Do NOT navigate to #/mailList?type=收件箱 and trust the result; click the left nav item `收件箱`. +- Real mail rows are `table.maillist-table tr`. +- The generated alias local-part may be hidden in sender tooltip/bounce text such as + `...-chickliu2031som6d8=2925.com@tm1.openai.com`, not visible row text. +- Open the newest matching row, then extract the strict 6-digit code near code words. + +Inputs: + --cdp http://127.0.0.1:9227 + --account chickliu2031@2925.com + --alias chickliu2031som6d8@2925.com + --out /tmp/openai_2925_email_code.txt + +The script prints JSON with code redacted and writes the raw code only to --out. +""" +import argparse, json, pathlib, re, sys, time, urllib.request, websocket, itertools + + +def tabs(cdp): + return json.loads(urllib.request.urlopen(cdp.rstrip('/') + '/json', timeout=10).read().decode()) + + +def connect(cdp): + pages = [t for t in tabs(cdp) if t.get('type') == 'page'] + sel = next((t for t in pages if '2925.com' in (t.get('url') or '')), None) or pages[0] + ws = websocket.create_connection(sel['webSocketDebuggerUrl'], timeout=20) + counter = itertools.count(1) + + def call(method, params=None, timeout=45): + i = next(counter) + ws.send(json.dumps({'id': i, 'method': method, 'params': params or {}})) + end = time.time() + timeout + while time.time() < end: + msg = json.loads(ws.recv()) + if msg.get('id') == i: + if 'error' in msg: + raise RuntimeError(msg['error']) + return msg.get('result', {}) + raise TimeoutError(method) + + call('Page.enable') + call('Runtime.enable') + return call + + +def ev(call, js, awaitp=False, timeout=45): + return call('Runtime.evaluate', { + 'expression': js, + 'awaitPromise': awaitp, + 'returnByValue': True, + }, timeout).get('result', {}).get('value') + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('--cdp', default='http://127.0.0.1:9227') + ap.add_argument('--account', required=True) + ap.add_argument('--alias', required=True) + ap.add_argument('--out', required=True) + args = ap.parse_args() + local = args.alias.split('@', 1)[0].lower() + + call = connect(args.cdp) + js = r'''(async(local,account)=>{ + const sleep=ms=>new Promise(r=>setTimeout(r,ms)); + const click=e=>{if(!e)return false; e.scrollIntoView({block:'center'}); e.dispatchEvent(new MouseEvent('mousedown',{bubbles:true})); e.dispatchEvent(new MouseEvent('mouseup',{bubbles:true})); e.click(); return true;}; + const all=[...document.querySelectorAll('*')]; + const inbox=all.find(e=>(e.innerText||e.textContent||'').trim()==='收件箱'); + const clickedInbox=click(inbox); await sleep(1800); + const refresh=[...document.querySelectorAll('*')].filter(e=>(e.innerText||e.textContent||'').trim()==='刷新').pop(); + const clickedRefresh=click(refresh); await sleep(3000); + const pageText=document.body?.innerText||''; + const accountOk=pageText.includes(account); + const rows=[...document.querySelectorAll('table.maillist-table tr')].filter(r=>/OpenAI|ChatGPT|验证码|临时验证码|verification|code/i.test(r.innerText||'')); + const rowSummaries=[]; + let chosen=null, chosenIndex=-1; + for(let i=0;i