Metadata-Version: 2.4
Name: nb-lab-runtime
Version: 0.1.0
Summary: AI-native notebooks from plain Python files.
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: fastapi>=0.135.1
Requires-Dist: matplotlib>=3.10.8
Requires-Dist: uvicorn>=0.42.0
Requires-Dist: watchdog>=6.0.0
Description-Content-Type: text/markdown

# nb-lab

AI-native notebooks from plain Python files.

`nb-lab` turns `.py:percent` scripts (with `# %%` cells) into notebook-style sessions with a simple HTTP API so humans and AI agents can read, edit, and run cells, and understand plots without `.ipynb`.

## Install
Package name on PyPI is `nb-lab-runtime`; the CLI remains `nb-lab`.

Use uv:
```bash
uv add nb-lab-runtime
```

For local development:
```bash
uv sync
```
This installs the `nb-lab` CLI entry point.
If you prefer, you can also use `uv run nb-lab ...` without installing globally.

## Recommended (after release): uvx
Once `nb-lab-runtime` is published, you can run it without cloning:
```bash
uvx --from nb-lab-runtime nb-lab serve /path/to/your-ds-repo/notebook.py
```

## Quick start
Create a `.py` file with `# %%` cells:
```python
# %% [markdown]
# Simple example

# %%
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 200)
y = np.sin(x)

# %%
plt.plot(x, y)
plt.title("Sine wave")
plt.show()
```

Start the server:
```bash
nb-lab serve path/to/script.py
```
If binding fails, use an ephemeral port:
```bash
nb-lab serve path/to/script.py --port 0
```

## Troubleshooting (restricted environments)
If you see cache or permission errors, set writable cache directories:
```bash
export NB_LAB_CACHE_DIR=/tmp/nb-lab-cache
export MPLCONFIGDIR=/tmp/mpl-cache
export UV_CACHE_DIR=/tmp/uv-cache
```

## Using nb-lab with another repo
Run nb-lab from this repo but point at a different project:
```bash
cd /path/to/nb-lab
uv sync
nb-lab serve /path/to/your-ds-repo/notebook.py
```

If `nb-lab` is not on your PATH, re-run `uv sync`.

### Recommended for multi-repo use (uv tool install)
Install nb-lab once and use it across projects:
```bash
uv tool install -e /path/to/nb-lab
nb-lab serve /path/to/your-ds-repo/notebook.py
```

This keeps your data science repo’s dependencies separate from nb-lab’s runtime.

Helper script:
```bash
bash scripts/install_tool.sh
```

List cells:
```bash
nb-lab list
```

Run a cell:
```bash
nb-lab run <CELL_ID> --mode incremental
```

Show outputs:
```bash
nb-lab show <CELL_ID>
```

Suppress image payloads:
```bash
nb-lab run <CELL_ID> --no-images
nb-lab show <CELL_ID> --compact
```

Check server status:
```bash
nb-lab status
```

Tip: `--base-version` is optional. If omitted, nb-lab fetches the latest version automatically.
You can also use `--cell-index N` to refer to the Nth cell in `nb-lab list`.

## Cache commands
```bash
nb-lab cache info
nb-lab cache list
nb-lab cache clear --yes
nb-lab cache clear --all
nb-lab cache clear --path path/to/notebook.py
```

## Smoke demo
Run an end-to-end demo that starts the server, runs a cell, and fetches outputs:
```bash
uv sync
bash scripts/smoke_demo.sh
```

## Live workflow demo (agent-in-the-loop)
Here is a concrete end-to-end workflow that shows why nb-lab exists. The agent never touches `.ipynb` and still iterates like a notebook.

1. Start the server on a real `.py:percent` notebook:
```bash
nb-lab serve examples/demo.py
```

2. List cells and grab the current version:
```bash
nb-lab list
```

3. Pick a target cell and run it (incremental, no full replay):
```bash
nb-lab run --cell-index 1 --mode incremental
```

4. Fetch outputs and inspect both text and plot metadata:
```bash
nb-lab show <CELL_ID>
```

