High Performance IDE for Jupyter notebooks.
Any Jupyter kernel, one static binary, a fraction of JupyterLab's resource use.
Documentation · Downloads · Installation · Slack
Zasper is an IDE for Jupyter notebooks, designed from the ground up for concurrency and a small
footprint. It implements Jupyter's wire protocol,
so it runs any Jupyter kernel, and it reads and writes the .ipynb format directly, so notebooks
move between Zasper and JupyterLab unchanged.
Zasper is a single static binary with no runtime dependencies. It serves its interface to your browser, on your own machine or on a server you share.
- Notebooks on any Jupyter kernel, with Plotly, ipywidgets, HTML and Markdown with LaTeX rendered inline. A save is byte-for-byte what Jupyter would have written.
- Coding agents in a panel beside your notebooks: Claude Code, Gemini CLI, Codex or any agent that speaks the Agent Client Protocol, with Zasper's tools to read, edit and run notebooks, and every change it makes reviewed and undone from the panel. See Coding agents.
- Data: a variables panel for the running kernel, DataFrames as a grid that becomes a chart, and SQL cells on the databases you connect.
- A notebook that keeps up: a change made to its file while it is open is taken in cell by cell, and a run started anywhere — another tab, an agent, no tab at all — reaches its cell.
- Editor, terminal and version control in the same window as your notebooks, with notebook diffs cell by cell.
- Command palette that finds files and every command in one search.
- Trusted folders: a folder you open runs no code until you trust it.
- Light and dark themes, and window zoom.
- Self-hosting built in: every session is protected by an access token.
- Performance
- Installation
- Getting started
- Self-hosting
- Jupyter kernels
- Language servers
- Coding agents
- Notebook compatibility
- Keyboard shortcuts
- Configuration
- Logging and privacy
- Architecture
- Building from source
- Roadmap
- Documentation
- Community and support
- Contributing
- License
In benchmarks against JupyterLab, Zasper uses:
- up to 5× less CPU,
- up to 40× less memory,
with higher throughput and lower latency, and it stays responsive under very high load.
The methodology and full results are in the benchmark report.
Current release version: v2.1.0
| Platform | Support |
|---|---|
| macOS | Supported |
| Linux | Supported |
| Windows | Binaries are published and notebooks run, but terminals are not available yet and some kernel paths are less well exercised. For terminals and the best experience, use WSL. |
Requirements. Zasper runs notebooks on Jupyter kernels but does not install one:
pip install ipykernel is enough to start (see Jupyter kernels). You also
need a modern browser, since Zasper serves its interface locally rather than being a separate
desktop application. Without a kernel, Zasper still starts and the Launcher tells you what to
install.
brew tap zasper-io/tap
brew trust zasper-io/tap
brew install zasper-io/tap/zasperHomebrew 6 loads nothing from a third-party tap until you trust it, which is what brew trust
records. If you installed 0.x through Homebrew, run brew uninstall zasper first: from 1.0,
Zasper is published as a cask rather than a formula.
sudo snap install zasperconda install -c conda-forge zasperEvery release ships a signed, notarized macOS
build and static binaries for Linux and Windows. The Linux archives are plain tarballs: one build
serves every distribution. There is no .deb or .rpm yet.
| Platform | Architecture | Archive |
|---|---|---|
| macOS | Apple Silicon | zasper-webapp-<version>-darwin-arm64.tar.gz |
| macOS | Intel | zasper-webapp-<version>-darwin-amd64.tar.gz |
| Linux | x86-64 | zasper-webapp-<version>-linux-amd64.tar.gz |
| Linux | ARM64 | zasper-webapp-<version>-linux-arm64.tar.gz |
| Linux | i386 | zasper-webapp-<version>-linux-386.tar.gz |
| Windows | x86-64 | zasper-webapp-<version>-windows-amd64.zip |
| Windows | ARM64 | zasper-webapp-<version>-windows-arm64.zip |
| Windows | i386 | zasper-webapp-<version>-windows-386.zip |
Each release carries a zasper_<version>_checksums.txt. Verify a download with
sha256sum -c zasper_<version>_checksums.txt --ignore-missing. The same archives are listed on the
downloads page.
An image can be built from docker/; see docker/README.md.
Run zasper in the directory you want to work in:
$ zasper
==========================================================
███████╗ █████╗ ███████╗██████╗ ███████╗██████╗
╚══███╔╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗
███╔╝ ███████║███████╗██████╔╝█████╗ ██████╔╝
███╔╝ ██╔══██║╚════██║██╔═══╝ ██╔══╝ ██╔══██╗
███████╗██║ ██║███████║██║ ███████╗██║ ██║
╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝
Zasper Server
Version: 2.1.0
----------------------------------------------------------
✅ Server started successfully!
📡 Bound to: 127.0.0.1:8048
🖥️ Webapp available at: http://127.0.0.1:8048
🔐 Server Access Token: 14be1b674a3b9196a82c01129028d0dd
🔗 Sign in with: http://127.0.0.1:8048/?token=14be1b674a3b9196a82c01129028d0dd
==========================================================Zasper opens the Sign in with link in your default browser, so you arrive already signed in.
It does this on macOS and Windows, and on Linux when there is a display. Pass --no-browser to
leave the browser closed, for example on a machine you reach over SSH, and open the link
yourself.
Zasper always runs in protected mode: every route except the health check needs a session, and a session comes from the access token printed at startup.
zasper --host=0.0.0.0 --no-browser--host makes the server reachable from other machines. Zasper binds 127.0.0.1 by default;
widen it only when you mean to. Bound to 127.0.0.1, it answers only to localhost, 127.0.0.1 and
[::1], which stops a web page that points its own domain at your machine from reaching it. Behind a reverse
proxy on the same machine, keep the default and name the proxy's host instead:
--allow-host=zasper.example.com. With --host=0.0.0.0 the banner reports the wider binding:
📡 Bound to: 0.0.0.0:8048
🖥️ Webapp available at: http://localhost:8048
🔐 Server Access Token: 14be1b674a3b9196a82c01129028d0dd
🔗 Sign in with: http://localhost:8048/?token=14be1b674a3b9196a82c01129028d0dd
On your own machine there is nothing to do: Zasper opens your browser already signed in. Otherwise, sign in either way:
- With the link. Open the Sign in with link from the banner. It signs you in straight away, and the page removes the token from the address bar as soon as it has used it. Treat the link like a password.
- With the token. Open Zasper in a browser, which takes you to the sign-in page, and paste the Server Access Token there.
The banner's links say localhost, because they are written for a browser on the server itself.
From another machine, replace localhost with the server's hostname or IP address, for example
http://my-server:8048/?token=….
When Zasper's output is not a terminal (under Docker, systemd, or piped to a file), it logs one
JSON line instead of the banner; the token is that line's access_token field.
ZASPER_LOG_FORMAT=console brings the banner back.
A session lasts 24 hours, after which you sign in again. It is an HttpOnly cookie, so no script
running in the page can read it, and Sign out ends it on the server, so a copy of it stops working
too. A client that is not a browser can send the token from /auth/login's answer as
Authorization: Bearer … instead. After ten wrong tokens in a minute, sign-in from that address waits
out the rest of the minute.
A new access token is generated every time the server starts, and open sessions are signed with a key derived from it — so a restart both changes what you type and signs everyone out. Set the token yourself to keep a link you have handed out working, and to leave signed-in browsers signed in:
export ZASPER_ACCESS_TOKEN=your-access-tokenThere is nothing else to configure: sessions follow the access token, and changing it signs everyone out on purpose. Sign-outs are remembered only while the server runs, so with the token pinned, a session signed out before a restart works again after it until its 24 hours are up.
The file browser and the editor follow symbolic links, including ones that lead out of the project,
so a data folder linked to a large mount opens like any other. It also means a project containing a
link to ~/.ssh shows those files, as a terminal in the project would. Saving over a file that is
itself a link out of the project is refused.
Zasper works with any kernel that implements the Jupyter protocol, including:
- Python, in any virtual environment, conda environment or uv project
- R (IRkernel)
- Julia (IJulia)
- Ruby (IRuby)
- JavaScript and TypeScript (Deno)
- Go (GoNB)
To see the kernels installed on your machine, run jupyter kernelspec list. The quickest way to
get a Python kernel is:
pip install ipykernelconda create --name my-env -c conda-forge ipykernel
conda activate my-env
python -m ipykernel install --user --name=my-envuv init my-project && cd my-project
uv add ipykernel
uv run python -m ipykernel install --user --name=my-projectOnce registered, the environment appears in the Launcher and in each notebook's kernel picker.
Errors as you type, completion, hover, go to definition and formatting come from a language server
you already have installed. For Python that is basedpyright, pyright or pylsp — whichever is
found first, in that order — and for Go gopls, with seven languages served in all. Zasper ships
none of them and starts the one it finds, in PATH and in the usual per-project places. The
status bar item at the right names the server, its state and, for Python, the interpreter it reads
imports with; its menu restarts it, shows its log, or names the command to install one.
Notebook cells are served too: the cells are given to the server as one document, IPython's magics
and ! escapes are hidden from it, and the interpreter it reads imports with is the one the
notebook's kernel runs.
Python type checking is off by default, as it is in VS Code: undefined names and imports that cannot be found are still reported, and nothing else is. Raise it under Settings → Language servers when you want a type checker's opinion on code you maintain.
See docs/LANGUAGE-SERVERS.md for those modes, what each server is told, and what to do when a correct line is squiggled.
The agent panel, on the right of the editor (the bot in the top bar), runs a coding agent you already have: Claude Code, Gemini CLI, Codex, or any other that speaks the Agent Client Protocol. It is signed in with your own account and uses your own subscription; Zasper holds no key and sends nothing anywhere.
- Zasper's tools for every agent, with no setup: list, read and edit notebooks cell by cell, run cells and whole notebooks, inspect a kernel's variables and DataFrames.
- Mentions:
@points the agent at a file, and a cell's menu adds the cell or its output to the next message. - Permission modes: ask every time, allow edits, or allow edits and commands.
- Review and undo: the files each turn changed are listed under it, with a diff per file and an undo for a block, a cell, a file or the whole turn.
- Past conversations in the project can be opened again.
An agent that runs on its own, in a terminal, gets the same tools from zasper mcp:
claude mcp add zasper -- zasper mcpdocs/AGENTS.md covers the panel and docs/MCP.md the tools. PRIVACY.md says what an agent is given and what Zasper keeps.
Zasper reads and writes the .ipynb format directly. Two guarantees are worth stating outright:
- A save produces no spurious diff. A notebook Zasper opens and saves comes out byte-for-byte the file Jupyter would have written, so saving does not manufacture merge conflicts.
- A file keeps its own nbformat minor version. A 4.2 notebook is written back as 4.2 rather than silently upgraded to the newest revision, which is what JupyterLab does. Notebooks in formats 2 and 3 are converted to 4.5 on read.
| Output | Status |
|---|---|
text/plain, stdout/stderr, tracebacks |
✅ |
text/html |
✅ |
image/png |
✅ |
Plotly figures (application/vnd.plotly.v1+json) |
✅ |
ipywidgets (application/vnd.jupyter.widget-view+json) |
✅ |
application/json |
✅ |
| Markdown cells: GFM tables, task lists, raw HTML, LaTeX via KaTeX | ✅ |
image/svg+xml |
✅ |
text/latex |
✅ |
image/jpeg |
✅ |
text/markdown |
✅ |
image/gif, image/webp |
✅ |
application/pdf |
✅ |
text/latex — what SymPy emits under init_printing() — is typeset with KaTeX, the same renderer
markdown cells use. image/svg+xml, which graphviz and networkx produce and which matplotlib
produces under %config InlineBackend.figure_format = 'svg', is inlined rather than wrapped in an
<img>, so a figure that sizes itself to the cell still can.
- Widget state is not written into the notebook. Closing and reopening a tab is fine: the kernel keeps running, and the widgets redraw from it. But the state lives only in the kernel, so once that is gone — after the server restarts, or when the file is opened anywhere else, such as on GitHub — the widget shows a placeholder until the cell is run again. JupyterLab can save widget state into the file so it renders without a kernel; Zasper does not write it yet.
- A saved output does not run its scripts. HTML and SVG read from a notebook are sanitised; only output a running kernel sends runs its scripts, which is how Bokeh and HTML Plotly outputs draw themselves, so after a notebook is reopened those cells have to be run again. Zasper does not implement Jupyter's notebook signatures.
Every action listed here is also in the command palette (⇧⌘P / Ctrl+Shift+P), which shows each
command's chord beside it, so the palette rather than this table is the thing to reach for when
you have forgotten one.
Where a row gives two chords for macOS, both work: ⌘ is the usual editor convention, and ⌃ is
what Zasper was bound to first.
| Action | macOS | Windows / Linux |
|---|---|---|
| Show All Commands | ⇧⌘P or ⌃⇧P |
Ctrl+Shift+P |
| Go to File | ⇧⌘O or ⌃⇧O |
Ctrl+Shift+O |
| Zoom In | ⌘= or ⌘+ |
Ctrl+= or Ctrl++ |
| Zoom Out | ⌘- |
Ctrl+- |
| Reset Zoom | ⌘0 |
Ctrl+0 |
Zoom scales the whole window, chrome included. To change only the size of code, terminal text and cell output, use Increase / Decrease Font Size from the palette; those have no chord.
| Action | macOS | Windows / Linux |
|---|---|---|
| Save Notebook | ⌘S or ⌃S |
Ctrl+S |
| Run Cell | ⌃⏎ |
Ctrl+Enter |
| Run Cell and Select Next | ⇧⏎ |
Shift+Enter |
| Insert Cell Above | ⌃⇧A |
Ctrl+Shift+A |
| Insert Cell Below | ⌃⇧B |
Ctrl+Shift+B |
| Move Cell Up | ⌃⇧↑ |
Ctrl+Shift+Up |
| Move Cell Down | ⌃⇧↓ |
Ctrl+Shift+Down |
| Delete Cell | ⌃⇧D |
Ctrl+Shift+D |
| Undo Cell Operation | ⇧⌘Z |
Ctrl+Shift+Z |
| Change Cell to Code | ⌃⇧Y |
Ctrl+Shift+Y |
| Change Cell to Markdown | ⌃⇧M |
Ctrl+Shift+M |
Cell operations are on ⌃⇧ rather than a bare ⌃ on purpose: ⌃A, ⌃B, ⌃E and ⌃K are the
system text-editing bindings on macOS, and Ctrl+A is select-all everywhere else, so a bare chord
would be swallowed before the cell's editor saw it.
Undo Cell Operation is the notebook's own history: it takes back an inserted, deleted, cut,
pasted or retyped cell, and a cleared output. ⌘Z inside a cell is the editor's, and still undoes
the text you typed there.
Move Cell Up / Down reorders the notebook and is not the same as Select Next / Previous Cell, which only moves the selection. The cell's hover toolbar has both pairs: chevrons move the selection, arrows move the cell. The focus travels with the cell, so the chord can be held to carry one cell several places.
These have no chord and live in the palette: Run All Cells, Cut / Copy / Paste Cell, Select Next / Previous Cell, Change Cell to Raw, Expand or Collapse Output, Clear Cell Output, Clear All Outputs, Interrupt Kernel, Restart Kernel, Restart Kernel and Run All Cells, Reconnect to Kernel and Change Kernel. The notebook toolbar and a cell's hover toolbar reach most of them in one click.
These belong to the editor rather than to a command, because what they do depends on where the cursor is.
| Key | What it does |
|---|---|
Tab |
Accepts the highlighted completion; with a word to the left of the cursor, asks the kernel for completions; otherwise indents |
↑ on the first line, ↓ on the last |
Moves to the cell above or below |
Double-click, or Enter on a selected markdown cell |
Opens its source for editing |
Escape, or running the cell, in a markdown cell |
Renders it again |
A single click on a rendered markdown cell only selects it; it stays rendered.
| Flag | Default | Description |
|---|---|---|
--cwd |
. |
Base directory of the project |
--host |
127.0.0.1 |
Interface to bind; 0.0.0.0 puts the server on the network |
--port |
:8048 |
Port to start the server on |
--allow-host |
Other host names a server on loopback answers to, comma-separated, such as a reverse proxy's | |
--no-browser |
off | Do not open the app in a browser on startup |
--trust |
off | Trust the project folder for this run: its kernels, .venv and language servers start without asking. See docs/TRUST.md |
--tracking |
true |
Send anonymous usage data; see Logging and privacy |
--debug |
off | Set the log level to debug |
--version |
Print the version and exit | |
--check-update |
Ask zasper.io for the newest version and exit: 0 when up to date, 1 when there is a newer one, 2 when the check failed |
--protected is still accepted but ignored: Zasper always runs in protected mode.
Once a day the server fetches https://zasper.io/latest.json, which names the newest release. When it
is newer than the running version, the startup banner and the status bar say so, and Settings shows
what the last check found. A check that fails is silent. The snap does not check, because snapd keeps
it up to date. The first launch after an upgrade opens the new version's release notes once.
| Variable | Description |
|---|---|
ZASPER_ACCESS_TOKEN |
A fixed access token, instead of a new one on every start; open sessions survive a restart when it is set |
ZASPER_ALLOWED_HOSTS |
Host names added to --allow-host's, comma-separated |
ZASPER_TELEMETRY |
0 or 1 to turn anonymous usage data off or on for this run |
ZASPER_LOG_FORMAT |
json or console; by default, console on a terminal and JSON otherwise |
ZASPER_ACCESS_LOG |
1 to log every request, not only the ones that failed |
ZASPER_TRUST_ALL |
1 to trust every folder, as the Docker image does; see docs/TRUST.md |
The server writes logs to standard output. Run it with --debug to raise the log level.
Zasper sends a small amount of anonymous usage data: counts of things like notebooks opened, cells run, terminals started and which kind of coding agent was used. It never sends file names, paths, code, project names, anything said to or by an agent, your username or your IP address, and there is no session recording or autocapture. PRIVACY.md lists every event and every property, and explains how the allowlist that enforces it works. To turn tracking off:
zasper --tracking=false # this run
ZASPER_TELEMETRY=0 zasper # this run, from the environmentOr clear Settings → Privacy → Send anonymous usage data, which is remembered. With tracking off nothing is collected and no request is made.
Requires Go 1.26+ and Node.js 22.12+ (.nvmrc pins the Node version, so nvm use picks it up).
git clone https://github.com/zasper-io/zasper
cd zasper
make install # install the frontend's dependencies, build, and install the zasper binarymake install puts zasper in your Go binary directory, so make sure that is on your
PATH, then check the install with zasper --version. make build builds the binary in the
repository instead, make dev runs the frontend and backend in development mode, and make test
runs both test suites. make on its own lists the targets. CONTRIBUTING.md
covers the development workflow.
- Built-in AI features that need no agent: explain and fix a cell's error, generate a cell, inline completion, with your own API key.
- Extensions, including agents, tools and model providers contributed by them.
- Support for data apps, beyond Jupyter notebooks.
- Zasper Hub, for self-hosted deployment in the cloud.
- zasper.io/docs: the user documentation.
- CHANGELOG.md: what changed in each release.
- docs/API.md: the HTTP and WebSocket API, covered by semantic versioning from 1.0.0 onwards.
- docs/LANGUAGE-SERVERS.md: which language servers Zasper starts, what they are told, and what they give a notebook that a file does not get.
- docs/RUNS.md: how a cell's output survives the browser going away, and reaches the file when nobody was watching.
- docs/DATAFRAMES.md: DataFrames as tables — in a cell's output, in a tab, and in the Variables panel — read from the kernel, including how polars frames are handled.
- docs/NOTEBOOK-DIFFS.md: how a changed notebook is compared in the git panel, cell by cell, and how its cells are matched.
- docs/TRUST.md: trusted folders and restricted mode — what runs when a folder opens, how Zasper asks first, and what the server refuses until it is trusted.
- docs/KERNEL-RESOURCES.md: the memory and GPU memory each kernel holds, in the status bar and the Jupyter info panel, and how it is measured on each platform.
- docs/SQL.md: SQL cells and data connections — the magic a SQL cell is saved as, where connections and their passwords are kept, and the Data panel.
- docs/CHARTS.md: a DataFrame's grid as a chart, counted in the kernel, and the code that draws it.
- docs/AGENTS.md: the agent panel — which agents it starts, what they can ask of Zasper, and how a turn's changes are reviewed and undone.
- docs/MCP.md: Zasper's MCP server and
zasper mcp, the tools any agent can use on your notebooks and kernels. - PRIVACY.md: what anonymous usage data is collected, event by event.
- PUBLISHING.md: how releases are cut.
- Questions and discussion: join the Zasper community on Slack.
- Bugs and feature requests: open an issue.
- Sponsorship: support Zasper's development through GitHub Sponsors.
Zasper's development has been supported by a grant from FOSS United and Zerodha.
Zasper would not exist without the Jupyter community. It uses the Jupyter wire protocol and draws on Jupyter's architecture, and our thanks go to everyone who has built that foundation.
Contributions of every kind are welcome: bug reports, documentation, and pull requests or reviews of them. CONTRIBUTING.md explains how to build Zasper and submit changes. Before your first pull request is merged, you will be asked to sign the Contributor License Agreement. Everyone taking part is expected to follow the Code of Conduct.
Copyright © 2024–2026 Prasun Anand.
Zasper is dual-licensed. You can use it under either of these:
- Open source: the GNU Affero General Public License, version 3 only (
AGPL-3.0-only). See LICENSE. Running Zasper, on your own machine or on a server, needs nothing more. If you distribute Zasper, or let people use a modified version over a network, the AGPL requires you to make the corresponding source, including your changes, available under the same license. - Commercial: for organizations that want to build Zasper into a proprietary product, keep their modifications private, or need terms the AGPL does not offer. Write to prasun@zasper.io.
Contributions are accepted under the Contributor License Agreement, which is what lets Zasper be offered under both licenses.











