cgs is a small CLI for inspecting and continuing your own ChatGPT web sessions from the terminal.
It deliberately separates read-only browsing from account-side writes:
| Command | Boundary | What it does |
|---|---|---|
cgs doctor |
read-only | Checks auth metadata, a tiny backend read probe, command aliases, and Chromium CDP health without printing secrets. |
cgs list |
read-only | Lists ChatGPT conversations through the web backend. |
cgs show <conversation_id> |
read-only | Prints one conversation as text or JSON. |
cgs export |
read-only | Writes raw conversation JSON files for a private, append-preserving archive. |
cgs chat |
write | Sends one prompt to a new ChatGPT web conversation, or continues an existing one when --conversation-id is provided. |
This project uses ChatGPT's unofficial web backend APIs, not the OpenAI Platform API. cgs export is a read-only local snapshot helper; it does not upload, delete, or mutate ChatGPT conversations.
cd /path/to/chatgpt-session-cli
uv run cgs --help
uv run cgs doctor --skip-read-probe --skip-cdpIf you want the command globally:
cd /path/to/chatgpt-session-cli
uv tool install -e . --force
cgs --helpcgs is the recommended short command. cgpts, cgpt-session, and chatgpt-session-cli are equivalent aliases. In an uninstalled checkout, prefix the examples below with uv run, for example uv run cgs list.
Full usage guide: docs/usage.md
The simplest read-only flow is to save your ChatGPT session JSON locally.
-
Open this while logged in to ChatGPT:
https://chatgpt.com/api/auth/session -
Copy the full JSON response.
-
Save it as the default auth file. On macOS, after copying the response:
mkdir -p ~/.config/chatgpt-session-cli pbpaste > ~/.config/chatgpt-session-cli/auth.json chmod 600 ~/.config/chatgpt-session-cli/auth.json
On other operating systems, paste the JSON into that same path with a local editor, then restrict the file to the current user when the platform supports it.
-
Verify the read-only setup, then browse conversations:
cgs doctor --skip-cdp cgs list
The file should look roughly like this:
{
"user": {
"email": "you@example.com"
},
"accessToken": "...",
"sessionToken": "...",
"expires": "..."
}accessToken is required for read-only backend calls. sessionToken is optional for list / show, but enables the experimental chat write-path to inject a logged-in ChatGPT browser cookie into the connected Chromium context. Keep both values local and treat them as account-equivalent secrets.
Do not commit auth files. .gitignore also protects an accidental repo-local file:
/auth.json
By default, cgs reads CHATGPT_SESSION_AUTH_FILE when set, then ~/.config/chatgpt-session-cli/auth.json. It never searches the caller's current directory or the source checkout, so the same local auth file works from any repository.
To use a different file explicitly:
cgs --auth-file ~/some/local/auth.json listOr set it once in your shell:
export CHATGPT_SESSION_AUTH_FILE="$HOME/some/local/auth.json"
cgs listDo not paste session JSON, session cookies, access tokens, or SSE stream bodies into chat logs. Keep them local.
Health check without printing secrets:
# Full local diagnostic: auth metadata, tiny read probe, aliases, and Chromium CDP health
cgs doctor
# Machine-readable diagnostic output
cgs doctor --jsonRefresh auth.json from a logged-in Chromium page connected over CDP without pasting tokens into chat or argv:
# Attach to the local CDP endpoint, or launch the default browser if it is not running
cgs login --from-cdp
# If you need to sign in manually, open a ChatGPT tab first, then poll while you finish login
cgs login --from-cdp --wait-seconds 300This flow fetches /api/auth/session inside the browser context, validates the returned access token against /backend-api/models, and writes only the local auth file. If it reports backend_token_invalid, sign in again in that same Chromium profile.
Session browsing:
# List active conversations, newest first
cgs list
# Show one conversation. Long output opens in a pager when needed.
cgs show <conversation_id>
# Force pager. show starts at the bottom when less is used.
cgs show <conversation_id> --pager always
# More rows
cgs list --limit 200
# No limit
cgs list --limit 0
# Archived only
cgs list --archived
# Active + archived
cgs list --all
# JSON for scripts
cgs list --json
cgs show <conversation_id> --json
# Export active conversations as raw API JSON for the private session archive
cgs export --output-dir ~/.config/chatgpt-session-cli/archive/conversations --limit 100
# Resume a partially completed backfill without refetching files already on disk
cgs export --output-dir ~/.config/chatgpt-session-cli/archive/conversations --limit 100 --skip-existing
# Include archived conversations too (one request mode at a time)
cgs export --all --output-dir ~/.config/chatgpt-session-cli/archive/conversations --limit 100
# Disable the inter-request delay only for a controlled local test
cgs export --request-delay-ms 0 --limit 1
# Disable pager explicitly
cgs show <conversation_id> --pager neverInteractive local login:
cgs loginThis prints the ChatGPT session JSON URL (https://chatgpt.com/api/auth/session) and accepts the complete JSON response directly in the terminal, including long or pretty-printed responses. It writes the default auth file containing the access token, optional session/CSRF tokens, and user email. A raw session token is also accepted for compatibility.
Env-var based flows, useful when you already have a token locally and do not want it in shell history or process args:
# Save an access token from a local env var
CHATGPT_ACCESS_TOKEN=*** cgs login --access-token-from-env
# Save from a session token stored in a local env var
CHATGPT_SESSION_TOKEN=*** cgs login --session-token-from-env
# Use an access token for one read-only command without writing auth.json
CHATGPT_ACCESS_TOKEN=*** cgs listUse this only when you explicitly want to mutate ChatGPT web conversation state. cgs chat sends one prompt through a Chromium page connected over CDP. When --conversation-id is omitted, it creates a new ChatGPT conversation. When --conversation-id is provided, it continues that existing conversation; --parent-message-id can pin a specific parent node.
The command attaches to http://127.0.0.1:9222 first. If the endpoint is unavailable, it launches the system-default Chromium-family browser with its existing profile when that browser is not already running. On macOS this follows the default browser app (including Dia); use --chromium-executable to override it. It never closes an already-running browser unless --relaunch-default-browser is explicitly passed. Pass --browser-profile-dir to use an isolated profile instead. If the auth file contains sessionToken or session_token, that value is injected as the __Secure-next-auth.session-token cookie before posting. No token value is accepted through argv or printed.
Important defaults:
| Option | Default | Notes |
|---|---|---|
--cdp-url |
http://127.0.0.1:9222 |
Loopback Chromium CDP endpoint. |
--browser-profile-dir |
detected browser profile on macOS; otherwise ~/.config/chatgpt-session-cli/chromium-cdp-profile |
Override the profile used when cgs launches Chromium. Pass a new path for isolation. |
--relaunch-default-browser |
off | Explicitly allow cgs to close and restart the running default browser profile to enable CDP. |
--request-delay-ms |
1500 |
Waits between backend calls to avoid ChatGPT “Too many session requests”. Use 0 only for debugging. |
--model |
ChatGPT account default, or gpt-5-5-thinking with --deep-research |
Validated against /backend-api/models before POST. |
--think / --thinking-effort |
unset | Must be supported by the selected model. |
--deep-research-version |
standard |
Experimental metadata observed from ChatGPT's Deep Research App. |
Deep Research is experimental. --deep-research now mirrors the observed ChatGPT web-app routing shape: it prefixes the user text with the Deep Research ecosystem mention, adds the connector:connector_openai_deep_research system_hints, includes deep_research_version=standard / venus_model_variant=standard plus the ecosystemMention serialization metadata, and sends the observed /backend-api/f/conversation/prepare connector-prepare call before the final conversation POST. It defaults the model to gpt-5-5-thinking unless --model is explicitly provided. Success is verified only from non-secret assistant/tool metadata such as Deep Research App_start, /connector_openai_deep_research/start, async_task_conversation_id, and venus_widget_state; user-turn metadata alone is not treated as activation.
Prerequisites:
- A Chromium-family browser can be connected over CDP at
--cdp-url, or cgs can find an installed browser to launch. - The connected page is logged into
https://chatgpt.com/, or the selected auth file containssessionToken/session_tokenfor cookie injection. - For continuation only: an existing
conversation_idfromcgs listor from ChatGPT's URL.
Some Chromium builds, including Chrome 136 and later, do not allow remote debugging with the ordinary default profile. Dia supports the default-profile flow. If the browser is already running without CDP, cgs refuses to close it by default; start it with CDP yourself, pass --relaunch-default-browser, or use an isolated --browser-profile-dir. Keep the CDP endpoint bound to 127.0.0.1 because it grants full control of the connected browser.
Help:
cgs chat --helpNew conversation:
cgs chat \
--prompt 'CLI POST connectivity test. Reply with exactly: ok'Continue an existing conversation:
cgs chat \
--conversation-id '<conversation-id>' \
--prompt 'Continue from here'Disable auth-file cookie import when you intentionally want to rely only on the existing Chromium CDP browser session:
cgs chat \
--conversation-id '<conversation-id>' \
--no-auth-injectOptional JSON output:
cgs chat \
--prompt 'Reply with exactly: ok' \
--jsoncgs chat returns explicit non-secret error codes for common write-path failures:
| Code | Meaning |
|---|---|
no_access_token |
The connected Chromium page reached /api/auth/session, but ChatGPT did not expose an access token. Log into ChatGPT in that profile or refresh auth.json. |
models_request_failed |
The account-visible model list could not be fetched, commonly because the access token is expired. |
prepare_request_failed / finalize_request_failed |
Sentinel chat-requirements negotiation failed before posting. |
post_request_failed |
The final /backend-api/conversation write failed. |
verification_request_failed |
The write may have happened, but the follow-up conversation fetch failed, so the CLI did not verify it. |
Start an experimental Deep Research request:
cgs chat \
--deep-research \
--request-delay-ms 3000 \
--prompt 'Research HoudahSpot from a technical architecture perspective' \
--jsonFor smoke tests, run it once and verify metadata rather than assistant prose:
deep_research.activation_detected
deep_research.async_task_conversation_id
deep_research.widget_status
Reuse an existing tab and parent node when you already know them:
cgs chat \
--cdp-url http://127.0.0.1:9222 \
--no-launch \
--conversation-id '<conversation-id>' \
--parent-message-id '<current-node-id>' \
--prompt 'Continue from this exact parent'Details and safety notes are in docs/usage.md and docs/cdp-post-poc.md.
Read-only browsing endpoints:
GET https://chatgpt.com/backend-api/conversations
GET https://chatgpt.com/backend-api/conversation/{conversation_id}
GET https://chatgpt.com/api/auth/session
Experimental cgs chat write-path flow inside Chromium browser context connected over CDP:
GET https://chatgpt.com/api/auth/session
GET https://chatgpt.com/backend-api/conversation/{conversation_id} # continuation only
GET https://chatgpt.com/backend-api/models
POST https://chatgpt.com/backend-api/sentinel/chat-requirements/prepare
POST https://chatgpt.com/backend-api/sentinel/chat-requirements/finalize
POST https://chatgpt.com/backend-api/conversation
GET https://chatgpt.com/backend-api/conversation/{conversation_id} # verification
For new conversations, the final verification id is parsed from the POST SSE stream. No PATCH, archive, delete, or bulk mutation endpoints are implemented.
Text output uses --pager auto by default.
auto: open$PAGER/lessonly when stdout is an interactive TTY and output is taller than the terminal.always: force pager.never: print directly.showstarts at the bottom when paging throughless, so the newest/end of the conversation is visible first.- JSON output is never paged.
- This uses unofficial ChatGPT web APIs. Endpoints and response shapes may change without notice.
- Auth files contain account-equivalent secrets. Treat them like passwords.
- Secret values are not accepted as direct
chatargument values, to avoid process-list and shell-history leaks. chatprints only non-secret verification metadata or non-secret JSON summaries.chatmutates ChatGPT conversation state and does not delete or roll back test messages.- Keep
auth.jsonlocal and out of Git.
Development checks:
uv run python -m unittest discover -s tests
uv run python -m compileall -q src tests scripts
uv run cgs chat --help