CLI and JupyterLab extension for AI agents to manage notebooks over HTTP
Project description
jupyterlab-cli
A CLI tool and JupyterLab extension for AI agents to manage Jupyter notebooks. Every CLI command is a single HTTP call — no local state. The package also ships frontend plugins for terminal copy-on-select, browser-to-server clipboard image sync, and a “Copy Context” button for notebook cells.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ JupyterLab Server Process │
│ │
│ jupyterlab_cli_extension (server extension) │
│ REST handlers: /jupyterlab-cli/* (Tornado) │
│ Lock registry: in-memory, one lock per notebook │
│ contents_manager: notebook read/write │
│ kernel_manager: kernel start/execute/restart │
│ Frontend bridge: WebSocket relay to browser │
│ Clipboard handler: POST /jupyterlab-cli/clipboard/image │
│ │
│ jupyterlab-cli-frontend (labextension) │
│ Frontend bridge plugin: routes cell ops via browser │
│ Copy-on-select plugin: terminal text → clipboard │
│ Clipboard image plugin: browser image → server file │
│ Copy Context plugin: selection → JSON clipboard │
│ │
│ Streaming SSE: execute-cell / execute-code │
└─────────────────────────────────────────────────────────────┘
jupyterlab-cli (CLI) — pure HTTP client, stateless
How it works
-
Server extension (
jupyterlab_cli_extension/) — A Python package that registers Tornado HTTP handlers on the running Jupyter server. It manages sessions, notebook locks, cell CRUD, and kernel execution. It communicates with the browser via a WebSocket bridge so cell operations update the JupyterLab UI in real time. -
Frontend extension (
src/→jupyterlab_cli_extension/labextension/) — TypeScript plugins compiled into a JupyterLab labextension. It handles the browser-side WebSocket bridge, terminal enhancements (copy-on-select and clipboard image sync), and a “Copy Context” floating button. -
CLI (
jupyterlab_cli/) — A Click-based command-line tool that makes HTTP calls to the server extension. It is stateless; all state lives on the server. It supports human-readable (rich) and machine-readable (--json) output.
Installation
From PyPI (recommended)
Install the published package (CLI + server extension + bundled JupyterLab frontend). You do not need Node.js on the machine where you run pip install.
pip install jupyterlab-cli
Upgrade to the latest release:
pip install -U jupyterlab-cli
Includes
- Command-line tool:
jupyterlab-cli - Jupyter server extension:
jupyterlab_cli_extension(REST + WebSocket under/jupyterlab-cli/…) - JupyterLab labextension (
jupyterlab-cli-frontend), shipped inside the wheel
Requires: Python 3.10+.
PyPI: pypi.org/project/jupyterlab-cli
Use a virtual environment or Conda env where you also install JupyterLab (or at least jupyter-server) on the host that runs the notebook server, then start JupyterLab as usual—the extension is registered with the server.
Verify after install
jupyter server extension list | grep jupyterlab_cli
jupyter labextension list | grep jupyterlab-cli-frontend
If the server extension does not appear, ensure you are using the same Python environment where jupyterlab-cli is installed, or run jupyter server extension enable jupyterlab_cli_extension (usually not needed when installing from the wheel).
Install from a local checkout (development)
Installing from the repository builds the frontend during packaging via hatch-jupyter-builder (jlpm build:prod). Build-time: Node.js + jlpm must be available (see pyproject.toml).
git clone https://github.com/wenmin-wu/jupyterlab-cli.git
cd jupyterlab-cli
pip install -e .
To skip the labextension build (Python-only):
export SKIP_JUPYTER_BUILDER=1
pip install -e .
Link the labextension for live frontend development:
jupyter labextension develop . --overwrite
Building wheels locally (maintainers / CI)
Release wheels on PyPI include the labextension; building from a clean tree needs Node. Example:
pip install build
python -m build --wheel
# or: pip wheel . -w dist/
Install the produced wheel:
pip install dist/jupyterlab_cli-*-py3-none-any.whl
Quick start
-
Start JupyterLab (extension loads automatically)
jupyter lab --IdentityProvider.token mytoken
-
Configure the CLI (one-time, interactive)
jupyterlab-cli configure -
Verify connection
jupyterlab-cli status -
Set your agent’s session
export JUPYTERLAB_CLI_SESSION=my-task
-
Attach to a notebook
jupyterlab-cli use-notebook notebooks/analysis.ipynb --parents
-
Work with cells
jupyterlab-cli add-cell --type code --content 'import pandas as pd' jupyterlab-cli execute-cell --index 0
-
Release the lock when done
jupyterlab-cli unuse-notebook
Configuration
- Config is stored in
~/.config/jupyterlab-cli/config.json(XDG-compliant). - Resolution order: CLI flags → environment variables → config file.
Interactive setup (prompts with current values as defaults):
jupyterlab-cli configure
Non-interactive setup:
jupyterlab-cli configure --server-url http://localhost:8888 --token mytoken
Show current config:
jupyterlab-cli configure --show
Environment variables
| Environment variable | Purpose | Default |
|---|---|---|
JUPYTERLAB_CLI_SERVER_URL |
JupyterLab server URL | http://localhost:8888 |
JUPYTERLAB_CLI_TOKEN |
Server auth token | (empty) |
JUPYTERLAB_CLI_SESSION |
Session ID for agent | Current PID |
Commands
Connection
| Command | Description |
|---|---|
configure [--server-url] [--token] [--show] |
Configure or display connection settings |
status |
Show connection health and active session info |
Notebook management
| Command | Description |
|---|---|
use-notebook <path> [--force] [--parents] |
Attach to notebook, acquire lock |
unuse-notebook |
Detach, release lock |
list-notebooks |
All active sessions across agents |
read-notebook [--format FORMAT] |
Show notebook structure; FORMAT is brief or detailed |
Cell operations
| Command | Description |
|---|---|
add-cell --type TYPE --content <text> [--index N] |
Insert a cell (TYPE: code, markdown, or raw) |
read-cell --index N [--no-outputs] |
Read cell source and outputs |
update-cell --index N --content <text> |
Replace cell source |
delete-cell --index N |
Remove a cell |
Execution
| Command | Description |
|---|---|
execute-cell --index N [--timeout] [--no-stream] |
Execute a cell, stream outputs |
execute-code --content <code> [--timeout] [--no-stream] |
Run ad-hoc code on kernel |
Kernel & filesystem
| Command | Description |
|---|---|
list-kernels |
Show all running kernels |
restart-kernel |
Restart this session’s kernel |
list-files [<path>] [--pattern] |
Browse the Jupyter root directory |
Agent output mode
Use --json before the subcommand for machine-readable output:
jupyterlab-cli --json status
jupyterlab-cli --json read-cell --index 0
Session management
Multiple agents can work on different notebooks concurrently. A notebook can only be locked by one session at a time.
# Agent A locks notebook1
JUPYTERLAB_CLI_SESSION=agent-a jupyterlab-cli use-notebook notebook1.ipynb
# Agent B locks notebook2 — OK
JUPYTERLAB_CLI_SESSION=agent-b jupyterlab-cli use-notebook notebook2.ipynb
# Agent C tries notebook1 — blocked (409)
JUPYTERLAB_CLI_SESSION=agent-c jupyterlab-cli use-notebook notebook1.ipynb
# Override a stale lock
JUPYTERLAB_CLI_SESSION=agent-c jupyterlab-cli use-notebook notebook1.ipynb --force
Frontend plugins
The labextension ships four plugins, all auto-enabled on install:
Frontend bridge
Routes jupyterlab-cli cell operations through the browser via WebSocket so JupyterLab’s UI updates in real time when cells are added, modified, or executed.
Terminal copy-on-select
Automatically copies selected text in JupyterLab terminals to the clipboard. Configurable via Settings → Terminal Copy-on-Select.
Clipboard image sync
Bridges your local clipboard to the remote server when pasting images in a JupyterLab terminal:
- Cmd+V (with image in clipboard) → image is uploaded to the server, saved as
/tmp/clipboard_xxx.png - Browser clipboard is replaced with the file path
- Cmd+V again → pastes the path as text into the terminal (e.g. for Claude Code to read)
Configurable via Settings → Terminal Clipboard Image Sync.
Copy Context button
A floating “Copy Context” button appears when selecting text in notebook cells or file editors. Clicking it copies a JSON object to the clipboard, for example:
{
"type": "notebook_cell",
"content": "selected code...",
"filePath": "notebooks/analysis.ipynb",
"language": "python",
"cellIndex": 3,
"startLine": 1,
"endLine": 5
}
Paste this into a Claude Code terminal to provide structured context.
Streaming execution
execute-cell and execute-code stream output via SSE (Server-Sent Events) as it arrives:
jupyterlab-cli execute-cell --index 2
# 0%| | 0/100 [00:00<?, ?it/s]
# 10%|█ | 10/100 [00:01<00:09]
# [done] ok (10.23s)
Use --no-stream to buffer output and save images to files:
jupyterlab-cli execute-cell --index 2 --no-stream
# Output saved to /tmp/a1b2c3d4e5f6/
# /tmp/a1b2c3d4e5f6/1.txt
# /tmp/a1b2c3d4e5f6/2.png
Extension REST API
| Method | Path | Description |
|---|---|---|
| GET | /jupyterlab-cli/healthz |
Health check |
| GET | /jupyterlab-cli/sessions |
List sessions |
| POST | /jupyterlab-cli/sessions |
use-notebook |
| DELETE | /jupyterlab-cli/sessions/{id} |
unuse-notebook |
| GET | /jupyterlab-cli/sessions/{id}/notebook |
Read notebook |
| POST | /jupyterlab-cli/sessions/{id}/cells |
Add cell |
| GET | /jupyterlab-cli/sessions/{id}/cells/{n} |
Read cell |
| PUT | /jupyterlab-cli/sessions/{id}/cells/{n} |
Update cell |
| DELETE | /jupyterlab-cli/sessions/{id}/cells/{n} |
Delete cell |
| POST | /jupyterlab-cli/sessions/{id}/execute-cell |
Execute cell (SSE) |
| POST | /jupyterlab-cli/sessions/{id}/execute-code |
Execute code (SSE) |
| POST | /jupyterlab-cli/sessions/{id}/restart |
Restart kernel |
| GET | /jupyterlab-cli/kernels |
List kernels |
| GET | /jupyterlab-cli/files |
List files |
| POST | /jupyterlab-cli/clipboard/image |
Upload clipboard image |
| WS | /jupyterlab-cli/ws |
Frontend bridge WebSocket |
Project structure
Python HTTP/WebSocket handlers are implemented in jupyterlab_cli_extension/routes.py (the tree below matches the original modular layout; split into separate files if you prefer).
jupyterlab-cli/
├── jupyterlab_cli/ # CLI (client-side)
│ ├── cli.py # Click entry point, configure, status
│ ├── client.py # HTTP client (requests)
│ ├── config.py # Config file + env var resolution
│ ├── output.py # Dual-mode output (human/JSON)
│ └── commands/ # Subcommands
│ ├── notebook.py
│ ├── cell.py
│ ├── execute.py
│ └── kernel.py
├── jupyterlab_cli_extension/ # Server extension
│ ├── extension.py # ExtensionApp, handler registration
│ ├── lock.py # Session lock registry
│ ├── kernel_ops.py # Kernel execution helpers
│ ├── frontend_bridge.py # WebSocket bridge singleton
│ └── handlers/ # Tornado request handlers
│ ├── session.py
│ ├── cell.py
│ ├── execute.py
│ ├── kernel.py
│ ├── clipboard.py
│ ├── ws.py
│ └── health.py
├── src/ # Frontend (TypeScript)
│ ├── index.ts # Plugin registration
│ ├── copy-on-select.ts # Terminal copy-on-select
│ ├── clipboard-image.ts # Clipboard image sync
│ └── copy-context.ts # Copy Context button
├── schema/ # JupyterLab settings schemas
│ ├── copy-on-select.json
│ └── clipboard-image.json
├── style/ # Frontend CSS
│ ├── index.css
│ └── base.css
├── jupyter-config/ # Auto-enable server extension
├── pyproject.toml # Python project (hatchling)
├── package.json
├── tsconfig.json
└── SKILL.md # Agent skill definitions
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 Distributions
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 jupyterlab_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: jupyterlab_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 111.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d49f7ffebebd5d898461e855a6cc6c8a777f232d507e4ee60109b8ed32701b7c
|
|
| MD5 |
108402c438736012473fbb99ac8af41c
|
|
| BLAKE2b-256 |
4ab9ae6cf049f4332a4ea6323a38407be4dd38c7e2c18678fc1b11c9d568298a
|