> ## Documentation Index
> Fetch the complete documentation index at: https://tesser.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI reference

> Every command, its flags, and what it prints

A few conventions apply to every command:

* A command that creates or selects something prints the resulting id as its
  only output on stdout. Progress messages and errors go to stderr, so a
  script or an agent can capture stdout without parsing prose.
* `exec`, `ssh`, and `logs` exit with the exit code of the remote command.
* Commands that list things accept `--json`.
* `stop`, `sleep`, and `rm` do not ask for confirmation. `make` is safe to
  retry: if the first attempt created a box, a retry waits for that box
  instead of creating another.

Box ids have the form `box_…`, org ids `org_…`, and API tokens `tsr_api_…`.

## Account

### `tesser login`

```text theme={"theme":"css-variables"}
tesser login [--org <org_id>] [--token <tsr_api_…>] [--no-browser]
```

Opens tesser.sh in the browser, where you sign in or create an account, choose
or create an org, and approve the CLI. The resulting token is saved to
`~/.config/tesser/credentials.json`, and that org becomes the default for
every later command. The token itself never passes through the browser.

`--org` skips the org picker and requires that specific org. `--token` saves
a token created on the dashboard instead of opening a browser, and must be
combined with `--org`. `--no-browser` prints the sign-in URL and waits; this
only works if a browser on the same machine can open it.

For every command, the org is taken from `TESSER_ORG` if set, otherwise from
the saved default, and the token from `TESSER_TOKEN` if set, otherwise from
the saved credential for that org.

### `tesser logout`

```text theme={"theme":"css-variables"}
tesser logout
```

Revokes the saved token on the server, deletes it from disk, and clears the
default org if it was this one. When the token comes from `TESSER_TOKEN`
there is nothing saved to remove, and the command says so.

### `tesser whoami`

```text theme={"theme":"css-variables"}
tesser whoami [--json]
```

Prints the signed-in email on stdout and, on stderr, which org is in use and
where the org and token were read from. Exits 1 with a hint when there is no
token.

### `tesser org`

```text theme={"theme":"css-variables"}
tesser org                  # print the current org id
tesser org ls [--json]      # list the orgs this laptop is signed in to
tesser org use <org_id>     # make one of them the default
```

`use` requires a saved credential for that org; run
`tesser login --org <org_id>` first if there is none.

### `tesser member`

```text theme={"theme":"css-variables"}
tesser member ls [--json]
tesser member add <email> [owner|member]
tesser member role <membership_id> <owner|member>
tesser member rm <membership_id>
```

These are available to owners only. `add` invites an email address with the
role `member` unless `owner` is given; the invitation is bound to the account
the first time that person signs in. `rm` removes the member and revokes every
token they hold. The last remaining owner cannot be demoted or removed.

### `tesser skill`

```text theme={"theme":"css-variables"}
tesser skill install [--project | --dir <path>]
tesser skill print
```

`install` writes the agent skill, which ships inside the binary, to
`~/.claude/skills/tesser/SKILL.md`. With `--project` it writes to the current
worktree's `.claude/skills/tesser/SKILL.md` instead, and with `--dir` to the
directory you name. `print` writes the skill to stdout.

## Boxes

### `tesser make`

```text theme={"theme":"css-variables"}
tesser make                                   # a workbench
tesser make <service>                         # an instance box for a service
tesser make <service> --ensure-running <sha>  # a pinned instance at a commit
```

Creates a box and prints its id. If a warm box is available in the pool it is
claimed in a second or two; otherwise a new machine is launched, which takes
about 90 seconds. If the command fails after the box was created but before it
came up, the id is still printed, and running the same `make` again resumes
waiting for that box rather than creating a second one.

`--ensure-running` creates the org's shared instance of `<service>` at the
given commit. If an instance at that commit is already running, the command
prints its box id and exits. If an instance at an older commit is running, the
new box is started, waits for the health check, takes over as the org's
default, and the old box is removed. This form requires a git worktree (the
commit is resolved locally), a manifest for the service, and values for every
name listed under `env.required`, set with `tesser env set`.

### `tesser ls`

```text theme={"theme":"css-variables"}
tesser ls [--json]
```

Lists every box in the org with its id, kind, service, power state, whether
it is online, and its public IP. Unclaimed pool boxes are not listed here;
their count is printed on stderr and `tesser pool ls` lists them.

### `tesser status`

```text theme={"theme":"css-variables"}
tesser status <box_id> [--json]
```

Prints one box in detail: owner, commit (for pinned boxes), power state,
whether the dev server is running and on which ports, IP addresses, creation
time, and time of last activity.

### `tesser sleep`

```text theme={"theme":"css-variables"}
tesser sleep <box_id>
```

Powers the box down immediately. Its id, disk, and private IP are kept. There
is no wake command: any command that targets a sleeping box wakes it first,
which takes about 40 seconds, and restarts its dev server.

### `tesser stop`

```text theme={"theme":"css-variables"}
tesser stop <box_id>
```

Stops the dev server and removes the box from the switcher, leaving the box
awake. Any box whose dependency pointed at this one keeps pointing at it and
gets connection refused until you run `dev` again; Tesser does not reroute
the dependency silently.

### `tesser rm`

```text theme={"theme":"css-variables"}
tesser rm <box_id>
```

