/** * Studio execution — a dry-by-default step runner for `vclaw studio --execute`. * * Studio's planner stays the source of truth: it emits a `StudioPlan` whose * steps are fully-resolved `vclaw video ...` command strings. This module does * NOT re-derive any orchestration decisions (readiness, route, approval) — it * just runs the emitted plan step-by-step by delegating each command to an * injectable {@link StudioStepRunner} (the CLI supplies a `spawnSync` wrapper; * tests inject a stub, so the whole runner is offline/deterministic). * * Credit safety is structural, not behavioural: * 1. Default `--execute` is dry: {@link classifyStep} REFUSES any spend/network * subcommand that lacks `--dry-run` (blocked-spend), and dry steps keep their * `--dry-run`. Real spend requires the explicit `--confirm-spend` (which strips * `--dry-run` from spend-dry steps); spend-live steps run only with it. * 2. `--auto-approve-storyboard` (only with `--confirm-spend`) is the ONLY way the * runner sets `VIDEOCLAW_APPROVE_STORYBOARD`; otherwise it strips it from the * child env, so the director storyboard-approval gate keeps blocking a real * render — the gate is inherited verbatim, never reimplemented here. * 3. FREE_SUBCOMMANDS is the ALLOW-LIST of verified-offline subcommands (local fs * reads/writes + pure logic only — no provider/Gemini/R2/credit call). The * classifier is FAIL-CLOSED: anything NOT on this allow-list is treated as a * spend step (and refused on a default dry `--execute` unless it carries * `--dry-run`). A new, unknown, or typo'd subcommand is therefore spend by * default — it can never silently run. To make a new subcommand free, verify * it is offline and add it here explicitly. */ import type { StudioGoal, StudioPlan, StudioPlanStep } from './types.js'; /** Env var that unblocks the director storyboard-approval gate. The runner must * never set it, so the gate keeps blocking inside any spawned execute. */ export declare const APPROVAL_ENV_VAR = "VIDEOCLAW_APPROVE_STORYBOARD"; export type StudioStepClassification = 'free' | 'spend-dry' | 'spend-live'; export declare class StudioExecuteError extends Error { readonly code: string; constructor(code: string, message: string); } /** * Tokenizes a Studio command template into argv, honoring single/double quotes * (recipe commands quote multi-word values, e.g. `--client "Acme Inc"`). */ export declare function parseStudioCommandArgv(command: string): string[]; /** * Returns the argv with a leading `vclaw` token removed (recipe commands are * written `vclaw video ...`; the spawned child is the vclaw binary itself). */ export declare function studioStepArgv(step: StudioPlanStep): string[]; /** * Classifies a step by credit risk, FAIL-CLOSED. A step is only `free` when its * subcommand is on the {@link FREE_SUBCOMMANDS} allow-list; everything else — * including an unknown, typo'd, or missing subcommand — is a spend step: * `spend-dry` when it carries `--dry-run`, else `spend-live` (refused by the * runner on a default dry `--execute`). An empty/undefined subcommand is the * safest classification, `spend-live`. */ export declare function classifyStep(step: StudioPlanStep): StudioStepClassification; export interface StudioStepRunResult { exitCode: number; stdout: string; stderr: string; /** Parsed child stdout JSON, when stdout was valid JSON. */ json?: unknown; } /** Injectable step runner. Receives child argv (no leading `vclaw`) + child env. */ export type StudioStepRunner = (argv: string[], env: NodeJS.ProcessEnv) => StudioStepRunResult; export interface StudioExecuteOptions { runStep: StudioStepRunner; /** Base env the child env is derived from. */ baseEnv?: NodeJS.ProcessEnv; /** * Permit credit-spending steps to RUN for real. When set, spend-dry steps are * promoted to live (their `--dry-run` is stripped) and spend-live steps run. * Default false → fully dry (spend-live is refused; spend-dry runs as dry). */ confirmSpend?: boolean; /** * Only meaningful with `confirmSpend`: set `VIDEOCLAW_APPROVE_STORYBOARD` in the * child env so a real render proceeds unattended past the director gate. Without * it the approval var is stripped, so the human storyboard-approval gate still * blocks a real render unless it was approved out-of-band. */ autoApproveStoryboard?: boolean; /** Resume from this step id (earlier steps are recorded as skipped). */ fromStepId?: string; } export type StudioStepStatus = 'ran' | 'skipped-before-resume' | 'skipped-approval' | 'blocked-spend' | 'blocked' | 'failed'; export interface StudioStepResult { id: string; command: string; classification: StudioStepClassification; status: StudioStepStatus; exitCode: number | null; /** `status` field parsed from the child's JSON output, when present. */ childStatus?: string; /** A storyboard markdown path surfaced by a director awaiting-approval child. */ markdownPath?: string; } export type StudioStopReason = 'approval' | 'spend-guard' | 'child-blocked' | 'child-failed' | 'missing-inputs'; export type StudioExecutionMode = 'dry' | 'confirm-spend' | 'auto-render'; export interface StudioExecutionReport { schemaVersion: 1; goal: StudioGoal; /** dry = no credits; confirm-spend = real spend, human-gated; auto-render = real spend, gate auto-approved. */ mode: StudioExecutionMode; executed: boolean; ranCount: number; stoppedAt?: string; stopReason?: StudioStopReason; /** Set in dry mode when the plan contains spend steps: how to render for real. */ hint?: string; results: StudioStepResult[]; warnings: string[]; } /** * Builds the child env. By default the storyboard-approval var is STRIPPED so the * director gate keeps blocking. Only when `autoApprove` is set (auto-render mode) * is it set to '1' so a real render proceeds unattended. */ export declare function studioChildEnv(base?: NodeJS.ProcessEnv, opts?: { autoApprove?: boolean; }): NodeJS.ProcessEnv; /** Returns argv with any `--dry-run` flag removed — used to promote a spend-dry * step to a real run under --confirm-spend. * * NOTE: the paid audio commands (`narrate`/`dialogue`/`sfx`/`soundtrack`) have * their OWN fail-closed CLI gate (`requireSpendConfirmation` in vclaw.ts) that * refuses a real run unless the argv carries `--dry-run` OR `--confirm-spend`. * No STUDIO_RECIPE emits those subcommands today, so stripping `--dry-run` here * is sufficient. If a recipe ever DOES emit one, this promotion must also append * `--confirm-spend` (otherwise the promoted child hits the CLI gate and fails). */ export declare function stripDryRunArgv(argv: string[]): string[]; /** * Runs a Studio plan's steps in order, stopping (fail-fast, no partial spend) at * the first spend-guard refusal, child block, or child failure. Pure control flow * — all I/O is delegated to `opts.runStep`. Never mutates the input plan. * * Modes (by flags): default = dry (free + spend-dry-as-dry; spend-live refused); * confirmSpend = real spend (spend-dry promoted by stripping --dry-run), still * human-gated; confirmSpend + autoApproveStoryboard = real spend with the director * storyboard gate auto-approved (unattended). */ export declare function runStudioPlan(plan: StudioPlan, opts: StudioExecuteOptions): StudioExecutionReport; //# sourceMappingURL=execute.d.ts.map