buttons.json is the project manifest. buttons-lock.json is the repeatable resolution. Installed button.json and drawer.json files stay focused on the local package spec; they do not store registry URLs, bearer keys, or the dependency source of truth.
Manifest
.buttons/buttons.json is user-authored desired state:
@desk/name.
Names are unique per desk across buttons and drawers. The package kind is registry metadata, not part of the name.
Version ranges and per-dependency option objects are intentionally not part of the MVP.
Lockfile
.buttons/buttons-lock.json records the exact resolved set:
buttons add, buttons install, and buttons update. It is committed for repeatable installs. It must never contain registry bearer keys or battery values.
button.json
Each installed button still has its own runtime spec:requires uses the same version policy as the root manifest: "latest" or an exact version. Transitive dependencies are resolved into the lockfile beside root dependencies.
drawer.json
Each installed drawer has its own workflow/package spec:.buttons/drawers/<name>/drawer.json and installs every button referenced by its steps. Step button names are local names; package resolution uses the drawer’s desk namespace. For example, @your-desk/deploy-pack with step "button": "build" installs @your-desk/build.
Command Flow
Use the registry like a package manager:buttons add @desk/namewrites.buttons/buttons.jsonwith"latest"and installs immediately. The package can be a button or drawer.buttons add @desk/name@1writes an exact pin and installs that immutable version.buttons installtakes no package argument. It reads.buttons/buttons.jsonand honors.buttons/buttons-lock.jsonfor repeatability.buttons statusreports available CLI and content updates.buttons updateupdates the CLI and floating package dependencies only.
buttons install @desk/name is intentionally invalid. Use buttons add @desk/name to add a dependency.
Auth
Hosted-registry requests are bearer-authed (Authorization: Bearer <key>). Read and write use distinct batteries:
The registry base URL comes from
$BUTTONS_REGISTRY_URL. This public repo does not bake in a real registry host.
Bearer keys authorize what an operation may do; the workspace’s agent identity proves which device is acting. Enroll a device once with buttons agent setup before publishing to a hosted registry.
Publish Flow
buttons publish @desk/name publishes a local button or drawer folder to the configured registry:
hello or deploy-pack), while @your-desk/name is the registry identity. Publish detects the package kind from button.json or drawer.json. Published versions are immutable. New buttons and drawers start at version 1; if that version already exists, publish bumps the local package spec to the next number and retries.
Publishing a button bundles button.json, code files, and AGENTS.md. Publishing a drawer bundles drawer.json and AGENTS.md. Neither path uploads pressed/ run history or battery values.
Version Example
- Publisher runs
buttons create hello --code 'echo hello'. - Publisher runs
buttons publish @your-desk/hello. - Agent workspace runs
buttons add @your-desk/hello. - Agent lockfile resolves
@your-desk/helloto version1. - Publisher changes the source button and publishes again. Publish auto-bumps to version
2if1already exists. - Agent workspace runs
buttons statusto see the available update. - Agent workspace runs
buttons updateto move the floating dependency to the new version.
buttons add @your-desk/hello@1, step 7 would leave it pinned at that exact version.
Hardening
A registry is treated as untrusted. Downloads are size-capped, tarball hashes are verified before extraction, file-content hashes are written to the lockfile, and extraction rejects absolute paths, traversal paths, macOS sidecar files, and run history. Before overwriting an installed package, update compares the current local files with the lockfile/install-state hash. Local edits are skipped, not overwritten.HTTP Contract
A hosted registry implements:GET /v1/meta— optional registry metadata, including minimum CLI version.GET /v1/index— catalog entries{ name, kind, version, tags, sha256 }, wherekindisbuttonordrawerandsha256is the tarball hash. Latest is the last entry for a given name.GET /v1/buttons/<@desk/name>/<version>/download— download the gzip tarball; should setX-Content-Sha256.POST /v1/buttons/<@desk/name>/<version>— publish an immutable version with headersContent-Type: application/gzip,X-Content-Sha256, andX-Button-Kind.