Skip to main content

Local Python access to OAuth-authenticated coding agents

Project description

PyPI - Version PyPI - Python Version PyPI - License PyPI - Downloads PyPI Downloads GitHub Actions Documentation Status

🚧 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 codex CLI via codex exec --json
  • Claude Code (Anthropic), driven by the official claude-agent-sdk
  • Gemini (Google), optionally driven by the official gemini CLI 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 normalized Event records.
  • 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, or external.
  • 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 interactive is only a local in-memory debugging facade.
  • Editing ~/.codex/auth.json or 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 codex or oauthpy 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 gemini opens the official Gemini CLI interactive auth flow and always uses external Gemini CLI state.
  • External session reuse: existing codex, claude, and gemini logins are still reusable out of the box. The default auth_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 or Client("claude", auth_source="external") for normal vendor behavior.

Provider-specific auth isolation:

  • Codex — install the codex CLI (npm i -g @openai/codex). In oauthpy source mode, oauthpy sets CODEX_HOME=~/.oauthpy/codex and ensures config.toml contains cli_auth_credentials_store = "file" unless you already set a supported value (file, keyring, or auto).
  • Claude — install the Claude Code CLI. The Python claude-agent-sdk dependency is installed with oauthpy by default. In oauthpy source mode, oauthpy sets CLAUDE_CONFIG_DIR=~/.oauthpy/claude for CLI status/login and SDK runs.
  • Gemini — install the Gemini CLI (npm install -g @google/gemini-cli). oauthpy uses gemini --prompt ... --output-format stream-json. Gemini auth currently stays external because the CLI documents ~/.gemini and env auth, but not a CODEX_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="."): ....

Structured output uses the official Claude Agent SDK first:

from oauthpy import Client

schema = {
    "type": "object",
    "properties": {
        "relationship": {"type": "integer"},
        "unrelated": {"type": "integer"},
    },
    "required": ["relationship", "unrelated"],
}

result = Client("claude").run(
    "Classify whether term A is related to term B",
    cwd=".",
    provider_options={
        "output_format": {"type": "json_schema", "schema": schema},
        "max_turns": 1,  # oauthpy raises this to 2 for Claude schema finalization.
    },
)
print(result.text)       # compact JSON, e.g. {"relationship":0,"unrelated":1}
print(result.transport)  # claude-agent-sdk, unless SDK capability fallback was needed.

For Claude schema runs, oauthpy passes output_format to ClaudeAgentOptions and reads ResultMessage.structured_output. It only falls back to claude --print --output-format json --json-schema ... when the installed SDK is missing or does not support output_format. SDK refusals, is_error=True results, and retryable SDK reader crashes are not hidden by CLI fallback.

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=low through --config unless you override it.
  • Claude Code uses the documented opus model alias and low effort by default through ClaudeAgentOptions(model="opus", effort="low").
  • Gemini uses the Gemini CLI's auto model 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], and opusplan.
  • 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, and gemini-2.5-flash-lite.

Inside oauthpy interactive, use /model NAME, /model clear, /effort LEVEL, /effort clear, /models, and /efforts.

Retry/backoff for transient provider failures

Retries are off by default. A plain Client("claude").run(...), Client("codex").run(...), or Client("gemini").run(...) call still fails fast and preserves existing one-shot semantics.

For long-running batch or benchmark workloads, opt in through common provider_options keys:

from oauthpy import Client

result = Client("claude").run(
    prompt,
    cwd=".",
    timeout=180,
    provider_options={
        "max_retries": 3,
        "retry_backoff_s": 2,
        "retry_backoff_max_s": 10,
        "retry_jitter_s": 0.25,
    },
)

oauthpy only retries provider/transport failures it can classify as transient. Examples include Claude SDK message-reader failures such as Fatal error in message reader, configured Codex/Gemini timeouts, and temporary CLI/network failures before usable output is emitted. It does not retry auth failures, invalid model/config errors, explicit permission or sandbox denials, deterministic output/schema failures, or timeouts unless retry_on_timeout=True is set.

If retries are exhausted, oauthpy raises one structured CommandExecutionError with redacted per-attempt diagnostics. If a later attempt succeeds, RunResult.raw["retry"] contains retry metadata such as attempt count, retry count, failed-attempt summaries, and total backoff time.

Use retries carefully with tool-mutating prompts: every retry can repeat provider work and may increase cost or side effects.

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-pkgs and XDG_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 --json mode that emits a JSONL event stream. oauthpy parses that stream into normalized Event records.
  • Claude Code ships an official Python SDK (claude-agent-sdk) with a streaming query(prompt, options=ClaudeAgentOptions(...)) entrypoint. oauthpy calls 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. oauthpy parses 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 with 0700 permissions 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=False everywhere);
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oauthpy-1.0.0a4.tar.gz (87.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oauthpy-1.0.0a4-py3-none-any.whl (54.5 kB view details)

Uploaded Python 3

File details

Details for the file oauthpy-1.0.0a4.tar.gz.

File metadata

  • Download URL: oauthpy-1.0.0a4.tar.gz
  • Upload date:
  • Size: 87.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for oauthpy-1.0.0a4.tar.gz
Algorithm Hash digest
SHA256 6b97574a8d284fd6ca3a3ff75ebd1e0cb9ef8b13f7e3467d800fa5ee9c3385f5
MD5 13e89b296fd8186cd752753fc9e4eeff
BLAKE2b-256 4c8853b66ed50c8ae383edf3a794af6dd6f52b68ca28328946f2518764739c38

See more details on using hashes here.

Provenance

The following attestation bundles were made for oauthpy-1.0.0a4.tar.gz:

Publisher: pypi-release.yml on brotherlattice/oauthpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oauthpy-1.0.0a4-py3-none-any.whl.

File metadata

  • Download URL: oauthpy-1.0.0a4-py3-none-any.whl
  • Upload date:
  • Size: 54.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for oauthpy-1.0.0a4-py3-none-any.whl
Algorithm Hash digest
SHA256 12830a1b0556a2b286532e5ce34036ac7236d6bb0ca7d09b4a89c1a28dcf4ce8
MD5 e942e5b0fc8f170a0220ba719be84c85
BLAKE2b-256 12888f563d4c8f9b894a527e54dc0eeef4ed9399f7998722dbfc73538ee66a90

See more details on using hashes here.

Provenance

The following attestation bundles were made for oauthpy-1.0.0a4-py3-none-any.whl:

Publisher: pypi-release.yml on brotherlattice/oauthpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page