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.

Install

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:

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:

pnpm sufleur init
pnpm sufleur add @acme/welcome-message
pnpm sufleur generate

See the CLI reference 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:

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

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:

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.