~/.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.
version, requires, requires_batteries), and triggers.
drawer.json
The reusable spec for a Drawer. Schema v2 discriminates pressable action Drawers from proactive flow Drawers.Installed buttons and pinning
Registry dependencies are tracked at the project root:.buttons/buttons.jsonis desired state: scoped package names mapped to"latest"or an exact version..buttons/buttons-lock.jsonis resolved state: exact versions, content hashes, local installed names, and timestamps..buttons/buttons/<name>/button.jsonis the installed button’s runtime/package spec..buttons/drawers/<name>/drawer.jsonis the installed drawer’s workflow/package spec.
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 runbuttons 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 asBUTTONS_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 bybuttons 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 committed — buttons init gitignores it. See Agent identity.
webhook.json
Named webhook setup stores Cloudflare tunnel configuration inwebhook.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 aqueue declaration use file locks here to enforce concurrency limits. Created lazily.
Overriding the home directory
SetBUTTONS_HOME to use a different base 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.Related
- button.json — button spec fields
- drawer.json — workflow spec fields
- History — local audit history
- JSON output — the shape of history records
- Code buttons — how
main.*is populated from scaffolded or imported scripts