Metadata-Version: 2.4
Name: jupyter_codex
Version: 0.1.1
Summary: JupyterLab extension that opens an OpenAI Codex chat in the left sidebar.
Project-URL: Homepage, https://github.com/yanndebray/jupyterlab-codex
Project-URL: Bug Tracker, https://github.com/yanndebray/jupyterlab-codex/issues
Project-URL: Repository, https://github.com/yanndebray/jupyterlab-codex.git
Author: jupyter-codex contributors
License: MIT License
        
        Copyright (c) 2026 Yann Debray
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: ChatGPT,Codex,Jupyter,JupyterLab,JupyterLab4,JupyterLite,OpenAI
Classifier: Framework :: Jupyter
Classifier: Framework :: Jupyter :: JupyterLab
Classifier: Framework :: Jupyter :: JupyterLab :: 4
Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
Classifier: Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Requires-Dist: jupyter-server<3,>=2.0.1
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: jupyterlab<5,>=4.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# jupyterlab-codex

A JupyterLab 4 extension that adds an **OpenAI Codex** chat panel to the
left sidebar. The OpenAI mark serves as the sidebar tab icon.

## Scope: notebook copilot, not project agent

This is a deliberately small tool. The agent can only:

- list variables in your notebook kernel
- describe a pandas DataFrame
- run short Python snippets in the kernel
- read the source / outputs of a selected cell or a cell by execution count
- insert (and optionally run) a new code cell below the active one

That's the complete tool surface. **No filesystem read/write. No shell.
No MCP. No project-wide context.** The agent sees what's in your kernel
and your active notebook — nothing else.

This is a feature, not a limitation. Claude Code, Cursor, and the Codex
CLI itself are excellent project agents — they edit files, run tests,
manage git. Use them for that. `jupyterlab-codex` is for the inner
loop *inside* a notebook: explain this cell, evaluate this model,
describe this DataFrame, write the next cell. It runs where your
exploratory work already lives, with zero context-switch and a live
view of kernel state.

If you find yourself wishing it could grep your repo, that's a sign you
want a different tool — and you almost certainly already have one open
in another window.

Authentication uses the user's ChatGPT subscription via OAuth (the same
flow the Codex CLI uses). Tokens live in `localStorage` and are
forwarded to a small proxy that talks to the Codex Responses API.

Tested in JupyterLab 4 and JupyterLite. Notebook 7 should work — file an issue if it doesn't.

## Two deployment modes

The extension needs a **proxy** because both
`chatgpt.com/backend-api/codex/responses` and `auth.openai.com` reject
browser fetches from arbitrary origins. Where the proxy lives depends on
how you're running JupyterLab.

### Classic JupyterLab — bundled, no external infra

`pip install jupyter-codex` ships a Tornado server extension that
proxies to OpenAI from same-origin. The browser fetches `/codex` and
`/codex/device/...` on whichever host the Jupyter server is exposed on
— no CORS, no third-party services.

```bash
pip install jupyter-codex        # (or pip install -e . for dev)
jupyter lab
```

Open the OpenAI mark in the left sidebar and click **Sign in with
ChatGPT**. That's it.

| Setting | Default | Notes |
|---|---|---|
| `codexProxyUrl` | `codex` | Resolved against the Jupyter base URL → `<base>/codex` |
| `codexModel` | `gpt-5.3-codex` | Must be a model your ChatGPT account is entitled to |

The bundled handlers live under the Jupyter `base_url`:

```
POST  <base>/codex                  → forwards Responses API call upstream
POST  <base>/codex/device/start     → request a device code
POST  <base>/codex/device/poll      → poll for completion + token exchange
POST  <base>/codex/refresh          → rotate the refresh_token
```

### JupyterLite — external proxy required

In JupyterLite there is **no Python server** (the kernel runs in
Pyodide, the page is static). The Python package isn't installed, so
the bundled proxy can't run. Host a proxy out-of-band and override
`codexProxyUrl` to point at it.

The bundled `jupyter_codex/handlers.py` is itself a reference
implementation — port it to whichever runtime you prefer (Netlify
Function, Cloudflare Worker, AWS Lambda, a small FastAPI service). Any
proxy works as long as it exposes the same four routes:

```
POST  <proxy>                       → forwards Responses API call upstream
POST  <proxy>/device/start
POST  <proxy>/device/poll
POST  <proxy>/refresh
```

Then in **Settings → jupyterlab-codex** set
`codexProxyUrl` to the proxy URL (e.g. `/.netlify/functions/codex` if
the proxy is co-hosted on the lite site, or
`https://my-proxy.example.com/codex` if it's elsewhere).

## URL resolution

`codexProxyUrl` is interpreted as follows:

| Value | Resolved to |
|---|---|
| `"codex"` (default) | `<jupyter-base-url>/codex` |
| `"/foo/bar"` | `/foo/bar` (absolute path on current origin) |
| `"https://x.com/y"` | used as-is |
| `""` | `<jupyter-base-url>/codex` |

This lets the same default work in classic Lab (hits the bundled server
extension), single-user JupyterHub (`<base>` becomes `/user/<name>/`),
and JupyterLite (user picks an absolute path or a full URL).

## Install for development

```bash
pip install -e ".[dev]"
jupyter labextension develop . --overwrite
jlpm build
```

`jlpm watch` rebuilds the labextension on change; refresh Lab to pick up
TypeScript changes.

## Project layout

```
src/
  index.ts              # plugin entry point — registers the sidebar
  widget.ts             # Lumino widget — chat UI + sign-in flow
  icon.ts               # OpenAI mark for the sidebar tab
  kernelRpc.ts          # run-Python + sentinel-parsing primitive
  agent/
    client.ts           # tool-use loop driver
    codexAuth.ts        # localStorage auth + device-code login
    codexProvider.ts    # Codex Responses API client (Anthropic-shape adapter)
    proxyUrl.ts         # codexProxyUrl resolution helper
    tools.ts            # kernel-side tool schemas + Python prelude
    toolRunner.ts       # injects prelude, parses sentinel-wrapped JSON
    frontendTools.ts    # browser-side tools (insert_cell, get_cell_context, …)
    transcriptStore.ts  # persists chat to .jupyterlab-codex-chat.json
    markdown.ts         # tiny markdown → HTML renderer
schema/plugin.json      # ISettingRegistry schema
style/                  # sidebar + chat CSS, OpenAI SVG mark
jupyter_codex/         # Python distribution name on PyPI: jupyter-codex
  __init__.py           # labextension paths + server-extension entry points
  handlers.py           # Tornado proxy: /codex, /codex/device/*, /codex/refresh
jupyter-config/         # auto-enables the server extension on install
```
