Mental model
Each drawer lives on disk as
drawer.json under the Buttons drawers directory and records its own run history under pressed/.
Create a drawer
add accepts button names. It also accepts drawer/<name> for sub-drawers, for_each:<button> for loops, and wait:<duration> for time pauses.
Wire data between steps
When a button prints valid JSON to stdout, the drawer parses it and exposes it as the step output.${step.output.field} only work against JSON output.
Auto-connect by schema
If a button declaresoutput_schema, buttons drawer <name> connect A to B can match compatible output fields to downstream args.
Drawer inputs
Any unfilled required button arg can be supplied at drawer press time by name.Run modes
By default a drawer runs its steps sequentially. Pass--mode parallel to run steps concurrently while honoring data dependencies — a step waits only for the steps its ${...} refs actually name. --concurrency N caps how many run at once (0 means NumCPU), and --on-failure stop|continue controls whether the first failure cancels in-flight siblings.
Step kinds
Sub-drawers
A drawer can call another drawer withdrawer/<name>.
return block in drawer.json so the parent can reference selected values as ${child-build.output.<field>}.
Loops
Usefor_each:<button> for per-item work.
The loop output includes
results in input order, so downstream refs stay deterministic even when iterations run concurrently.
Switches
Switch steps run the first case whosewhen expression is truthy. Author them in drawer.json.
steps on a switch act as the default branch.
Aggregates
Aggregate steps collect values from an array, often after afor_each.
{ "values": [...], "count": N }, with values in the same order as the input.
Waits
Use waits to pause between steps.Failure handling
By default, a failing step fails the drawer. Step-levelon_failure can be authored in drawer.json for retry or continue behavior:
on_error to point at a separate error-handler drawer, but this is reserved: the v1 executor parses it and does not yet run it. The field exists so a drawer can declare the intent ahead of the executor. When it ships, the handler will receive drawer, run_id, failed_step, error, and redacted inputs, and the failure will still remain visible in the original drawer run history.
Webhook-triggered drawers
Webhook triggers currently attach to drawers:${inputs.webhook} inside the drawer:
--auth basic|header|jwt (plus the matching --auth-user/--auth-pass, --auth-header-name/--auth-header-value, or --jwt-secret flags) to trigger webhook; the default is --auth none. Secret values accept $ENV{VAR}, resolved at match time, so drawer.json stays commit-safe.
buttons webhook listen runs the foreground dispatcher behind a Cloudflare tunnel, so it requires cloudflared on your PATH. Run buttons webhook setup once for a stable hostname, or let listen spin up an ephemeral *.trycloudflare.com URL per run.
Test a webhook drawer without standing up the listener by synthesizing the request payload at press time:
Inspect and debug
--summary previews a drawer mutation or press without running it. Use --json when an agent or script needs structured output.
buttons drawer schema prints the embedded JSON Schema for drawer.json. See drawer.json for the full field reference.