sufleur-cli
The CLI for Sufleur — the registry where you author, version, and publish LLM prompts. This is the consumer side: it installs prompts from your Sufleur workspace into your project the way pip installs packages — declared in sufleur.yaml, locked to sufleur-lock.yaml, generated into one Python module with full types and runtime helpers.
Create a workspace and start authoring prompts at https://sufleur.com.
What you call from your code
from generated.prompts import get_prompt
review = get_prompt("@my-workspace/code-review")
rendered = review.render("en", {"diff": "...", "language": "go"})
prompt: str = rendered["prompt"] # ready-to-send prompt string
result = review.parse_output(llm_response_text)
if result["success"]:
result["data"] # Pydantic model, validated against the prompt's output schema
else:
result["error"]
"@my-workspace/code-review" is checked at type-check time (mypy / pyright): typos fail, the entrypoint name "en" is narrowed against the prompt's available entrypoints (via @overload), and the input is a TypedDict derived from the JSON Schema declared on that entrypoint. The version that resolves at codegen time is pinned in sufleur-lock.yaml.
Install
pip install sufleur-cli
sufleur --help
Or with pipx for an isolated install:
pipx install sufleur-cli
The wrapper ships the prebuilt binary inside a per-platform wheel — pip selects the right one via PEP 425 platform tags. There's no Python interpreter in the invocation hot path; sufleur is the native binary on your PATH.
Quick start
mkdir my-app && cd my-app
sufleur init # creates sufleur.yaml interactively
sufleur add @my-workspace/code-review ^1.0.0 # add + fetch + lock
sufleur generate # writes ./generated/prompts.py
The generated module imports two runtime peers. Install them with the [generated] extra:
pip install 'sufleur-cli[generated]'
…or add chevron (Mustache templating) and pydantic (output-schema validation, only needed when prompts have output schemas) directly to your project's dependencies. The CLI itself has no Python runtime deps; the [generated] extra exists so users who run init/add/install but never generate aren't forced to install code they don't use.
The generated code targets Python 3.10+ (PEP 604 union syntax).
What sufleur generate emits
A single .py module containing every prompt inlined (no runtime fetches). The public API is get_prompt(name), which returns a result object with:
render(entrypoint, input)→{"prompt": str}— Chevron renders the entrypoint template againstinput. The signature is narrowed via@overloadper entrypoint, so type checkers reject the wrong input shape.metadata— aTypedDictcontainingversion, your workspace's custom metadata, and (when applicable)outputSchema.parse_output(raw)(only present if the prompt has an output schema) — strips ``` fences, JSON-parses, and validates with a Pydantic model generated from the prompt's JSON Schema. Returns{"success": True, "data": <Model>}or `{"success": False, "error": str}`.
Plus generated TypedDicts per entrypoint, with field docstrings for any schema property that has a description:
class CodeReview_EnInput(TypedDict):
diff: str
"""The unified diff to review."""
language: str
Optional schema properties are wrapped in typing.NotRequired[...], and oneOf schemas become PEP 604 unions (X | Y).
Prompts published with DRAFT status emit a warnings.warn(...) when their get_prompt is called.
Decision prompts (TypeSafe Jev)
Prompts for System-One decision models such as TypeSafe's Jev are question templates: typed questions (noul, choice, score) your code asks about a state it builds. They're emitted as get_decision(name), which returns an object with:
batch()—ask(question_id, inputs, key=..., options=...)renders a question and returns a typedDecisionHandle(the key defaults to the question id; ask a question more than once under distinct keys).items()lists(key, question_id, question)to send.read(answers)validates each answer against its question (get(handle)→AnswerResult,get_or_throw(handle)→ the answer model);read_all(answers)fails unless every answer is valid.question(question_id, inputs, options=...)andparse_answer(question_id, raw)— render or validate a single question.metadata—versionand the recommendedmodelConfig, as forget_prompt.
triage = get_decision("@my-workspace/ticket-triage")
batch = triage.batch()
team = batch.ask("department", {"product": "Acme"})
res = httpx.post(
"https://api.typesafe.ai/v1/systemone",
headers={"Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}"},
json={
"model": triage.metadata["modelConfig"]["model"],
"state": {"ticket": ticket},
"questions": {key: question for key, _, question in batch.items()},
},
).json()
batch.read(res["answers"]).get_or_throw(team).choice # "billing" | "technical" | "sales"
The generated code has no provider request or response shape: you make the call (TypeSafe, OpenRouter's decisions endpoint, …) and add no runtime dependencies beyond chevron and pydantic. Guide: https://sufleur.com/docs/decision-models.
sufleur.yaml
The manifest. Looks like:
api_keys:
my-workspace: ${MY_WORKSPACE_API_KEY}
prompts:
'@my-workspace/greeting': '*'
'@my-workspace/code-review': '^2.0.0'
# alias: keep two pinned versions side-by-side under different names
'@my-workspace/code-review-strict': '@my-workspace/code-review@~1.4.0'
output:
language: python
file: ./generated/prompts.py
api_keys is optional: public prompts install without any key (like public npm packages). A key is only needed for the private prompts of a workspace.
Constraints are npm-style semver ranges (^, ~, >=, exact, *). The resolution is recorded in sufleur-lock.yaml. Commit both files — sufleur.yaml is the source of truth, sufleur-lock.yaml is the receipt.
CI usage
sufleur install --frozen # fail if lockfile is stale
sufleur generate
--frozen is the pip-compile-equivalent: refuses to update the lockfile, hard-errors if the manifest and lockfile disagree.
Commands
| Command | Description |
|---|---|
sufleur init |
Interactive scaffolding for sufleur.yaml. |
sufleur add @ws/name [range] |
Add a prompt, fetch it, update the lockfile. --alias <name> keeps multiple versions; --force overwrites an existing entry. |
sufleur remove @ws/name |
Remove a prompt from the manifest and prune its cache (kept if another alias still resolves to the same version). |
sufleur install |
Resolve the manifest, fetch what's missing, refresh the lockfile. --frozen for CI. |
sufleur update [@ws/name] |
Re-resolve constraints — one prompt or all. |
sufleur generate |
Regenerate the output file from the lockfile + cache. |
-v / --verbose enables HTTP request/response logs on any command. Variables in .env are loaded automatically; per-workspace API keys can be referenced as ${ENV_VAR_NAME} in sufleur.yaml.
Authoring prompts from the CLI
The commands above install published prompts into your project. The CLI also exposes the full authoring side — designed so a coding agent (Claude Code, Cursor, etc.) can create, version, and edit prompts in your Sufleur workspace on your behalf.
Hand it to your agent
sufleur skill prints a markdown skill description — when to use the CLI, FQ-name format, the full command surface, JSON flags. Pipe it wherever your agent loads skills from:
# Claude Code (each skill is a directory with a SKILL.md inside)
mkdir -p ~/.claude/skills/sufleur && sufleur skill > ~/.claude/skills/sufleur/SKILL.md
# Cursor
sufleur skill > .cursor/rules/sufleur.md
The skill ships inside the binary, so it always matches the sufleur version on your PATH.
Log in
sufleur login # device-code flow — opens a browser, polls until approved
sufleur me # show the authenticated user
sufleur logout # revoke the stored credential
Credentials land in $XDG_CONFIG_HOME/sufleur/credentials.yaml (or ~/.config/sufleur/credentials.yaml). This user credential is separate from the workspace API keys referenced in sufleur.yaml — those stay machine-to-machine, this one identifies you as the author.
Authoring commands
All accept --json. Prompts are addressed as @workspace/name, versions as @workspace/name@version (use the literal label draft while the version is unpublished).
| Command | What it does |
|---|---|
workspace list |
List the workspaces you belong to, with your role |
prompt create @ws/name --description "..." |
Create a new prompt in a workspace |
prompt create @ws/name --kind system-one |
Create a decision-model prompt (TypeSafe Jev); the kind is fixed |
prompt list @ws [--search ... --limit ... --offset ...] |
List prompts in a workspace |
prompt get @ws/name |
Show one prompt's details |
prompt update @ws/name --description "..." |
Update the description |
version draft @ws/name |
Fork the latest published version into a new draft |
version list @ws/name [--status DRAFT|PUBLISHED] |
List versions of a prompt |
version get @ws/name@version |
Show one version's details |
version delete @ws/name@draft |
Delete a draft (published versions are immutable) |
version publish @ws/name 1.3.0 |
Publish the draft as that semver (must exceed the latest published version); needs CLI publishing enabled for the workspace |
version set-metadata @ws/name@draft --string K=V (or --from-file …) |
Patch or sync metadata |
version delete-metadata @ws/name@draft --key K |
Remove a metadata key |
version set-output-schema @ws/name@draft --file schema.json |
Replace the version's output schema |
version set-decision-spec @ws/name@draft --from-file decision.yaml |
Set a decision prompt's question templates (types, criteria, optionCriteria) |
version set-model-config @ws/name@draft --provider anthropic --model NAME [--params '{...}'] |
Set the version's provider/model/parameters |
version set-readme @ws/name@draft [--content STR | --file PATH] |
Replace the version's README |
version get-readme @ws/name@version |
Print the version's README to stdout (raw markdown) |
version dump @ws/name@version --to ./dir |
Export files, output schema, README, and metadata to disk |
file list @ws/name@version |
List files in a version |
file create @ws/name@draft --file path.mustache [--entrypoint] [--format text|yaml] |
Add a new file (.yaml.mustache implies --format yaml) |
file update @ws/name@draft --name X [--file ...] [--rename Y] |
Replace content and/or rename |
file delete @ws/name@draft --name X |
Delete a file |
file set-entrypoint @ws/name@draft --name X [--clear] |
Mark (or unmark) a file as an entrypoint |
eval get @ws/name@version [--file PATH] |
Print the version's eval YAML (skeleton if none) |
eval validate @ws/name@version --file PATH |
Parse + type-check an eval YAML; saves nothing |
eval push @ws/name@version --file PATH |
Validate, then save the eval |
eval delete @ws/name@version |
Remove the eval from a version |
eval run @ws/name@version [--watch] |
Enqueue a run; --watch streams to completion (CI gate) |
eval runs @ws/name@version |
List recent runs, newest first |
eval show <run-id> / eval watch <run-id> |
Summarise or follow a single run |
eval cases <run-id> [--failed] |
Per-case pass/fail table for a succeeded run |
eval case <run-id> <index> [--prompts] |
One case's inputs, output, assertions, and judges |
dataset create @ws/name [--description ...] |
Create a dataset and its initial draft (private) |
dataset list @ws / dataset get @ws/name |
List datasets, or show one with its versions |
dataset update @ws/name --description "..." |
Edit the description |
dataset dump @ws/name@version --to ./dir |
Export schema.json, cases.jsonl, and dataset.yaml to a directory |
dataset cases push @ws/name@draft --file cases.jsonl |
Upload cases (JSONL/JSON/CSV); schema inferred on first upload |
dataset cases pull @ws/name@version [--to cases.jsonl] |
Download a version's cases as JSONL |
dataset schema get / set @ws/name@version [--file PATH] |
Read or refine a draft's JSON Schema |
dataset version draft @ws/name |
New draft, carrying forward the latest published schema + cases |
dataset version list @ws/name [--status DRAFT|PUBLISHED] |
List a dataset's versions |
dataset version validate @ws/name@draft |
Check every case against the schema (non-zero exit on a violation) |
dataset version publish @ws/name 1.3.0 |
Publish the validated draft as that semver; needs CLI publishing enabled |
tool create @ws/name [--description ...] |
Create a tool contract and its initial draft (private) |
tool list @ws / tool get @ws/name |
List tools, or show one with its dependents and versions |
tool update @ws/name --description "..." |
Edit the catalog blurb (never sent to the model) |
tool dump @ws/name@version --to ./dir |
Export both schemas, the model description, README and metadata |
tool version draft @ws/name |
New draft, carrying the latest published contract forward |
tool version list @ws/name [--status DRAFT|PUBLISHED] |
List a tool's versions |
tool version get @ws/name@version |
Show the model description and both schemas |
tool version delete @ws/name@draft |
Delete a draft version |
tool version publish @ws/name 1.3.0 |
Publish the draft as that semver; needs CLI publishing enabled |
tool version set-description @ws/name@draft [--content | --file] |
Set the model-facing description (versioned) |
tool version set-readme @ws/name@draft [--content | --file] |
Set the README |
tool version set-metadata @ws/name@draft [--from-file | --string K=V] |
Replace or patch free-form metadata |
tool schema get / set @ws/name@version [--output] [--file PATH] |
Read or replace the input (or --output) JSON Schema |
version tools list @ws/name@version |
List the tool contracts a version pins |
version tools add @ws/name@draft @ws/tool@^1.0.0 [--as NAME] |
Pin a tool version (constraint resolved once, at link time) |
version tools rename @ws/name@draft @ws/tool --as NAME |
Change the wire name the model sees |
version tools remove @ws/name@draft @ws/tool |
Remove a pin from a draft |
A tool contract is the model-facing definition of a tool — its wire name, the description that steers when it gets called, and the JSON Schema of its arguments — versioned like a prompt. A prompt version pins concrete tool versions, and sufleur generate turns those pins into typed bindings, provider-neutral definitions and a dispatch_tool that validates what the model sent. tool schema set checks a schema locally first, against the subset the generators can express, so nothing silently degrades to unknown/Any. As with prompts, a tool version can be published from the CLI when the workspace has enabled it (see below); changing its visibility is done in the web app.
An eval scores a prompt version against a dataset — judges, CEL assertions, and a passing threshold. Datasets are versioned collections of test cases, authored from the CLI too (the dataset … commands above) and referenced from the eval YAML via dataset.ref. Like prompts, a dataset version can be published from the CLI when the workspace has enabled it; changing its visibility is done in the web app. Full guides: https://sufleur.com/docs/evals and https://sufleur.com/docs/datasets.
Publishing from the CLI
version publish, tool version publish and dataset version publish publish the current draft as a semver you choose, which must be greater than the latest published version. Publishing from the CLI is off by default: a workspace owner or admin has to turn on "Allow CLI and agent publishing" under Settings → Publish rules in the web app, and it cannot be changed from the CLI. While it is off the command exits 1 with CLI publishing is disabled for workspace @ws … (with --json, code CLI_PUBLISHING_DISABLED). Existing publish rules (such as a required passing eval) still apply, a dataset must pass validation, and a prompt cannot be published while it pins a draft tool version. Published versions are immutable. Changing visibility, deleting a tool, and configuring provider credentials remain web-app only.
Render before publishing
sufleur prompt render <dir> --entrypoint <name> [--vars '{...}' | --vars-file path.json] runs the same Mustache pipeline as the generated runtime — useful for previewing a draft locally before publishing, or for quick experimentation against a version dump directory. No auth required. For a decision prompt, pass --question <id> instead to print one rendered question (add --options '{...}' for an open choice).
Invocation modes
The sufleur command on your PATH is the Go binary itself. For tools that prefer module-style invocation:
python -m sufleur_cli --help
This goes through a tiny Python wrapper that locates the binary and os.execvps it (POSIX) or subprocess.runs it (Windows). Slightly slower because Python boots first, but useful when invoking the CLI programmatically from a Python tool that wants to be sure it's calling the binary in the active environment.
The find_sufleur_bin() helper is also importable:
from sufleur_cli import find_sufleur_bin
print(find_sufleur_bin()) # absolute path to the binary
Supported platforms
| OS | Architectures |
|---|---|
| macOS | x86_64, arm64 |
| Linux | x86_64, aarch64 (manylinux 2.17 / glibc 2.17+) |
| Windows | x86_64, arm64 |
Alpine / musl libc is currently unsupported (no musllinux wheel) — pip will refuse with "no matching distribution" rather than silently producing a broken install. There is no source distribution.
Links
- Sufleur platform — author and manage prompts: https://sufleur.com
- Source code, issues, release notes: https://github.com/sufleur/cli
License
MIT.
Metadata
Release files for sufleur-cli 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| sufleur_cli-0.9.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| sufleur_cli-0.9.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| sufleur_cli-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| sufleur_cli-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| sufleur_cli-0.9.0-py3-none-macosx_11_0_x86_64.whl | Python 3 | none | macOS 11.0+ x86-64 | Details |
| sufleur_cli-0.9.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 42.9 MB
Release files / sufleur_cli-0.9.0-py3-none-win_arm64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-win_arm64.whl |
|---|---|
| Size | 6.9 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
6bc5415a48f292fdeae1548666f128d40aece0eb274d129f10a2c9b18197faf8
|
|
BLAKE2b-256 checksum How to use checksums |
9fe3fa5d420f9ee7e20105fe0a67dfeba8e9e1117d7b977e7fb679d4f187f630
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / sufleur_cli-0.9.0-py3-none-win_amd64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-win_amd64.whl |
|---|---|
| Size | 7.5 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
1273581341acca6521c06f0b6a1a63b484067102924d184c42bf59bb7e2ee694
|
|
BLAKE2b-256 checksum How to use checksums |
43b31e362fbd60674ebe3f7331b88b128ed12bc70e865fab1859208dacf6800d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / sufleur_cli-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 7.4 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
1ec4568678aeccbc7eaf002c589377060d91621c7c2c69879666779e2d58cbee
|
|
BLAKE2b-256 checksum How to use checksums |
9dfc61a85805425094d4aa0c8c2c13a40382270a39f6453675a85cd1a9e25cff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / sufleur_cli-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 6.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
62bef018e0d8c6362cda715b5842ac37c27d231a401c1667c5da535b3731e5e8
|
|
BLAKE2b-256 checksum How to use checksums |
3b5cc30041b970ab8d94ba4b025dbaf2c79e9a3d9bd428ab5a815109eb32486b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / sufleur_cli-0.9.0-py3-none-macosx_11_0_x86_64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-macosx_11_0_x86_64.whl |
|---|---|
| Size | 7.3 MB |
| Tags | Python 3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
1ba41815ace54ed780560427c52ea12cb5c5226a7a10074d9377e12a08a14daf
|
|
BLAKE2b-256 checksum How to use checksums |
af293e6e94b2f2d606466039602424afdce7592dc0b1c069e002fc85a0fb9c4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / sufleur_cli-0.9.0-py3-none-macosx_11_0_arm64.whl
| Download URL | sufleur_cli-0.9.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.9 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3de294086777d85fce1e010b2d4cfdfc156bedc023190abfcb8f0672fc338d08
|
|
BLAKE2b-256 checksum How to use checksums |
2423bcec9fa4d483ce65729c6d6c486d96cae4ecd34ad64fe5fbfecb52028761
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log