sidepage
Local-first hosting and tunneling for code, static sites, and notebooks.
sidepage serve wraps almost anything — a script, a static site, a
Streamlit, FastAPI, or Gradio app, a Python MCP server, a Jupyter notebook
— behind a local reverse proxy and hands you a URL. sidepage proxy does
the same for a service you already have running (npm run dev, a
container, anything already listening on a port).
Everything shipped is tested end to end; anything needing a cloud backend that doesn't exist yet says "not implemented" rather than failing silently (Project status).
Install
pip install sidepage
sidepage setup # installs cloudflared — needed for --anon/--domain tunneling
Needs Python 3.12+ and uv on PATH — sidepage
shells out to it to run whatever serve points at. Contributing? See
docs/DEVELOPMENT.md.
Quickstart
sidepage serve ./my-site --name demo # static site
sidepage serve app.py --name demo --auth token # gated behind a token
sidepage serve notebook.ipynb --name demo # editable Jupyter Lab, live kernel
sidepage secrets set MY_KEY # inject a secret, expose publicly
sidepage serve app.py --env MY_KEY --anon
sidepage pull hf:kalpi314/tiny_notes # pull a huggingface space and register as app
sidepage proxy --port 5173 --name my-vite-app # wrap `npm run dev`
sidepage serve app.py --anon --pwa --qr # installable, with a QR to scan
--type is auto-detected, and within code so is the framework —
Streamlit/FastAPI/MCP/Gradio each recognized by import and started with
their real launcher. That's why several lines above are an identical
sidepage serve app.py: dispatch reads content, not filenames. Both
commands block until Ctrl+C or sidepage stop <app-name>; --detach
backgrounds them (below).
How it works
- A local reverse proxy in front of the app's real port: enforces
--auth, counts usage, shows a holding page while the app boots, and proxies HTTP + WebSockets. The wrapped app needs zero sidepage-specific code. - A tunnel, per call:
--anonfor a free, no-account*.trycloudflare.comURL, or--domain <domain>for your own Cloudflare domain. Neither flag means127.0.0.1only.
Commands
| Command | What it does |
|---|---|
serve <target> |
Wrap and host a static dir, script, or app. |
proxy --port <n> |
Wrap an already-running local service. |
stop <app-name> |
Tear down a running app (serve or proxy). |
ls / status / usage |
List apps, check one, read its request counts. |
inspect [<app-name>] |
Interactive HTTP console against a running app. |
secrets set|list|remove |
Encrypted local vault for standing credentials. |
account domain set / new <name> |
Provision a BYO Cloudflare domain / scaffold a static site. |
app register|list|show|unregister|delete |
Save and manage serve invocations. |
pull <source> |
Fetch a Hugging Face Space and register it, without running it. |
promote, login, account status |
Not built yet; they say so. |
sidepage serve <target> [--type auto|code|static|notebook] [--name <app-name>]
[--auth open|token] [--anon | --domain <domain>] [--no-suffix] [--token <v>]
[--env <SECRET_NAME>]... [--timeout <s>] [--idle-timeout <s>] [--qr]
[--peer <role>=<app-name>]... [--pwa [--pwa-*]...] [--autoregister]
[--detach] [--json]
--auth token gates the app behind a header, query param, or cookie set by a
gate page. --env is repeatable and fails loud on an unknown name. --anon
and --domain are mutually exclusive. sidepage <command> --help has the rest.
Guides
- proxy.md — read before proxying anything public: forwarded headers, localhost-trust, per-framework fixes.
- timeouts-and-peers.md — auto-teardown, lazy start, wiring apps together.
- pwa.md — home-screen install and every
--pwa-*flag. - registry.md — saving invocations, override and merge semantics.
- byo-domain.md — your own Cloudflare domain, token scopes,
--no-suffix. - pull.md — Hugging Face Spaces, and the gate before running downloaded code.
For agents and harnesses
serve and proxy block by default, which is right for a terminal and
wrong for anything automated. Pass --detach --json and they return
as soon as the app is genuinely serving — or has definitively failed —
with one parseable line on stdout:
sidepage serve app.py --name demo --anon --detach --json
{"status":"running","app":"demo","pid":12345,"url":"https://random-words.trycloudflare.com","local_url":"http://127.0.0.1:8501","log":"~/.local/state/sidepage/logs/demo.log"}
Readiness is the registry entry the serving process writes once port,
subprocess, and tunnel are all up — not a URL spotted in a log — so
"running" means serving. A failed launch reports the real error and exits
- Under
--jsonall prose moves to stderr, so stdout pipes into a parser.
This repo is also a plugin
marketplace: in
Claude Code, /plugin marketplace add dolphinsdotdev/sidepage then /plugin install sidepage@sidepage. That installs
plugin/skills/sidepage-serve/, a
Skill teaching an agent when to
reach for serve vs proxy, which flags matter, and what to surface
before pointing a tunnel at something. It bundles no executables, so it
also installs cleanly under organization settings. For any other harness,
copy that directory to wherever it looks for skills.
Development
uv sync # install runtime + dev deps
uv run ruff check . # lint
uv run pytest # full suite (~2 min; mostly first-run dependency resolves)
Layout and test-machine requirements: docs/DEVELOPMENT.md.
Project status
Real and tested end to end: serve/proxy for static, code, and
notebook targets, open/token auth, --env secrets, --anon and
BYO-domain tunneling, stop/ls/status/usage, inspect, the app
registry, --timeout/--idle-timeout/--peer, --pwa/--qr, and
--detach/--json.
Not implemented, and says so rather than silently no-op'ing: brokered
tunneling, login/account status, the directory beyond this machine,
--guardrail, --auth network/oauth, MCP tool browsing in inspect,
and an OS-keychain vault backend. One known limitation, investigated not
fixed: HMR for a Vite target proxied through --anon.
Full breakdown in docs/CHECKLIST.md; rationale in
docs/OPEN_QUESTIONS.md.
Metadata
Release files for sidepage 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sidepage-0.2.4.tar.gz | 144.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sidepage-0.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 316.3 kB
Release files / sidepage-0.2.4.tar.gz
| Download URL | sidepage-0.2.4.tar.gz |
|---|---|
| Size | 144.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba4e23c08e17c84caf8c45f8425f80cf59c2b3b4b2cf3c2a2c9dfe0a9cd6b3b4
|
|
BLAKE2b-256 checksum How to use checksums |
69d5f2e751195b5082335321f5fd7e4fee02a6ef68f70069ab9fcd4b25d6d300
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / sidepage-0.2.4-py3-none-any.whl
| Download URL | sidepage-0.2.4-py3-none-any.whl |
|---|---|
| Size | 171.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2dd04061c29f3d00127b1e7c1992faa35360b34751b08373c4ae77cb5fcd571
|
|
BLAKE2b-256 checksum How to use checksums |
facd87be009a8c281a8d54b34711b18fdca9b52a9154bc975227c22c9928e6a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|