Local Python access to OAuth-authenticated coding agents
Project description
🚧 Under Development
This project is still in an alpha stage. Expect rapid changes, incomplete features, and possible breaking updates between releases.
- The API may evolve as we stabilize core functionality.
- Documentation and examples are incomplete.
- Feedback and bug reports are especially valuable at this stage.
oauthpy
Local Python access to OAuth-authenticated coding agents.
oauthpy is a local, user-operated Python library that wraps local Codex, Claude Code, and optional Gemini CLI sessions behind a small, typed, async-core-with-sync-facade API:
- Codex (OpenAI), driven by the official
codexCLI viacodex exec --json - Claude Code (Anthropic), driven by the official
claude-agent-sdk - Gemini (Google), optionally driven by the official
geminiCLI in headless JSON mode
It is not a hosted service, a multi-user gateway, or a credential broker. It runs on your machine and lets you either isolate provider login state under ~/.oauthpy/ or explicitly reuse the normal vendor CLI/session state.
Scope
In scope for v0.1:
- One-shot execution via
Client.run(prompt, cwd=..., model=..., timeout=..., env=..., provider_options=...). - Streaming via
Client.stream(...)as an async iterator of normalizedEventrecords. - Best-effort, read-only
Client.auth_status()per provider. Client.login()that shells out to the provider's official login flow.Client.available()installed/provider-ready check.- Auth-source selection:
auto,oauthpy, orexternal. - A tiny debugging CLI (
oauthpy run,oauthpy interactive,oauthpy auth login,oauthpy auth status,oauthpy available).
Out of scope for v0.1:
- Hosting, relaying, or proxying anyone else's OAuth.
- Reverse-engineering vendor web endpoints.
- Scraping TUI output.
- Wire-compatibility with vendor cloud APIs.
- Persistent multi-turn session management.
oauthpy interactiveis only a local in-memory debugging facade. - Editing
~/.codex/auth.jsonor Claude credential files directly. - Isolated Gemini OAuth state until Gemini CLI documents a safe config/auth-root override.
Installation
python -m pip install oauthpy
Tested on Python 3.10-3.13 across Windows, Linux, and macOS.
Gemini support has no extra Python dependency, but the optional extra is reserved so users can opt into the provider surface explicitly:
python -m pip install "oauthpy[gemini]"
You still need to install the external Gemini CLI separately, for example with npm install -g @google/gemini-cli.
Auth prerequisites
oauthpy never implements vendor OAuth itself in v0.1. It delegates login, refresh, and credential formats to the provider's official local tooling.
- Default isolated login:
oauthpy auth login --provider codexoroauthpy auth login --provider claude. This creates~/.oauthpy/<provider>/with private directory permissions where supported, then runs the official CLI login with provider-specific config env vars. - Gemini login:
oauthpy auth login --provider geminiopens the official Gemini CLI interactive auth flow and always uses external Gemini CLI state. - External session reuse: existing
codex,claude, andgeminilogins are still reusable out of the box. The defaultauth_source="auto"prefers authenticated oauthpy-isolated state where supported, then falls back to normal vendor CLI/session state. - Forced source: use
Client("codex", auth_source="oauthpy")for isolated state orClient("claude", auth_source="external")for normal vendor behavior.
Provider-specific auth isolation:
- Codex — install the
codexCLI (npm i -g @openai/codex). Inoauthpysource mode, oauthpy setsCODEX_HOME=~/.oauthpy/codexand ensuresconfig.tomlcontainscli_auth_credentials_store = "file"unless you already set a supported value (file,keyring, orauto). - Claude — install the Claude Code CLI. The Python
claude-agent-sdkdependency is installed with oauthpy by default. Inoauthpysource mode, oauthpy setsCLAUDE_CONFIG_DIR=~/.oauthpy/claudefor CLI status/login and SDK runs. - Gemini — install the Gemini CLI (
npm install -g @google/gemini-cli). oauthpy usesgemini --prompt ... --output-format stream-json. Gemini auth currently stays external because the CLI documents~/.geminiand env auth, but not aCODEX_HOME/CLAUDE_CONFIG_DIR-style override.
Normal Claude login uses claude auth login. claude setup-token is a separate headless/CI helper that prints a long-lived token; oauthpy does not use it for regular login.
See docs/auth.md for details.
Codex quickstart
from oauthpy import Client
client = Client("codex")
result = client.run("Summarize this repo", cwd=".")
print(result.text)
Streaming:
import asyncio
from oauthpy import Client
async def main():
async for event in Client("codex").stream("Refactor module X", cwd="."):
print(event.kind, event.text)
asyncio.run(main())
Claude quickstart
from oauthpy import Client
client = Client("claude")
result = client.run("Write a failing test first for foo()", cwd=".")
print(result.text)
Streaming is identical: async for event in Client("claude").stream(prompt, cwd="."): ....
Gemini quickstart
Gemini is optional and shells out to the installed gemini CLI:
from oauthpy import Client
client = Client("gemini")
result = client.run("Summarize this repo", cwd=".")
print(result.text)
Use oauthpy auth login --provider gemini or run gemini directly to configure the official CLI login. Environment auth such as GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_GENAI_USE_VERTEXAI, or GOOGLE_GENAI_USE_GCA is detected without printing values.
Model and reasoning defaults
oauthpy applies lightweight per-run defaults without editing your vendor config files:
- Codex uses the Codex CLI's provider/default model, but sends
model_reasoning_effort=lowthrough--configunless you override it. - Claude Code uses the documented
opusmodel alias andloweffort by default throughClaudeAgentOptions(model="opus", effort="low"). - Gemini uses the Gemini CLI's
automodel alias by default. This lets the upstream CLI choose the model while oauthpy still exposes explicit model selection; reasoning-effort or thinking-budget is not exposed because the CLI does not document a stable flag through oauthpy.
Override the model with the shared model= argument:
Client("codex").run("summarize", cwd=".", model="gpt-5.3-codex")
Client("claude").run("summarize", cwd=".", model="sonnet")
Client("gemini").run("summarize", cwd=".", model="pro")
Override reasoning effort through provider options:
Client("codex").run("deep review", provider_options={"reasoning_effort": "high"})
Client("claude").run("deep review", provider_options={"reasoning_effort": "high"})
CLI equivalents:
oauthpy run --provider codex --reasoning-effort high "review this repo"
oauthpy run --provider claude --model sonnet --reasoning-effort low "summarize this repo"
oauthpy run --provider gemini --model flash-lite "summarize this repo"
Interactive helpers mirror the upstream naming:
- Codex reasoning efforts:
minimal,low,medium,high,xhigh. - Claude effort levels:
low,medium,high,xhigh,max. - Claude model aliases include
default,best,sonnet,opus,haiku,sonnet[1m],opus[1m], andopusplan. - Gemini model aliases/examples include
auto,pro,flash,flash-lite,gemini-3-pro-preview,gemini-3-flash-preview,gemini-2.5-pro,gemini-2.5-flash, andgemini-2.5-flash-lite.
Inside oauthpy interactive, use /model NAME, /model clear, /effort LEVEL, /effort clear, /models, and /efforts.
Auth-source selection
Client(provider, auth_source="auto", oauthpy_home=None) keeps the shared API small while making auth state explicit:
| Source | Behavior |
|---|---|
auto |
Prefer authenticated ~/.oauthpy/<provider>/ state; otherwise reuse normal vendor CLI/session auth; if login is needed, create isolated oauthpy state. |
oauthpy |
Force isolated state under OAUTHPY_HOME or ~/.oauthpy. |
external |
Force normal vendor behavior without oauthpy env overrides. |
For Gemini, auto and external both use the official external Gemini CLI state. oauthpy source is reported as unsupported until Gemini CLI documents a safe isolated config/auth-root override.
CLI equivalents:
oauthpy auth login --provider codex # defaults to --source oauthpy
oauthpy auth login --provider claude --source external
oauthpy auth login --provider gemini # defaults to --source external
oauthpy auth status --provider codex --source auto
oauthpy run --provider claude --source oauthpy "summarize this repo"
oauthpy interactive --provider codex --source auto --cwd .
Interactive CLI
Use oauthpy interactive to debug setup and try repeated prompts without writing Python:
oauthpy interactive --provider codex --source auto --cwd .
oauthpy interactive --provider claude --source auto --cwd .
oauthpy interactive --provider gemini --source auto --cwd .
Plain text sends a transcript-aware chat turn. Slash commands handle setup and diagnostics: /status, /available, /login, /provider, /source, /cwd, /model, /models, /effort, /reasoning, /efforts, /timeout, /events, /run, /stream, /clear, /help, and /exit.
Slash-command tab completion is enabled when prompt-toolkit is installed. It is part of oauthpy's default install; if it is unavailable, the CLI falls back to standard input() without completion.
Example Claude session:
$ oauthpy interactive --provider claude --source auto --cwd .
oauthpy interactive. Type /help for commands; /exit to quit.
oauthpy[claude:auto]> Hello, is claude code ready?
claude> Yes, Claude Code is ready! How can I help you today?
oauthpy[claude:auto]>
In this example, --source auto does not ask oauthpy to implement OAuth itself. It asks oauthpy to resolve local auth state: first use authenticated isolated Claude Code config under ~/.oauthpy/claude if present, otherwise fall back to the normal Claude Code CLI/session auth on the machine. oauthpy passes the resolved non-secret environment/config location to the official Claude Code CLI/SDK and never prints token values.
oauthpy chat remains as a compatibility alias for the same local in-memory interaction mode.
CLI setup debugging walk-through
Use this sequence when validating a fresh machine or debugging provider setup. It checks the Python package, the vendor CLIs, auth-source resolution, one-shot runs, and the examples separately so failures are easier to localize.
Create a clean environment and install oauthpy:
conda create -y -n oauthpy python=3.12 pip
conda activate oauthpy
python -m pip install -e ".[dev]"
python -c "from oauthpy import Client; print(Client)"
oauthpy --help
Check that the provider CLIs are installed and visible:
codex --version
claude --version
gemini --version
Inspect auth without printing secrets:
oauthpy auth status --provider codex --source auto --json
oauthpy auth status --provider claude --source auto --json
oauthpy auth status --provider gemini --source auto --json
oauthpy available --provider codex
oauthpy available --provider claude
oauthpy available --provider gemini
oauthpy interactive --provider codex --source auto --cwd .
If either provider is unauthenticated, use oauthpy-isolated login by default:
oauthpy auth login --provider codex
oauthpy auth login --provider claude
oauthpy auth login --provider gemini
To debug against the normal vendor CLI/session state instead, force external mode:
oauthpy auth status --provider codex --source external --json
oauthpy auth status --provider claude --source external --json
oauthpy auth status --provider gemini --source external --json
Run minimal one-shot smoke tests:
oauthpy run --provider codex --source auto --cwd . "Reply with exactly oauthpy-codex-smoke"
oauthpy run --provider claude --source auto --cwd . "Reply with exactly oauthpy-claude-smoke"
oauthpy run --provider gemini --source auto --cwd . "Reply with exactly oauthpy-gemini-smoke"
Then run the examples:
python examples/basic_codex.py
python examples/basic_claude.py
python examples/basic_gemini.py
python examples/stream_codex.py
python examples/stream_claude.py
python examples/stream_gemini.py
Failure triage:
- If Conda cannot create the environment, check network access to the configured channels and use writable cache directories such as
CONDA_PKGS_DIRS=/tmp/oauthpy-conda-pkgsandXDG_CACHE_HOME=/tmp/oauthpy-cache. - If auth succeeds but Codex runs fail with a read-only filesystem error, ensure Codex can write its session/config state, or run an oauthpy-isolated login with a writable
OAUTHPY_HOME. - If Claude auth succeeds but runs hang, test the upstream CLI directly with
claude -p "Reply with exactly oauthpy-claude-smoke"to separate Claude Code/network issues from oauthpy wrapper issues. - If Gemini auth status is inconclusive, test the upstream CLI directly with
gemini -p "Reply with exactly oauthpy-gemini-smoke" --output-format json. Gemini does not currently expose a separate documented auth-status command. - Do not copy, paste, commit, or share credential files from
~/.oauthpy/or the normal vendor config directories.
Architecture note
Codex, Claude Code, and Gemini expose different integration surfaces on the supported, local path:
- Codex does not have a stable Python SDK in v0.1, but its official CLI already has a mature
codex exec --jsonmode that emits a JSONL event stream.oauthpyparses that stream into normalizedEventrecords. - Claude Code ships an official Python SDK (
claude-agent-sdk) with a streamingquery(prompt, options=ClaudeAgentOptions(...))entrypoint.oauthpycalls that directly instead of shelling out. - Gemini ships an official CLI with headless JSON and JSONL output via
gemini --prompt ... --output-format json|stream-json.oauthpyparses that output and reuses normal Gemini CLI auth/config state.
Both adapters normalize to the same Event / RunResult / AuthStatus models and preserve the raw provider payload on Event.raw so advanced callers can drop down a level when they need to.
Security note
oauthpy is designed to run on your machine, for your account. It:
- never prints or persists OAuth tokens beyond what the upstream tool already does;
- creates
~/.oauthpy/and provider subdirectories with0700permissions where the OS supports it; - never copies existing vendor tokens into
~/.oauthpy/by default; - never edits normal vendor credential files directly;
- does not inspect Gemini OAuth credential files; it only reads non-secret Gemini settings to identify a plausible selected auth type;
- passes subprocess arguments as argv lists (
shell=Falseeverywhere); - redacts secrets from logs, reprs, and exception messages on a best-effort basis.
File-based OAuth credential storage is sensitive. If Codex stores credentials in auth.json, Claude stores state under CLAUDE_CONFIG_DIR, or Gemini stores state under ~/.gemini, treat those files like passwords: do not commit them, paste them into tickets, or share them. Some upstream tools may use an OS keychain depending on platform and config; oauthpy does not abstract that away.
This is not a hosted credential relay. Do not deploy it as a gateway for other users. If you need that, build your own service on top of vendor-approved primitives.
Anthropic compliance note
The official claude-agent-sdk documentation states:
Unless previously approved, Anthropic does not allow third party developers to offer claude.ai login or rate limits for their products, including agents built on the Claude Agent SDK.
oauthpy is a local wrapper for the user's own Claude auth. It does not offer Claude.ai login to other people. If you fork this to build a third-party product that re-distributes Claude.ai access, you need vendor-approved authentication and policy review. See docs/limitations.md.
Development
python -m pip install -e .[dev]
pre-commit install
pytest -m "not live_codex and not live_claude and not live_gemini"
ruff check .
Docs:
python -m pip install -e .[docs]
sphinx-build -b html docs docs/_build/html
See docs/development.md for the full developer guide.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oauthpy-1.0.0a3.tar.gz.
File metadata
- Download URL: oauthpy-1.0.0a3.tar.gz
- Upload date:
- Size: 74.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47f2aa0d135e5e797a45c6cddd0f30fa2dfe1e2bf86485968ed8aca8daf97cca
|
|
| MD5 |
256f30c2b6b741585f002078e4cb8211
|
|
| BLAKE2b-256 |
a246522f0f89bd109533b96be5e2ce0536efa2e9d76beb7e9964c58305315b92
|
Provenance
The following attestation bundles were made for oauthpy-1.0.0a3.tar.gz:
Publisher:
pypi-release.yml on brotherlattice/oauthpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oauthpy-1.0.0a3.tar.gz -
Subject digest:
47f2aa0d135e5e797a45c6cddd0f30fa2dfe1e2bf86485968ed8aca8daf97cca - Sigstore transparency entry: 1393418046
- Sigstore integration time:
-
Permalink:
brotherlattice/oauthpy@cf78c235365c78d9f5bc3acb5dba03a0c96942b6 -
Branch / Tag:
refs/tags/v1.0.0a3 - Owner: https://github.com/brotherlattice
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@cf78c235365c78d9f5bc3acb5dba03a0c96942b6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file oauthpy-1.0.0a3-py3-none-any.whl.
File metadata
- Download URL: oauthpy-1.0.0a3-py3-none-any.whl
- Upload date:
- Size: 46.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaae950a5574a57099c7ebb2c05b5ec14e4c2ba8bb9df1f5249043626af85da0
|
|
| MD5 |
38f79ba5dfbabfbfcb32a4d6d12a2abb
|
|
| BLAKE2b-256 |
b3006d4048bd597d02c3373f3f5247eec3a7cb6455e76b58993e1624f6c0cbfb
|
Provenance
The following attestation bundles were made for oauthpy-1.0.0a3-py3-none-any.whl:
Publisher:
pypi-release.yml on brotherlattice/oauthpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oauthpy-1.0.0a3-py3-none-any.whl -
Subject digest:
aaae950a5574a57099c7ebb2c05b5ec14e4c2ba8bb9df1f5249043626af85da0 - Sigstore transparency entry: 1393418050
- Sigstore integration time:
-
Permalink:
brotherlattice/oauthpy@cf78c235365c78d9f5bc3acb5dba03a0c96942b6 -
Branch / Tag:
refs/tags/v1.0.0a3 - Owner: https://github.com/brotherlattice
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@cf78c235365c78d9f5bc3acb5dba03a0c96942b6 -
Trigger Event:
push
-
Statement type: