Metadata-Version: 2.4
Name: login-with-chatgpt
Version: 0.1.2
Summary: Use the OpenAI Python SDK with your own ChatGPT subscription
Author: YoseiUshida
License-Expression: MIT
License-File: LICENSE
Keywords: chatgpt,codex,oauth,openai,responses
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: anyio<5,>=4
Requires-Dist: filelock<4,>=3.16
Requires-Dist: httpx<1,>=0.27
Requires-Dist: keyring<27,>=25
Requires-Dist: openai<3,>=2.45.0
Requires-Dist: platformdirs<5,>=4
Description-Content-Type: text/markdown

# login-with-chatgpt

Use the official `openai` Python SDK with your own ChatGPT subscription.
`login_with_chatgpt.OpenAI()` returns a real `openai.OpenAI` client while replacing
API-key authentication and transport with ChatGPT OAuth and the Codex backend.

> [!WARNING]
> This package uses private, undocumented ChatGPT Codex endpoints. It is not the
> public OpenAI API, has no compatibility or availability guarantee, and may stop
> working when the upstream protocol changes. Requests consume the signed-in
> user's ChatGPT plan and limits, not OpenAI API credits.

## Requirements

- Python 3.11 or newer
- A ChatGPT account with Codex access
- An operating-system keyring supported by `keyring`

Codex CLI is not required at runtime. Its source and behavior are protocol
references only; no Codex checkout is imported or included in distributions.

## Install and sign in

```console
uvx login-with-chatgpt login
uv add login-with-chatgpt
```

Browser PKCE is the default. For SSH, containers, and other headless environments:

```console
uvx login-with-chatgpt login --device
```

Credentials are stored in the operating-system keyring. The SDK does not fall
back to a plaintext token file. Access tokens are refreshed automatically before
expiry and once after an HTTP 401 response.

Confirm the active account and available conversation models:

```console
uvx login-with-chatgpt status
uvx login-with-chatgpt models
uvx login-with-chatgpt doctor
```

## Quick start

```python
from login_with_chatgpt import OpenAI

client = OpenAI()
response = client.responses.create(
    model="gpt-5.6-sol",
    input="Explain Python descriptors in three sentences.",
)
print(response.output_text)
client.close()
```

`OpenAI()` returns an upstream `openai.OpenAI` object, so upstream response models,
exceptions, Pydantic parsing, streaming helpers, and function-tool helpers remain
available on the supported endpoints. Async applications use `AsyncOpenAI()`.

```python
from login_with_chatgpt import AsyncOpenAI


async def main() -> None:
    client = AsyncOpenAI()
    try:
        response = await client.responses.create(
            model="gpt-5.6-sol",
            input="Reply with only: async ok",
        )
        print(response.output_text)
    finally:
        await client.close()
```

## Supported API surface

| Python surface | Codex endpoint | Status |
| --- | --- | --- |
| `client.responses.create()` | `POST /responses` | Supported |
| `client.responses.parse()` | `POST /responses` | Supported |
| `client.responses.stream()` | `POST /responses` | Supported |
| `client.images.generate()` | `POST /images/generations` | Supported, non-streaming |
| `client.images.edit()` | `POST /images/edits` | Supported, non-streaming |
| `ChatGPTAccount().list_models()` | `GET /models` | Supported |
| Other upstream resources | Other endpoints | Rejected before network access |

The transport only allows the ChatGPT Codex origin and the methods and paths in
this table. In particular, use `ChatGPTAccount().list_models()` instead of
`client.models.list()`.

## Responses

Verified Responses features are:

- text input and output
- synchronous and asynchronous SSE streaming
- Pydantic structured output through `responses.parse()`
- function tools and parsed function arguments
- image URL and image data URL input
- function-result continuation
- stateless multi-turn history replay

### Structured output

```python
from pydantic import BaseModel

from login_with_chatgpt import OpenAI


class Answer(BaseModel):
    summary: str
    points: list[str]


client = OpenAI()
response = client.responses.parse(
    model="gpt-5.6-sol",
    input="Summarize OAuth PKCE.",
    text_format=Answer,
)
print(response.output_parsed)
client.close()
```

### Streaming

```python
from login_with_chatgpt import OpenAI

client = OpenAI()
with client.responses.stream(
    model="gpt-5.6-sol",
    input="Write one short sentence.",
) as stream:
    for event in stream:
        if event.type == "response.output_text.delta":
            print(event.delta, end="", flush=True)
client.close()
```

