---
title: "CLI reference"
description: "Every tatolab command, its options, and what it prints."
type: "docs-page"
url: "https://tatolab.com/docs/reference/cli"
site: "Tatolab"
section: "Reference"
---

# CLI reference

> Every tatolab command, its options, and what it prints.

`tatolab` creates projects and talks to the machine's runtime. Every command except `new`, `enable-virtual-camera`, and `logs` reading a file from disk needs the runtime running; without it, they stop at once with:

```txt
error: no runtime is running on this machine: nothing answers at <socket>. Start one by running `tatolabd` in a terminal.
```

If another user's runtime holds the machine, the message names it. Run `tatolab <command> --help` for the same details in your terminal, and `tatolab --version` for the version.

## Projects and streams

### `tatolab new`

```txt
tatolab new <DIRECTORY> [--test-pattern]
```

Writes a working stream project into `DIRECTORY`: `stream.py`, `nodes/` (a GPU effect and a CPU meter), `pyproject.toml`, `.python-version` (3.12) and `.gitignore`. The stream shows the camera, inverted, in a window.

| Option | |
|---|---|
| `--test-pattern` | Use the built-in color bars instead of the camera, for a machine with no camera |

It refuses to write into a directory that already has any of those files, and names them. Afterwards: `cd DIRECTORY`, `uv sync`, `tatolab dev`.

### `tatolab run`

```txt
tatolab run [TARGET] [-f FILE] [--dir DIR] [--name NAME] [-d]
```

Asks the runtime to load a stream. The runtime runs the stream function with the project's `.venv` and starts the graph it builds. Without `-d`, the stream is attached: its log prints in your terminal, and Ctrl-C, closing the terminal or killing the command stops it.

| Option | |
|---|---|
| `TARGET` | Which stream: `<file>.py[:<function>]` or `<module>:<function>`. Default: the one `@stream` in `stream.py`. |
| `-f`, `--file FILE` | Load from another file instead of `stream.py` (not with `TARGET`) |
| `--dir DIR` | The project folder, with its `.venv`. Default: the current folder; parent folders aren't searched. |
| `--name NAME` | Load the stream under this name instead of its function's |
| `-d`, `--detach` | Hand the stream to the runtime to keep, print one line, and return |

With `-d`, it prints `<stream> kept (project <dir>)`. A kept stream runs until `tatolab stop` or `tatolab rm`, and the runtime loads it again every time it starts. Running `run -d` again for the same project and function replaces the kept record. A name already in use is refused, naming the project that has it. A kept stream holds its name even while it's stopped or failed, until `tatolab rm`.

### `tatolab dev`

```txt
tatolab dev [TARGET] [-f FILE] [--dir DIR] [--name NAME]
```

`run`, attached, plus a watch on the project's `.py` files and `pyproject.toml`: each save loads the stream again. A save that fails to load prints the error and leaves the previous version running. If the runtime goes away, `dev` waits for it and loads the stream again. Options as for `run`, without `-d`.

### `tatolab streams`

```txt
tatolab streams
```

Lists the streams the runtime holds:

```txt
NAME            STATE     NODES  PROJECT
motion_watcher  kept      6      /home/you/motion-watcher
garage          failed    -      /home/you/cameras

garage failed: <the reason>. `tatolab start garage` retries it, `tatolab rm garage` forgets it.
```

States are `attached`, `kept`, `stopped` and `failed`. With no streams, it prints `No streams in this runtime.`

### `tatolab stop`

```txt
tatolab stop <STREAM>
```

Unloads a stream. A kept stream is recorded as stopped and stays stopped across restarts: ``<stream> stopped; it stays stopped across restarts until `tatolab start <stream>` ``. A stopped kept stream still holds its name. Stopping an attached stream ends it, like Ctrl-C in its terminal.

### `tatolab start`

```txt
tatolab start <STREAM>
```

Loads a stopped kept stream again, or retries a failed one. Prints `<stream> started (<n> nodes)`.

### `tatolab rm`

```txt
tatolab rm <STREAM>
```

Unloads a stream and forgets it. It's the only command that removes a kept stream's record.

### `tatolab expose`

```txt
tatolab expose <STREAM> <NODE> <PORT> [--public | --remove]
```

