Platforms
Command line interface
The edge CLI runs on macOS, Linux and WSL. It embeds the engine the JS host loads, a compiler.wasm built from the same source and precompiled for the machine.
The system calls run in SpiderMonkey inside the binary. A program answers the same on both hosts.
edge run app.py # run a script, a .edge, or stdin (--web)
edge build # pack a portable .edge (--app, --web)
edge actor actor.yml # run a pool of cooperative actors
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 publish app.edge # send a packed .edge to the registry
edge add json # add a package to edge.json
edge remove json # remove a package from edge.json
edge lock # resolve edge.json into edge.lock
edge uninstall # remove the binary and the PATH entryInstall
The prebuilt binary is the recommended install.
curl -fsSL https://cdn.edgepython.com/cli/install.sh | sh- The script drops the binary at
~/.local/bin/edge. - It appends that directory to your
~/.bashrcor~/.zshrcunless it is already there. - Open a new shell and
edge --versionanswers. - Run the same line again to upgrade.
- The Linux binaries are static and run on any distribution.
To install one release instead of the latest, pass its tag, as in curl -fsSL https://cdn.edgepython.com/cli/install.sh | sh -s vX.Y.Z. Every release from 1.0 on keeps a frozen copy of itself on the CDN. A pinned install and a downgrade read that copy.
To build from source, run these two lines at the repo root.
make wasm-cli js lucide
cargo install --path clicargo installembeds whatmake wasm-cli js lucidebuilt and fetches nothing.EDGE_COMPILER_WASMpoints the build at anothercompiler.wasm.- The first build compiles SpiderMonkey from source. It needs clang, python3 and
llvm-objdumpon the path, which on macOS is a symlink to/usr/bin/objdump. - The full setup is in CONTRIBUTING on GitHub.
edge run
Runs a script and streams its output to the terminal. An uncaught error prints a traceback to stderr and exits 1.
$ edge run broken.py
before
error: ZeroDivisionError: division by zero
--> broken.py:2:1
|
2 | x = 1 / 0
| ^| Input | Example | Notes |
|---|---|---|
| A file | edge run app.py | Piped stdin feeds input() one line per call |
| Inline code | edge run -c 'print(1)' | Piped stdin feeds input() the same way |
| Piped stdin | cat app.py piped into edge run | Stdin is the script, and a terminal on stdin is an error |
| A packed artifact | edge run app.edge | A .edge or an app binary runs exactly as ./app would |
raise SystemExit(code) with no argument or an integer exits cleanly with that code and no traceback. A string argument surfaces as a regular error and exits 1.
Run flags
| Flag | Effect |
|---|---|
--events <f> | Each line of the file or FIFO feeds one receive(). End of input parks the script. |
--save-state <f> | On a wait the engine cannot serve, write a snapshot to the file and exit 0. |
--restore-state <f> | Boot from a snapshot instead of a script and keep running. An unreadable file exits 2. |
--preempt <n> | Yield every n loop back-edges and resume. A program with no suspension point stays snapshottable. |
--memory <MB> | Memory the run may hold, 256 by default. See Limits. |
--ops <n> | Operations the run may spend, 100 million by default. |
--web | Run on the browser host in headless Chrome instead of the native engine. |
- Without
--save-state, a script parked on a wait the engine cannot serve prints an error and exits 1. edge testandedge repltake--memoryand--opstoo.
Run on the browser host
--web runs the script on the browser host in headless Chrome. The JS host and the engine come out of the binary and are served on a loopback port. Only the modules the manifest declares by URL leave the machine.
- It drives a Chrome the system already has.
- Without one, it offers to download one the first time into
~/.local/share/edge/chromium.edge uninstalloffers to remove it. EDGE_CHROME_PATHnames a browser directly.--events,--save-state,--restore-stateand--preemptbelong to the native engine. They are refused beside--webrather than ignored.--memoryand--opsapply on both hosts.
Module resolution
Every run happens under the sandbox limits with a real wall clock. sleep() and timeouts wait in real time.
- A bare import resolves through
edge.json. Declare it withedge addand resolve it withedge lockfirst. - A declared version resolves through the
edge.lockbeside the manifest that declared it. - A relative import loads from disk relative to the importing file.
- A dotted import loads from the nearest
edge.jsondirectory.
Cache
| What | Where | Rule |
|---|---|---|
| Downloaded modules | ~/.cache/edge/modules | Each URL downloads once, 64 MB at most |
| Module pins | a .lock sidecar beside each download | Holds its SHA-256. A later run refuses on drift until you remove the entry. |
| Absent remote manifests | a .missing marker | A remote edge.json that answered 404 is skipped on later runs |
| Compiled plugins | ~/.cache/edge/plugins | Cranelift machine code keyed by the hash of the .wasm, compiled on first use |
$XDG_CACHE_HOME replaces ~/.cache when it is set.
Module errors
A module that fails to load fails at compile time, at the import that names it. (via <importer>) is appended when another module reached it.
| Module | Message |
|---|---|
A .so or .dylib | module 'fastmath' is not supported, ship a .wasm |
A .wasm that does not load | module 'fastmath' is not a valid plugin, <reason> |
A .js or .mjs | module 'charts' is JavaScript, ship a .py or a .wasm |
| A plugin, or a URL off the official CDN, in an eval group | module 'fastmath' is not available to untrusted eval runs |
edge serve
Serves the current directory for browser apps. It reloads the page on any file change through a small polling client injected into served HTML.
$ edge serve
http://localhost:5173
watching .| Flag | Default | Effect |
|---|---|---|
--port <n> | 5173 | Port to listen on |
--host <addr> | 127.0.0.1 | Bind address. With 0.0.0.0 the banner adds your LAN URL for a phone on the same network. |
--open | off | Open the app in a browser once the server is up |
edge repl
$ edge repl
Edge Python 0.9.5 · .reset to start fresh · .exit, Ctrl+C or Ctrl+D to quit
>>> from math import sqrt
>>> print(sqrt(2))
1.4142135623730951One interpreter stays alive across prompts.
- Imports, definitions and mutations persist. An input that raises keeps the effects it made before the error.
- Each line is one input. A compound statement goes on a single line, such as
def double(n): return n * 2. - Expression results are not printed. Use
print(). - Arrow-key history works within the session and is not saved to disk.
.resetwipes the session state..exit,Ctrl+CandCtrl+Dquit.
edge test
Discovers *_test.py files recursively and runs each in a fresh interpreter. It skips dist/ and hidden directories and prints a verdict per file. A directory argument narrows discovery to that subtree, and a file argument runs that file alone.
$ 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 · 0.0s- Test files declare tests with the
testpackage and do not callrun(). The runner drives it after the file loads. - The verdict comes from the
SystemExitcode of the file, never from parsed output. - A file that registers no tests fails. State never leaks between files.
- The project must declare
testinedge.json. Otherwise the runner stops withdeclare test in edge.json (edge add test).
| Exit code | Meaning |
|---|---|
0 | Every file passed |
1 | A file failed, or no *_test.py was found |
2 | The engine session could not start |
--web runs the same files on the browser host in headless Chrome. A package proves it works where the system calls run in a real browser. One browser serves the whole suite with a fresh page per file.
- Discovery, the
testdeclaration and the exit codes stay the same. - The engine comes out of the binary the way
edge run --webgets it.
edge init
Creates a ready-to-serve project. With no argument it scaffolds the current directory.
$ edge init my-app
created my-app/
├─ index.html
├─ main.py
└─ edge.json
cd my-app && edge serve--bareskipsindex.htmlfor script-only projects.- The manifest holds only the
edgeversion that scaffolded it. Nothing resolves untiledge addfills it.
edge add and edge remove
Edits edge.json by name. You never paste URLs. Each line prints the name and what it wrote.
$ edge add math
+ math 0.1.0
updated edge.json, run edge lock to resolve it| Form | Writes |
|---|---|
edge add json | The newest version in the registry |
edge add json@0.1.0 | That version |
edge add foo=https://example.com/foo.wasm | That URL |
- An unknown name or version aborts the whole command before any write.
- A URL goes to
importstoo. Each host tells a code module from a native module by the artifact. - A
.jsor.mjsURL is refused before anything is written. edge addkeepsextendsand every other key already in the manifest.edge removedeletes entries by name the same way.- Neither command touches
edge.lock.
edge lock
Turns each version edge.json declares into the URL and digest of that release.
It writes them to edge.lock beside the manifest. Commit the lock. Every other command reads it.
$ edge lock
+ json https://cdn.edgepython.com/pkg/json/0.1.0/app.edge
wrote edge.lock- This is the only command that asks a registry where a name points.
- It rebuilds the whole lock each time. Run it again after editing
edge.jsonto bring the two back in step. - It records a digest for a bare URL too. Those bytes are pinned as well.
edge run,edge testandedge buildread the lock and never resolve a version themselves.
edge build
Packs the project and its imports into one artifact.
| Command | Writes | Carries | Runs on |
|---|---|---|---|
edge build | app.edge | The project, its declared modules, each lock, the readme, the license and the docs | A host that has edge, or an actor eval group |
edge build --app | app, a standalone binary | The same, appended to this edge binary, without the docs | Any host of the same OS and CPU, offline, with nothing installed |
edge build --web | dist/ | The JS host under dist/js/, compiler.wasm, every declared module and a rewritten edge.json | Any browser, offline |
$ edge build
packed app.edge (2 files)
0.06 KB
run edge run app.edge or send it to an actor eval group--out <path> replaces the default output, app.edge, app or dist/ per mode.
A build refuses to pack while a declared version is unlocked. Run edge lock after every change to edge.json.
- Every mode carries each module the manifest declares by URL, with the files it imports beside it. The artifact is the whole program.
- A
.edgeand an app binary carry the lock beside each manifest they carry. The artifact resolves its own names. - A
--webbuild rewrites every root import to a vendored path, its relative imports and a siblingedge.jsonincluded. It leaves the root lock out ofdist/, and a nested package keeps its own. - An app binary honors the run flags
--save-state,--restore-state,--preemptand--events. - A
.edgeand an app binary carry theREADME.mdand anyLICENSEat the project root. - A
.edgecarries the pages whenedge.jsondeclares adocsdirectory. They sit under a reserved@docs/prefix no import can reach.
Pages must follow the package docs convention, or the build fails.
edge publish
Uploads a .edge packed by edge build. Set EDGE_TOKEN to a token from the settings first.
$ export EDGE_TOKEN=edge_pat_...
$ edge publish app.edge
published slugify 0.1.0
add it with edge add slugify
https://cdn.edgepython.com/pkg/slugify/0.1.0/app.edge- The artifact is the whole request. The name and version come from the manifest inside it.
- The CLI signs each publish with the token rather than sending it. The token never leaves your machine.
- A bundle that carries a
.jsor.mjsfile is refused before it is sent. - Publishing a version that already exists exits 2 instead of 1. A script can tell it apart from a failure.
What the registry reads, the naming rules, the quotas and the signing are in Publishing.
edge actor
Runs many programs as cooperative actors over a few threads, described by an actor.yml. The manifest, the groups, the server and untrusted code are in Actors.
edge actor actor.ymledge uninstall
Removes the binary and its PATH entry. The non-interactive equivalent is curl -fsSL https://cdn.edgepython.com/cli/uninstall.sh | sh.
Global flags
| Flag | Effect |
|---|---|
--manifest <file> | Use a specific manifest instead of ./edge.json |
--version, -v | Print the version |
--help, -h | Print the command list |
Ctrl+C cancels a running command with exit code 130.