# Python (sufleur-cli)

The pip wrapper around the Sufleur CLI and the Python code it generates.

The `sufleur-cli` package on PyPI wraps the Sufleur CLI for Python projects. It bundles the platform-appropriate Go binary inside the wheel and provides the typed runtime the generated code depends on.

The PyPI listing: [pypi.org/project/sufleur-cli](https://pypi.org/project/sufleur-cli/).

## Install

```bash
uv add --dev sufleur-cli
# or: pip install sufleur-cli
# or: poetry add --group dev sufleur-cli
```

The wheel is built by GoReleaser per platform — installing on macOS arm64 pulls a wheel containing the macOS arm64 binary, on Linux x86\_64 you get the Linux x86\_64 binary, and so on. There's no separate download step at install time.

## Peer dependencies

The generated code imports two libraries; install them in your project:

```bash
uv add chevron pydantic
# or: pip install chevron pydantic
```

`chevron` is the runtime Mustache implementation (chosen for full-spec compliance). `pydantic` powers the output-schema models. Both are intentionally peer deps.

## Running the CLI

The package installs a `sufleur` console script:

```bash
sufleur init
sufleur add @acme/welcome-message
sufleur generate
```

You can also invoke it as a module — useful in venvs where the script shim isn't on `PATH`:

```bash
python -m sufleur_cli generate
```

See the [CLI reference](/docs/cli) for every command and flag.

## What the generated code looks like

`sufleur generate` writes a single file (default: `./generated/prompts.py`) with TypedDicts for inputs, Pydantic `BaseModel`s for outputs, and a typed `get_prompt` factory. Excerpt:

```python title="generated/prompts.py"
from __future__ import annotations
from typing import Any, Literal, TypedDict
import chevron
import json
from pydantic import BaseModel


class _WelcomeMessage_UserPromptInput_User(TypedDict):
    age: int
    """User's age in years"""
    name: str
    """User's name"""


class WelcomeMessage_UserPromptInput(TypedDict):
    isWeatherGood: bool
    user: _WelcomeMessage_UserPromptInput_User


class WelcomeMessageOutput(BaseModel):
    email: str
    id: int
    name: str


PromptName = Literal["@acme/welcome-message"]


def get_prompt(prompt_name: PromptName): ...
```

A few things to note:

1. Nested objects become their own TypedDict (with the `_` prefix marking them as internal). `@doc` annotations land as field docstrings.
2. Output schemas become Pydantic `BaseModel`s — fully validated, with `.model_dump()` and friends available out of the box.
3. The `Literal` type for `PromptName` means your editor autocompletes prompt names.

## Calling a prompt

```python
from generated.prompts import get_prompt

welcome = get_prompt('@acme/welcome-message')

result = welcome.render('userPrompt', {
    'isWeatherGood': True,
    'user': {'name': 'Ada', 'age': 32},
})

# `result['prompt']` is the rendered string.
print(result['prompt'])

# `welcome.metadata` exposes model, temperature, version, and (if set) the raw output_schema.
print(welcome.metadata['version'])  # "1.2.3"
```

If a prompt declares an `outputSchema`, the returned object exposes `parse_output`:

```python
result = welcome.parse_output(llm_response_text)
if result['success']:
    user = result['data']        # typed as WelcomeMessageOutput
    print(user.email)
else:
    print(result['error'])
```

`parse_output` strips a fenced \`\`\`code block if the model wrapped its JSON, then validates against the Pydantic model. The return is always a TypedDict union — no exception path to catch.

## Draft warnings

Pinning to a `draft` version is supported but produces a runtime warning the first time you call the prompt:

```
UserWarning: [sufleur] Prompt "@acme/welcome-message" is a draft version
```

Drafts are mutable in the registry, so they're appropriate for local iteration — not for production.

## Versioning the generated file

Commit `generated/prompts.py` to your repo alongside `sufleur.yaml` and `sufleur-lock.yaml`. The trio is what makes prompt changes reviewable in PRs — reviewers see the new template, the version bump, and the calling code in the same diff.
