# Node (@sufleur/cli)

The npm wrapper around the Sufleur CLI and the TypeScript code it generates.

The `@sufleur/cli` npm package wraps the Sufleur CLI for Node projects. It does two things: ships the platform-appropriate Go binary on install so you can run `sufleur` commands without a separate toolchain, and provides the TypeScript runtime hooks the generated code depends on.

The npm listing: [npmjs.com/package/@sufleur/cli](https://www.npmjs.com/package/@sufleur/cli).

## Install

```bash
pnpm add -D @sufleur/cli
# or: npm i -D @sufleur/cli
# or: yarn add -D @sufleur/cli
```

The postinstall script downloads the binary for your platform from the matching GitHub release, verifies its SHA-256 against the published checksum, and caches it inside `node_modules`. On subsequent installs the cached binary is reused — there's no per-machine setup beyond `pnpm install`.

## Peer dependencies

The generated code imports two libraries you need to add to your project:

```bash
pnpm add mustache zod
pnpm add -D @types/mustache
```

`mustache` is the runtime template engine. `zod` powers the output-schema parser. Both are intentionally peer deps so your project controls the version, and so the generated file doesn't drag a runtime into a tree that already has its own.

## Running the CLI

The package exposes the binary as a `sufleur` script. Run it through your package manager:

```bash
pnpm sufleur init
pnpm sufleur add @acme/welcome-message
pnpm sufleur 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.ts`) containing types, schemas, templates, and a typed `getPrompt` factory. Here's a representative excerpt drawn from a prompt that has two entrypoints (`systemPrompt`, `userPrompt`) and an output schema:

```ts title="generated/prompts.ts"
import Mustache from 'mustache'
import { z } from 'zod'

export type WelcomeMessage_UserPromptInput = {
  isWeatherGood: boolean
  user: {
    /** User's age in years */
    age: number
    /** User's name */
    name: string
  }
}

export const WelcomeMessageOutputSchema = z.object({
  email: z.string(),
  id: z.number().int(),
  name: z.string(),
})
export type WelcomeMessageOutput = z.infer<typeof WelcomeMessageOutputSchema>

export interface EntrypointMapping {
  '@acme/welcome-message': {
    systemPrompt: WelcomeMessage_SystemPromptInput
    userPrompt: WelcomeMessage_UserPromptInput
  }
}

export type PromptName = '@acme/welcome-message'

export function getPrompt<N extends PromptName>(promptName: N): PromptResult<N>
```

Three things are worth noting:

1. Every entrypoint has its own input type, with `@doc` annotations preserved as JSDoc comments. Your IDE picks them up on hover.
2. The output schema is a real Zod schema, not a string description. You can extend it, refine it, or compose it with other schemas if you need to.
3. `EntrypointMapping` is what makes `render(entrypoint, input)` strongly typed — autocompleting entrypoint names per prompt and narrowing the input shape accordingly.

## Calling a prompt

```ts
import { getPrompt } from './generated/prompts'

const welcome = getPrompt('@acme/welcome-message')

// Type-checked: 'systemPrompt' | 'userPrompt' only.
const { prompt } = welcome.render('userPrompt', {
  isWeatherGood: true,
  user: { name: 'Ada', age: 32 },
})

// `welcome.metadata` exposes model, temperature, version, and (if set) the raw outputSchema.
console.log(welcome.metadata.version) // "1.2.3"
```

If a prompt declares an `outputSchema`, the returned object also has a `parseOutput` function:

```ts
const result = welcome.parseOutput(llmResponseText)
if (result.success) {
  // result.data is typed as WelcomeMessageOutput
  console.log(result.data.email)
} else {
  console.error(result.error)
}
```

`parseOutput` strips a fenced \`\`\`code block if the model wrapped its JSON, then runs the Zod schema. It always returns a discriminated union — there's no exception path to handle.

## Draft warnings

Pinning to a `draft` version is supported but loud about it. The generated code logs a warning the first time you call a draft prompt at runtime:

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

That's a feature: drafts are mutable in the registry, so you should never run them in production.

## Versioning the generated file

Commit `generated/prompts.ts` to your repo. The lockfile + the file together are reviewable in PRs — reviewers see exactly which prompt version changed and what the new template looks like, alongside the calling code that depends on it.
