| name | chatgpt-pro-browser |
| description | Use the user's already-logged-in ChatGPT (incl. Pro/Plus) directly from the terminal by driving the real Chrome browser — bypassing the broken API-key/HTTP-cookie paths. Use whenever the user wants to call ChatGPT (especially Pro models like GPT-5 Pro / GPT-5.5 Pro) from scripts, automation, or another agent, send prompts to ChatGPT, upload files to ChatGPT, do multi-turn conversations with ChatGPT, or anything where they say "用我的 ChatGPT", "调用 ChatGPT Pro", "用浏览器调 chatgpt", "复用我登录的 chatgpt", "drive chatgpt", "ask chatgpt pro", or reference GPT-5/5.5 Pro. macOS only. |
ChatGPT Pro Browser
Drive the user's real, already-logged-in ChatGPT session (incl. Pro) from code by decrypting Chrome's login cookies and injecting them into a Playwright-controlled real Chrome. This is not a reverse-engineered API — it operates the actual chatgpt.com web UI, so it works even though Cloudflare blocks raw HTTP clients (a confirmed 403 on the session endpoint).
When to use this skill
Use it whenever the user wants to programmatically call ChatGPT from their own machine, especially:
- "用我登录的 ChatGPT" / "用我的 Pro" / "调用 chatgpt pro"
- Send a prompt to ChatGPT and get the reply as a string
- Upload files (PDF/DOCX/XLSX/images/code/etc.) and have ChatGPT read them
- Multi-turn conversations with state retention
- Long/deep tasks (GPT-5 Pro reasoning, big inputs, long outputs)
- Bridging ChatGPT into another tool or agent that only speaks HTTP
Do not use this for:
- The official OpenAI API (
platform.openai.com) — that's a normal API key, no browser needed.
- Non-macOS machines — this skill relies on the macOS Keychain + Chrome.
How it works (one paragraph)
Chrome stores ChatGPT's login cookies (__Secure-next-auth.session-token, _puid, cf_clearance, …) AES-encrypted (v10 scheme) in ~/Library/Application Support/Google/Chrome/Default/Cookies. The AES key is derived from the "Chrome Safe Storage" secret in the macOS Keychain. We decrypt those cookies, inject them into a fresh Playwright context launched with channel="chrome" (the real Google Chrome binary — critical for Cloudflare's cf_clearance TLS-fingerprint check), then drive the ProseMirror composer via real keyboard events. Verified end-to-end: logs in as Pro, submits prompts, gets GPT-5.5 Pro responses.
Prerequisites
Read and run the bundled scripts/prereq_check.sh first — it verifies:
- macOS (uses Keychain + Chrome paths)
- Google Chrome installed at
/Applications/Google Chrome.app
- The user has logged into chatgpt.com in Chrome at least once (so the cookies exist)
- Python 3.10+ with
playwright, cryptography installed
- Playwright's Chromium browser downloaded (
python -m playwright install chromium)
bash skills/chatgpt-pro-browser/scripts/prereq_check.sh
The first time this skill reads cookies, macOS will pop a Keychain authorization dialog for "Chrome Safe Storage". The user must click Allow (or Always Allow). This is one-time.
Atomic CLI (the supported interface)
Seven scripts in scripts/. These are the only supported entry points — everything else in the harness is private (__all__ defines the public surface).
| Script | Purpose |
|---|
daemon.py | Start a persistent Chrome (CDP port 9223); stays alive for reuse |
close.py | Stop the daemon + clear lock |
submit.py <prompt> | Fire a task → print chat URL → exit (fire-and-forget) |
status.py <url> | One-shot peek: GENERATING / DONE / EMPTY |
save.py <url> --out f | Poll a chat to completion, save markdown |
ask.py <prompt> | Convenience: submit + wait + print (blocking) |
chat.py | Interactive REPL (auto-starts/ connects to daemon) |
Daemon reuse is the key pattern: start daemon.py once, then submit/status/save connect to it (~3s/call) instead of cold-starting Chrome (~15s). Lock at ~/.chatgpt-pro-browser.lock.
Downgrade protection (critical)
ChatGPT Pro enforces per-model usage allowances. When exceeded, a banner appears ("You're out of messages with the Pro model. Responses will use a less powerful model until [reset]") and subsequent turns silently route to a weaker model.
The harness refuses to accept a downgraded answer. It checks for the banner:
- Before submitting (
ensure_pro) → DowngradeError
- Every ~15s during generation (
_wait_turn_done) → aborts mid-task with DowngradeError
Per the user directive: 只要 pro 页面不报错,还在进行中禁止降级 — if the page is fine and still generating, wait; if it shows a downgrade banner, fail loudly rather than return a fake Pro answer.
Timing
- Single-turn ceiling: 600s (10 min) by default (
--timeout / timeout= raises it). Short tasks finish in seconds; long tasks wait until done/stall.
- Internal poll: every 0.3–5s (cheap innerText reads); heartbeat callback every ~30s for progress logging.
- Stall detection: if generating but no text change for
stall_threshold (5 min), treat as page hang and return partial + completed=False (resumable).
The core driver: lib/harness.py
This is the reusable module. Import ChatGPTSession and call .ask().
import asyncio, sys
sys.path.insert(0, "<repo>/lib")
from harness import ChatGPTSession
async def main():
async with ChatGPTSession(headless=False) as s:
await s.ensure_pro()
r = await s.ask("Write a haiku about SSH keys.")
print(r.text)
print(r.elapsed)
r = await s.ask(
"Summarize this PDF.",
attachments=["/path/to/doc.pdf"],
)
r2 = await s.ask("Now translate that to Chinese.")
asyncio.run(main())
headless=False is recommended — some Cloudflare challenges need a visible window. For unattended runs, headless=True works once the session is warm but may occasionally trip a challenge.
API reference (full detail in references/api.md)
ChatGPTSession(headless=False, viewport=(1280,800)) — async context manager.
await s.ensure_pro() — verifies plan is "pro"; raises RuntimeError if not logged in or on a lower tier. Call once at startup.
await s.current_plan() — returns the plan string ("pro", "plus", "free", or "NO_TOKEN").
await s.ask(prompt, attachments=None, type_delay=6, timeout=180) → TurnResult(text, plan, elapsed).
await s.upload(*paths) — attach files without submitting.
await s.new_chat() — start a fresh isolated chat. Always call this between independent tasks — chatgpt.com/ resumes the last chat otherwise (cross-talk bug, see references/pitfalls.md).
Important behaviors (learned the hard way — read references/pitfalls.md)
- ProseMirror, not textarea. ChatGPT's input is
div.ProseMirror[contenteditable]. page.fill() does NOT work — use keyboard.type() (the harness already does).
- Wait for the send button to re-enable after uploads. Binary files (PDF/DOCX/images) take server-side parse time; pressing Enter too early silently does nothing (looks like a 120s hang). The harness's
upload() blocks until ready.
- New chat via button, not URL. Navigating to
chatgpt.com/ often resumes the previous conversation. Use new_chat() which clicks [data-testid="create-new-chat-button"].
- Done-detection. Don't trust text-stability alone — "Pro 思考中" / "Thinking…" placeholders appear during generation. The harness waits for the stop button to disappear + send to reappear + 0.9s stability.
cf_clearance is IP+UA-bound. Works on the same Mac/network. If the user changes network, they may need to re-login in Chrome.
Performance expectations
| Task | Typical time |
|---|
| Simple prompt | 12-25s |
| File read (txt/json/code) | 13-20s |
| Binary file (pdf/docx/xlsx) | 18-55s |
| Multi-file (3-5 files) | 19-30s |
| Big file locate (130KB) | ~60s |
| Long output (2000+ words) | ~240s |
| Deep reasoning (GPT-5 Pro) | 20-60s |
Wrapping as an HTTP API (optional)
If the user wants an OpenAI-compatible endpoint, wrap the harness in FastAPI. The existing glm-pool project (~/ZCodeProject/glm-pool/server.py) is a good template — single-file, binds 127.0.0.1, maps /v1/chat/completions. Point the bridge at this harness's ask(). Details in references/http-bridge.md.
Testing
The repo ships a full test suite (20/20 passing). Run it to verify the install works on this machine:
cd <repo>
python run_suite.py --only single
python run_suite.py --only multi,long,multi-turn
Results land in results/*.jsonl. See TEST_REPORT.md for the reference run.
Privacy & safety
- Cookies are decrypted in-process only and never written to disk or logged.
- The skill binds to the local machine; it does not exfiltrate credentials.
- This drives the ChatGPT web UI, which is against OpenAI's ToS for automated access. Use for personal/research purposes only, same as any browser-automation tool.