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 or Python SDK pages.

The CLI is open source: 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).

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:

NameDescription
--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.
--forceOverwrite 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:

NameDescription
--frozenCI 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: updategenerate.

sufleur.yaml

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

api_keys:
  acme: ${ACME_API_KEY}
  internal-tools: ${INTERNAL_API_KEY}
 
prompts:
  '@acme/welcome-message': '*'
  '@acme/legacy-welcome': '@acme/[email protected]'
 
output:
  language: typescript
  file: ./generated/prompts.ts
NameTypeDescription
api_keysmapOne 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.
promptsmapMap 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.languagestringEither typescript or python.
output.filestringWhere 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) before resolving references. If a referenced variable is unset, the command fails with a clear message naming the missing variable.

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

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.