Sets how far one output port can be read from outside its stream, while it runs: private by default, public with `--public`, internal again with `--remove`. A reader the new level no longer allows is cut off at once. For a kept stream, the level is recorded and takes precedence over what the stream function exposes, across restarts. See [Connect streams](https://tatolab.com/docs/share/between-streams).

## Observing

### `tatolab graph`

```txt
tatolab graph [--stream STREAM]
```

Prints the live graph as JSON: nodes, ports, links, states and counters. Without `--stream`, every loaded stream, under the machine's name as `machine`; with it, that stream alone. See [Inspect a running stream](https://tatolab.com/docs/run/inspect#tatolab-graph) for the fields.

### `tatolab tap`

```txt
tatolab tap <CHANNEL> [--stream STREAM] [--count N] [--max-bag-bytes BYTES]
```

Collects up to `--count` bags an output sends within half a second, without slowing it. On a quiet output, it returns a partial sample instead of waiting. The output needs a reader, a link in its own stream or another stream reading it, or the tap is refused with `no tappable channel named …`. Exposure doesn't matter.

| Option | |
|---|---|
| `CHANNEL` | The output's address: `<stream>/<node>/<port>`, `<node>/<port>` with `--stream`, or `<machine>/<stream>/<node>/<port>` naming this machine |
| `--stream STREAM` | The stream, when the channel doesn't name it |
| `--count N` | How many bags to collect, at most |
| `--max-bag-bytes BYTES` | The largest bag returned whole. A bigger bag comes back marked truncated and can't be decoded. |

### `tatolab logs`

```txt
tatolab logs --stream <STREAM> [-f] [filters]
tatolab logs <RUNTIME_ID-STREAM> [-f] [filters]
tatolab logs --list
```

With `--stream`, reads a loaded stream's log records from the runtime. With a log name, reads that stream's log file from disk, in the project's `.streamlib/` folder; run it in the project folder, and use `--list` to see the names.

| Option | |
|---|---|
| `-f`, `--follow` | Keep following new records |
| `--level LEVEL` | Only this severity and above: `trace`, `debug`, `info`, `warn`, `error` |
| `--source SOURCE` | Only records from `rust` (the runtime and built-ins) or `python` (your nodes) |
| `--processor ID` | Only records from one node, by its ID |
| `--pipeline ID` | Only records carrying this `pipeline_id`, a field the runtime sets on some of its own records |
| `--rhi` | Only GPU-layer records |
| `--intercepted-only` | Only output captured from `print()`, stdout and stderr |

### `tatolab exchange`

```txt
tatolab exchange <SURFACE_ID> --out <DIR>
tatolab exchange --channel <CHANNEL> [--stream STREAM] [--count N] [--every N] [--field NAME] --out <DIR>
```

Turns frames into exact, full-resolution PNG files, and prints each file's path. With a surface ID (as a bag carries it, such as `7#42`), exchanges that frame. With `--channel`, samples an output and exchanges the frame each bag names; no window or display is needed.

| Option | |
|---|---|
| `--out DIR` | Where to write the PNGs; created if needed, never emptied |
| `--channel CHANNEL` | An output's address, as for `tap` |
| `--count N` | Frames to save. Default 1. |
| `--every N` | Save every Nth sampled bag. Default 1. |
| `--field NAME` | The bag key holding the surface ID. Default `surface_id`. |
| `--stream STREAM` | The stream, when the channel doesn't name it |

## Agents

### `tatolab mcp`

```txt
tatolab mcp
```

Connects an MCP client to the runtime over standard input and output. You don't run it yourself; your client does:

```sh
claude mcp add tatolab -- tatolab mcp
claude mcp add lab -- ssh lab-machine .local/bin/tatolab mcp
```

A stream the client loads attached lives as long as this connection. See [Agents and MCP](https://tatolab.com/docs/share/agents).

## Setup

### `tatolab enable-virtual-camera`

```txt
tatolab enable-virtual-camera [--print]
```

Linux only. Grants this machine's users the permission `VirtualCameraSink` needs to create v4l2loopback cameras: it loads the module with no devices at boot, and lets the logged-in user create cameras. It's one privileged step, behind your desktop's password prompt (`sudo` in a terminal without a desktop). `--print` shows the files and commands without changing anything. See [Virtual camera](https://tatolab.com/docs/build/virtual-camera).

## The runtime: `tatolabd`

`tatolabd` is the runtime itself. The Mac app and the Linux installer run it for you; after a source build, run it in a terminal of its own:

```sh
tatolabd
```

It runs in the foreground until Ctrl-C or a stop signal. A second `tatolabd` on the same machine is refused with a message naming the first one's user, process ID and executable.
