Skip to main content
Buttons stores data under ~/.buttons/ by default. In a project initialized with buttons init, the active data directory is the closest .buttons/ folder in the current directory tree. BUTTONS_HOME overrides both. Each button gets its own subdirectory with a spec file, optional code file, agent context, and run history. Drawers get the same pattern for workflow specs and workflow history.

Directory layout

buttons init creates buttons/ and drawers/ (mode 0700). apps/, idempotency/, and queues/ are created lazily the first time they are needed.

button.json

The spec file for a button. It describes the button type, arguments, runtime, and all other configuration. schema_version is the on-disk format counter — currently 1, and distinct from the package version field.
See button.json for every field, including HTTP host locks, queues, output schemas, packaging metadata (version, requires, requires_batteries), and triggers.

drawer.json

The reusable spec for a Drawer. Schema v2 discriminates pressable action Drawers from proactive flow Drawers.
See drawer.json for the full workflow schema.

Installed buttons and pinning

Registry dependencies are tracked at the project root:
  • .buttons/buttons.json is desired state: scoped package names mapped to "latest" or an exact version.
  • .buttons/buttons-lock.json is resolved state: exact versions, content hashes, local installed names, and timestamps.
  • .buttons/buttons/<name>/button.json is the installed button’s runtime/package spec.
  • .buttons/drawers/<name>/drawer.json is the installed drawer’s workflow/package spec.
When you install with buttons add @desk/name or buttons install, button packages are written into buttons/<name>/, drawer packages are written into drawers/<name>/, and the dependency resolution is written to the lockfile. A drawer package also installs every button referenced by its steps. Dependencies declared in button.json requires are installed transitively and recorded in the lockfile too. To share authored local buttons and drawers with a team, commit their folders under buttons/ and drawers/. To share registry dependencies with a team, commit buttons.json and buttons-lock.json so teammates install the same resolved versions. To push a button or drawer to a registry, use buttons publish @desk/<name>; versions are simple numbers starting at 1, and registry versions are immutable.

history.json

history.json records local create, edit, install, update, delete, and rollback events. It is a per-machine audit log and is gitignored. Commit the authored button and drawer folders — their button.json / drawer.json specs, code files, and AGENTS.md. Do not commit history.json, batteries.json, webhook.json, or the pressed/ run traces; the .gitignore written by buttons init already excludes them.

main.*

The script file for code buttons. The extension matches the runtime: main.sh for shell or Bash, main.py for Python, main.js for Node. HTTP, prompt-only, and app buttons do not have a main.* file.

AGENTS.md

Present on every button (and drawer). For prompt buttons, it contains the raw instruction text stored at create time via --prompt; when pressed, the contents are returned in the prompt field of the JSON output. For other button types, AGENTS.md holds notes and context an agent can read to understand when and how to press the button. Units created before the rename may have a singular AGENT.md; it is still read as a legacy fallback, but every write uses AGENTS.md. This per-unit file is distinct from two project-level files with the same name: the .buttons/AGENTS.md folder reference written by buttons init, and the repo-root AGENTS.md, where the CLI maintains a press-ranked list of the project’s buttons between <!-- BUTTONS:LIST --> markers (refreshed on create/delete/add/import). See Agent instructions.

pressed/

One JSON file per run, named by UTC timestamp. Each file records the input arguments, exit code, stdout, stderr (each truncated to 1 MB), duration, and status. The directory is capped at 100 runs — the oldest are pruned — and is gitignored because run traces may contain press-time argument values.

apps/

Lazily created the first time you run buttons create --app <git-url|path>. The app source is cloned (shallow, .git dropped) or copied here alongside a button.json with kind: "app" and a serve block. App buttons have no main.* file.

batteries.json

Batteries are key/value pairs injected into button presses as BUTTONS_BAT_<KEY>. Keys must match [A-Z][A-Z0-9_]*. Project-local batteries live at .buttons/batteries.json. Global batteries live at ~/.buttons/batteries.json. Local values override global values on key collision.

agent.json

Created by buttons agent setup. Holds the workspace’s Ed25519 device seed (the private key, base64), the registered slug, and the backing tunnel id and run-token, written 0600. Because it contains the private seed, it is machine-local and must never be committedbuttons init gitignores it. See Agent identity.

webhook.json

Named webhook setup stores Cloudflare tunnel configuration in webhook.json. It is machine-specific and should not be committed.

idempotency/

Successful presses with --idempotency-key are cached here until their TTL expires. Created lazily.

queues/

Buttons with a queue declaration use file locks here to enforce concurrency limits. Created lazily.

Overriding the home directory

Set BUTTONS_HOME to use a different base directory:
This is useful in CI, tests, or container environments where you want an isolated state directory.
The ~/.buttons/ directory and the files inside it are created with restrictive permissions (0700 for directories, 0600 for secret files). Other users on the same system cannot read button specs or run history.