Command line
yeet is a daemon (yeetd) and a CLI that talks to it. The daemon owns every running script; the CLI spawns them, attaches a terminal, and lets you list, inspect, and stop them. This page covers the commands you'll reach for day to day, and goes deep on yeet run, which is where most of the options live.
yeet run <script> [options] [-- args...] # run a script in a fresh isolate
yeet ps # list running isolates
yeet attach -c <id> # watch a running isolate's console
yeet kill <id> # stop one
Run yeet --help or yeet <command> --help for the full option list; the help text is kept current with the binary and is the source of truth when it disagrees with this page.
yeet run
yeet run [OPTIONS] [SCRIPT] [ARGS]...
What you can run
SCRIPT defaults to ., so yeet run alone in a directory with an index.js runs it.
| Form | Example | Notes |
|---|---|---|
| Local path | yeet run ./app.jsx | A file, or a directory with an entry point |
| Git repo | yeet run github:yeet-src/httpinspect | gh: is a short form |
| URL | yeet run https://example.com/tool.js | Fetched once per run |
| Stdin | yeet run - <<'EOF' … EOF | Plain JavaScript on stdin; relative imports resolve against the current directory |
Anything after the script is handed to the script as yeet.args, parsed minimist-style. Put a -- before them so a flag meant for your script is never mistaken for one of run's own:
yeet run github:yeet-src/heatsink -- --sort head # yeet.args: { sort: "head", _: [] }
cat script.js | yeet run - one two # yeet.args: { _: ["one", "two"] }
run's own flags can go before or after the script, but they must come before the first script argument. Once the parser meets something that isn't a run flag, everything after it belongs to the script:
yeet run app.js -p console:ringbuf # -p is run's
yeet run app.js -- -p console:ringbuf # -p is the script's
yeet run app.js --sort mem -p console:ringbuf # -p is the script's: --sort started its arguments
Terminal or pipe
run picks one of two modes from where stdout is going:
- Terminal. When stdout is a terminal the script gets a pseudo-terminal (PTY). Everything it draws lands in your terminal, your keystrokes go to it, and
console.logoutput is mirrored into the same terminal. This is the mode a TUI tool wants. - Pipe. When stdout is a pipe or a file there is no PTY.
console.loglines go to stdout andconsole.errorlines to stderr, one line per call, soyeet run tool.js | grepandyeet run tool.js > out.txtbehave like any other command. A crash exits non-zero. There is nottyglobal in this mode; see Terminal.
-T / --no-tty forces pipe mode in a terminal. Ctrl+C stops the script in either mode.
Options
| Flag | What it does |
|---|---|
-w, --watch | Re-spawn when the script or any sibling source file changes. Survives crashes and reload errors. Local paths only, and needs a terminal. |
-d, --detach | Spawn and return immediately. The script keeps running under the daemon with no terminal; see it with yeet ps, watch its console with yeet attach -c, stop with yeet kill. |
-N, --name <NAME> | Name the isolate, as shown by yeet ps. Defaults to the script argument as you typed it. |
-p, --portal <SPEC> | Route the script's tty or console somewhere other than your terminal. Repeatable. See Portals below. |
-H, --heap-limit <SIZE> | Old-generation heap budget, e.g. 512MiB, 1GiB, 2GB. Default is 384MiB. |
-M, --mapping <HOST:ISOLATE> | Add an entry to the isolate's path mapping table. Repeatable. |
-b, --buffer <BYTES> | Size of the shared-memory output ring between the daemon and this terminal. |
-y, --yes | Auto-accept consent prompts, for unattended runs. |
-q, --quiet | Suppress progress banners such as git clone output and watch status lines. |
-%, --inflation-ratio <PERCENT|unlimited> | Cap on how far a fetched archive may inflate when decompressed. Default 20000 (200x). |
Portals
A running script has two output channels:
- tty is the pseudo-terminal: the rendered screen, cursor movement, colors, and the keystrokes going back in. It's what a TUI tool draws to.
- console is the stream of
console.log/console.warn/console.errormessages, one per call.
By default the tty is wired to your terminal through a local shared-memory ring, and the console is mirrored into that same terminal. -p rebinds either channel to a different backend, so a script can keep rendering while its screen is served to a browser, its logs are tailed by curl on another machine, or both.
Syntax
-p <channel>:<backend>
Channels are tty and console. The flag repeats, once per channel.
| Backend | tty | console | Meaning |
|---|---|---|---|
ringbuf | default | ✓ | A local shared-memory ring to the yeet process. For console, this prints messages to stdout and stderr with no PTY, the same as piping. |
attached | default | Mirror console messages into the tty. | |
ws://<ip>:<port>[/path] | ✓ | ✓ | The daemon listens on that address and serves the channel to WebSocket clients. |
http://<ip>:<port>[/path] | ✓ | The daemon listens on that address and streams the console to plain HTTP GET clients. |
Address rules for ws:// and http:// targets:
- The host must be a literal IPv4 address, such as
127.0.0.1or0.0.0.0. Hostnames are rejected. - The path is optional and matched verbatim: a listener at
/aanswers/aand nothing else, and any other path is a 404. No path means/. - Query strings and fragments are rejected rather than silently dropped.
- Two channels can share one port by using different paths, for example
ttyat/ttyandconsoleat/console. Give them the same address and path and only the tty is served.
What a client receives
A tty served over WebSocket speaks a small protocol:
- Binary frames from the daemon are the PTY's output bytes, exactly what the script wrote to the screen.
- Binary frames from the client are keystrokes, written to the PTY as-is.
- A text frame of
{"type":"resize","rows":24,"cols":80}from the client resizes the PTY. The script sees the new size throughtty.size()and the layout reflows.
Several clients can be connected at once and all see the same screen.
A console served over WebSocket sends one text frame per console message: the message text with a trailing newline, nothing else.
A console served over HTTP answers a GET with text/plain, transfer-encoding: chunked, one message per line. The response stays open until the script ends or the client leaves. Nothing flows the other way.
Lifetime
$ yeet run pulse.js -p tty:ws://127.0.0.1:7777
Redirecting tty to ws://127.0.0.1:7777/
The daemon is listening. The yeet run process stays in the foreground and doubles as a connection log:
18:12:20.843: Peer 127.0.0.1:55250 connected on tty
18:12:23.821: Peer 127.0.0.1:55250 disconnected on tty
Ctrl+C there kills the script and frees the port. If the yeet process dies without sending a kill, the daemon reaps the script and closes the listener anyway, so a redirect can't leak. A client disconnecting never affects the script.
With --detach the script is daemon-owned and outlives the yeet command. Clients can connect and disconnect freely. yeet ps lists the redirect next to the isolate, and yeet kill <id> stops it and frees the port.
Watch mode
--watch re-spawns the script on every save, and each respawn reserves the redirect again, so a redirected console works under watch and its URL stays stable:
yeet run ./app.jsx -w -p console:http://127.0.0.1:8080
A redirected tty cannot be respawned in place, so --watch with -p tty:ws://… is refused.
Combinations that are refused
run checks the flag set up front and refuses these rather than silently dropping output:
| Combination | Why |
|---|---|
-p tty:ws://… with -t or -T | The -p spec already decides where the tty goes. |
-p tty:ws://… with --watch | A redirected tty can't be respawned. |
-p tty:ws://… with --buffer | --buffer sizes a local ring; there is none when the tty is redirected. |
-p tty:ws://… with -p console:ringbuf | ringbuf pipes the console to this process, which isn't holding a terminal. |
-p tty:attached or -p tty:http://… | Both backends are console-only. |
| The same channel twice | Each channel binds once. |
Portals in practice
These recipes show what -p is for, with what each side sees. Every output below was captured from a real run; only ports and timestamps differ from what you'll get.
Forward a TUI to a browser
Anything built on yeet:tui can be forwarded as-is. Save this as clock.jsx:
import { Box, Text, signal } from "yeet:tui";
const ticks = signal(0);
setInterval(() => {
ticks.update((n) => n + 1);
console.log(`tick ${ticks.get()}`);
}, 1000);
export default () => (
<Box border="round" padding={1}>
<Text>{() => `ticks: ${ticks.get()}`}</Text>
</Box>
);
Serve the screen on one path and the log on another, sharing a port:
$ yeet run clock.jsx -p tty:ws://127.0.0.1:7777/tty -p console:ws://127.0.0.1:7777/console
Redirecting tty to ws://127.0.0.1:7777/tty
Redirecting console to ws://127.0.0.1:7777/console
Nothing is drawn in this terminal and nothing is logged here either; both channels are waiting for clients. A page with an xterm.js terminal and a <pre id="log"> needs this much script:
const term = new Terminal();
term.open(document.getElementById("screen"));
const tty = new WebSocket("ws://127.0.0.1:7777/tty");
tty.binaryType = "arraybuffer";
tty.onmessage = (e) => term.write(new Uint8Array(e.data)); // screen bytes
term.onData((data) => tty.send(new TextEncoder().encode(data))); // keys back to the PTY
tty.onopen = () => tty.send(JSON.stringify({ type: "resize", rows: term.rows, cols: term.cols }));
const con = new WebSocket("ws://127.0.0.1:7777/console");
con.onmessage = (e) => { document.getElementById("log").textContent += e.data; }; // one line per message
The box renders at the browser terminal's size, keys typed into it reach the script, and each console.log line lands in the log pane. Back at the run, each connection shows up:
18:35:54.945: Peer 127.0.0.1:36756 connected on tty
18:35:54.945: Peer 127.0.0.1:36772 connected on console
If you only forward the tty, the console lines print in the terminal you ran from instead:
$ yeet run clock.jsx -p tty:ws://127.0.0.1:7777
Redirecting tty to ws://127.0.0.1:7777/
tick 1
tick 2
A script for the rest of the recipes
The remaining recipes use this script, which draws on the tty and logs a JSON line per tick. The tty guard lets it also run where there is no PTY, such as a pipe:
// pulse.js
let n = 0;
setInterval(() => {
n++;
if (typeof tty !== "undefined") {
tty.clear();
tty.write(style.bold(`pulse ${n}`) + "\n");
}
console.log(JSON.stringify({ tick: n, at: Date.now() }));
}, 1000);
Run it plainly and the counter and the JSON lines share one terminal:
$ yeet run pulse.js
pulse 3
{"tick":3,"at":1790618854494}
Split the screen from the logs
A TUI tool and its logs fight over one terminal: every console.log scribbles over the screen, or gets hidden behind it. -p takes one channel each, so the screen and the log go to different places and neither has to compromise.
Redirect only the console and the screen stays with you:
$ yeet run pulse.js -p console:http://127.0.0.1:8080/pulse
Redirecting console to http://127.0.0.1:8080/pulse
pulse 1
Any HTTP client in another terminal, or on another machine if you bound 0.0.0.0, gets the log lines and nothing else:
$ curl -N http://127.0.0.1:8080/pulse
{"tick":2,"at":1790618948886}
{"tick":3,"at":1790618949887}
Or redirect both, the screen to a browser and the log to curl, and use the starting terminal for neither:
$ yeet run pulse.js -p tty:ws://127.0.0.1:7777 -p console:http://127.0.0.1:8080
Redirecting tty to ws://127.0.0.1:7777/
Redirecting console to http://127.0.0.1:8080/
Let a pipeline tail your logs
Something that only speaks HTTP needs the log: a log shipper, a health check, an agent with curl. Serve the console over HTTP and keep the tty where it is:
$ yeet run pulse.js -p console:http://127.0.0.1:8080/pulse
Redirecting console to http://127.0.0.1:8080/pulse
pulse 1
$ curl -N http://127.0.0.1:8080/pulse
{"tick":2,"at":1790618948886}
{"tick":3,"at":1790618949887}
The response is text/plain, chunked, one message per line, and stays open until the script ends or the reader leaves. A reader that leaves does not affect the script. When the script ends, the response ends with it and curl exits 0.
Run headless on a server, look in later
Start the tool once and walk away:
$ yeet run pulse.js -p tty:ws://0.0.0.0:7777 --detach
Redirecting tty to ws://0.0.0.0:7777/
detached; stop: kill 2087
The yeet command has exited. The script is still running under the daemon and the listener is still up:
$ yeet ps
ID STATUS UPTIME EVAL HEAP REDIRECTS NAME
2087 running 64ms 4ms 276.2 KiB tty:ws://0.0.0.0:7777/ pulse.js
Browsers can connect and disconnect whenever they like. To glance at the log from the server itself, yeet attach -c is a read-only view, and Ctrl+C there leaves the script running:
$ yeet attach -c 2087
{"tick":1,"at":1790620057918}
{"tick":2,"at":1790620058917}
When you are done:
$ yeet kill 2087
Iterate with the log served
While you edit a script, keep its console served so a second terminal or an agent can follow along. Watch mode reserves the listener again on every respawn, so the URL stays stable across saves:
$ yeet run pulse.js -w -p console:http://127.0.0.1:8080
[Watching /home/you/pulse.js]
Redirecting console to http://127.0.0.1:8080/
pulse 1
$ curl -N http://127.0.0.1:8080
{"tick":2,"at":1790619018821}
{"tick":3,"at":1790619019821}
Save pulse.js and the run prints [Reloaded Isolate …] and redirects again. The curl stream ends with the old isolate; run it again to follow the new one.
Get plain lines with no terminal at all
For a script that only logs, skip the PTY even in a terminal, so the output is line-buffered text you can redirect:
$ yeet run pulse.js -p console:ringbuf > pulse.ndjson
$ cat pulse.ndjson
{"tick":1,"at":1790619026764}
{"tick":2,"at":1790619027763}
This is the same mode yeet run pulse.js > pulse.ndjson gets automatically. console.error lines go to stderr, and a crash exits non-zero.
Managing running scripts
| Command | What it does |
|---|---|
yeet ps | List running isolates: id, status, uptime, eval time, heap, any -p redirects with their URLs, and name. |
yeet attach <id> | Attach your terminal to a running isolate's tty, as if you had started it here. Ctrl+C stops the script, the same as it would in the original terminal. |
yeet attach -c <id> | Read-only view of the isolate's console stream. Ctrl+C detaches and leaves it running. |
yeet kill <id> | Stop an isolate. |
Other commands
| Command | What it does |
|---|---|
yeet status | Check the daemon's status. |
yeet login / yeet logout / yeet whoami | Platform authentication for this host. |
yeet new | Scaffold a new project from the template. |
yeet graph dump | Print the system graph's GraphQL schema (SDL). See Building tools. |
yeet trace | Tail CLI logs or the kernel's trace pipe. |
yeet service … | Manage services. yeet service --help for the sub-commands. |
yeet toolchain | Manage the BPF toolchain. |
yeet version | Print the build and version. |
Global options such as --socket, --config, and --api-url go before the sub-command and are listed in yeet --help.