5. Let the agent edit a cell in the middle and rerun just what’s needed:
```bash
cat <<'PY' | nb-lab edit --cell-index 1 --from-stdin
<NEW_CODE>
PY
```

6. Re-run the changed cell; nb-lab replays only the dirty prefix:
```bash
nb-lab run --cell-index 1
```

This is the critical workflow: the agent can read/edit/run cells and reason about plots via `plot_meta` without JSON notebooks or manual state management.

### Mini transcript (agent loop)
```
User: "Check if the sine plot has a mean offset, and annotate it."

Agent -> GET /cells
Agent: Finds plot cell id = c3, base_version = 7

Agent -> POST /cells/c3/run
Agent -> GET /outputs/c3
Agent: Sees plot_meta.summary_text = "Line plot with 1 series."

Agent -> POST /cells/c2/edit
Agent: Inserts mean calculation and axhline overlay

Agent -> POST /cells/c3/run
Agent: Receives updated plot_meta + image_png

User: "Looks good—ship it."
```

## Execution semantics
- The `.py` file is the source of truth. Any edit (API or manual) writes to disk and triggers a reparse.
- Runs are incremental: when you run a cell, nb-lab replays only the minimal dirty prefix (cells whose source changed) up to the target cell.
- If nothing is dirty, nb-lab returns the cached output for that cell.

This favors iteration speed over strict correctness and mirrors how users actually iterate in notebooks.

## Cell ID storage
Stable cell IDs are stored in a user cache directory (default `~/.cache/nb-lab`). Set `NB_LAB_CACHE_DIR` to override.

## HTTP API (MVP)
Base URL: `http://localhost:8787`

- `GET /cells`  
  Returns `{ "version": int, "cells": [CellSummary...] }`

- `GET /status`  
  Returns `{ "version": int, "running_cell_id": string | null, "running_since": float | null }`

- `GET /cells/{cell_id}`  
  Returns full `Cell`.

- `POST /cells/{cell_id}/run`  
  Body: `{ "mode": "incremental" | "run_above" | "single", "timeout_ms": 60000, "base_version": int }`  
  Returns `ExecutionResult`.

- `POST /run_up_to/{cell_id}`  
  Same body as above. Runs prefix up to `cell_id` based on mode.

- `GET /outputs/{cell_id}`  
  Returns `{ "cell_id": str, "result": ExecutionResult }`.

- `POST /cells/{cell_id}/edit`  
  Body: `{ "new_source": "...", "base_version": int }`  
  Returns updated `Cell`.

- `POST /cells/insert_after`  
  Body: `{ "after_id": "cell-id-or-null", "source": "...", "kind": "code" | "markdown", "base_version": int }`  
  Returns new `Cell`.

- `DELETE /cells/{cell_id}`  
  Body: `{ "base_version": int }`  
  Returns updated cell list.

Concurrency: If `base_version` does not match the current notebook version, the server returns `409`.

## Output payloads
`ExecutionResult.outputs` is a list of `OutputItem`:
- `text`: captured stdout
- `stderr`: captured stderr
- `repr`: last expression repr
- `plot_meta`: JSON summary of plot structure and data
- `image_png`: PNG image encoded as hex

Note: `plot_meta.series[].sample` is downsampled (default max 200 points), while `n_points` reports the full series length.

## Agent tooling
See `docs/agent-tools.md` for function-calling tool definitions and suggested usage patterns.

For a copy-pasteable, agent-ready prompt that drives a full analysis run, see `docs/agent-prompting.md`.
For a human-readable demo flow, see `docs/demo-transcript.md`.

## OpenAPI + MCP
- OpenAPI schema: `docs/openapi.json`
- MCP manifest: `docs/mcp.json`
- Tool examples: `docs/tool-examples.md`
 - MCP hosting note: `docs/mcp-hosting.md`

To regenerate the OpenAPI schema:
```bash
bash scripts/gen_openapi.sh
```

## Status
nb-lab is early and experimental. Contributions, issues, and ideas are welcome.