The Codex Responses endpoint is treated as stateless. The following are rejected
rather than silently removed or ignored:

- `store=True`
- `previous_response_id` and `conversation`
- background responses
- `max_output_tokens` and `max_completion_tokens`
- hosted tools other than function tools
- file and audio inputs

## GPT Image 2

Image generation and editing use the standalone ChatGPT Codex image endpoints.
The return value remains the upstream `openai.types.ImagesResponse` model.

### Generate an image

```python
import base64
from pathlib import Path

from login_with_chatgpt import OpenAI

client = OpenAI()
result = client.images.generate(
    model="gpt-image-2",
    prompt="A quiet Tokyo street after rain",
    background="opaque",
    quality="low",
    size="1024x1024",
)

assert result.data and result.data[0].b64_json
Path("tokyo.png").write_bytes(base64.b64decode(result.data[0].b64_json))
client.close()
```

### Edit one or more images

The normal `openai-python` file interface is preserved. PNG, JPEG, and WebP files
are converted to the data URL JSON format expected by Codex.

```python
import base64
from pathlib import Path

from login_with_chatgpt import OpenAI

client = OpenAI()
with Path("source.png").open("rb") as source:
    result = client.images.edit(
        model="gpt-image-2",
        image=source,
        prompt="Change the lighting to late afternoon",
        quality="medium",
        size="1024x1024",
    )

assert result.data and result.data[0].b64_json
Path("edited.png").write_bytes(base64.b64decode(result.data[0].b64_json))
client.close()
```

The verified image request controls are `background`, `n`, `quality`, `size`, and
`response_format="b64_json"`. Editing accepts up to five source images, with each
source limited to 50 MiB.

Image streaming, masks, transparent `gpt-image-2` backgrounds, other image models,
and unverified output controls are rejected before network access. This includes
`output_format`, `output_compression`, `partial_images`, `input_fidelity`,
`moderation`, `style`, and `user`.

`login-with-chatgpt models` intentionally lists conversation models only.
`gpt-image-2` is selected directly on `client.images` and is not inserted into the
server's conversation-model catalog.

## Profiles and CLI

```console
login-with-chatgpt --profile work login
login-with-chatgpt profiles
login-with-chatgpt use work
login-with-chatgpt status
login-with-chatgpt models
login-with-chatgpt doctor
login-with-chatgpt logout
```

Profile precedence is:

1. Explicit `--profile` or Python `profile=` argument
2. `LOGIN_WITH_CHATGPT_PROFILE`
3. Active profile selected by `login-with-chatgpt use`
4. `default`

Python code can select a profile without changing the CLI default:

```python
from login_with_chatgpt import OpenAI

work = OpenAI(profile="work")
personal = OpenAI(profile="personal")
```

The library does not rotate profiles automatically. Applications that need account
selection or rotation should implement that policy explicitly.

## Protocol compatibility

The package version and tested Codex protocol version are independent:

| login-with-chatgpt | Tested Codex protocol | openai-python |
| --- | --- | --- |
| `0.1.1` | `0.144.1` | `>=2.45.0,<3` |

The tested protocol version is the default. For an urgent compatibility override,
use either the environment or the Python constructor:

```console
$env:LOGIN_WITH_CHATGPT_CLIENT_VERSION = "0.145.0"
login-with-chatgpt doctor
```

```python
from login_with_chatgpt import OpenAI

client = OpenAI(client_version="0.145.0")
```

`doctor` reports the effective protocol version and, when installed, the local
Codex CLI version. Codex CLI detection is diagnostic only and never changes the
SDK version automatically.

## Development

```console
uv sync --dev
uv run ruff check .
uv run pyright
uv run pytest
uv build
```

Real-account contract checks are opt-in because they consume subscription usage.

Responses contract test:

```console
$env:LOGIN_WITH_CHATGPT_LIVE_MODEL = "gpt-5.6-sol"
uv run pytest tests/test_live.py -m live
```

Image generation and edit contract test, which creates two images:

```console
$env:LOGIN_WITH_CHATGPT_LIVE_IMAGE_MODEL = "gpt-image-2"
uv run pytest tests/test_live_images.py -m live
```

On POSIX shells, use `export NAME=value` instead of PowerShell's `$env:NAME =
"value"` syntax. Live tests use the currently active credential profile and are
never run by CI.
