The edge developer CLI. Write .py, run it, serve it, ship it. You never compile anything yourself. edge hosts the Edge Python runtime in a headless Chromium provisioned at install time, then runs your code against it. You point it at a file.
edge run app.py # run a script
edge serve # dev server with live reload
edge repl # interactive shell
edge test # run *_test.py files
edge init my-app # scaffold a project
edge add network # add a package to packages.json
edge remove network # remove a package from packages.json
edge build # bundle to dist/
edge uninstall # remove the binary, PATH entry, optionally the bundled browserThe runtime does the actual work. edge is the loop around it. It launches the headless browser, serves the runtime alongside your code, runs everything in that browser, and streams output back to your terminal. edge serve instead serves your project directory to your own browser, with live reload.
Install
# Prebuilt binary (recommended), compatible with macOS, Linux and WSL
curl -fsSL https://cdn.edgepython.com/cli/install.sh | sh
# Or from source (any platform with Rust and Cargo)
cargo install --path cliinstall.sh drops the binary at ~/.local/bin/edge and appends that directory (plus EDGE_CHROME_PATH, when the bundled browser is downloaded) to your ~/.bashrc or ~/.zshrc unless the file already has it. Open a new shell (or source the file it printed) and edge --version should work. Re-run the same curl … | sh line any time to upgrade. To remove everything: curl -fsSL https://cdn.edgepython.com/cli/uninstall.sh | sh (non-interactive: it leaves the bundled browser cache in place; edge uninstall asks before removing it).
install.sh also provisions a headless browser when none is reachable; details and overrides in Bring your own browser.
edge run: run a Python file
Runs a script and streams its output to the terminal. Bare imports resolve through packages.json; quoted relative imports load from your project files, resolved against the script’s own directory (for a script outside the working directory — an absolute path or ../ — they resolve against the working directory instead). Uncaught errors print a traceback to stderr and exit with code 1.
$ edge run hello.py
Hello from Edge Python
the sum is 42$ edge run broken.py
before
error: ZeroDivisionError: division by zero
--> <input>:2:1
|
2 | x = 1 / 0
| ^A raise SystemExit(code) with an integer (or no argument) exits cleanly with that code and no traceback. A string argument is reported as an error and exits 1.
Flags: --packages <file> (custom manifest). With no path, edge run reads from stdin if it is piped (cat hello.py | edge run). It errors out if stdin is a terminal.
edge serve: local dev server
A dev server for browser apps. Serves your project directory and reloads the page on any file change via an injected polling client.
$ edge serve
http://localhost:5173
watching .Flags: --port <n> (default 5173), --open (open the browser), --host <addr> (bind address, default 127.0.0.1).
--host 0.0.0.0 exposes the server on your LAN — the banner adds the network URL, so you can open the app from a phone on the same network:
$ edge serve --host 0.0.0.0
http://localhost:5173
http://192.168.1.34:5173
watching .edge repl: interactive shell
An interactive Edge Python shell for quick experiments.
$ edge repl
Edge Python 0.1.0 · .reset to start fresh · .exit, Ctrl+C or Ctrl+D to quit
>>> from math import sqrt, pi
>>> print(sqrt(2))
1.4142135623730951
>>> print([n * n for n in range(5)])
[0, 1, 4, 9, 16]
>>> .exitHistory (arrow keys) is supported. Each line runs as one input, so compound statements go on a single line (def double(n): return n * 2). .exit, Ctrl+C, or Ctrl+D quit. .reset wipes the session state and clears the screen. Expression results are not auto-printed. Use print() explicitly.
The worker keeps one interpreter alive across prompts: each input compiles and runs once, and imports, definitions, and mutations persist in place. Side effects never re-fire, and an input that raises keeps the effects it made before the error.
edge test: test runner
Discovers *_test.py files (recursively, skipping dist/ and hidden directories), runs each in a fresh interpreter inside one shared browser session, and prints a verdict per file. edge test path/ narrows discovery to a directory; edge test file.py runs one file.
my-app/
├─ packages.json
├─ main.py
├─ main_test.py
├─ lib/
│ ├─ parse.py
│ ├─ fixtures.py
│ └─ parse_test.py
└─ dist/edge test from the root runs both test files; edge test lib/ scopes discovery to that subtree. Each file’s quoted imports resolve from its own directory ("./parse.py", "../lib/parse.py"), clamped at the project root. Bare names (test, math, the rest of the registry) resolve everywhere. State never leaks between files: every file starts in a fresh interpreter.
$ edge test
PASS - adds
1 passed, 0 failed
(successful) main_test.py
PASS - parses
1 passed, 0 failed
(successful) lib/parse_test.py
2/2 files passed · 1.6sTest files declare tests with the test package and don’t need to call run() — the runner drives it after the file loads:
from test import test
@test("adds")
def t_add():
assert 1 + 1 == 2A file that calls run() itself also works: either way its SystemExit code is the file’s verdict, so the reported result never depends on parsing printed output. A file that registers no tests fails.
Exit codes: 0 every file passed, 1 a file failed or no *_test.py was found, 2 the browser session could not start.
edge init: scaffold a workspace
Scaffolds a ready-to-run project: an entry script, an HTML host page, and a manifest.
$ edge init my-app
created my-app/
├─ index.html
├─ main.py
└─ packages.json
cd my-app && edge serve--bare skips index.html for script-only projects.
edge add / edge remove: package manager
Manage packages.json by name. edge knows the official std (json, re, math, struct, test) and host (dom, network, storage, time) packages, so you don’t paste URLs. Most std packages are .wasm. test is pure Edge Python, so it resolves to test.py. See Official packages for the full catalog.
$ edge add math network
+ math std
+ network host
updated packages.json$ edge remove network
- network
updated packages.jsonPoint a package at a custom URL with edge add foo=https://example.com/foo.wasm.
edge build: portable bundle
Bundles your app into a self-contained dist/ for offline use or self-hosting. It vendors the runtime, the compiler.wasm, your scripts, and every package your scripts import, so nothing is fetched at runtime.
$ edge build
(successful) vendored runtime
(successful) fetched compiler.wasm
(successful) vendored packages
bundled to dist/
13 runtime files + compiler.wasm
2 packages
3 scripts
1.24 MB · 5.3sFlags: --out <dir> (default dist/).
edge uninstall
Removes the binary and its PATH entry. Asks before removing the bundled chrome-headless-shell cache (never touches system Chromium). The uninstall.sh one-liner in Install does the same non-interactively, leaving the cache in place.
Global flags
| Flag | Effect |
|---|---|
--packages <file> | Use a specific manifest instead of ./packages.json (read by run, repl, test, add, remove, build) |
--version / -V | Print version |
Ctrl+C cancels any running command cleanly.
Bring your own browser
edge uses, in order:
EDGE_CHROME_PATHif set.- The bundled
chrome-headless-shellin~/.cache/edge(override the root withEDGE_CHROME_DIR). - A system
google-chrome/chromium/chromeonPATH(or theCHROMEenv var). - Playwright’s Chromium, if installed.
install.sh downloads chrome-headless-shell when none is present. Linux arm64 has no build, so install Chrome/Chromium manually and point EDGE_CHROME_PATH=/path/to/chrome at it.