Skip to main content

jenai

Open a real terminal in a Google Colab cell — including the free tier.

!pip install jenai
%load_ext jenai
%xterm

Open In Colab

What it is

A terminal server, a single-page client, and a notebook magic. The client is one <div>; there is no toolbar, no status bar and no settings panel. The server is a hand-written asyncio HTTP/1.1 + RFC 6455 implementation that drives a real pty from the event loop itself — no threads in the data path, no StreamReader, and one WebSocket per tab carrying raw bytes.

old new
transport tornado + HTTP long-poll one WebSocket, binary frames
client input base64 in a URL path, batched every 100 ms sent on the keystroke, as UTF-8 bytes
client output base64 in a JSON body, polled raw pty bytes
server data path thread-pool read() on the pty master loop.add_reader on the master fd
page webpack bundle + separate assets one inlined index.html (93 KiB brotli)
reload started a new shell re-attaches to the running one

Measured on the same machine, against the previous implementation:

metric legacy new
echo round trip (p50 / p99) 3.7 ms / 7.6 ms see make bench
input ceiling ~32 KiB per paste, base64 in a URL unlimited (verified to 1 MiB and beyond)
added input latency up to 100 ms of client batching none
first-paint payload 426 KiB JS + HTML 91 KiB, single request

Run make bench for the live numbers on your own hardware.

Options

%xterm height=1000 port=10001
option description
height height of the terminal panel, in pixels
port server port

Development

Requires Python 3.11+ and Node 20+.

make install     # pip install -e '.[dev]' plus the client toolchain
make build       # bundle the client into jenai/client/dist/index.html
make test        # the whole suite (add -m "not slow" to skip shells/browser)
make lint        # ruff check, ruff format --check, mypy --strict, tsc --noEmit
make bench       # echo latency, ping, output and input throughput
make profile     # cProfile report: server, codec and bundle

Layout

jenai/
  app.py          CLI entry point, uvloop setup, readiness handshake
  server.py       asyncio.Protocol HTTP+WebSocket server
  wsproto.py      RFC 6455 codec
  httpproto.py    HTTP/1.1 parsing and rendering
  assets.py       in-memory, pre-compressed static assets
  pty_session.py  pty sessions driven from the event loop
  wire.py         control-message protocol shared with the browser
  notebook.py     the %xterm magic
  client/
    build.ts      esbuild -> one inlined index.html
    src/          TypeScript: transport, terminal, protocol

Testing

tests/ runs against real sockets, real forked shells and — where Playwright is installed — a real headless Chromium. The browser tests are not decoration: they are what caught the client never wiring onData to the transport, which no Python test could have seen.

make test                     # everything
make test-fast                # no shells, no browser
make test-browser             # Playwright only
pip install playwright && playwright install chromium   # for the browser tests

Design notes

  • Binary frames carry pty bytes. The old client base64-encoded input into a URL path and output into JSON; both put a ceiling on what a user could type or what the server could send. Binary frames have neither the ceiling nor the encoding cost.
  • No client-side batching. The old client accumulated keystrokes for 100 ms before sending. Every keystroke now goes out immediately, and xterm.js coalesces renders on its own schedule.
  • Back-pressure pauses the pty. If a browser falls behind, the server stops reading the pty master instead of buffering output in Python, so a runaway cat cannot balloon the server's memory.
  • A page reload re-attaches. Session ids live in sessionStorage; the server keeps the shell alive across the reconnect.
  • The bundle is inlined. One request, no waterfall, and .gz/.br siblings the server serves straight from memory.

Screenshots

Credits

Built on xterm.js and forked from colab-xterm, which got the idea.

Metadata

Release files for jenai 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jenai 1.0.0
File Size Uploaded
jenai-1.0.0.tar.gz 413.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jenai 1.0.0
File Interpreter ABI Platform
jenai-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 806.2 kB

Release files / jenai-1.0.0.tar.gz

Download URL jenai-1.0.0.tar.gz
Size 413.1 kB
Tags Source
SHA-256 checksum
How to use checksums
3afd0c693f69fd89d75557c9b1a44bbbda0ef538e8b7d3a7ea18f55bf6d06303
BLAKE2b-256 checksum
How to use checksums
1ccfb5b6605c5ab81229dfc7fc4b6c8c8087c0256863e3f1f35ac31fd6f2cebc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / jenai-1.0.0-py3-none-any.whl

Download URL jenai-1.0.0-py3-none-any.whl
Size 393.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
85969f3d11bffa4d6dd0de36df43b45ae95406f0dcc702ba3dd1f2af1021eac9
BLAKE2b-256 checksum
How to use checksums
d704d40a4c2a4b4530df668c7a77ba53318f69775b7e4385ede00f8ef8385e9d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page