Removes the box, its disk, and every dependency that pointed at it. This
cannot be undone and does not ask for confirmation.

### `tesser pool`

```text theme={"theme":"css-variables"}
tesser pool fill [N]      # keep N blank boxes warm (default 2, maximum 8)
tesser pool ls [--json]
tesser pool drain
```

A warm pool makes `make` take a second or two instead of about 90 seconds. An
unclaimed pool box removes itself after an hour.

## Code and commands

### `tesser sync`

```text theme={"theme":"css-variables"}
tesser sync <box_id> [--restart] [--force]
```

Copies the current worktree to the box. Only changed files are transferred,
and `.gitignore` is respected except for the files the manifest lists under
`[env] files`. If the sync would delete most of the files on the box, it stops
and reports it, since that usually means the box came from a different
worktree; `--force` overrides this check. `--restart` runs the manifest's
`setup` and `dev` again after the sync, whether or not anything changed, for
changes a running server cannot pick up by itself. Pinned boxes cannot be
synced to. `exec` and `dev` perform this sync on their own before they run.

### `tesser exec`

```text theme={"theme":"css-variables"}
tesser exec [box_id] [--in <service>] [--deps-of <service>] [--force] -- <cmd…>
```

Syncs the worktree, runs the command in `~/workspace` on the box, streams its
output, and exits with the command's exit code. Without a box id it uses the
worktree's workbench, creating one the first time. `--in` runs the command in
the named service's `root` directory. `--deps-of` gives the workbench the
named service's dependency ports for the duration of the command. Stdin is
forwarded to the command.

### `tesser dev`

```text theme={"theme":"css-variables"}
tesser dev [service] [--force]
tesser dev <box_id> [--force] [-- <cmd…>]
```

Starts the dev server for a service from the current worktree. It creates or
reuses the box for this worktree and service, syncs the worktree, makes sure
the manifest's dependencies are running, runs `setup` and then `dev`, waits
for the port to answer, connects the box to its worktree siblings, and prints
the box id. The first `dev` in a session selects its box in the switcher.
Running `dev` again replaces the server, which is also how you recover from a
crash. When the repository has exactly one manifest, the service name can be
omitted.

With a box id and a trailing command, `dev` runs that command instead of the
manifest's recipe. If the box has no manifest, the command must listen on
port 3000.

### `tesser logs`

```text theme={"theme":"css-variables"}
tesser logs <box_id> [-f]
```

Prints the dev server's last 200 lines of output, or follows the output with
`-f`. Pressing Ctrl-C stops following; the server keeps running.

### `tesser ssh`

```text theme={"theme":"css-variables"}
tesser ssh <box_id> [-- <cmd…>]
```

Opens an interactive shell in `~/workspace`, or runs a single command there.
Unlike `exec`, it does not sync first. Use it for inspection rather than
editing, since an edit made on the box is overwritten by the next sync.

## Services

### `tesser env`

```text theme={"theme":"css-variables"}
tesser env push <box_id> [file…]
tesser env set <service> KEY=VALUE…
tesser env unset <service> KEY…
tesser env ls <service>
```

`push` sends gitignored env files to a box: by default every `.env*` file at
the worktree root that git ignores, or the files you name. Pushed files stay
in place across later syncs. Listing a file under `[env] files` in the
manifest has the same effect on every sync.

`set`, `unset`, and `ls` manage the values Tesser holds for a service's
pinned instances. `ls` prints the names only, and marks any name listed under
`env.required` that has no value yet.

### `tesser override`

```text theme={"theme":"css-variables"}
tesser override <box_id> <service> <target_box_id>
tesser override <box_id> <service> --clear
```

Points one box's `<service>` dependency at a specific box. `<service>` must be
one of that box's dependencies. `--clear` returns the dependency to what
Tesser would choose on its own: the worktree sibling that serves that
service, if there is one, otherwise the org's shared instance.

## The daemon

### `tesser daemon`

```text theme={"theme":"css-variables"}
tesser daemon [--port <n>] [--widget-port <n>] [--allow-lan]
```

Runs the local proxy, which serves `localhost:3000` (the selected box), the
`<box_id>.localhost:<port>` addresses, and the switcher widget at
`localhost:4100`, and maintains the SSH tunnels and the laptop-side
dependency ports. It answers requests from your own machine and from
Tailscale peers; `--allow-lan` also admits the rest of the local network. If
a port is already in use, the error names the flag that moves it.

### `tesser use`

```text theme={"theme":"css-variables"}
tesser use <box_id|target_id>
```

Points `localhost:3000` at a box or a local target. The panel and the widget
do the same thing with a click.

### `tesser target`

```text theme={"theme":"css-variables"}
tesser target add <id> <port> [--label <text>]
tesser target rm <id>
tesser target ls [--json]
```

Adds a server running on this laptop to the switcher. `ls` marks the selected
entry with `*`.

## Other

### `tesser nuke`

```text theme={"theme":"css-variables"}
tesser nuke [--force]
```

Lists every box in the org and, after you type `yes`, removes all of them.
`--force` skips the prompt. This is the recovery command for when something
has gone wrong with the whole fleet.

### `tesser version`, `tesser help`

`--version`, `-v`, `--help`, and `-h` are accepted as well. An unknown command
prints the command list to stderr and exits 2.
