CLI reference
All commands assume a lazybox binary on your PATH — installed via Homebrew
(brew tap AntoineToussaint/lazybox && brew trust AntoineToussaint/lazybox && brew install lazybox) or a contributor source build (see the
Quickstart). From a source checkout without a
built binary on PATH, substitute cargo run -p lazybox-tui-boot --.
An lb binary ships alongside lazybox — a short alias with the identical
entrypoint, so every command below works as lb … too.
Make targets
Section titled “Make targets”For day-to-day development, the Makefile wraps the common flows.
| Target | What it does |
|---|---|
make setup |
One-time online prep: fetch the checksum-verified pinned Zig 0.16.0 to ~/.cache/lazybox/zig/, cache the Ghostty source and the locked Cargo graph, and prebuild the native terminal dependency so later builds run offline |
make run |
Build and run lazybox |
make build |
Build the workspace |
make release |
Build in release mode |
make test |
Run the test suite |
make lint |
Run clippy |
make fmt |
Format the workspace |
make dev |
Run a side-by-side instance against LAZYBOX_HOME=~/.lazybox-dev |
make run-fresh |
Run with state wiped and the setup wizard re-run |
make run-test |
Run the seeded throwaway instance |
Direct cargo
Section titled “Direct cargo”| Command | What it does |
|---|---|
cargo build |
Build |
cargo run -p lazybox-tui-boot |
Build and run the TUI binary |
cargo nextest run --workspace |
Run all tests with the repository’s per-test timeout policy |
cargo clippy --workspace --all-targets -- -D warnings |
Lint, warnings as errors |
If you build with cargo directly (no make), put Zig 0.16.0 on your PATH
first.
lazybox run modes
Section titled “lazybox run modes”| Command | What it does |
|---|---|
lazybox |
Default — in-process daemon plus TUI |
lazybox --help, -h |
Print the command overview and exit |
lazybox --version, -V |
Print the version and exit |
lazybox --fresh |
Wipe ~/.lazybox/v2/state.db and re-run the setup wizard |
lazybox --test |
Throwaway tempdir repo with one seeded workspace, no GitHub |
lazybox --connect [socket] |
Connect to a remote daemon over a Unix socket; with no path, defaults to ~/.lazybox/run/daemon.sock |
lazybox --connect-relay <box-id> --relay <addr> --box-key <hex> |
Attach a TUI to a box reached through a rendezvous relay (encrypted, ciphertext-only tunnel); see --connect-relay |
lazybox --workspace <key> |
Preselect a workspace on startup |
lazybox --session <id> |
Preselect a session on startup |
lazybox scan [ROOTS...] [--depth N] [--hidden] |
Find existing git clones and linked worktrees without modifying them |
lazybox worktree list |
Report every managed worktree with its size and orphan reasons, plus the on-disk and reclaimable totals |
lazybox worktree gc [--force] [--dry-run] |
Reclaim the safe orphaned worktrees (confirms first unless --force) |
lazybox hook-ingest --backend-key <key> |
Internal: forward an agent lifecycle hook payload (stdin JSON) to the daemon — lazybox injects this into spawned agents’ hook config; not typically run by hand |
lazybox scan
Section titled “lazybox scan”lazybox scan is a read-only inventory of git checkouts created outside
lazybox. Pass one or more roots directly, or configure scan.roots and run it
with no positional arguments:
lazybox scan ~/code ~/work --depth 5lazybox scan --hidden| Option | Effect |
|---|---|
[ROOTS...] |
Directories to walk; overrides scan.roots when present |
--depth N |
Maximum levels below each root; overrides scan.max_depth |
--hidden |
Include dot-directories, which are skipped by default |
Results are ordered by recent commit activity and identify the branch, path,
linked worktrees, dirty checkouts, and any checkout lazybox already tracks.
Lazybox’s own managed worktree directory is excluded. The command is read-only —
it only reports what it finds. To import a discovered checkout in place, use
the in-app import flow: press x i inside lazybox to scan and link a checkout
as a workspace without moving it.
lazybox worktree
Section titled “lazybox worktree”Every workspace opens an isolated git worktree. Over time some are left behind —
their PR merged, their branch gone, their session removed — and the disk they
hold adds up. lazybox worktree inspects and reclaims the worktrees lazybox
provisions, without touching checkouts you created yourself.
lazybox worktree list # read-only reportlazybox worktree gc --dry-run # show what gc would reclaimlazybox worktree gc # reclaim the safe orphans (confirms first)list prints every managed worktree with its branch, size, and orphan reasons
(tagged DIRTY / UNPUSHED / safe-reclaim), then the totals that make a leak
visible: bytes on disk, bytes auto-reclaimable
across the safe orphans, and bytes held by orphans that need a look first (no
backing bare clone, or uncommitted, unpushed, or locked). Bare clones under
repos/ are not counted.
| Command | What it does |
|---|---|
lazybox worktree list |
Read-only inventory of every managed worktree and the disk totals |
lazybox worktree gc |
Reclaim the safe orphaned worktrees, confirming first |
lazybox worktree gc --dry-run |
Report what gc would reclaim without deleting anything |
lazybox worktree gc --force |
Reclaim without the confirmation prompt |
gc only ever deletes a worktree that is flagged orphaned and passes the
safety gate — it never removes one with uncommitted or unpushed work, a lock,
or a live session. Orphans that need review are left for the in-app worktree
inspector — open the Settings palette (,) and choose Inspect worktrees…,
which can force a per-row reclaim. gc also refuses to run while lazybox is
open, since a standalone reap can’t see the daemon’s live sessions; quit lazybox
first, or reclaim from the inspector.
lazybox server
Section titled “lazybox server”| Command | What it does |
|---|---|
lazybox server start |
Start the daemon in the foreground (blocks until shutdown — run it in tmux, nohup, or a service unit) |
lazybox server stop |
Stop the daemon |
lazybox server status |
Report whether the daemon is running |
lazybox server api [addr:port] [--insecure-no-auth] |
Start the HTTP API gateway |
lazybox server api auth
Section titled “lazybox server api auth”The API gateway refuses to start without an auth decision: either set
LAZYBOX_API_TOKEN (clients then send Authorization: Bearer <token>), or
pass --insecure-no-auth to explicitly serve unauthenticated (a warning is
printed).
The listen address resolves in order: the [addr:port] argument, then the
LAZYBOX_API_ADDR environment variable, then the default 127.0.0.1:8787.
The gateway has no built-in TLS, so non-loopback binds are refused. Forward the
loopback port through an encrypted SSH tunnel for remote use. Direct routable
transport requires future encryption and principal-scoped authorization.
POST /v1/commands waits for the command handler to finish before returning
{"ok":true,"completed":true}. Provider/domain outcomes still arrive as
normal events. Streaming requests are connection- and command-bounded, so a
client must reconnect after the documented stream limit instead of growing an
unbounded daemon queue. GET /v1/workspaces includes a warnings array when
an unreadable row was preserved and omitted from the decoded workspace list.
lazybox serve
Section titled “lazybox serve”lazybox serve dials out to a rendezvous relay and holds the connection
open, so clients can reach this box’s daemon behind NAT — no inbound
ports, DNS, or TLS certs. The relay brokers a client to the box by box-id and
forwards opaque Noise ciphertext; it executes nothing. The box proves possession
of its persistent Ed25519 identity when registering with a hosted relay.
lazybox serve --relay relay.example.com:9443| Option | Effect |
|---|---|
--relay <host:port> |
Relay address to dial (or the LAZYBOX_RELAY env var). Required. |
--insecure-no-auth |
Disable the end-to-end Noise channel for loopback testing. Never use this with an internet-facing relay. |
--box-id <id> |
Override the box-id. By default a persistent id is generated on first run and stored at ~/.lazybox/v2/box-id. |
--socket <path> |
Local daemon socket to bridge to (defaults to the standard daemon socket). |
Run the daemon (lazybox server start) alongside serve: each brokered
client is bridged to that daemon over the local socket.
The encrypted channel uses a persistent X25519 box key that the connecting
client pins with --box-key; the relay only sees ciphertext. The separate
Ed25519 box identity is used to prove which subscription a hosted relay should
check. --insecure-no-auth bypasses the encrypted channel and is only intended
for loopback testing.
The relay itself is a separate deployable (lazybox-relay, in crates/relay)
and is not part of the client installers.
lazybox slack
Section titled “lazybox slack”| Command | What it does |
|---|---|
lazybox slack init |
Interactively validate and store bot/app tokens, then join the configured anchor channel |
lazybox slack doctor |
Diagnose token, scope, and connectivity issues |
lazybox slack prune |
Archive stale per-workspace channels (Slack has no channel delete) |
lazybox slack prune options
Section titled “lazybox slack prune options”The command computes a plan, prints it, and prompts before archiving anything.
| Option | Effect |
|---|---|
--dry-run |
List what would be archived without touching Slack |
--yes, -y |
Skip the confirmation prompt |
--older-than DUR |
Only archive channels stale for at least this long (e.g. 7d) |
--workspace KEY |
Restrict pruning to one workspace’s channels |
lazybox account
Section titled “lazybox account”Link a box to a lazybox-platform organization with a one-time claim code:
lazybox account claim ABCD1234 --name my-dev-boxlazybox account status| Command or option | Effect |
|---|---|
account claim <code> |
POST the one-time code, box public key, and display name to /v1/devices/claim, then cache the returned non-secret organization/plan state |
--platform-url <url> |
Override the platform (LAZYBOX_PLATFORM_URL, cached URL, then https://platform.lazybox.ai) |
--name <name> |
Box display name; defaults to the host name |
account status |
Show the linked organization, plan, and cached entitlement without requiring a platform credential |
Claim codes and the box private key are never written to config.yaml.
lazybox device mint still succeeds for local/self-hosted use when unlinked,
but warns that a hosted relay may refuse the credential.
lazybox --connect-relay
Section titled “lazybox --connect-relay”Attach a TUI to a box reached through a rendezvous relay, rather than a local
Unix socket. It connects through the relay, runs the end-to-end Noise handshake
pinned to the box’s channel key, and drives the daemon over the encrypted,
ciphertext-only tunnel. The box side is served by lazybox serve.
lazybox --connect-relay <box-id> --relay relay.example.com:9443 --box-key <hex>| Option | Effect |
|---|---|
<box-id> |
The box to reach (first positional argument). Required. |
--relay <host:port> |
Relay address to dial (or the LAZYBOX_RELAY env var). Required. |
--box-key <hex> |
The box’s channel (X25519) key to pin the encrypted channel to. Required. |
--smoke |
Run a one-shot encrypted round-trip check and exit instead of launching the TUI. |
lazybox sandbox
Section titled “lazybox sandbox”Drive a remote dev-box’s lifecycle from the CLI. The box, its placement, and the
socket the connect forward carries come from the sandbox
config block; each verb’s flags override what’s set there. The same box is what
the sidebar r-spawn brings up lazily.
| Command | What it does |
|---|---|
lazybox sandbox ensure |
Provision the box (Terraform / provider apply) if it doesn’t exist |
lazybox sandbox wake (alias start) |
Wake a stopped box |
lazybox sandbox sleep (alias stop) |
Stop the box to stop billing compute |
lazybox sandbox status |
Report the box’s current lifecycle state |
lazybox sandbox connect |
Bring up the supervised forward carrying the daemon socket + workload ports |
lazybox sandbox rebuild |
Rebuild the box’s lazybox daemon in place |
lazybox sandbox destroy |
Tear the box down (confirms first unless --yes) |
Every verb accepts --worktree <key> to address a specific per-key box (default:
the one shared box). Verbs also accept the placement/credential overrides that
mirror the config block (--provider, --project, --region, --zone,
--template, --timeout-seconds, --terraform-dir, --deployment, --user,
--remote-socket, --local-socket, --ports, --service-account-key,
--impersonate-service-account, --gcloud-config-dir).
lazybox auth
Section titled “lazybox auth”Manage provider login credentials stored by lazybox (independent of gh auth).
| Command | What it does |
|---|---|
lazybox auth login [github] |
Log in via the GitHub OAuth device flow and store the token |
lazybox auth status |
Show the stored login status |
lazybox auth logout [github] |
Remove the stored token |
The provider defaults to github. Linear login is not yet implemented — set
LINEAR_API_KEY or install the linear CLI for now.
lazybox device
Section titled “lazybox device”Manage this box’s pairing identity and the credentials paired devices use to reach it over a relay.
| Command | What it does |
|---|---|
lazybox device box [--format base64] |
Show this box’s pairing identity (box id + public key) |
lazybox device mint --name <name> |
Mint a credential for a new paired device |
lazybox device list |
List paired devices |
lazybox device revoke <id> |
Revoke one device |
lazybox device token <id> |
Reprint a device’s pairing token |
lazybox workspace
Section titled “lazybox workspace”The agent-facing surface over the running daemon: lets a spawned agent (or a script) create a workspace in lazybox itself, not just act on the repo.
lazybox workspace create --name "spike auth" --repo owner/repo --agent claude| Command / option | Effect |
|---|---|
workspace create |
Create a taskless pre-PR workspace by sending CreateWorkspace to the daemon |
--name <name> |
Workspace display name. Required (non-empty). |
--project <key> |
Target an existing project by key |
--repo <owner/repo> |
Target a repo (an alternative to --project) |
--agent <id> |
Spawn this agent into the fresh workspace so a live session lands in it |
--cwd <path> |
Directory used to infer the project when neither --project nor --repo is given (default: the process cwd) |
--socket <path> |
Daemon socket to send to (defaults to the standard socket) |
The project resolves from --project / --repo, else it is inferred from the
checkout at --cwd — so an agent running inside a worktree needs only --name.
Unlike fire-and-forget hooks, a failure exits non-zero: the caller asked for a
workspace and is told if the daemon was unreachable or the project couldn’t be
resolved.
lazybox task status
Section titled “lazybox task status”Answers “is anyone working on owner/repo#N?” — the supported lookup for a
person at a shell and for an agent that cannot receive MCP tools. Read-only: it
never spawns, resumes, claims or changes anything.
lazybox task status obin-ai/core-solutions#151lazybox task status https://github.com/obin-ai/core-solutions/issues/151 --jsonlazybox task status 187 --repo obin-ai/core-solutions| Command / option | Effect |
|---|---|
task status <ref> |
Look the record up: owner/repo#N, a GitHub issue/PR URL, a Linear key, or #N beside --repo |
--repo <owner/repo> |
Repo used to resolve the bare #N / N forms |
--issue / --pr / --ticket <ref> |
Aliases for the positional reference |
--json |
Emit the full structured report (versioned by schema_version) instead of the summary |
--socket <path> |
Daemon socket to query (defaults to the standard socket) |
The daemon answers from its own live state, so the record resolves through every id its workspace holds — an issue still answers after its PR has taken over the row, and every matching workspace is reported rather than an arbitrary first one.
The report keeps apart facts that are easy to conflate. A finished agent turn is
not task completion; an unexpired lazybox:w: claim is not proof of a
running process (it has a one-hour TTL a crashed worker stops renewing); a
retained session worktree is not an agent turn. Where the evidence is
missing or contradictory the verdict is unknown rather than a guess.
Exit codes: 0 when status was established (including “nobody is working on
it”, which is a real answer), 2 for a reference that cannot be resolved, 1
when the daemon is unreachable or could not read its state — so a script never
reads a failed lookup as “no worker”.
The same report is available to a wired agent as the task_status MCP tool.
lazybox gh
Section titled “lazybox gh”The gh shim lazybox installs at ~/.lazybox/shims/gh and puts on every
spawned session’s PATH. It runs real gh for everything; what it adds is
routing through the daemon, so a fleet of agents shares one GitHub budget
rather than racing each other for it:
- an identical read from another session is answered from the daemon’s cache, costing nothing upstream;
- each session is paced by its own quota, and reads stand down while the budget is into the reserve the poller depends on;
- a mutation tells the daemon what it changed, so an issue closed with
ghinside a workspace flips its row within seconds — no sweep, no budget.
You normally never type this. A session’s gh already is this.
Anything the shim does not recognise is passed through untouched, and it
degrades to plain gh whenever the daemon is unreachable or slow. Two escape
hatches take it out of the path entirely:
gh.real pr list # the real binary, beside the shimLAZYBOX_GH_SHIM=0 gh pr list # opt one command (or a whole session) outConfigure it under providers.github.gh_shim,
where enabled: false removes the shim instead of leaving it on PATH.
Environment variables
Section titled “Environment variables”| Variable | Effect |
|---|---|
GH_TOKEN, GITHUB_TOKEN |
GitHub credential (otherwise gh auth token is used) |
LINEAR_API_KEY |
Credential for the Linear provider |
SLACK_BOT_TOKEN |
Slack bot credential; overrides slack.bot_token |
SLACK_APP_TOKEN |
Slack Socket Mode app credential; overrides slack.app_token |
RUST_LOG |
Log filter, e.g. RUST_LOG=lazybox=debug for verbose logs |
LAZYBOX_GH_SHIM |
Set to 0 to bypass the gh shim and run the real binary directly |
LAZYBOX_GH_SHIM_DIR |
Directory holding the shim; set on every spawn so the shim can resolve past itself |
LAZYBOX_GH_SHIM_DEPTH |
How many shim invocations deep this process is. The shim stamps it on each gh it runs and refuses past 4, so a misidentified shim bounds instead of recursing |
LAZYBOX_HOME |
Overrides every path lazybox writes under ~/.lazybox: state, config, worktrees, runtime dir, tmux socket. Logs are separate — they default to /tmp/lazybox.log (override with ui.log_path) |
LAZYBOX_RUNTIME_DIR |
Overrides just the daemon runtime directory (daemon.sock / daemon.pid); wins over LAZYBOX_HOME’s default <home>/run/ |
LAZYBOX_API_TOKEN |
Bearer token for lazybox server api (required unless --insecure-no-auth) |
LAZYBOX_API_ADDR |
Listen address for lazybox server api when no [addr:port] argument is given |
LAZYBOX_RELAY |
Relay address for lazybox serve when no --relay is given |
LAZYBOX_PLATFORM_URL |
Platform base URL used by lazybox account claim and hosted relay configuration |
LAZYBOX_PLATFORM_API_KEY |
Platform bearer used by a configured lazybox-relay entitlement gate |
| Path | Contents |
|---|---|
~/.lazybox/v2/state.db |
Persistent state (read/unread, snooze, sessions) |
~/.lazybox/config.yaml |
Configuration (see Configuration) |
~/.lazybox/run/daemon.sock |
Daemon Unix socket (lazybox server start / --connect) |
~/.lazybox/snippets.yaml |
Global snippet library (client-wide launch-directory override: <launch-dir>/.lazybox/snippets.yaml) |
/tmp/lazybox.log |
Logs (override with ui.log_path) |
~/.cache/lazybox/zig/ |
Pinned Zig toolchain from make setup |
~/.cache/lazybox/ghostty/ |
Pinned Ghostty source and Zig package cache used by offline builds |