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/cliThe 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/mustachemustache 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 generateSee 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:
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:
- Every entrypoint has its own input type, with
@docannotations preserved as JSDoc comments. Your IDE picks them up on hover. - 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.
EntrypointMappingis what makesrender(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.