Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.buttons.sh/llms.txt

Use this file to discover all available pages before exploring further.

2026-04-20
Drawer workflows, live streaming, webhooks, and more

Drawer workflows, live streaming, webhooks, and more

Drawers graduate from a concept into a full workflow engine with typed steps, cross-step data flow, CEL expressions, sub-drawers, iteration, conditionals, retries, and error handlers. The CLI gains live output streaming, idempotency keys, concurrency queues, and a webhook trigger system.

New features

  • Drawer workflow engine — chain buttons into typed, schema-validated workflows with buttons drawer create, buttons drawer add, and buttons drawer press. Steps reference upstream outputs via ${step.output.field} expressions, wired automatically or explicitly with buttons drawer connect. Per-step retry policies with exponential backoff handle transient failures. See the drawer CLI reference.
  • CEL expressions${...} contents now support full CEL expressions: string concatenation, arithmetic, ternaries, and null coalescing. Simple dotted paths like ${build.output.version} still work unchanged.
  • Sub-drawers — compose workflows from reusable building blocks. Add a child drawer to a parent and its return values flow back into the parent’s step context.
  • for_each steps — iterate over an array, running nested steps per item. Set parallelism to run iterations concurrently with a bounded worker pool. Use on_item_failure: continue to tolerate per-item errors without aborting the whole step.
  • switch steps — conditional branches using CEL predicates. The first truthy case runs, the rest are skipped.
  • aggregate steps — pluck a field from each entry in an array into a flat list.
  • wait steps — pause for a duration or until a specific timestamp. Honors cancellation so Ctrl-C aborts cleanly.
  • Drawer-level on_error handler — declare a fallback drawer that runs when any step fails. The handler receives the error payload and context so it can notify, roll back, or triage.
  • buttons summary — one command, full workspace snapshot: buttons, drawers, recent runs, active runs, recent failures, and webhook listener state. Pass --json for structured output or --deep to inline full schemas and run history. Running bare buttons with no subcommand now invokes summary automatically.
  • --summary universal flag — attach --summary to any mutating command to preview the plan without executing. Never mutates, never touches the network.
  • buttons press --follow — stream a button’s stdout and stderr to your terminal as the press runs. The final structured result still goes to stdout so you can pipe to jq while watching progress on stderr. See the press CLI reference.
  • buttons NAME logs --follow — follow the latest press’s output as plain-text JSONL on stdout. Pipes cleanly with no TUI or color codes.
  • Live output in the board — pressing a button on the board now auto-opens the logs pane and streams stdout/stderr in real time with a ● live badge that flips to · done when the press finishes.
  • buttons tail — follow a button’s structured progress events in real time. Scripts emit JSONL progress events that buttons tail -f streams to stdout.
  • Idempotency keysbuttons press --idempotency-key=K --idempotency-ttl=24h caches successful results. A second press with the same key within the TTL returns the cached result instantly. Only successful results are cached — failures always retry.
  • Per-button concurrency queues — declare a queue with a name and concurrency limit. Multiple buttons sharing the same queue name pool their slots. An optional key scopes concurrency per dimension (e.g. three concurrent presses per user).
  • buttons ignore / buttons unignore — keep scratch or test buttons out of git without touching your repo’s .gitignore. Pass --ignore on buttons create to ignore a new button in one step.
  • Webhook triggers — drawers can now be invoked by incoming HTTP requests, routed through a Cloudflare tunnel to your local machine. Run buttons webhook setup once to configure, then buttons webhook listen to start the dispatcher. See the webhook CLI reference.
  • Webhook auth — four built-in auth modes: open (default), HTTP Basic, arbitrary header, and HS-family JWT with optional claim checks. Secrets can reference environment variables so you don’t commit sensitive values.
  • Webhook dry-run — test webhook-triggered drawers locally with buttons drawer press --webhook-body without needing a running listener.

Updates

  • NAME-first log syntaxbuttons NAME logs is now the canonical form; the verb-first buttons logs NAME still works. Same for drawer logs.
  • Retry policies on drawer steps — each step can declare error handling with configurable max attempts, exponential backoff with optional jitter, and a retry-on allowlist of error codes.
  • Output schemas on buttons — optional JSON Schema describing a button’s stdout shape. Drawers use it at connect time to type-check references before you press.
  • HTTP button host-locking — the scheme and host of an HTTP button’s URL are now locked at create time. Template arguments can no longer modify the host portion of the URL, closing a class of request-routing vulnerabilities. See SSRF protection.
2026-04-20
Webhook reliability fixes

Webhook reliability fixes

Bug fixes

  • Graceful shutdown for webhook listeners — pressing Ctrl+C while buttons webhook listen has in-flight presses no longer abandons them. The listener now drains running presses for up to 30 seconds before exiting, so webhook-triggered drawers finish cleanly instead of being dropped mid-run. See the webhook CLI reference.
  • Stronger tunnel readiness checkbuttons webhook listen now verifies that the Cloudflare tunnel is fully connected end-to-end before accepting requests. Previously, a stale DNS record could cause the readiness check to pass prematurely. The listener now confirms the tunnel routes all the way back to your local process before reporting ready.
  • Fixed a memory leak in the webhook dispatcher — long-running webhook listeners no longer accumulate stale tracking state for completed requests. The dispatcher now cleans up immediately after each request finishes.