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

# CLI reference

> Every levers9 command, grouped by what you are doing

`levers9 --help` prints the command list; `levers9 COMMAND --help` prints its options. Every command takes
`-c, --context NAME` to target a context other than `default`; a context is one environment's token, so
`-c staging` deploys into staging.

## Install and connect

| command | what it does |
| - | - |
| `uv tool install levers9` · `uv add levers9` · `pip install levers9` · `brew install gnomefin/levers9/levers9` | install the CLI and SDK (uv first: isolated tool, or a project dependency) |
| `levers9 login` | sign in through the browser (Google); saves your workspace token as the `default` context |
| `levers9 login --env staging --name staging` | the same, for one [environment](https://levers9.mintlify.site/dashboard/workspaces.md) of your workspace, saved as its own context |
| `levers9 config create [--tls]` | add a context by hand: controlplane host, port and a token an admin gave you. `--tls` when that controlplane speaks TLS on a port other than 443 |
| `levers9 config list` · `select NAME` · `delete NAME` | switch between clusters |
| `levers9 config export` | print the current workspace configuration |
| `levers9 token list` · `create` · `delete ID` · `toggle ID` | manage access tokens for the workspace (admins). `list --format json` shows each token's `key_hint` (its first and last characters), not its key; `create` shows the key once |

## Run and deploy code

| command | what it does |
| - | - |
| `python app.py` | run the jobs in a script; `.remote()` and `.map()` execute on the cluster |
| `levers9 deploy app.py:fn --name NAME` | deploy a job, endpoint, queue, cron or box with a versioned URL |
| `levers9 deploy --dockerfile Dockerfile --name NAME` | deploy a service from a Dockerfile, an image or a command instead of a handler |
| `levers9 serve app.py:fn` | temporary URL with hot reload while you develop (`-t SECONDS` inactivity timeout) |
| `levers9 run [app.py:fn]` | a one-off container; `--sync` mirrors the current directory and watches for changes, `--detach` returns immediately |
| `levers9 shell [app.py:fn]` | an interactive shell in a container with the same blueprint and mounts as the handler; `--container-id ID` joins a running one |
| `levers9 dev` | a remote development environment |
| `levers9 db` | create and manage database services |

Deploy options worth knowing: `--replicas N` (fixed replica count), `--rollout auto|blue-green|replace` for
always-on services, `--always-on / --scale-to-zero`, `--tcp` for raw TCP through a box, `--json` for machine-readable
output. Model endpoints take `--llm` plus `--llm-model-id`, `--llm-engine vllm|sglang|tensorrt-llm`,
`--llm-context-length` and `--llm-served-model-name` so the controlplane can route and meter them as OpenAI-compatible
services.

## Watch what is running

| command | what it does |
| - | - |
| `levers9 deployment list` · `stop ID` · `start ID` · `delete ID` · `scale ID` | the versioned things you deployed |
| `levers9 task list [--filter status=error]` · `stop ID` | individual calls: pending, running, complete, error |
| `levers9 container list` · `stop ID` · `attach ID` · `checkpoint ID` | live containers; `attach` streams a container's output |
| `levers9 worker list` · `cordon` · `uncordon` · `drain` | the worker processes on each node and their maintenance state |

## Files: volumes

Volumes are persistent folders mounted into containers with `Volume(name=..., mount_path=...)`. Paths are `VOLUME/path/inside`.

```bash theme={null}
levers9 volume list | create NAME | delete NAME
levers9 ls  myvol/subdir                        # list; shell wildcards work: 'myvol/subdir/*.txt'
levers9 cp  ./data levers9://myvol/data         # upload a file or folder
levers9 cp  levers9://myvol/out/metrics.json .  # download
levers9 mv  myvol/a myvol/b                     # move inside one volume
levers9 rm  'myvol/tmp/*.json'                  # delete
```

`cp` uses multipart transfers through the cluster's object store when the controlplane supports them; remote paths
are then written `levers9://VOLUME/path`. Older controlplanes only stream uploads through the controlplane (`levers9 cp
./data myvol/data`, no downloads). If a multipart transfer fails with a DNS error for an in-cluster host, pass
`--no-multipart` to stream through the controlplane instead.

## Secrets

```bash theme={null}
levers9 secret list
levers9 secret create OPENAI_API_KEY sk-...     # attach with secrets=["OPENAI_API_KEY"], read from os.environ
levers9 secret show NAME | modify NAME VALUE | delete NAME
```

## Datasets

The [Data Lab](https://levers9.mintlify.site/lab/datasets.md) from a terminal. These talk to the dashboard API with the same workspace token,
so the CLI and the Datasets page always show the same datasets.

```bash theme={null}
levers9 dataset ls
levers9 dataset upload ./recordings --to calls-sept            # onto the datasets volume (uploads/calls-sept)
levers9 dataset inspect uploads/calls-sept                     # columns, a sample, and a suggested role for each
levers9 dataset create calls-sept --kind stt --path uploads/calls-sept \
        --map audio=FileName --map text=GroundTruthText       # --map is optional: columns are matched by name
levers9 dataset destinations                                       # where you can send it
levers9 dataset publish calls-sept --to staging --only-validated     # a bucket Levers operates: no credential
levers9 dataset publish calls-sept --bucket my-corpora --secret MY_SA # your own bucket
levers9 dataset config calls-sept                              # what it was built with
levers9 dataset set-text turn-ends texts.jsonl                  # a sound dataset: the text under each clip ({"id", "text"} lines)
levers9 dataset pii scan calls-sept                            # find personal data in its text, print the report
levers9 dataset pii scan calls-sept --names-model names-qwen   # also ask a model on your own cluster for names
levers9 dataset pii report calls-sept                          # the last report again; --json for the API's answer
levers9 dataset pii policy calls-sept [off|flag|mask]          # who reads the text as it is; no value prints the policy
levers9 dataset export calls-sept --text masked -o calls-sept-masked.tsv   # the masked copy, as a share link gets it
levers9 dataset pii review draw calls-sept                     # a random sample of rows for people to review
levers9 dataset pii review status calls-sept                   # how far the review is, and what it says of the scan
levers9 dataset pii review link calls-sept --for Fatima        # a link for a reviewer; it shows original text
levers9 dataset links calls-sept                               # every share link, and what each one opens
levers9 dataset pii export calls-sept --require-measured       # a file to train on, with the personal data taken out
levers9 dataset pii exports calls-sept                         # the exports made, newest first
levers9 dataset pii export-delete calls-sept FILE              # remove one export and its report
```

`inspect` prints the `--map` flags it suggests, so the usual flow is inspect, check the sample, then paste them
into `create`. `create` and `publish` follow the run and print its stages; `--no-wait` returns immediately, and
Ctrl+C stops watching without stopping the run.

`--map` takes two more roles for a dataset of any kind, `known_phone` and `known_name`: the row's own phone
number and the customer's name. They're handed to the personal-data scan so it can find them in the text, and
they're never stored in the rows and never exported
([Known values](https://levers9.mintlify.site/lab/datasets.md#known-values-the-customers-phone-number-and-name)). Give one twice when it sits in
two columns:

```bash theme={null}
levers9 dataset create calls-oct --kind llm --path uploads/calls-oct --map context=Transcript \
        --map known_phone=Mobile --map known_name=FirstName --map known_name=LastName
```

`pii scan` runs the [personal-data scan](https://levers9.mintlify.site/lab/personal-data.md#running-a-scan) and prints its report: rows, rows with
findings, findings by type and by how each was found, and what the scan learned from the dataset as a whole
(names where a caller's script puts them, names given as an answer, Latin names found written in Arabic). A
small dataset is scanned at once. A large one is
scanned on the cluster, and the command follows it until it's done (`--no-wait` comes straight back, and
`pii report` shows how it's going). The scan runs on the platform, and with no option it asks no model.

A model can be asked for the names the scan misses, the ones said with nothing beside them
([Asking a model for names](https://levers9.mintlify.site/lab/personal-data.md#asking-a-model-for-names)). A scan takes one of the two:

```bash theme={null}
levers9 dataset pii scan calls-sept --names-model names-qwen     # an endpoint of this workspace, by name or URL
levers9 dataset pii scan calls-sept --names-model last           # the model that read this dataset last
levers9 dataset pii scan calls-sept --names-model names-qwen --on-cluster   # as jobs on the cluster: for an endpoint that has to wake up
levers9 dataset pii scan demo-calls --outside-model              # Anthropic or OpenAI: the rows as they are
```

`--names-model` asks a language model you serve on levers9. It reads each text after the scan has masked what
it found, and the text stays on your cluster, so this is the one for real customer data. It's much slower than
a plain scan, since the model reads every text. The report then says how many texts it read and gave no answer
for, the names it returned, kept and dropped, and the findings it added. A later scan with no option keeps
those names for every text that hasn't changed, and says how many rows are new or changed and so read by no
model. `pii report` and `pii policy` say which model read the dataset last.

An address that isn't an endpoint of your workspace is an outside model. It's asked only with
`--allow-outside` (and `--model`, the name it serves its model under), it's sent the masked text, and the
command prints a warning line first. `--outside-model` asks Anthropic or OpenAI and sends the text of every row
as it is, also behind a warning line. Leave both off for real customer data. Whichever you use, the report says
in its own line whether text left the platform.

`pii policy` shows or sets who reads a dataset's text as it is
([Off, Flag and Mask](https://levers9.mintlify.site/lab/personal-data.md#who-reads-the-text-off-flag-and-mask)): `off` (everyone reads the text as
it is), `flag` (nothing is hidden, and each row says how many findings the scan has in it) or `mask` (every
reader but the owner gets the masked text: a share link, the published manifest, the model that drafts,
Insights, a dataset built from this one and the Arena's import). Mask covers text. Audio still plays as
recorded.

With no value it prints the policy and how far the scan covers the rows: how many rows there are, how many are
flagged, and how many aren't scanned yet. Under `mask` the ones not scanned yet are held back from every reader
but the owner, and the dataset can't be published until they're scanned. Setting `mask` on a dataset that was
never scanned starts a scan. The command follows it and prints its report, and `--no-wait` comes straight
back.

`pii review` is how you [check the scan](https://levers9.mintlify.site/lab/personal-data.md#check-the-scan): people read a random sample of rows
as they are and settle what the scan found, and the scan is measured against that.

```bash theme={null}
levers9 dataset pii review draw calls-sept --size 600 --seed 7    # fixed once drawn; --redraw draws another
levers9 dataset pii review status calls-sept                      # --json for the API's answer
levers9 dataset pii review link calls-sept --for "Audit team" --view-only --days 14
```

`draw` picks 300 rows unless `--size` says otherwise, every row with the same chance. The dataset has to be
scanned first, and a sample stays fixed until `--redraw`, which keeps the reviews already made. `status` prints
how many sampled rows are reviewed and by whom. Once 50 are (or the whole sample when it's smaller) it prints a
table: precision and recall per type and for all types, each with its 95% range and the counts it comes from.
A wide range means too few findings of that type were reviewed. The line under the table is the share of
personal data Mask really hides, whatever type the scan gave it. Above the table it says which names model the
measured scan asked, and whether it kept a model's names from an earlier scan: a scan that asked no model and
kept a model's names isn't a scan without one.

`link` makes a link for a reviewer. It's not an ordinary share link: it shows the original text of the sampled
rows, behind an agreement the reviewer has to accept, and it opens nothing else of the dataset. The command
prints one warning line first. `levers9 dataset links NAME` lists every share link of a dataset with what it
opens (the rows and labels, or the scan review) and whether it still works.

`pii export` writes the dataset out for training with its personal data taken out
([Export for training](https://levers9.mintlify.site/lab/personal-data.md#export-for-training)): one JSON line per row, and a report beside it.

```bash theme={null}
levers9 dataset pii export calls-sept --require-measured -o calls-sept.jsonl      # made-up names and numbers
levers9 dataset pii export calls-sept --render mask --status validated --split train
levers9 dataset pii export calls-sept --min-recall name=0.95 --leave-out-held-back
levers9 dataset pii exports calls-sept                                             # --json for the API's answer
levers9 dataset pii export-delete calls-sept deid-surrogate-20261007T101500Z.jsonl
```

`--render surrogate`, the default, swaps every name and number for a made-up one of the same kind, the same
one for the same person all through a call. Pick it to train a model that should say names and numbers the way
people do. `--render mask` writes placeholders like `[NAME]`, for a file people will read. The first thing the
command prints is whether the scan was measured, then the recall for names with its range, the rows written and
left out with why, where the two files are on the `datasets` volume, and what the file doesn't cover. Audio is
never de-identified.

An export of a scan nobody measured is made, and says so first. `--require-measured` refuses it instead.
`--min-recall TYPE=N` refuses it when the review's recall for a type is under the number, held against the
lower end of its 95% range unless you say `--by point`. A row that holds text no scan has read as it is now
stops the export, unless `--leave-out-held-back` leaves it out. `-o FILE` also saves the file where you say,
with its report beside it (`calls-sept.report.json`), and won't write over a file without `--force`.
`--new-salt` makes the dataset a new salt first, after which earlier surrogate exports no longer line up with
new ones: it asks before it acts, unless you add `--yes`. A large dataset is written on the cluster and the
command follows it (`--no-wait` comes straight back, `--on-cluster` sends a small one there too).

`pii exports` lists what was made, each file with its render, when, its rows, and whether its scan was
measured, with the recall for names when it was. `pii export-delete` removes one export and its report, and
asks first unless you add `--yes`.

`export` downloads a dataset of any kind as TSV: every row with its latest labels, to stdout or to a file. It's
your own export, so it holds the original text under every policy. `--text masked` downloads the masked copy
instead, which is the file a share link would get under `mask`.

```bash theme={null}
levers9 dataset export calls-sept --status validated -o calls-sept.tsv
levers9 dataset export calls-sept --text masked -o calls-sept-masked.tsv
```

A `decision` dataset is a human gold set for a decision model ([Decision gold sets](https://levers9.mintlify.site/lab/decision-gold-sets.md)).
It takes its questions from a file, a Decision API can draft the answers, and it exports as JSON lines and as
a schema:

```bash theme={null}
levers9 dataset create leads-gold --kind decision --path uploads/leads-gold \
        --questions questions.json --actions actions.json            # --questions: the reviewers' questions (or carry them in the records)
levers9 dataset create leads-gold --kind decision --path uploads/leads-gold --questions questions.json \
        --provider decision_api --endpoint https://levers9.uselevers.com/asgi/lev/v1    # drafts from a Decision API
levers9 dataset export leads-gold --format jsonl --status validated --twins -o gold.jsonl   # the gold set
levers9 dataset export leads-gold --format schema -o schema.json                            # its questions
```

`--format` is `tsv` (the default, any kind), `jsonl` or `schema` (a decision dataset). `--status` keeps the rows
with one status (`pending`, `validated` or `flagged`). `--twins` goes with `jsonl` and adds one record for every
counterfactual. `--text masked` goes with `tsv` and `jsonl`.

Publishing reads a service-account key from a workspace secret — see
[Publishing to a bucket](https://levers9.mintlify.site/lab/publishing.md). `LEVERS9_DASHBOARD_API_URL` points these commands at another
dashboard (self-hosting, or a local BFF on `http://localhost:8100`).

## Hardware: machines and pools

| command | what it does |
| - | - |
| `levers9 machine list` | GPU inventory: what is available serverless right now, and on-demand offers when a vendor is configured |
| `levers9 machine reserve --gpu TYPE` · `release ID` | reserve and release on-demand hardware |
| `levers9 machine create --pool NAME` | register your own box in a pool. The join command, with the token and keys it needs, goes to `~/.levers9/machines/ID.sh`, readable by you only: copy that file to the box and run it with `sh`. The terminal shows the command with the secrets hidden; `--show-secrets` prints it whole, for scripts that read it |
| `levers9 machine ssh ID` · `drain` · `cordon` · `uncordon` · `delete` | operate a machine |
| `levers9 workspace multi-gpu [on\|off]` | whether containers here may take more than one GPU; needed before `gpu_count=2`, `4` or `8` |
| `levers9 pool list` | the pools, their free capacity, and **Workers now**: how many more workers each can start at this moment and why (pending cap, per-minute budget, nodes) |
| `levers9 cluster join NAME --gpu TYPE [--apply]` | attach a Kubernetes cluster you run as a pool: prints (or applies) the agent manifest; see [Attach a cluster](https://levers9.mintlify.site/guides/clusters.md) |
| `levers9 cluster list` · `nodes NAME` · `manifest NAME` · `leave NAME` | the clusters, a cluster's nodes and free cards, the manifest again, detach |
| `levers9 pool list` · `create` · `scale` · `extend` · `delete` | named groups of machines |
| `levers9 pool join-command` · `join` | the installer for joining a machine to a private pool |
| `levers9 disk` | durable, fixed-size block storage on local machines |

## AI agents

| command | what it does |
| - | - |
| `levers9 skills list` · `show NAME` · `install [--global] [--to DIR] [NAME...]` | the bundled agent skills, into `.claude/skills` by default |
| `levers9 mcp [--transport http --port N] [--read-only]` | an MCP server over this context's workspace (`uv tool install "levers9[mcp]"` or `pip install "levers9[mcp]"`); `--read-only` offers only the tools that change nothing |

See [Skills and MCP for AI agents](https://levers9.mintlify.site/guides/agent-skills.md).

## Which workspace a run goes to

Every remote run prints `=> Workspace NAME · context NAME · <dashboard>/apps` first. The `default` context is
what `levers9 login` wrote and what the dashboard you signed into shows; `-c NAME`, `LEVERS9_TOKEN` or
`CONFIG_PATH` send the run elsewhere, and the line says so. `LEVERS9_QUIET=1` silences it;
`LEVERS9_DASHBOARD_URL` sets the link when self-hosting.

## Where things live

* `~/.levers9/config`: contexts (host, port, token, and `controlplane_http_url` when REST answers on a different hostname than gRPC, as on the hosted cluster: `https://levers9.uselevers.com`). Lost it? `levers9 login` again; the same workspace
  token comes back. It holds tokens, so the CLI keeps it readable by you only (the file 0600, `~/.levers9`
  0700\); an older one is tightened the next time a command saves it. A `--config-path` somewhere else gets a
  0600 file, and its folder is left as it is.
* `tls` in a context says whether its controlplane speaks TLS. `levers9 login` writes `tls = true` for the
  hosted one. Left out, TLS is used on port 443 and not on other ports; `LEVERS9_CONTROLPLANE_TLS=true` or
  `false` decides for contexts that don't say. When a token would go without TLS to a host that isn't local or
  in-cluster, the CLI warns once.
* `levers9 login` sends a PKCE challenge, so the browser brings back a one-time code, not the token, and the CLI
  trades the code for the token over HTTPS. A dashboard from before the exchange still sends the token, and
  that's accepted.
* The current directory is synced into every container at run time; put a `.levers9ignore` file next to your
  script to leave things out (same syntax as `.gitignore`). Files that look like secrets (`.env`, `*.pem`,
  `*.key`, `id_rsa*`, `.aws/`, `.netrc`, `.npmrc`, `.pypirc`) are named in a warning when they're uploaded; see
  [Parameters](https://levers9.mintlify.site/sdk/parameters.md).
* REST: everything the CLI does is also on `https://levers9.uselevers.com/api/v1/...` with the same token, see the
  [API reference](https://levers9.mintlify.site/api-reference/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.