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

# CLI Tools in Tasks

> Give the agent any vendor CLI: install it in setup, authenticate it with environment variables

The workspace image ships languages, runtimes and the agent CLIs (see [Preinstalled Toolchain](/concepts/workspaces#preinstalled-toolchain)). Everything else — Fly, Infisical, Doppler, Vercel, Supabase, a cloud SDK — follows one pattern. There is no per-vendor switch to flip.

1. **Install the CLI in `setup`**, into `~/.local/bin`.
2. **Give it a token as a project environment variable.** Every mainstream CLI reads its token from the environment without an interactive login.
3. **Wrap services that need it in `terminals`.**

## Where things live

* `~/.local/bin` is on the `PATH` of the agent, of `setup` and `setup_on_boot`, and of every `terminals` entry, so the agent calls the CLI by name. It is inside your home directory, which is the project volume: a [seed build](/guides/environment-json#seed-volumes-and-caching) that ran `setup` once keeps the install for every later boot that skips `setup`. Nothing under it needs root.
* `/usr/local` and other system paths are the VM's root filesystem, which is **not** kept across boots that skip `setup`. An installer that defaults to `/usr/local/bin` must be pointed at your home directory instead — most take an install directory as an environment variable or a flag.
* [Project environment variables](/guides/environment-variables) are injected before `setup_on_boot`, before `terminals` start and before the agent starts, so all three see the token.

## Example: Fly and Infisical

`.aether/environment.json`:

```json theme={"dark"}
{
  "setup": "FLYCTL_INSTALL=$HOME/.local bash -o pipefail -c 'curl -fsSL https://fly.io/install.sh | sh' && npm install -g --prefix $HOME/.local @infisical/cli",
  "terminals": [
    {
      "name": "Dev Server",
      "command": "INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_UNIVERSAL_AUTH_CLIENT_ID --client-secret=$INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET --silent --plain) && export INFISICAL_TOKEN && infisical run --env=dev -- bun run dev",
      "port": 3000
    }
  ]
}
```

Two details of that file matter. The Fly installer is a `curl … | sh` pipeline, so it runs under `bash -o pipefail`: a failed download fails `setup` instead of letting the seed cache a workspace without Fly. And `infisical run` does not exchange a machine identity's client credentials for a token itself, so the terminal does the exchange first with `infisical login --method=universal-auth` and only starts the dev server once it has a token — a wrong identity fails the login, and the service does not start.

Tokens, set once per project:

```bash theme={"dark"}
aether env set FLY_API_TOKEN            # a deploy token: fly tokens create deploy -x 720h
aether env set INFISICAL_UNIVERSAL_AUTH_CLIENT_ID
aether env set INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET
aether env set INFISICAL_PROJECT_ID
```

Omitting `--value` prompts for the value, so it never lands in your shell history.

From the next task on, `fly deploy`, `fly logs`, `fly ssh console` and `infisical secrets` work for the agent as they would for you, and the dev server starts with the `dev` environment's secrets in its process environment.

## Tokens other CLIs read

| CLI | Environment variable | Install into `~/.local/bin` |
| - | - | - |
| Fly | `FLY_API_TOKEN` | `FLYCTL_INSTALL=$HOME/.local bash -o pipefail -c 'curl -fsSL https://fly.io/install.sh \| sh'` |
| Infisical | `INFISICAL_TOKEN` — minted at run time by `infisical login --method=universal-auth` from `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` + `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET`, with `INFISICAL_PROJECT_ID` naming the project | `npm install -g --prefix $HOME/.local @infisical/cli` |
| Doppler | `DOPPLER_TOKEN` | the vendor's installer, pointed at `~/.local/bin` |
| Vercel | `VERCEL_TOKEN` | `npm install -g --prefix $HOME/.local vercel` |
| Supabase | `SUPABASE_ACCESS_TOKEN` | `npm install -g --prefix $HOME/.local supabase` |
| AWS | `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | `uv tool install awscli` |

GitHub needs none of this: every task already carries a repository-scoped token and a configured `gh` (see [GitHub integration](/guides/github-integration)).

## What the agent can do with the token

A token in the project environment is readable by the agent and by anything it runs. The CLI makes that power concrete: a Fly deploy token means the agent can deploy; an Infisical identity with write access means it can change secrets. Scope tokens to exactly what a task should be able to do — a Fly token to one app, an Infisical machine identity to one project and one environment — and give them an expiry.

Values are masked in `setup` output and never shown by `aether env list`.

## Notes

* Installing a CLI needs no secrets, so it belongs in `setup`, which the seed caches. Changing `.aether/environment.json` invalidates the seed, and the next boot runs `setup` again.
* A CLI that insists on an interactive `login` is not usable in a task. All of the CLIs above accept a token from the environment; prefer that path.
* Pin versions where it matters to you (`@infisical/cli@x.y.z`); the seed otherwise captures whatever the installer fetched when it last ran.


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