> ## 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.

# Services

> Describe how to run your app in one small TOML file per service

A service is anything that listens on a port, such as a web app, an API, or a
worker with a health endpoint. Tesser learns about your services from TOML
files in `.claude/skills/tesser/`, one per service. The filename is the
service name.

```text theme={"theme":"css-variables"}
.claude/skills/tesser/
├── SKILL.md     # the agent skill, if you installed it with --project
├── web.toml     # service "web"
└── api.toml     # service "api"
```

## The smallest manifest

```toml theme={"theme":"css-variables"}
# .claude/skills/tesser/web.toml
ports = [3000]

[run]
setup = "pnpm install"
dev   = "pnpm dev"
```

`ports` lists the ports the app listens on. `setup` prepares the box and runs
before every `dev`, so it needs to be safe to run repeatedly, and `dev` starts
the server. With this file in place, `tesser dev web` does everything else.

The [manifest reference](/docs/manifest) describes every field. The ones most
projects use are:

* `root = "apps/web"`, for a monorepo, so commands run inside the package
  directory.
* `[health] path = "/healthz"`, when a port being open is not enough to know
  the app is ready.
* `[deps]`, when this service calls another one. See
  [Wiring services together](/docs/wiring).
* `[env]`, for env files and the names of required secrets, described below.

## Starting, restarting, and stopping

`tesser dev web` creates or reuses the box for this worktree and service,
syncs the worktree, runs `setup`, starts `dev`, waits until the port answers,
and prints the box id. Running it again replaces the server, which is also
how you recover from a crashed one. `tesser dev <box_id> -- <cmd>` runs a
command you specify instead of the manifest's `dev`.

`tesser sync <box_id>` pushes your edits so the server's hot reload can pick
them up. `tesser sync <box_id> --restart` pushes the edits and then runs
`setup` and `dev` again, which you need for changes the running server cannot
take on its own, such as env changes, config changes, or a new dependency.

`tesser stop <box_id>` stops the server and leaves the box running.
`tesser logs <box_id> -f` follows the server's output.

## Env files

Sync follows `.gitignore`, so a file like `.env.local` is not copied to the
box. When a dev server fails because a variable is missing, this is usually
the reason. There are two ways to handle it.

The first is to list the file in the manifest, after which every sync
includes it:

```toml theme={"theme":"css-variables"}
[env]
files = [".env.local"]
```

The second is to push it once with `tesser env push <box_id>`, which sends
every gitignored `.env*` file at the worktree root, or the specific files you
name. After a push, run `tesser sync <box_id> --restart` so the server starts
with the new values.

## Secrets for shared instances

A pinned instance is the shared copy of a service that teammates' boxes
connect to (see [Wiring services together](/docs/wiring)). It has no laptop
attached, so its env values come from the org instead:

```sh theme={"theme":"css-variables"}
tesser env set api DATABASE_URL=postgres://… STRIPE_KEY=sk_…
tesser env ls api
```

Tesser stores the values and injects them when the instance starts. They
never appear in the manifest, which is committed to the repository. Listing a
variable in the manifest as `env.required = ["DATABASE_URL"]` makes a missing
value a clear error at start time instead of a crash later.

## Several services in one repository

Each service gets its own file. `tesser dev api` and `tesser dev web` then
create two boxes from the same worktree, and if `web.toml` lists `api` as a
dependency, the web box's `api` port is connected to your api box
automatically. When a repository has more than one manifest, `dev` and `make`
require the service name; when it has exactly one, that service is the
default.

Service names are shared across the whole org, so `api` refers to the same
service in every repository. If a second repository tries to register a name
that the first already owns, the registration fails with an error.
