# CLI reference

The sufleur CLI — commands, configuration, and lockfile.

The Sufleur CLI is a Go binary that resolves your project's prompt dependencies from the registry, caches them locally, and generates typed code from them. The same binary ships through two language-specific wrappers; this page covers the binary itself. For installation, see the [Node](/docs/sdk/node) or [Python](/docs/sdk/python) SDK pages.

The CLI is open source: [github.com/sufleur/cli](https://github.com/sufleur/cli).

## Commands

Every command is run from a directory containing `sufleur.yaml`. Add `-v` / `--verbose` to any command to see detailed request logs.

### `sufleur init`

Interactive bootstrap. Prompts for your workspace name, the env var that holds your API key, the output language, and the output file path. Writes a starter `sufleur.yaml`. Refuses to overwrite an existing file.

### `sufleur add @workspace/prompt [constraint]`

Adds a prompt to `sufleur.yaml`, validates that it exists in the registry, then runs install. The constraint defaults to `*` (latest published).

```bash
sufleur add @acme/welcome-message            # latest published
sufleur add @acme/welcome-message ^1.2.0     # caret range
sufleur add @acme/welcome-message 0.1.0      # exact version
```

Flags:

| Name | Description |
| --- | --- |
| `--alias <name>` | Install the prompt under a different name in the same workspace, so you can keep multiple versions side-by-side. The alias becomes a separate entry in the generated SDK. |
| `--force` | Overwrite the entry if the prompt is already in `sufleur.yaml`. |

### `sufleur remove @workspace/prompt`

Removes a prompt from `sufleur.yaml` and from the lockfile. Cleans up the on-disk cache only if no other alias still resolves to the same backing version. Aliased as `sufleur rm`.

### `sufleur install`

Reads `sufleur.yaml`, resolves every prompt against its constraint, fetches anything missing or stale into `.sufleur/`, and writes `sufleur-lock.yaml`. Run this after editing `sufleur.yaml` by hand, or on `git pull` to bring teammates in sync.

Flags:

| Name | Description |
| --- | --- |
| `--frozen` | CI mode. Fail if the lockfile is out of date relative to `sufleur.yaml`. Use this in your build pipeline to catch unintentional version drift. |

### `sufleur update [@workspace/prompt]`

Re-resolves prompts against the registry and updates the lockfile. With no argument, updates everything; with one argument, updates only that prompt.

### `sufleur generate`

Reads `sufleur-lock.yaml`, loads each prompt from the cache, and writes typed code to `output.file`. Emits one stderr line per Mustache inference warning so you can fix unannotated ambiguous variables.

If you change templates upstream, the order is: `update` → `generate`.

## `sufleur.yaml`

The project file. Three top-level keys, all required:

```yaml
api_keys:
  acme: ${ACME_API_KEY}
  internal-tools: ${INTERNAL_API_KEY}

prompts:
  '@acme/welcome-message': '*'
  '@acme/legacy-welcome': '@acme/welcome-message@0.1.0'

output:
  language: typescript
  file: ./generated/prompts.ts
```

| Name | Type | Description |
| --- | --- | --- |
| `api_keys` | `map` | One entry per workspace you fetch prompts from. The value uses the `${VAR}` syntax to reference an env var resolved at runtime — never put the literal key in version control. |
| `prompts` | `map` | Map of `@workspace/name` → constraint. The plain form ties the alias key to the same package; the `@workspace/pkg@constraint` form lets the same backing prompt be installed under a different alias (useful for keeping an old version alongside the latest). |
| `output.language` | `string` | Either `typescript` or `python`. |
| `output.file` | `string` | Where the generated code is written. Typically `./generated/prompts.ts` or `./generated/prompts.py`. |

### Environment variables

API keys are referenced by env var, not embedded. The CLI loads a local `.env` file automatically (via [godotenv](https://github.com/joho/godotenv)) before resolving references. If a referenced variable is unset, the command fails with a clear message naming the missing variable.

```bash
# .env
ACME_API_KEY=sk-...
INTERNAL_API_KEY=sk-...
```

## Workspaces, naming, and semver

Prompt names follow `@workspace/name`, the same shape as scoped npm packages. The workspace prefix is the permission boundary — your API key is scoped to a single workspace.

Constraints accept the full [semver](https://semver.org) range syntax: `*` (any), `1.2.3` (exact), `^1.2.0` (compatible-major), `~0.1.0` (compatible-minor), `>=1.0.0 <2.0.0` (range). The string `draft` resolves to the current draft version of the prompt, which is mutable — `install` will warn loudly if you depend on a draft.

## The lockfile

`sufleur-lock.yaml` is generated by `install` / `update` and **must be committed**. Every entry pins:

- The exact resolved version
- The integrity SHA-256 of the cached payload
- The constraint that produced the resolution
- The version's status (`PUBLISHED` or `DRAFT`)

```yaml
resolved:
  '@acme/welcome-message':
    version: 1.2.3
    integrity_sha: sha256-...
    constraint: '*'
    status: PUBLISHED
    resolved_at: 2026-04-12T18:23:11Z
```

`generate` reads the lockfile, not `sufleur.yaml`, which guarantees that two developers with the same lockfile produce byte-identical code. `--frozen` in CI catches the case where someone forgot to commit a refreshed lockfile.

> [!WARNING] Don't edit the lockfile by hand
>
> The integrity hashes are checked at generate time. Edit `sufleur.yaml` and run `install` —
> never modify `sufleur-lock.yaml` directly.

## Cache

Resolved prompts are cached under `.sufleur/` in your project. The cache key includes the package ref + version, so multiple aliases of the same underlying version share a single cache entry. Add `.sufleur/` to your `.gitignore` — the lockfile is the source of truth, and `install` will rebuild the cache from it.
